38 Commits

Author SHA1 Message Date
fierke b3ab21ae38 [fix?:] windows paths (ai-based since windows is boring) 2026-05-18 11:42:32 +02:00
fierke 67d007bb31 [fix:] inconsistency with config.json 2026-05-18 11:37:10 +02:00
fierke bf28ba1914 [chore:] logging for StableFontResolver.cs 2026-05-16 18:19:50 +02:00
fierke 40630dee35 [chore:] logging for PdfBuilder.cs 2026-05-16 18:19:36 +02:00
fierke c6baa6a187 [chore:] logging for MarkdownRenderer.cs 2026-05-16 18:19:27 +02:00
fierke 557052bbc7 [chore:] logging for EditorWindow.axaml.cs 2026-05-16 18:19:19 +02:00
fierke 696d0f8fcb [chore:] logging for DataImport.cs 2026-05-16 18:19:09 +02:00
fierke 201b19cbb5 [chore:] logging for CsvBuilder.cs 2026-05-16 18:18:58 +02:00
fierke d7d4b3b31b [file:] removing docs.tex since we're using a markdown wiki 2026-05-16 16:14:31 +00:00
fierke fa10b5c7d0 [chore:] logging for ResultWindow.axaml.cs 2026-05-16 15:10:55 +02:00
fierke 85685d95bb [chore:] logging for NamingWindow.axaml.cs 2026-05-16 15:10:49 +02:00
fierke 8a42f8dc7d [chore:] logging for CombineAddresses.cs 2026-05-16 15:09:55 +02:00
fierke cdc4fb70cd [chore:] logging for AddressShortener.cs 2026-05-16 15:09:50 +02:00
fierke 10dfd1e080 [chore:] logging for AddressRepair.cs 2026-05-16 15:09:45 +02:00
fierke 4b98f53881 [chore:] logging for AddressPatch.cs 2026-05-16 15:09:38 +02:00
fierke 5f79df55d2 [chore:] logging for AddressCreation.cs 2026-05-16 15:09:32 +02:00
fierke 2d33326bab [chore:] logging for AddressCheck.cs 2026-05-16 15:09:23 +02:00
fierke b48652910e [fix:] format errors 2026-05-16 14:44:38 +02:00
fierke a74a97559b [fix:] typo 2026-05-16 14:42:28 +02:00
fierke b5b6dc8de7 [chore:] basic saving logging 2026-05-16 14:41:28 +02:00
fierke 6dfab5e73a [chore:] basic logging 2026-05-16 14:41:16 +02:00
fierke 6f03a26c29 [fix:] console logging printed an array 👀 2026-05-16 14:41:06 +02:00
fierke 7c81920e84 [init:] logging system 2026-05-16 14:12:16 +02:00
fierke d337f94851 [fix?:] trying 2026-05-15 18:56:55 +02:00
fierke 1922b30ada [fix?:] will this work? 2026-05-15 18:41:45 +02:00
fierke 68541621d9 [fix?:] windows file path for label export? 2026-05-15 18:34:25 +02:00
fierke 474b628f0b [chore:] renaming address sets now gives the current name as a base 2026-05-15 17:51:35 +02:00
fierke 7fe45ce4e3 [feat:] csv-divider-buttons 2026-05-06 07:07:41 +02:00
fierke a46539c3cc [fix:] wrong sorting of imported address sets 2026-05-06 06:35:23 +02:00
fierke 72a5630db1 [chore:] enable shortener usage 2026-05-06 06:34:48 +02:00
fierke 7697edac5e [fix:] shorten-button was enabled too early 2026-05-06 06:33:55 +02:00
fierke 028b9793db [chore:] basic address shortener 2026-05-06 06:33:37 +02:00
fierke 2c5f2ed48b [feat:] (opt) delete original sets after merging 2026-05-03 09:21:57 +02:00
fierke f0598a39ec [chore:] listbox item type is no string anymore (hopefully I tested everything?) 2026-05-02 16:27:43 +02:00
fierke f9e419d573 [chore:] cleanup 2026-05-02 16:20:18 +02:00
fierke 56233e6f5c [chore:] tiny improvements for address check 2026-05-02 15:39:42 +02:00
fierke 488830cdad [fix:] multiple little fixed for the combining methods 2026-05-02 12:56:27 +02:00
fierke 52fbefb803 [chore:] added missing help- and about-links 2026-04-29 14:58:53 +02:00
23 changed files with 2221 additions and 1749 deletions
+22
View File
@@ -60,6 +60,11 @@ public class KasAddressList //Address-Set
var id = listItemName.Split(" - ")[0]; var id = listItemName.Split(" - ")[0];
return int.Parse(id); return int.Parse(id);
} }
public override string ToString()
{
return Name + " (" + KasPersons.Count + " Einträge)";
}
} }
public class KasPerson public class KasPerson
@@ -188,6 +193,18 @@ public class KasPerson
return highest + base_id + 1; return highest + base_id + 1;
} }
public override string ToString()
{
if (refsid != null && refsid != 0)
{
return refsid + " - " + name;
}
else
{
return id + " - " + name;
}
}
} }
public class KasPersonError public class KasPersonError
@@ -212,4 +229,9 @@ public class KasPersonError
return output; return output;
} }
public string ToString(KasPerson person)
{
return "ID:"+person.id + "; Name: " +person.name +"; Errors: " + GetString();
}
} }
+37 -25
View File
@@ -12,8 +12,8 @@ public class Settings
public Customers customers = new(); public Customers customers = new();
public PdfExportSettings pdfExport { get; set; } = new(); public PdfExportSettings pdfExport { get; set; } = new();
public string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // public string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "config.json"); // "logofclient", "config.json");
public Settings() public Settings()
{ {
@@ -23,31 +23,28 @@ public class Settings
public static void Save() public static void Save()
{ {
if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), if (!Directory.Exists(Global._instance.config_path))
"logofclient"))) Directory.CreateDirectory(Global._instance.config_path);
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), // if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
"logofclient"));
if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
var json = JsonConvert.SerializeObject(_instance); var json = JsonConvert.SerializeObject(_instance);
File.WriteAllText(_instance.settingsPath, json); File.WriteAllText(Path.Combine(Global._instance.config_path,"config.json"), json);
} }
public static void Load() public static void Load()
{ {
if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path; //if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
try try
{ {
var contents = File.ReadAllText(_instance.settingsPath); var contents = File.ReadAllText(Path.Combine(Global._instance.config_path, "config.json"));
_instance = JsonConvert.DeserializeObject<Settings>(contents); _instance = JsonConvert.DeserializeObject<Settings>(contents);
MainWindow._instance.RefreshCustomerItems(); MainWindow._instance.RefreshCustomerItems();
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Logger.Log($"Error while reading settings. Generating new... {ex.Message}", Logger.LogType.Warning);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Error while reading settings. Generating new...");
_instance = new Settings(); _instance = new Settings();
} }
} }
@@ -83,9 +80,13 @@ public class Global
public string config_path { get; set; } = Path.Combine( public string config_path { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "logofclient");
"config.json");
public void SetConfigPath(string path)
{
if (!string.IsNullOrWhiteSpace(path))
config_path = PathUtilities.NormalizeFileSystemPath(path);
}
public string wiki_storage_path { get; set; } = Path.Combine( public string wiki_storage_path { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "logofclient",
@@ -119,23 +120,29 @@ public class Global
var contents = File.ReadAllText(Path.Combine( var contents = File.ReadAllText(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
"global.config")); "global.config"));
_instance = JsonConvert.DeserializeObject<Global>(contents); _instance = JsonConvert.DeserializeObject<Global>(contents) ?? new Global();
_instance.NormalizePaths();
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Logger.Log($"Error while reading global settings. Generating new... {ex.Message}", Logger.LogType.Warning);
Console.WriteLine(ex.StackTrace);
Console.WriteLine("Error while reading global settings. Generating new...");
_instance = new Global(); _instance = new Global();
Save(); Save();
} }
} }
private void NormalizePaths()
{
config_path = PathUtilities.NormalizeFileSystemPath(config_path);
wiki_storage_path = PathUtilities.NormalizeFileSystemPath(wiki_storage_path);
font_path = PathUtilities.NormalizeFileSystemPath(font_path);
}
} }
public class Customers public class Customers
{ {
public List<Customer> customers = new(); public List<Customer> customers = new();
public int current { get; set; } = 0; public Customer current { get; set; } = null;
} }
public class Customer public class Customer
@@ -149,6 +156,11 @@ public class Customer
ID = highestID + 1; ID = highestID + 1;
} }
public override string ToString()
{
return name;
}
public string name { get; set; } = ""; public string name { get; set; } = "";
public string description { get; set; } = ""; public string description { get; set; } = "";
@@ -158,11 +170,11 @@ public class Customer
public int ID { get; } public int ID { get; }
public static int GetIDByCustomerListItem(string item_content) // public static int GetIDByCustomerListItem(string item_content)
{ // {
var id = item_content.Split(" - ")[0]; // var id = item_content.Split(" - ")[0];
return int.Parse(id); // return int.Parse(id);
} // }
} }
public class AddressSets public class AddressSets
+40
View File
@@ -0,0 +1,40 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace Logof_Client;
public static class Logger
{
public static void Log(string text, LogType logType = LogType.Info)
{
try
{
string config_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient");
if (!Directory.Exists(config_path))
Directory.CreateDirectory(config_path);
string log_path = Path.Combine(config_path, $"log-{DateTime.Now:dd-MM-yy}.log");
if(!File.Exists(log_path))
File.Create(log_path).Close();
string line = $"[{DateTime.Now:dd.MM.yyyy - hh:mm:ss}]: ({logType.ToString()}) {text}";
Console.WriteLine(line);
File.AppendAllLines(log_path, [line]);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public enum LogType
{
Error,
Warning,
Info
}
}
+22 -6
View File
@@ -121,7 +121,7 @@
FontWeight="Bold" /> FontWeight="Bold" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Width="250" IsEnabled="False" <Button Width="250" IsEnabled="False" Click="BtnShorten_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnShorten" HorizontalContentAlignment="Center" x:Name="BtnShorten"
Margin="0,0,0,10"> Margin="0,0,0,10">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
@@ -220,10 +220,18 @@
</StackPanel> </StackPanel>
<Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" /> <Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" />
</StackPanel> </StackPanel>
</Button> </Button>
</StackPanel> </StackPanel>
<CheckBox HorizontalAlignment="Center" x:Name="CbMergeExportUnmerged" IsChecked="False">Speichere Unverarbeitete in neuem Verteiler</CheckBox> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="50">
<StackPanel Orientation="Vertical" >
<RadioButton Content="Vergleiche nach refsid" IsChecked="True" x:Name="RbComprefsid"></RadioButton>
<RadioButton Content="Vergleiche nach finaler Adresse" IsChecked="False" x:Name="RbCompfinAd"></RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical">
<CheckBox HorizontalAlignment="Left" x:Name="CbMergeExportUnmerged" IsChecked="False">Speichere Unverarbeitete in neuem Verteiler</CheckBox>
<CheckBox HorizontalAlignment="Left" x:Name="CbMergeDeleteOld" IsChecked="False">Lösche ursprüngliche Sets</CheckBox>
</StackPanel>
</StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid Grid.Row="2" Margin="20" ColumnDefinitions="*,5*,*" IsVisible="False" x:Name="GrdExportMarginOptions"> <Grid Grid.Row="2" Margin="20" ColumnDefinitions="*,5*,*" IsVisible="False" x:Name="GrdExportMarginOptions">
@@ -381,7 +389,7 @@
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5"> <StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5"> <StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch" <TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/config.json" /> Watermark="/home/username/.config/logofclient/" />
<Button x:Name="BtnConfigPath" HorizontalAlignment="Right"> <Button x:Name="BtnConfigPath" HorizontalAlignment="Right">
<Button.Content> <Button.Content>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
@@ -477,12 +485,20 @@
TextChanged="TbSettingsCustomerSenderAddress_OnTextChanged" TextChanged="TbSettingsCustomerSenderAddress_OnTextChanged"
x:Name="TbSettingsCustomerSenderAddress" /> x:Name="TbSettingsCustomerSenderAddress" />
</Grid> </Grid>
<Grid ColumnDefinitions="150,*"> <Grid ColumnDefinitions="150,*,Auto">
<Label Content="CSV-Trennzeichen" /> <Label Content="CSV-Trennzeichen" />
<TextBox Grid.Column="1" Watermark="," <TextBox Grid.Column="1" Watermark=","
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch" Margin="0,0,5,0"
TextChanged="TbSettingsCustomerCsvSeparator_OnTextChanged" TextChanged="TbSettingsCustomerCsvSeparator_OnTextChanged"
x:Name="TbSettingsCustomerCsvSeparator" /> x:Name="TbSettingsCustomerCsvSeparator" />
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="5">
<Button Click="ChangeCSVDivider" Content=","></Button>
<Button Click="ChangeCSVDivider" Content=";"></Button>
<Button Click="ChangeCSVDivider" Content="TAB"></Button>
<Button Click="ChangeCSVDivider" Content="|"></Button>
<Button Click="ChangeCSVDivider" Content="SPACE"></Button>
<Button Click="ChangeCSVDivider" Content=":"></Button>
</StackPanel>
</Grid> </Grid>
<Grid ColumnDefinitions="150,*"> <Grid ColumnDefinitions="150,*">
<Label Content="Address-Patch-Info" /> <Label Content="Address-Patch-Info" />
+429 -337
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -52,7 +52,7 @@ public partial class NamingWindow : Window
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("Error while showing naming window: " + ex.Message); Logger.Log("Error while showing naming window: " + ex.Message, Logger.LogType.Warning);
return Task.FromResult<string>(null!); return Task.FromResult<string>(null!);
} }
} }
+2 -2
View File
@@ -16,9 +16,9 @@ public partial class ProgressWindow : Window
PbProgress.Value = percentage; PbProgress.Value = percentage;
} }
public void AddToLog(string message) public void AddToLog(string message, string percent)
{ {
TbLog.Text = message; TbLog.Text = message + $"\n{percent}%";
//ScvLog.ScrollToEnd(); //ScvLog.ScrollToEnd();
} }
} }
+3 -1
View File
@@ -20,7 +20,9 @@
Margin="10,10,10,10" /> Margin="10,10,10,10" />
</Grid> </Grid>
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="StkResults" Orientation="Vertical" Margin="10" /> <!-- <TextBlock x:Name="TbResults"></TextBlock> -->
<ListBox x:Name="LbResults"></ListBox>
<!-- <StackPanel x:Name="StkResults" Orientation="Vertical" Margin="10" /> -->
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
+72 -40
View File
@@ -28,12 +28,25 @@ public partial class ResultWindow : Window
private void GenerateView(List<KasPerson> result) private void GenerateView(List<KasPerson> result)
{ {
// Filter to only show persons with errors try
var result_with_errors = result.Where(p => p.PersonError != null).ToList(); {
LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse";
StkResults.Children.Clear();
foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person)); // Filter to only show persons with errors
var result_with_errors = result.Where(p => p.PersonError != null).ToList();
LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse";
// TbResults.Text = "";
// foreach (var person in result_with_errors) TbResults.Text += person.PersonError.GetString()+"\n";
LbResults.Items.Clear();
foreach (var person in result_with_errors) LbResults.Items.Add(person.PersonError.ToString(person));
// StkResults.Children.Clear();
// foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person));
}
catch (Exception ex)
{
Logger.Log("Error while generating result view: " + ex.Message, Logger.LogType.Warning);
}
} }
private Grid CreatePersonGrid(KasPerson person) private Grid CreatePersonGrid(KasPerson person)
@@ -102,44 +115,52 @@ public partial class ResultWindow : Window
private void Load(List<KasPerson> result) private void Load(List<KasPerson> result)
{ {
var knownErrors = new List<AddressCheck.ErrorTypes>(); try
var knownWarnings = new List<AddressCheck.WarningTypes>();
foreach (var person in result)
{ {
if (person.PersonError == null) continue; var knownErrors = new List<AddressCheck.ErrorTypes>();
var knownWarnings = new List<AddressCheck.WarningTypes>();
foreach (var errtyp in person.PersonError.errors) foreach (var person in result)
if (!knownErrors.Contains(errtyp)) {
knownErrors.Add(errtyp); if (person.PersonError == null) continue;
foreach (var wartyp in person.PersonError.warnings) foreach (var errtyp in person.PersonError.errors)
if (!knownWarnings.Contains(wartyp)) if (!knownErrors.Contains(errtyp))
knownWarnings.Add(wartyp); knownErrors.Add(errtyp);
foreach (var wartyp in person.PersonError.warnings)
if (!knownWarnings.Contains(wartyp))
knownWarnings.Add(wartyp);
}
foreach (var errtype in knownErrors)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = errtype.ToString();
//cb.Click += (sender, e) => UpdateFilter();
errortypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
foreach (var wartype in knownWarnings)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = wartype.ToString();
//cb.Click += (sender, e) => UpdateFilter();
warningtypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
GenerateView(result);
} }
catch (Exception ex)
foreach (var errtype in knownErrors)
{ {
var cb = new CheckBox(); Logger.Log("Error while showing naming window: " + ex.Message, Logger.LogType.Warning);
cb.IsChecked = true;
cb.Content = errtype.ToString();
//cb.Click += (sender, e) => UpdateFilter();
errortypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
} }
foreach (var wartype in knownWarnings)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = wartype.ToString();
//cb.Click += (sender, e) => UpdateFilter();
warningtypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
GenerateView(result);
} }
private void BtnUpdateFilter_OnClick(object? sender, RoutedEventArgs e) private void BtnUpdateFilter_OnClick(object? sender, RoutedEventArgs e)
@@ -148,7 +169,9 @@ public partial class ResultWindow : Window
private void UpdateFilter() private void UpdateFilter()
{ {
var temp_result = new List<KasPerson>(); try
{
var temp_result = new List<KasPerson>();
var checkedErrors = new HashSet<AddressCheck.ErrorTypes>(); var checkedErrors = new HashSet<AddressCheck.ErrorTypes>();
var checkedWarnings = new HashSet<AddressCheck.WarningTypes>(); var checkedWarnings = new HashSet<AddressCheck.WarningTypes>();
@@ -201,8 +224,17 @@ public partial class ResultWindow : Window
LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse"; LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse";
StkResults.Children.Clear(); LbResults.Items.Clear();
foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person)); foreach (var person in temp_result) LbResults.Items.Add(person.PersonError.ToString(person));
// TbResults.Text = "";
// foreach (var person in temp_result) TbResults.Text += person.PersonError.GetString() +"\n";
// StkResults.Children.Clear();
// foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person));
} catch (Exception ex)
{
Logger.Log("Error while updating filter: " + ex.Message, Logger.LogType.Warning);
}
} }
+210 -196
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -38,227 +39,240 @@ public class AddressCheck
public async Task<List<KasPerson>> Perform(int id) public async Task<List<KasPerson>> Perform(int id)
{ {
// Find the index of the address set with the given id try
var adset_index = -1;
for (var i = 0; i < Settings._instance.addressSets.addresses.Count; i++)
if (Settings._instance.addressSets.addresses[i].ID == id)
{
adset_index = i;
break;
}
if (adset_index == -1) return new List<KasPerson>();
var adset = Settings._instance.addressSets.addresses[adset_index];
var total = adset.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{ {
foreach (var person in adset.KasPersons) // Find the index of the address set with the given id
var adset_index = -1;
for (var i = 0; i < Settings._instance.addressSets.addresses.Count; i++)
if (Settings._instance.addressSets.addresses[i].ID == id)
{
adset_index = i;
break;
}
if (adset_index == -1) return new List<KasPerson>();
var adset = Settings._instance.addressSets.addresses[adset_index];
var total = adset.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{ {
var errors = new List<ErrorTypes>(); foreach (var person in adset.KasPersons)
var warnings = new List<WarningTypes>();
var hasFaults = false;
var address_component_count = 2; // cause anrede and name are first
// PLZ-Prüfung
if (person.plz == "" || person.plz == null)
{ {
hasFaults = true; var errors = new List<ErrorTypes>();
warnings.Add(WarningTypes.NoPLZ); var warnings = new List<WarningTypes>();
} var hasFaults = false;
else
{ var address_component_count = 2; // cause anrede and name are first
if (!AddressCreator.CheckPLZ(person.plz, person.land))
// PLZ-Prüfung
if (person.plz == "" || person.plz == null)
{ {
hasFaults = true; hasFaults = true;
errors.Add(ErrorTypes.PlzNotUsable); warnings.Add(WarningTypes.NoPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.plz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PlzNotUsable);
}
// if ((person.plz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz < 10000 && person.land == "GER") ||
// (person.plz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooShort);
// }
// else if ((person.plz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz > 99999 && person.land == "GER") ||
// (person.plz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooLong);
// }
} }
// if ((person.plz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz < 10000 && person.land == "GER") ||
// (person.plz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooShort);
// }
// else if ((person.plz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz > 99999 && person.land == "GER") ||
// (person.plz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooLong);
// }
}
// PPLZ-Prüfung // PPLZ-Prüfung
if (person.pplz == "" || person.pplz == null) if (person.pplz == "" || person.pplz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.pplz, person.land))
{ {
hasFaults = true; hasFaults = true;
errors.Add(ErrorTypes.PPlzNotUsable); warnings.Add(WarningTypes.NoPPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.pplz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PPlzNotUsable);
}
// if ((person.pplz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz < 10000 && person.land == "GER") ||
// (person.pplz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooShort);
// }
// else if ((person.pplz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz > 99999 && person.land == "GER") ||
// (person.pplz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooLong);
// }
} }
// if ((person.pplz < 10000 && string.IsNullOrWhiteSpace(person.land)) || if (warnings.Contains(WarningTypes.NoPLZ) && warnings.Contains(WarningTypes.NoPPLZ))
// (person.pplz < 10000 && person.land == "GER") ||
// (person.pplz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooShort);
// }
// else if ((person.pplz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz > 99999 && person.land == "GER") ||
// (person.pplz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooLong);
// }
}
if (warnings.Contains(WarningTypes.NoPLZ) && warnings.Contains(WarningTypes.NoPPLZ))
{
hasFaults = true;
errors.Add(ErrorTypes.NoPLZorPPLZ);
}
// Ort-Prüfung
if (string.IsNullOrWhiteSpace(person.ort))
{
hasFaults = true;
warnings.Add(WarningTypes.NoCity);
}
else
{
address_component_count++;
}
// Street-Number
var street = person.strasse.ToCharArray();
var intcount = 0;
foreach (var c in street)
{
int maybe;
if (int.TryParse(c.ToString(), out maybe)) intcount++;
}
if (intcount == 0)
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreetNumber);
}
// Last-Name
if (string.IsNullOrWhiteSpace(person.name))
{
hasFaults = true;
warnings.Add(WarningTypes.NoLastName);
}
// First-Name
if (string.IsNullOrWhiteSpace(person.vorname))
{
hasFaults = true;
warnings.Add(WarningTypes.NoFirstName);
}
// Street-Check
if (string.IsNullOrWhiteSpace(person.strasse))
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreet);
}
else
{
address_component_count++;
}
// Address-Component-Count
if (!string.IsNullOrWhiteSpace(person.strasse2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.land)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name1)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name3)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name4)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name5)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktionad)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.abteilung)) address_component_count++;
// Double-Refsid or DoubleAddresses
foreach (var person2 in adset.KasPersons)
{
if (adset.KasPersons.IndexOf(person) == adset.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid) // trifft auf Patch-Addressen nicht zu
{ {
hasFaults = true; hasFaults = true;
warnings.Add(WarningTypes.DoubledRefsid); errors.Add(ErrorTypes.NoPLZorPPLZ);
} }
if (person.name == person2.name && // Ort-Prüfung
person.strasse == person2.strasse && if (string.IsNullOrWhiteSpace(person.ort))
person.vorname == person2.vorname &&
person.ort == person2.ort &&
person.funktion == person2.funktion &&
person.funktion2 == person2.funktion2 &&
person.funktionad == person2.funktionad &&
person.abteilung == person2.abteilung &&
person.name1 == person2.name1 &&
person.name2 == person2.name2 &&
person.name3 == person2.name3 &&
person.name4 == person2.name4 &&
person.name5 == person2.name5) //
{ {
hasFaults = true; hasFaults = true;
errors.Add(ErrorTypes.MayBeSameAddress); warnings.Add(WarningTypes.NoCity);
} }
} else
// Adressen-Länge
if (address_component_count > 10)
{
hasFaults = true;
warnings.Add(WarningTypes.FullAddressTooLong);
}
if (hasFaults)
lock (Settings._instance.addressSets.addresses)
{ {
// Directly set PersonError in the address set address_component_count++;
var person_index = adset.KasPersons.IndexOf(person); }
if (person_index >= 0)
Settings._instance.addressSets.addresses[adset_index].KasPersons[person_index].PersonError = // Street-Number
new KasPersonError((errors, warnings)); var street = person.strasse.ToCharArray();
var intcount = 0;
foreach (var c in street)
{
int maybe;
if (int.TryParse(c.ToString(), out maybe)) intcount++;
}
if (intcount == 0)
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreetNumber);
}
// Last-Name
if (string.IsNullOrWhiteSpace(person.name))
{
hasFaults = true;
warnings.Add(WarningTypes.NoLastName);
}
// First-Name
if (string.IsNullOrWhiteSpace(person.vorname))
{
hasFaults = true;
warnings.Add(WarningTypes.NoFirstName);
}
// Street-Check
if (string.IsNullOrWhiteSpace(person.strasse))
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreet);
}
else
{
address_component_count++;
}
// Address-Component-Count
if (!string.IsNullOrWhiteSpace(person.strasse2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.land)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name1)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name3)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name4)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name5)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktionad)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.abteilung)) address_component_count++;
// Double-Refsid or DoubleAddresses
foreach (var person2 in adset.KasPersons)
{
if (adset.KasPersons.IndexOf(person) == adset.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid) // trifft auf Patch-Addressen nicht zu
{
hasFaults = true;
warnings.Add(WarningTypes.DoubledRefsid);
}
if (person.name == person2.name &&
person.strasse == person2.strasse &&
person.vorname == person2.vorname &&
person.ort == person2.ort &&
person.funktion == person2.funktion &&
person.funktion2 == person2.funktion2 &&
person.funktionad == person2.funktionad &&
person.abteilung == person2.abteilung &&
person.name1 == person2.name1 &&
person.name2 == person2.name2 &&
person.name3 == person2.name3 &&
person.name4 == person2.name4 &&
person.name5 == person2.name5) //
{
hasFaults = true;
errors.Add(ErrorTypes.MayBeSameAddress);
}
}
// Adressen-Länge
if (address_component_count > 10)
{
hasFaults = true;
warnings.Add(WarningTypes.FullAddressTooLong);
} }
// Fortschritt aktualisieren
Interlocked.Increment(ref current);
var percent = current / (double)total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults) if (hasFaults)
_progress.AddToLog($"Person mit id {person.id} ist fehlerhaft"); lock (Settings._instance.addressSets.addresses)
{
// Directly set PersonError in the address set
var person_index = adset.KasPersons.IndexOf(person);
if (person_index >= 0)
Settings._instance.addressSets.addresses[adset_index].KasPersons[person_index]
.PersonError =
new KasPersonError((errors, warnings));
}
_progress.ChangePercentage(percent); // Fortschritt aktualisieren
}); Interlocked.Increment(ref current);
} var percent = current / (double)total * 100;
}); await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults)
_progress.AddToLog($"Person mit id {person.id} ist fehlerhaft",
Convert.ToInt32(percent).ToString());
Settings.Save(); _progress.ChangePercentage(percent);
});
}
});
// Return only the persons with errors from the address set Settings.Save();
return Settings._instance.addressSets.addresses[adset_index].KasPersons
.Where(p => p.PersonError != null) // Return only the persons with errors from the address set
.ToList(); return Settings._instance.addressSets.addresses[adset_index].KasPersons
.Where(p => p.PersonError != null)
.ToList();
}
catch (Exception ex)
{
Logger.Log($"Error while performing address check: {ex.Message}", Logger.LogType.Error);
}
return null;
} }
} }
+203 -175
View File
@@ -43,177 +43,195 @@ public static class AddressCreator
public static string? CreateFinalMarkdownString(int id) public static string? CreateFinalMarkdownString(int id)
{ {
// Maximum seven lines of information // Maximum seven lines of information
try
// find the address
KasPerson? address = null;
var string_address = "";
var address_line_count = 0;
foreach (var set in Settings._instance.addressSets.addresses)
{ {
var temp = set.KasPersons.FirstOrDefault(obj => obj.id == id); // find the address
if (temp != null) KasPerson? address = null;
{ var string_address = "";
address = temp; var address_line_count = 0;
break; foreach (var set in Settings._instance.addressSets.addresses)
} {
} var temp = set.KasPersons.FirstOrDefault(obj => obj.id == id);
if (temp != null)
// no address found
if (address == null) return null;
// let's get started: we start from the bottom
// if the country is not Germany, set it; try to map via Global countries alternatives -> translation
var trimmedLand = (address.land ?? "").Trim();
var trimmedLowerLand = trimmedLand.ToLower();
var isGermany = trimmedLowerLand == "germany" || trimmedLowerLand == "ger" ||
trimmedLowerLand == "" || trimmedLowerLand == "de" ||
trimmedLowerLand == "deutschland";
if (!isGermany)
{
var countryToShow = trimmedLand; // default: use raw land value
if (!string.IsNullOrEmpty(trimmedLand))
// search for matching country via alternatives using for-loops
for (var ci = 0; ci < Global._instance.countries.Count; ci++)
{
var country = Global._instance.countries[ci];
for (var ai = 0; ai < country.alternatives.Count; ai++)
try
{ {
var alt = (country.alternatives[ai] ?? "").Trim(); address = temp;
if (string.Equals(alt, trimmedLand, StringComparison.OrdinalIgnoreCase)) break;
}
}
// no address found
if (address == null) return null;
// let's get started: we start from the bottom
// if the country is not Germany, set it; try to map via Global countries alternatives -> translation
var trimmedLand = (address.land ?? "").Trim();
var trimmedLowerLand = trimmedLand.ToLower();
var isGermany = trimmedLowerLand == "germany" || trimmedLowerLand == "ger" ||
trimmedLowerLand == "" || trimmedLowerLand == "de" ||
trimmedLowerLand == "deutschland";
if (!isGermany)
{
var countryToShow = trimmedLand; // default: use raw land value
if (!string.IsNullOrEmpty(trimmedLand))
// search for matching country via alternatives using for-loops
for (var ci = 0; ci < Global._instance.countries.Count; ci++)
{ {
countryToShow = string.IsNullOrWhiteSpace(country.translation) var country = Global._instance.countries[ci];
? country.name for (var ai = 0; ai < country.alternatives.Count; ai++)
: country.translation; try
goto CountryFound; {
var alt = (country.alternatives[ai] ?? "").Trim();
if (string.Equals(alt, trimmedLand, StringComparison.OrdinalIgnoreCase))
{
countryToShow = string.IsNullOrWhiteSpace(country.translation)
? country.name
: country.translation;
goto CountryFound;
}
}
catch
{
// ignore malformed alternative
}
} }
}
catch CountryFound:
string_address = "**" + countryToShow + "**"; // Needs to be bold
address_line_count++;
}
// Alternative A: pplz valid and city existing
if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.pplz, address.land))
{
string_address = address.pplz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.postfach))
{ {
// ignore malformed alternative string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
} }
} else if (!string.IsNullOrWhiteSpace(address.strasse.Trim()))
{
CountryFound: string_address = address.strasse.Trim() + "\n" + string_address;
string_address = "**" + countryToShow + "**"; // Needs to be bold address_line_count++;
address_line_count++; }
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
// REIHENFOLGE
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Alternative B: plz valid and city existing
else if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.plz, address.land))
{
string_address = address.plz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.strasse))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.postfach.Trim()))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Error-Handling?
if (address_line_count > 1) return string_address;
return null;
}
catch (Exception ex)
{
Logger.Log($"Error while creating markdown string: {ex.Message}", Logger.LogType.Error);
} }
// Alternative A: pplz valid and city existing
if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.pplz, address.land))
{
string_address = address.pplz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.postfach))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.strasse.Trim()))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
// REIHENFOLGE
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Alternative B: plz valid and city existing
else if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.plz, address.land))
{
string_address = address.plz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.strasse))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.postfach.Trim()))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Error-Handling?
if (address_line_count > 1) return string_address;
return null; return null;
} }
public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel, public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel,
string name, string namezus) string name, string namezus)
{ {
if (!string.IsNullOrWhiteSpace(anredezus)) try
{
if (!string.IsNullOrWhiteSpace(anredezus))
return string.Join(" ",
new[] { anredezus, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
// else
return string.Join(" ", return string.Join(" ",
new[] { anredezus, titel, vorname, adel, name } new[] { anrede, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s)) .Where(s => !string.IsNullOrWhiteSpace(s))
) )
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})"); + (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
}
catch (Exception ex)
{
Logger.Log($"Error while performing address name line creation: {ex.Message}", Logger.LogType.Error);
}
// else return null;
return string.Join(" ",
new[] { anrede, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
} }
/// <summary> /// <summary>
@@ -224,29 +242,39 @@ public static class AddressCreator
/// <returns>true or faslse, depending on result :+1:</returns> /// <returns>true or faslse, depending on result :+1:</returns>
public static bool CheckPLZ(string plz, string land) public static bool CheckPLZ(string plz, string land)
{ {
if (string.IsNullOrWhiteSpace(plz)) return false; try
{
if (string.IsNullOrWhiteSpace(plz)) return false;
var trimmedPlz = plz.Trim();
var trimmedLand = land.ToLower().Trim();
// Check if it's a German country code
var isGermany = trimmedLand == "germany" || trimmedLand == "ger" || trimmedLand == "de" ||
trimmedLand == "deutschland" || trimmedLand == "";
if (isGermany)
// For Germany (including empty land), accept numeric postal codes with 5 digits
try
{
var iplz = Convert.ToInt32(trimmedPlz);
if (trimmedPlz.Length == 5) return true;
return false;
}
catch
{
return false;
}
// For non-German countries, accept any non-empty postal code
return true;
}
catch (Exception ex)
{
Logger.Log($"Error while performing address plz check: {ex.Message}", Logger.LogType.Error);
}
var trimmedPlz = plz.Trim(); return false;
var trimmedLand = land.ToLower().Trim();
// Check if it's a German country code
var isGermany = trimmedLand == "germany" || trimmedLand == "ger" || trimmedLand == "de" ||
trimmedLand == "deutschland" || trimmedLand == "";
if (isGermany)
// For Germany (including empty land), accept numeric postal codes with 5 digits
try
{
var iplz = Convert.ToInt32(trimmedPlz);
if (trimmedPlz.Length == 5) return true;
return false;
}
catch
{
return false;
}
// For non-German countries, accept any non-empty postal code
return true;
} }
} }
+67 -45
View File
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using Logof_Client;
public class AddressPatch public class AddressPatch
{ {
@@ -58,61 +59,82 @@ public class AddressPatch
public static AddressPatch Import(Uri filename) public static AddressPatch Import(Uri filename)
{ {
var patch = new AddressPatch(); try
// Alle Zeilen aus der Datei laden
var lines = File.ReadAllLines(filename.LocalPath);
// Alle Properties der Klasse (Strings und bools)
var properties = typeof(AddressPatch).GetProperties(BindingFlags.Public | BindingFlags.Instance);
// Nur die Properties, die mit _is enden (also die String-Werte)
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
foreach (var prop in stringProps)
{ {
// Beispiel: prop.Name = "name_is" var patch = new AddressPatch();
var baseName = prop.Name.Substring(0, prop.Name.Length - 3); // "name"
// Alle Zeilen aus der Datei laden
// In der Datei wird nach "name:" gesucht (ohne _is) var lines = File.ReadAllLines(filename.LocalPath);
var line = lines.FirstOrDefault(l => l.StartsWith(baseName + ":"));
if (line != null) // Alle Properties der Klasse (Strings und bools)
{ var properties = typeof(AddressPatch).GetProperties(BindingFlags.Public | BindingFlags.Instance);
// Wert extrahieren (alles nach dem Doppelpunkt)
var value = line.Substring(line.IndexOf(':') + 1).Trim(); // Nur die Properties, die mit _is enden (also die String-Werte)
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
// Wert im Patch-Objekt setzen
prop.SetValue(patch, value); foreach (var prop in stringProps)
{
// Passendes has_ Feld aktivieren, z.B. "has_name" // Beispiel: prop.Name = "name_is"
var hasProp = properties.FirstOrDefault(p => p.Name == "has_" + baseName); var baseName = prop.Name.Substring(0, prop.Name.Length - 3); // "name"
if (hasProp != null && hasProp.PropertyType == typeof(bool)) hasProp.SetValue(patch, true);
} // In der Datei wird nach "name:" gesucht (ohne _is)
var line = lines.FirstOrDefault(l => l.StartsWith(baseName + ":"));
if (line != null)
{
// Wert extrahieren (alles nach dem Doppelpunkt)
var value = line.Substring(line.IndexOf(':') + 1).Trim();
// Wert im Patch-Objekt setzen
prop.SetValue(patch, value);
// Passendes has_ Feld aktivieren, z.B. "has_name"
var hasProp = properties.FirstOrDefault(p => p.Name == "has_" + baseName);
if (hasProp != null && hasProp.PropertyType == typeof(bool)) hasProp.SetValue(patch, true);
}
}
return patch;
}
catch (Exception ex)
{
Logger.Log($"Error while importing address patch: {ex.Message}", Logger.LogType.Error);
} }
return patch; return null;
} }
public override string ToString() public override string ToString()
{ {
var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); try
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
var lines = new StringBuilder();
foreach (var prop in stringProps)
{ {
// passendes has_ Feld var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var hasProp = var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
properties.FirstOrDefault(p => p.Name == "has_" + prop.Name.Substring(0, prop.Name.Length - 3));
if (hasProp != null && (bool)hasProp.GetValue(this)) var lines = new StringBuilder();
{
var value = (string)prop.GetValue(this); foreach (var prop in stringProps)
lines.AppendLine($"{prop.Name} => {value}"); {
} // passendes has_ Feld
var hasProp =
properties.FirstOrDefault(p => p.Name == "has_" + prop.Name.Substring(0, prop.Name.Length - 3));
if (hasProp != null && (bool)hasProp.GetValue(this))
{
var value = (string)prop.GetValue(this);
lines.AppendLine($"{prop.Name} => {value}");
}
}
return lines.ToString().TrimEnd();
}
catch (Exception ex)
{
Logger.Log($"Error while parsing: {ex.Message}", Logger.LogType.Error);
} }
return lines.ToString().TrimEnd(); return "Error while parsing";
} }
} }
+11 -9
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Logof_Client; namespace Logof_Client;
@@ -9,15 +10,16 @@ public class AddressRepair(ProgressWindow progressWindow)
public KasAddressList Perform(KasAddressList all_addresses, public KasAddressList Perform(KasAddressList all_addresses,
List<(int, List<AddressCheck.ErrorTypes>)> failed_addresses) List<(int, List<AddressCheck.ErrorTypes>)> failed_addresses)
{ {
// foreach (var k in all_addresses.KasPersons) try
// foreach (var p in failed_addresses) {
// {
// if (k.refsid != p.Item1) continue; }
// catch (Exception ex)
// if (p.Item1.Contains(AddressCheck.WarningTypes.DoubledRefsid)) {
// { Logger.Log($"Error while performing address repair: {ex.Message}", Logger.LogType.Error);
// } }
// }
return null;
return null; return null;
} }
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace Logof_Client;
public class AddressShortener(ProgressWindow progressWindow)
{
private readonly ProgressWindow _progress = progressWindow;
public async Task Perform(KasAddressList list)
{
try
{
List<int> doubled_ids = new List<int>();
for (int i = 0; i < list.KasPersons.Count; i++)
{
var address = list.KasPersons[i];
for (int j = 0; j < list.KasPersons.Count; j++)
{
if (i == j) continue;
var sec_address = list.KasPersons[j];
if (address.refsid == sec_address.refsid && !doubled_ids.Contains(address.refsid) && address.refsid != 0)
{
doubled_ids.Add(address.refsid);
}
}
}
// delete doubled addresses by refsid
foreach (int id in doubled_ids)
{
// does this remove both of the doubled addresses?
list.KasPersons.Remove(list.KasPersons.FirstOrDefault(x => x.refsid == id));
}
List<int> toRemove = new List<int>();
foreach (var address in list.KasPersons)
{
try
{
if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.NoPLZorPPLZ))
{
toRemove.Add(address.id);
}
else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PlzNotUsable) &&
address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PPlzNotUsable))
{
toRemove.Add(address.id);
} else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PlzNotUsable) &&
address.PersonError.warnings.Contains(AddressCheck.WarningTypes.NoPPLZ))
{
toRemove.Add(address.id);
}
else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PPlzNotUsable) &&
address.PersonError.warnings.Contains(AddressCheck.WarningTypes.NoPLZ))
{
toRemove.Add(address.id);
}
}
catch
{
Console.WriteLine("PersonError not accessible: " + address.id);
}
}
// delete doubled addresses by refsid
foreach (int id in toRemove)
{
// does this remove both of the doubled addresses?
list.KasPersons.Remove(list.KasPersons.Find(x => x.id == id));
}
}
catch (Exception ex)
{
Logger.Log($"Error while performing address shortener: {ex.Message}", Logger.LogType.Error);
}
}
}
+243 -158
View File
@@ -15,16 +15,59 @@ public class CombineAddresses
{ {
_progress = progressWindow; _progress = progressWindow;
} }
public enum CombineType
{
refsid,
final_adress,
none
}
public async Task<(KasAddressList, KasAddressList)> Perform(List<KasAddressList> address_lists, string type, public async Task<(KasAddressList, KasAddressList)> Perform(List<KasAddressList> address_lists, string type, CombineType comb_type,
bool? exportUnused, bool? deleteOld)
{
try
{
var result = await Execute(address_lists,type,comb_type,exportUnused);
if (deleteOld == true)
{
foreach (var list in address_lists)
{
Settings._instance.addressSets.addresses.Remove(list);
}
}
return result;
}
catch (Exception ex)
{
Logger.Log($"Error while performing address combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
private async Task<(KasAddressList, KasAddressList)> Execute(List<KasAddressList> address_lists, string type, CombineType comb_type,
bool? exportUnused) bool? exportUnused)
{ {
if (type == "difference") return await Difference(address_lists, exportUnused); try
if (type == "union") return await Union(address_lists, exportUnused); {
if (type == "intersection") return await Intersection(address_lists, exportUnused); if (type == "difference") return await Difference(address_lists, comb_type, exportUnused);
if (type == "symdiff") return await SymmetricDifference(address_lists, exportUnused); if (type == "union") return await Union(address_lists, comb_type, exportUnused);
if (type == "intersection") return await Intersection(address_lists, comb_type, exportUnused);
if (type == "symdiff") return await SymmetricDifference(address_lists, comb_type, exportUnused);
return (null, null);
}
catch (Exception ex)
{
Logger.Log($"Error while executing address combining: {ex.Message}", Logger.LogType.Error);
}
return (null, null); return (null,null);
} }
@@ -35,11 +78,13 @@ public class CombineAddresses
/// <param name="second">Second address to compare</param> /// <param name="second">Second address to compare</param>
/// <param name="only_refsid">If true, only a refsid-check will be done</param> /// <param name="only_refsid">If true, only a refsid-check will be done</param>
/// <returns></returns> /// <returns></returns>
public bool CompareAddresses(KasPerson first, KasPerson second, bool only_refsid = false) public bool CompareAddresses(KasPerson first, KasPerson second, CombineType comb_type)
{ {
// A refsid of 0 means "missing", so it must not collapse unrelated entries. // A refsid of 0 means "missing", so it must not collapse unrelated entries.
if (first.refsid != 0 && second.refsid != 0 && first.refsid == second.refsid) return true; if(comb_type == CombineType.refsid)
if (!only_refsid) if (first.refsid != 0 && second.refsid != 0 && first.refsid == second.refsid) return true;
if (comb_type == CombineType.final_adress)
if (first.name == second.name && if (first.name == second.name &&
first.anrede == second.anrede && first.anrede == second.anrede &&
first.anredzus == second.anredzus && first.anredzus == second.anredzus &&
@@ -68,85 +113,105 @@ public class CombineAddresses
return false; return false;
} }
public async Task<(KasAddressList, KasAddressList)> Difference(List<KasAddressList> address_lists, public async Task<(KasAddressList, KasAddressList)> Difference(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, bool? return_unused,
Progress? progress = null) Progress? progress = null)
{ {
if (address_lists == null || address_lists.Count == 0) try
return (new KasAddressList(await KasAddressList.GenerateName("difference")), null);
progress ??= new Progress
{ {
TotalPersons = address_lists.Sum(l => l.KasPersons.Count), if (address_lists == null || address_lists.Count == 0)
ComparedPersons = 0 return (new KasAddressList(await KasAddressList.GenerateName("difference")), null);
};
progress ??= new Progress
// Vereinigung aller Listen außer der ersten {
var restUnion = new List<KasPerson>(); TotalPersons = address_lists.Sum(l => l.KasPersons.Count),
for (var i = 1; i < address_lists.Count; i++) ComparedPersons = 0
restUnion.AddRange(address_lists[i].KasPersons); };
var result = new KasAddressList(await KasAddressList.GenerateName("difference"));
var second_result = new KasAddressList("none"); // Vereinigung aller Listen außer der ersten
if(return_unused == true) var restUnion = new List<KasPerson>();
second_result = new KasAddressList(await KasAddressList.GenerateName("difference_rest", false)); for (var i = 1; i < address_lists.Count; i++)
restUnion.AddRange(address_lists[i].KasPersons);
foreach (var person in address_lists[0].KasPersons) var result = new KasAddressList(await KasAddressList.GenerateName("difference"));
var second_result = new KasAddressList("none");
if(return_unused == true)
second_result = new KasAddressList(await KasAddressList.GenerateName("difference_rest", false));
foreach (var person in address_lists[0].KasPersons)
{
var isDouble = restUnion.Any(p => CompareAddresses(person, p, comb_type));
if (!isDouble)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
progress.Increment();
if (progress.LogAction == null) continue;
var logMessage =
$"Person mit id {person.id} verglichen mit {restUnion.Count} Personen des Restes.";
await Dispatcher.UIThread.InvokeAsync(() => progress.LogAction?.Invoke(logMessage));
}
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{ {
var isDouble = restUnion.Any(p => CompareAddresses(person, p)); Logger.Log($"Error while performing difference-combining: {ex.Message}", Logger.LogType.Error);
if (!isDouble)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
progress.Increment();
if (progress.LogAction == null) continue;
var logMessage =
$"Person mit id {person.id} verglichen mit {restUnion.Count} Personen des Restes.";
await Dispatcher.UIThread.InvokeAsync(() => progress.LogAction?.Invoke(logMessage));
} }
if (return_unused == true) return (result, second_result); return (null,null);
return (result, null);
} }
public async Task<(KasAddressList, KasAddressList)> Union(List<KasAddressList> address_lists, bool? return_unused, public async Task<(KasAddressList, KasAddressList)> Union(List<KasAddressList> address_lists, CombineType comb_type, bool? return_unused,
Progress progress = null) Progress progress = null)
{ {
var result = new KasAddressList(await KasAddressList.GenerateName("union")); try
var second_result = new KasAddressList("none");
if(return_unused == true)
second_result = new KasAddressList(await KasAddressList.GenerateName("union_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{ {
if (!result.KasPersons.Any(existing => CompareAddresses(existing, person))) var result = new KasAddressList(await KasAddressList.GenerateName("union"));
result.KasPersons.Add(person); var second_result = new KasAddressList("none");
else if(return_unused == true)
second_result.KasPersons.Add(person); second_result = new KasAddressList(await KasAddressList.GenerateName("union_rest", false));
processed++; if (address_lists == null || address_lists.Count == 0)
var percent = processed / (double)total * 100; return (result, null);
var logMessage =
$"{percent:F1}%: Person mit {person.id} hinzugefügt (aktuell {result.KasPersons.Count} Einträge)"; var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
if (progress == null) continue;
if (Dispatcher.UIThread.CheckAccess()) foreach (var list in address_lists)
progress.LogAction?.Invoke(logMessage); foreach (var person in list.KasPersons)
else {
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage)); if (!result.KasPersons.Any(existing => CompareAddresses(existing, person, comb_type)))
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} hinzugefügt (aktuell {result.KasPersons.Count} Einträge)";
if (progress == null) continue;
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing union-combining: {ex.Message}", Logger.LogType.Error);
} }
if (return_unused == true) return (result, second_result); return (null,null);
return (result, null);
} }
@@ -155,110 +220,130 @@ public class CombineAddresses
return null; return null;
} }
public async Task<(KasAddressList, KasAddressList)> Intersection(List<KasAddressList> address_lists, public async Task<(KasAddressList, KasAddressList)> Intersection(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, Progress progress = null) bool? return_unused, Progress progress = null)
{ {
var result = new KasAddressList(await KasAddressList.GenerateName("intersection")); try
var second_result = new KasAddressList("none");
if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("intersection_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
// Nur die erste Liste als Ausgangspunkt verwenden
var baseList = address_lists[0];
var otherLists = address_lists.Skip(1).ToList();
var total = baseList.KasPersons.Count;
var processed = 0;
foreach (var person in baseList.KasPersons)
{ {
// Prüfen, ob Person in *allen* anderen Listen vorkommt var result = new KasAddressList(await KasAddressList.GenerateName("intersection"));
var isInAll = otherLists.All(list => var second_result = new KasAddressList("none");
list.KasPersons.Any(existing => CompareAddresses(existing, person))); if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("intersection_rest", false));
if (isInAll) if (address_lists == null || address_lists.Count == 0)
result.KasPersons.Add(person); return (result, null);
else
second_result.KasPersons.Add(person); // Nur die erste Liste als Ausgangspunkt verwenden
var baseList = address_lists[0];
processed++; var otherLists = address_lists.Skip(1).ToList();
var percent = processed / (double)total * 100;
var logMessage = var total = baseList.KasPersons.Count;
$"{percent:F1}%: Person mit {person.id} geprüft {(isInAll ? "in allen enthalten" : "nicht überall vorhanden")}"; var processed = 0;
// Sicher und nicht blockierend loggen foreach (var person in baseList.KasPersons)
if (progress != null)
{ {
if (Dispatcher.UIThread.CheckAccess()) // Prüfen, ob Person in *allen* anderen Listen vorkommt
progress.LogAction?.Invoke(logMessage); var isInAll = otherLists.All(list =>
list.KasPersons.Any(existing => CompareAddresses(existing, person, comb_type)));
if (isInAll)
result.KasPersons.Add(person);
else else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage)); second_result.KasPersons.Add(person);
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} geprüft {(isInAll ? "in allen enthalten" : "nicht überall vorhanden")}";
// Sicher und nicht blockierend loggen
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
} }
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing intersection-combining: {ex.Message}", Logger.LogType.Error);
} }
if (return_unused == true) return (result, second_result); return (null,null);
return (result, null);
} }
public async Task<(KasAddressList, KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists, public async Task<(KasAddressList, KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, Progress progress = null) bool? return_unused, Progress progress = null)
{ {
var result = new KasAddressList(await KasAddressList.GenerateName("symmetric_difference")); try
var second_result = new KasAddressList("none");
if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("symmetric_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
// Wir sammeln alle Personen + zählen, wie oft sie vorkommen
var allPersons = new List<(KasPerson person, int count)>();
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{ {
// Prüfen, ob schon vorhanden var result = new KasAddressList(await KasAddressList.GenerateName("symmetric_difference"));
var existing = allPersons.FirstOrDefault(p => CompareAddresses(p.person, person)); var second_result = new KasAddressList("none");
if (existing.person != null) if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("symmetric_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
// Wir sammeln alle Personen + zählen, wie oft sie vorkommen
var allPersons = new List<(KasPerson person, int count)>();
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{ {
// Falls schon drin → Vorkommen erhöhen // Prüfen, ob schon vorhanden
var index = allPersons.IndexOf(existing); var existing = allPersons.FirstOrDefault(p => CompareAddresses(p.person, person, comb_type));
allPersons[index] = (existing.person, existing.count + 1); if (existing.person != null)
} {
else // Falls schon drin → Vorkommen erhöhen
{ var index = allPersons.IndexOf(existing);
// Neu hinzufügen allPersons[index] = (existing.person, existing.count + 1);
allPersons.Add((person, 1)); }
}
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} verarbeitet (Zwischengröße {allPersons.Count})";
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage)); {
// Neu hinzufügen
allPersons.Add((person, 1));
}
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} verarbeitet (Zwischengröße {allPersons.Count})";
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
} }
// Nur diejenigen übernehmen, die *genau einmal* vorkamen
foreach (var (person, count) in allPersons)
if (count == 1)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing symdiff-combining: {ex.Message}", Logger.LogType.Error);
} }
// Nur diejenigen übernehmen, die *genau einmal* vorkamen return (null,null);
foreach (var (person, count) in allPersons)
if (count == 1)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
if (return_unused == true) return (result, second_result);
return (result, null);
} }
} }
+16 -5
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -65,11 +66,21 @@ public class CsvBuilder
private string EscapeCsvField(string? value) private string EscapeCsvField(string? value)
{ {
var field = value ?? string.Empty; try
var mustQuote = field.Contains(Separator) || field.Contains('"') || field.Contains('\r') || field.Contains('\n'); {
if (!mustQuote) var field = value ?? string.Empty;
return field; var mustQuote = field.Contains(Separator) || field.Contains('"') || field.Contains('\r') || field.Contains('\n');
if (!mustQuote)
return field;
return "\"" + field.Replace("\"", "\"\"") + "\"";
}
catch (Exception ex)
{
Logger.Log($"Error while escapting csv field: {ex.Message}",Logger.LogType.Warning);
}
return "";
return "\"" + field.Replace("\"", "\"\"") + "\"";
} }
} }
+237 -210
View File
@@ -20,210 +20,227 @@ public class DataImport
private static async Task<(bool, KasAddressList)> ImportKasAddressListWithoutPatch(Uri pathToCsv, char separator) private static async Task<(bool, KasAddressList)> ImportKasAddressListWithoutPatch(Uri pathToCsv, char separator)
{ {
if (!File.Exists(pathToCsv.LocalPath)) try
{ {
Console.WriteLine($"File not found: {pathToCsv}"); if (!File.Exists(pathToCsv.LocalPath))
return (false, null);
}
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
if (parts.Length < 24)
{ {
Console.WriteLine($"Not enough columns in line: {line}"); Console.WriteLine($"File not found: {pathToCsv}");
continue;
}
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count),
ParseInt(parts[0]),
parts[1],
parts[2],
parts[3],
parts[4],
parts[5],
parts[6],
parts[7],
parts[8],
parts[9],
parts[10],
parts[11],
parts[12],
parts[13],
parts[14],
parts[15],
parts[16],
parts[17],
parts[18],
parts[19],
parts[20],
parts[21],
parts[22],
parts[23]
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Console.WriteLine($"Error while parsing line: {line} - {ex.Message}");
Console.WriteLine(ex.StackTrace);
return (false, null); return (false, null);
} }
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
if (parts.Length < 24)
{
Console.WriteLine($"Not enough columns in line: {line}");
continue;
}
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count),
ParseInt(parts[0]),
parts[1],
parts[2],
parts[3],
parts[4],
parts[5],
parts[6],
parts[7],
parts[8],
parts[9],
parts[10],
parts[11],
parts[12],
parts[13],
parts[14],
parts[15],
parts[16],
parts[17],
parts[18],
parts[19],
parts[20],
parts[21],
parts[22],
parts[23]
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Logger.Log($"Error while creating new kas person (import): {ex.Message}",Logger.LogType.Error);
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
return (true, imported);
}
catch (Exception ex)
{
Logger.Log($"Error while importing kas address list without patch: {ex.Message}",Logger.LogType.Error);
} }
return (true, imported); return (false, null);
} }
private static async Task<(bool, KasAddressList)> ImportKasAddressListWithPatch(Uri pathToCsv, AddressPatch patch, private static async Task<(bool, KasAddressList)> ImportKasAddressListWithPatch(Uri pathToCsv, AddressPatch patch,
char separator) char separator)
{ {
if (!File.Exists(pathToCsv.LocalPath)) try
{ {
Console.WriteLine($"File not found: {pathToCsv}"); if (!File.Exists(pathToCsv.LocalPath))
return (false, null);
}
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var headers = ParseCsvLine(headerLine, separator);
var imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var patchType = typeof(AddressPatch);
var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
var hasProperties = patchType.GetProperties(binding)
.Where(p => p.PropertyType == typeof(bool) && p.Name.StartsWith("has_", StringComparison.OrdinalIgnoreCase))
.ToArray();
//var last_refsid = 1000000;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
var fieldValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
//var refsid_existing = false;
foreach (var hasProp in hasProperties)
{ {
var fieldName = hasProp.Name.Substring(4); Console.WriteLine($"File not found: {pathToCsv}");
var hasObj = hasProp.GetValue(patch);
var has = hasObj is bool b && b;
var patchProp = patchType.GetProperty(fieldName + "_is", binding);
string desiredHeader = null;
if (has && patchProp != null)
desiredHeader = patchProp.GetValue(patch)?.ToString();
else
desiredHeader = fieldName;
var resolvedValue = "";
if (!string.IsNullOrEmpty(desiredHeader))
{
var idx = Array.FindIndex(headers,
h => string.Equals(h, desiredHeader, StringComparison.OrdinalIgnoreCase));
if (idx >= 0 && idx < parts.Length)
{
resolvedValue = parts[idx];
}
else
{
var altIdx = Array.FindIndex(headers, h =>
string.Equals(h, fieldName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(h, fieldName + "_is", StringComparison.OrdinalIgnoreCase));
if (altIdx >= 0 && altIdx < parts.Length)
resolvedValue = parts[altIdx];
else
resolvedValue = "";
}
}
fieldValues[fieldName] = resolvedValue ?? "";
}
string GetField(string name)
{
return fieldValues.TryGetValue(name, out var v) ? v : "";
}
var refsid = 0;
if (patch.has_refsid)
refsid = ParseInt(GetField("refsid"));
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count), refsid,
GetField("anrede"),
GetField("titel"),
GetField("vorname"),
GetField("adel"),
GetField("name"),
GetField("namezus"),
GetField("anredzus"),
GetField("strasse"),
GetField("strasse2"),
GetField("plz"),
GetField("ort"),
GetField("land"),
GetField("pplz"),
GetField("postfach"),
GetField("name1"),
GetField("name2"),
GetField("name3"),
GetField("name4"),
GetField("name5"),
GetField("funktion"),
GetField("funktion2"),
GetField("abteilung"),
GetField("funktionad")
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Console.WriteLine($"Error while parsing line: {line} - {ex.Message}");
Console.WriteLine(ex.StackTrace);
return (false, null); return (false, null);
} }
}
return (true, imported); using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var headers = ParseCsvLine(headerLine, separator);
var imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var patchType = typeof(AddressPatch);
var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
var hasProperties = patchType.GetProperties(binding)
.Where(p => p.PropertyType == typeof(bool) && p.Name.StartsWith("has_", StringComparison.OrdinalIgnoreCase))
.ToArray();
//var last_refsid = 1000000;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
var fieldValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
//var refsid_existing = false;
foreach (var hasProp in hasProperties)
{
var fieldName = hasProp.Name.Substring(4);
var hasObj = hasProp.GetValue(patch);
var has = hasObj is bool b && b;
var patchProp = patchType.GetProperty(fieldName + "_is", binding);
string desiredHeader = null;
if (has && patchProp != null)
desiredHeader = patchProp.GetValue(patch)?.ToString();
else
desiredHeader = fieldName;
var resolvedValue = "";
if (!string.IsNullOrEmpty(desiredHeader))
{
var idx = Array.FindIndex(headers,
h => string.Equals(h, desiredHeader, StringComparison.OrdinalIgnoreCase));
if (idx >= 0 && idx < parts.Length)
{
resolvedValue = parts[idx];
}
else
{
var altIdx = Array.FindIndex(headers, h =>
string.Equals(h, fieldName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(h, fieldName + "_is", StringComparison.OrdinalIgnoreCase));
if (altIdx >= 0 && altIdx < parts.Length)
resolvedValue = parts[altIdx];
else
resolvedValue = "";
}
}
fieldValues[fieldName] = resolvedValue ?? "";
}
string GetField(string name)
{
return fieldValues.TryGetValue(name, out var v) ? v : "";
}
var refsid = 0;
if (patch.has_refsid)
refsid = ParseInt(GetField("refsid"));
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count), refsid,
GetField("anrede"),
GetField("titel"),
GetField("vorname"),
GetField("adel"),
GetField("name"),
GetField("namezus"),
GetField("anredzus"),
GetField("strasse"),
GetField("strasse2"),
GetField("plz"),
GetField("ort"),
GetField("land"),
GetField("pplz"),
GetField("postfach"),
GetField("name1"),
GetField("name2"),
GetField("name3"),
GetField("name4"),
GetField("name5"),
GetField("funktion"),
GetField("funktion2"),
GetField("abteilung"),
GetField("funktionad")
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Logger.Log($"Error while creating kas person (import, patch): {ex.Message}",Logger.LogType.Error);
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
return (true, imported);
}
catch (Exception ex)
{
Logger.Log($"Error while importing kas address list with patch: {ex.Message}",Logger.LogType.Error);
}
// int GenerateNewRefsid() // int GenerateNewRefsid()
// { // {
@@ -236,6 +253,7 @@ public class DataImport
// last_refsid = biggest + 1; // last_refsid = biggest + 1;
// return last_refsid; // return last_refsid;
// } // }
return (false, null);
} }
@@ -246,38 +264,47 @@ public class DataImport
private static string[] ParseCsvLine(string line, char separator) private static string[] ParseCsvLine(string line, char separator)
{ {
var fields = new List<string>(); try
var current = new StringBuilder();
var inQuotes = false;
for (var i = 0; i < line.Length; i++)
{ {
var c = line[i]; var fields = new List<string>();
var current = new StringBuilder();
var inQuotes = false;
if (c == '"') for (var i = 0; i < line.Length; i++)
{ {
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"') var c = line[i];
if (c == '"')
{ {
current.Append('"'); if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
i++; {
current.Append('"');
i++;
continue;
}
inQuotes = !inQuotes;
continue; continue;
} }
inQuotes = !inQuotes; if (c == separator && !inQuotes)
continue; {
fields.Add(current.ToString().Trim());
current.Clear();
continue;
}
current.Append(c);
} }
if (c == separator && !inQuotes) fields.Add(current.ToString().Trim());
{ return fields.ToArray();
fields.Add(current.ToString().Trim()); }
current.Clear(); catch (Exception ex)
continue; {
} Logger.Log($"Error while persing csv line: {ex.Message}",Logger.LogType.Error);
current.Append(c);
} }
fields.Add(current.ToString().Trim()); return [];
return fields.ToArray();
} }
} }
+303 -210
View File
@@ -18,45 +18,73 @@ public class PdfBuilder
public PdfBuilder(PdfExportSettings? settings = null) public PdfBuilder(PdfExportSettings? settings = null)
{ {
EnsureFontResolverRegistered();
_settings = settings ?? new PdfExportSettings();
// Select first font from build output fonts folder (AppContext.BaseDirectory/fonts)
var chosenFamily = "Arial";
try try
{ {
if (Directory.Exists(Global._instance.font_path)) EnsureFontResolverRegistered();
{ _settings = settings ?? new PdfExportSettings();
var first = Directory.EnumerateFiles(Global._instance.font_path, "*.ttf").FirstOrDefault();
if (!string.IsNullOrEmpty(first)) // Select first font from build output fonts folder (AppContext.BaseDirectory/fonts)
chosenFamily = StripStyleSuffix(Path.GetFileNameWithoutExtension(first)) ?? chosenFamily; var chosenFamily = "Arial";
} try
} {
catch if (Directory.Exists(Global._instance.font_path))
{ {
chosenFamily = "Arial"; var first = Directory.EnumerateFiles(Global._instance.font_path, "*.ttf").FirstOrDefault();
} if (!string.IsNullOrEmpty(first))
chosenFamily = StripStyleSuffix(Path.GetFileNameWithoutExtension(first)) ?? chosenFamily;
}
}
catch (Exception ex)
{
Logger.Log($"Error while searching for fonts: {ex.Message}",Logger.LogType.Error);
chosenFamily = "Arial";
}
_boldFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Bold);
_regularFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Regular);
_smallFont = new XFont(chosenFamily, _settings.smallFontSize, XFontStyleEx.Regular);
}
catch (Exception ex)
{
Logger.Log($"Error while font resolving: {ex.Message}",Logger.LogType.Error);
}
_boldFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Bold);
_regularFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Regular);
_smallFont = new XFont(chosenFamily, _settings.smallFontSize, XFontStyleEx.Regular);
} }
private static void EnsureFontResolverRegistered() private static void EnsureFontResolverRegistered()
{ {
if (GlobalFontSettings.FontResolver != null) return; try
//var fontsDir = Path.Combine(AppContext.BaseDirectory, "fonts"); {
GlobalFontSettings.FontResolver = new StableFontResolver(Global._instance.font_path); if (GlobalFontSettings.FontResolver != null) return;
//var fontsDir = Path.Combine(AppContext.BaseDirectory, "fonts");
GlobalFontSettings.FontResolver = new StableFontResolver(Global._instance.font_path);
}
catch (Exception ex)
{
Logger.Log($"Error while ensuring font resolver register state: {ex.Message}",Logger.LogType.Error);
}
} }
private static string StripStyleSuffix(string name) private static string StripStyleSuffix(string name)
{ {
if (string.IsNullOrEmpty(name)) return name; try
var idx = name.IndexOf('-'); {
if (idx < 0) idx = name.IndexOf('_'); if (string.IsNullOrEmpty(name)) return name;
if (idx > 0) var idx = name.IndexOf('-');
return name.Substring(0, idx); if (idx < 0) idx = name.IndexOf('_');
return name; if (idx > 0)
return name.Substring(0, idx);
return name;
}
catch (Exception ex)
{
Logger.Log($"Error while stripping style suffix: {ex.Message}",Logger.LogType.Error);
}
return null;
} }
@@ -69,42 +97,50 @@ public class PdfBuilder
public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText, public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText,
string outputPath) string outputPath)
{ {
// Find the AddressSet by ID
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
if (addressSet == null)
throw new ArgumentException($"AddressSet with ID {addressSetId} not found");
if (addressSet.KasPersons == null || addressSet.KasPersons.Count == 0)
throw new ArgumentException($"AddressSet with ID {addressSetId} contains no addresses");
// Generate markdown addresses from all KasPersons in the set
//var addresses = new string?[addressSet.KasPersons.Count];
var addresses = new List<string>();
// find customer (owner) to include sender_address
string senderLine = null;
try try
{ {
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id); // Find the AddressSet by ID
if (owner != null && !string.IsNullOrWhiteSpace(owner.sender_address)) var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
senderLine = "<font6>" + owner.sender_address.Replace("\n", " ").Trim() + "</font6>\n"; if (addressSet == null)
} throw new ArgumentException($"AddressSet with ID {addressSetId} not found");
catch
{
senderLine = null;
}
for (var i = 0; i < addressSet.KasPersons.Count; i++) if (addressSet.KasPersons == null || addressSet.KasPersons.Count == 0)
{ throw new ArgumentException($"AddressSet with ID {addressSetId} contains no addresses");
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
if (string.IsNullOrWhiteSpace(addr)) continue;
if (!string.IsNullOrEmpty(senderLine))
addresses.Add(senderLine + (addr ?? ""));
else
addresses.Add(addr);
}
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath); // Generate markdown addresses from all KasPersons in the set
//var addresses = new string?[addressSet.KasPersons.Count];
var addresses = new List<string>();
// find customer (owner) to include sender_address
string senderLine = null;
try
{
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id);
if (owner != null && !string.IsNullOrWhiteSpace(owner.sender_address))
senderLine = "<font6>" + owner.sender_address.Replace("\n", " ").Trim() + "</font6>\n";
}
catch
{
senderLine = null;
}
for (var i = 0; i < addressSet.KasPersons.Count; i++)
{
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
if (string.IsNullOrWhiteSpace(addr)) continue;
if (!string.IsNullOrEmpty(senderLine))
addresses.Add(senderLine + (addr ?? ""));
else
addresses.Add(addr);
}
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
}
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf from address set with placeholder: {ex.Message}",Logger.LogType.Error);
}
} }
/// <summary> /// <summary>
@@ -115,26 +151,34 @@ public class PdfBuilder
/// <param name="outputPath">Path where the PDF should be saved</param> /// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfWithPlaceholder(List<string> addresses, string placeholderText, string outputPath) public void CreateAddressLabelPdfWithPlaceholder(List<string> addresses, string placeholderText, string outputPath)
{ {
if (addresses == null || addresses.Count == 0) try
throw new ArgumentException("Addresses array cannot be null or empty");
var document = new PdfDocument();
var addressIndex = 0;
var isFirstCell = true;
while (addressIndex < addresses.Count || isFirstCell)
{ {
var page = document.AddPage(); if (addresses == null || addresses.Count == 0)
page.Size = PageSize.A4; throw new ArgumentException("Addresses array cannot be null or empty");
using (var gfx = XGraphics.FromPdfPage(page)) var document = new PdfDocument();
{
DrawPageWithPlaceholder(gfx, addresses, ref addressIndex, ref isFirstCell, placeholderText); var addressIndex = 0;
} var isFirstCell = true;
while (addressIndex < addresses.Count || isFirstCell)
{
var page = document.AddPage();
page.Size = PageSize.A4;
using (var gfx = XGraphics.FromPdfPage(page))
{
DrawPageWithPlaceholder(gfx, addresses, ref addressIndex, ref isFirstCell, placeholderText);
}
}
document.Save(outputPath);
} }
catch (Exception ex)
document.Save(outputPath); {
Logger.Log($"Error while creating address label pdf with placeholder: {ex.Message}",Logger.LogType.Error);
}
} }
private void DrawPage(XGraphics gfx, List<string> addresses, ref int addressIndex) private void DrawPage(XGraphics gfx, List<string> addresses, ref int addressIndex)
@@ -159,193 +203,242 @@ public class PdfBuilder
private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex, private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex,
ref bool isFirstCell, string placeholderText) ref bool isFirstCell, string placeholderText)
{ {
for (var row = 0; row < _settings.rowsPerPage; row++) try
for (var col = 0; col < _settings.columnsPerPage; col++)
{ {
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm()); for (var row = 0; row < _settings.rowsPerPage; row++)
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm()); for (var col = 0; col < _settings.columnsPerPage; col++)
{
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
// First cell: placeholder // First cell: placeholder
if (isFirstCell) if (isFirstCell)
{ {
DrawCell(gfx, x, y, placeholderText); DrawCell(gfx, x, y, placeholderText);
isFirstCell = false; isFirstCell = false;
} }
else if (addressIndex < addresses.Count) else if (addressIndex < addresses.Count)
{ {
DrawCell(gfx, x, y, addresses[addressIndex]); DrawCell(gfx, x, y, addresses[addressIndex]);
addressIndex++; addressIndex++;
} }
else else
{ {
DrawEmptyCell(gfx, x, y); DrawEmptyCell(gfx, x, y);
}
} }
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing page with placholder: {ex.Message}",Logger.LogType.Error);
}
} }
private void DrawCell(XGraphics gfx, double x, double y, string? address) private void DrawCell(XGraphics gfx, double x, double y, string? address)
{ {
var cellWidthPoints = MmToPoints(GetCellWidthMm()); try
var cellHeightPoints = MmToPoints(GetCellHeightMm()); {
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
// Draw cell border // Draw cell border
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints); var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect); gfx.DrawRectangle(XPens.Black, rect);
// Draw address content if available // Draw address content if available
if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints); if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints);
}
catch (Exception ex)
{
Logger.Log($"Error while drawing cell: {ex.Message}",Logger.LogType.Error);
}
} }
private void DrawEmptyCell(XGraphics gfx, double x, double y) private void DrawEmptyCell(XGraphics gfx, double x, double y)
{ {
var cellWidthPoints = MmToPoints(GetCellWidthMm()); try
var cellHeightPoints = MmToPoints(GetCellHeightMm()); {
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints); var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect); gfx.DrawRectangle(XPens.Black, rect);
}
catch (Exception ex)
{
Logger.Log($"Error while drawing empty cell: {ex.Message}",Logger.LogType.Error);
}
} }
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight) private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
{ {
var paddingLeftPoints = MmToPoints(_settings.cellPaddingLeftMm); try
var paddingRightPoints = MmToPoints(_settings.cellPaddingRightMm);
var paddingTopPoints = MmToPoints(_settings.cellPaddingTopMm);
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
// Split text by newlines and remove empty trailing lines
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
var lines = rawLines.Where(l => l != null).ToArray();
// Use a conservative line height in points
var lineHeight = _regularFont.Size * 1.2;
// Calculate total height of the text block
var totalHeight = lines.Length * lineHeight;
// Start drawing from the top of the cell (align addresses to top)
var startY = y + paddingTopPoints;
var currentY = startY;
foreach (var line in lines)
{ {
// Stop if we've reached the top boundary var paddingLeftPoints = MmToPoints(_settings.cellPaddingLeftMm);
if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001) var paddingRightPoints = MmToPoints(_settings.cellPaddingRightMm);
break; var paddingTopPoints = MmToPoints(_settings.cellPaddingTopMm);
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
// Parse and draw the line with markdown support var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
currentY += lineHeight; // Split text by newlines and remove empty trailing lines
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
var lines = rawLines.Where(l => l != null).ToArray();
// Use a conservative line height in points
var lineHeight = _regularFont.Size * 1.2;
// Calculate total height of the text block
var totalHeight = lines.Length * lineHeight;
// Start drawing from the top of the cell (align addresses to top)
var startY = y + paddingTopPoints;
var currentY = startY;
foreach (var line in lines)
{
// Stop if we've reached the top boundary
if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001)
break;
// Parse and draw the line with markdown support
DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
currentY += lineHeight;
}
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown text: {ex.Message}",Logger.LogType.Error);
}
} }
private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth) private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
{ {
if (string.IsNullOrWhiteSpace(line)) return; try
var currentX = x;
var i = 0;
while (i < line.Length)
{ {
if (currentX - x >= maxWidth) if (string.IsNullOrWhiteSpace(line)) return;
break; var currentX = x;
var i = 0;
var remainingWidth = maxWidth - (currentX - x); while (i < line.Length)
// Check for small-font tag <font6> ... </font6>
if (i <= line.Length - 7 && line.Substring(i, 7) == "<font6>")
{ {
var endTag = line.IndexOf("</font6>", i + 7, StringComparison.Ordinal); if (currentX - x >= maxWidth)
if (endTag != -1) break;
{
var inner = line.Substring(i + 7, endTag - (i + 7));
if (!string.IsNullOrEmpty(inner))
{
var measuredSmall = gfx.MeasureString(inner, _smallFont);
if (measuredSmall.Width > remainingWidth) var remainingWidth = maxWidth - (currentX - x);
// Check for small-font tag <font6> ... </font6>
if (i <= line.Length - 7 && line.Substring(i, 7) == "<font6>")
{
var endTag = line.IndexOf("</font6>", i + 7, StringComparison.Ordinal);
if (endTag != -1)
{
var inner = line.Substring(i + 7, endTag - (i + 7));
if (!string.IsNullOrEmpty(inner))
{ {
inner = TruncateTextToWidth(gfx, inner, _smallFont, remainingWidth); var measuredSmall = gfx.MeasureString(inner, _smallFont);
measuredSmall = gfx.MeasureString(inner, _smallFont);
if (measuredSmall.Width > remainingWidth)
{
inner = TruncateTextToWidth(gfx, inner, _smallFont, remainingWidth);
measuredSmall = gfx.MeasureString(inner, _smallFont);
}
gfx.DrawString(inner, _smallFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _smallFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measuredSmall.Width;
} }
gfx.DrawString(inner, _smallFont, XBrushes.Black, i = endTag + 8; // move past </font6>
new XRect(currentX, y, remainingWidth, _smallFont.Size * 1.2), continue;
XStringFormats.TopLeft);
currentX += measuredSmall.Width;
} }
i = endTag + 8; // move past </font6>
continue;
} }
}
// Check for bold marker (**) // Check for bold marker (**)
if (i < line.Length - 1 && line[i] == '*' && line[i + 1] == '*') if (i < line.Length - 1 && line[i] == '*' && line[i + 1] == '*')
{
// Find closing **
var endIndex = line.IndexOf("**", i + 2, StringComparison.Ordinal);
if (endIndex != -1)
{ {
var boldText = line.Substring(i + 2, endIndex - (i + 2)); // Find closing **
var measured = gfx.MeasureString(boldText, _boldFont); var endIndex = line.IndexOf("**", i + 2, StringComparison.Ordinal);
if (endIndex != -1)
{
var boldText = line.Substring(i + 2, endIndex - (i + 2));
var measured = gfx.MeasureString(boldText, _boldFont);
if (measured.Width > remainingWidth)
{
boldText = TruncateTextToWidth(gfx, boldText, _boldFont, remainingWidth);
measured = gfx.MeasureString(boldText, _boldFont);
}
// Draw bold text and measure width accurately
gfx.DrawString(boldText, _boldFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _boldFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measured.Width;
i = endIndex + 2;
continue;
}
}
// Regular text until next ** or end of line
var nextBoldIndex = line.IndexOf("**", i, StringComparison.Ordinal);
var textEnd = nextBoldIndex == -1 ? line.Length : nextBoldIndex;
var regularText = line.Substring(i, textEnd - i);
if (!string.IsNullOrEmpty(regularText))
{
var measured = gfx.MeasureString(regularText, _regularFont);
if (measured.Width > remainingWidth) if (measured.Width > remainingWidth)
{ {
boldText = TruncateTextToWidth(gfx, boldText, _boldFont, remainingWidth); regularText = TruncateTextToWidth(gfx, regularText, _regularFont, remainingWidth);
measured = gfx.MeasureString(boldText, _boldFont); measured = gfx.MeasureString(regularText, _regularFont);
} }
// Draw bold text and measure width accurately gfx.DrawString(regularText, _regularFont, XBrushes.Black,
gfx.DrawString(boldText, _boldFont, XBrushes.Black, new XRect(currentX, y, remainingWidth, _regularFont.Size * 1.2), XStringFormats.TopLeft);
new XRect(currentX, y, remainingWidth, _boldFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measured.Width; currentX += measured.Width;
i = endIndex + 2;
continue;
}
}
// Regular text until next ** or end of line
var nextBoldIndex = line.IndexOf("**", i, StringComparison.Ordinal);
var textEnd = nextBoldIndex == -1 ? line.Length : nextBoldIndex;
var regularText = line.Substring(i, textEnd - i);
if (!string.IsNullOrEmpty(regularText))
{
var measured = gfx.MeasureString(regularText, _regularFont);
if (measured.Width > remainingWidth)
{
regularText = TruncateTextToWidth(gfx, regularText, _regularFont, remainingWidth);
measured = gfx.MeasureString(regularText, _regularFont);
} }
gfx.DrawString(regularText, _regularFont, XBrushes.Black, i = textEnd;
new XRect(currentX, y, remainingWidth, _regularFont.Size * 1.2), XStringFormats.TopLeft);
currentX += measured.Width;
} }
i = textEnd;
} }
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown line: {ex.Message}",Logger.LogType.Error);
}
} }
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth) private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
{ {
if (string.IsNullOrEmpty(text)) try
return text;
for (var len = text.Length; len > 0; len--)
{ {
var truncated = text.Substring(0, len); if (string.IsNullOrEmpty(text))
var measured = gfx.MeasureString(truncated, font); return text;
if (measured.Width <= maxWidth)
return truncated; for (var len = text.Length; len > 0; len--)
{
var truncated = text.Substring(0, len);
var measured = gfx.MeasureString(truncated, font);
if (measured.Width <= maxWidth)
return truncated;
}
return string.Empty;
}
catch (Exception ex)
{
Logger.Log($"Error while truncating text to width: {ex.Message}",Logger.LogType.Error);
} }
return string.Empty; return null;
} }
/// <summary> /// <summary>
+85 -58
View File
@@ -62,75 +62,102 @@ public class StableFontResolver : IFontResolver
} }
} }
} }
catch catch (Exception ex)
{ {
// ignore resolver init errors Logger.Log($"Error while initializing FontResolver: {ex.Message}",Logger.LogType.Error);
} }
} }
private static string NormalizeStyle(string s) private static string NormalizeStyle(string s)
{ {
if (string.IsNullOrEmpty(s)) return "Regular"; try
s = s.ToLowerInvariant();
if (s.Contains("bold") && s.Contains("italic")) return "BoldItalic";
if (s.Contains("bold")) return "Bold";
if (s.Contains("italic") || s.Contains("oblique")) return "Italic";
if (s.Contains("regular") || s == "r") return "Regular";
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s);
}
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
// If requested family exists, pick corresponding style if available
string familyToUse = null;
if (!string.IsNullOrEmpty(familyName) && _families.ContainsKey(familyName))
familyToUse = familyName;
if (familyToUse == null && _orderedFamilies.Count > 0)
familyToUse = _orderedFamilies[0];
if (familyToUse == null)
return new FontResolverInfo("Arial");
var style = "Regular";
if (isBold && isItalic) style = "BoldItalic";
else if (isBold) style = "Bold";
else if (isItalic) style = "Italic";
// Face name format: Family#Style
return new FontResolverInfo($"{familyToUse}#{style}");
}
public byte[] GetFont(string faceName)
{
if (string.IsNullOrEmpty(faceName)) return null;
// faceName expected as "Family#Style" or just "Family"
var family = faceName;
var style = "Regular";
var idx = faceName.IndexOf('#');
if (idx >= 0)
{ {
family = faceName.Substring(0, idx); if (string.IsNullOrEmpty(s)) return "Regular";
style = faceName.Substring(idx + 1); s = s.ToLowerInvariant();
if (s.Contains("bold") && s.Contains("italic")) return "BoldItalic";
if (s.Contains("bold")) return "Bold";
if (s.Contains("italic") || s.Contains("oblique")) return "Italic";
if (s.Contains("regular") || s == "r") return "Regular";
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s);
} }
catch (Exception ex)
style = NormalizeStyle(style);
if (_families.TryGetValue(family, out var dict))
{ {
// Try exact style Logger.Log($"Error while normalizing style: {ex.Message}",Logger.LogType.Error);
if (dict.TryGetValue(style, out var data)) return data;
// Fallback order
if (style != "Regular" && dict.TryGetValue("Regular", out data)) return data;
if (dict.TryGetValue("Bold", out data)) return data;
if (dict.TryGetValue("Italic", out data)) return data;
// Any available
foreach (var kv in dict) return kv.Value;
} }
return null; return null;
} }
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
try
{
// If requested family exists, pick corresponding style if available
string familyToUse = null;
if (!string.IsNullOrEmpty(familyName) && _families.ContainsKey(familyName))
familyToUse = familyName;
if (familyToUse == null && _orderedFamilies.Count > 0)
familyToUse = _orderedFamilies[0];
if (familyToUse == null)
return new FontResolverInfo("Arial");
var style = "Regular";
if (isBold && isItalic) style = "BoldItalic";
else if (isBold) style = "Bold";
else if (isItalic) style = "Italic";
// Face name format: Family#Style
return new FontResolverInfo($"{familyToUse}#{style}");
}
catch (Exception ex)
{
Logger.Log($"Error while resolving typeface: {ex.Message}",Logger.LogType.Error);
}
return null;
}
public byte[] GetFont(string faceName)
{
try
{
if (string.IsNullOrEmpty(faceName)) return null;
// faceName expected as "Family#Style" or just "Family"
var family = faceName;
var style = "Regular";
var idx = faceName.IndexOf('#');
if (idx >= 0)
{
family = faceName.Substring(0, idx);
style = faceName.Substring(idx + 1);
}
style = NormalizeStyle(style);
if (_families.TryGetValue(family, out var dict))
{
// Try exact style
if (dict.TryGetValue(style, out var data)) return data;
// Fallback order
if (style != "Regular" && dict.TryGetValue("Regular", out data)) return data;
if (dict.TryGetValue("Bold", out data)) return data;
if (dict.TryGetValue("Italic", out data)) return data;
// Any available
foreach (var kv in dict) return kv.Value;
}
return null;
}
catch (Exception ex)
{
Logger.Log($"Error while getting font: {ex.Message}",Logger.LogType.Error);
}
return [];
}
} }
+21 -15
View File
@@ -11,19 +11,23 @@ public partial class EditorWindow : Window
public EditorWindow(string filename = "") public EditorWindow(string filename = "")
{ {
InitializeComponent(); try
this.filename = filename;
if (!string.IsNullOrWhiteSpace(filename) && File.Exists(filename))
{ {
var content = TbContent; InitializeComponent();
if (content != null) content.Text = File.ReadAllText(this.filename); this.filename = PathUtilities.NormalizeFileSystemPath(filename);
Title = "Wiki Editor - " + filename; if (!string.IsNullOrWhiteSpace(this.filename) && File.Exists(this.filename))
} {
else if (!string.IsNullOrWhiteSpace(filename)) var content = TbContent;
{ if (content != null) content.Text = File.ReadAllText(this.filename);
MessageBox.Show(null, "Die Datei existiert nicht", "Fehler"); Title = "Wiki Editor - " + filename;
Close(); }
} else if (!string.IsNullOrWhiteSpace(this.filename))
{
MessageBox.Show(null, "Die Datei existiert nicht", "Fehler");
Close();
}
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
} }
private void BtnSave_OnClick(object? sender, RoutedEventArgs e) private void BtnSave_OnClick(object? sender, RoutedEventArgs e)
@@ -38,13 +42,15 @@ public partial class EditorWindow : Window
MessageBox.Show(null, MessageBox.Show(null,
"Es ist ein Fehler aufgetreten. Bitte senden Sie ihn über git.mypapercloud.de/fierke/logofclient ein:\n" + "Es ist ein Fehler aufgetreten. Bitte senden Sie ihn über git.mypapercloud.de/fierke/logofclient ein:\n" +
ex.StackTrace, "Fehler"); ex.StackTrace, "Fehler");
Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);
} }
} }
private void BtnSaveAs_OnClick(object? sender, RoutedEventArgs e) private void BtnSaveAs_OnClick(object? sender, RoutedEventArgs e)
{ {
MessageBox.Show(null, MessageBox.Show(null,
"Feature noch nicht implemetiert.\nErstelle neue Dateien unter " + Global._instance.wiki_storage_path, "Feature noch nicht implemetiert.\nErstelle neue Dateien unter " +
PathUtilities.NormalizeFileSystemPath(Global._instance.wiki_storage_path),
"Fehler"); "Fehler");
} }
@@ -55,9 +61,9 @@ public partial class EditorWindow : Window
try try
{ {
File.Delete(filename); File.Delete(filename);
} catch {} } catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
MainWindow._instance.PopulateNavTree(); MainWindow._instance.PopulateNavTree();
Close(); Close();
} }
} }
+107 -96
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Media; using Avalonia.Media;
@@ -12,118 +13,128 @@ public static class MarkdownRenderer
{ {
public static Control Render(string markdown) public static Control Render(string markdown)
{ {
var panel = new StackPanel { Spacing = 6 }; try
if (string.IsNullOrWhiteSpace(markdown)) return panel;
var doc = Markdown.Parse(markdown);
foreach (var block in doc)
{ {
switch (block) var panel = new StackPanel { Spacing = 6 };
if (string.IsNullOrWhiteSpace(markdown)) return panel;
var doc = Markdown.Parse(markdown);
foreach (var block in doc)
{ {
case HeadingBlock hb: switch (block)
{ {
var text = GetInlineText(hb.Inline); case HeadingBlock hb:
var tb = new TextBlock
{ {
Text = text, var text = GetInlineText(hb.Inline);
FontWeight = FontWeight.Bold, var tb = new TextBlock
Margin = new Avalonia.Thickness(0, hb.Level == 1 ? 6 : 2, 0, 2)
};
tb.FontSize = hb.Level switch { 1 => 22, 2 => 18, 3 => 16, _ => 14 };
panel.Children.Add(tb);
break;
}
case ParagraphBlock pb:
{
var text = GetInlineText(pb.Inline);
var tb = new TextBlock { Text = text, TextWrapping = Avalonia.Media.TextWrapping.Wrap };
panel.Children.Add(tb);
break;
}
case FencedCodeBlock cb:
{
var sb = new StringBuilder();
foreach (var line in cb.Lines.Lines)
{
sb.Append(line.ToString());
}
var codeBox = new TextBox
{
Text = sb.ToString(),
FontFamily = "Consolas, monospace",
IsReadOnly = true,
AcceptsReturn = true
};
panel.Children.Add(codeBox);
break;
}
case ListBlock lb:
{
var sp = new StackPanel { Spacing = 2 };
var number = 1;
foreach (var item in lb)
{
if (item is ListItemBlock lib)
{ {
var itemText = new StringBuilder(); Text = text,
foreach (var sub in lib) FontWeight = FontWeight.Bold,
{ Margin = new Avalonia.Thickness(0, hb.Level == 1 ? 6 : 2, 0, 2)
if (sub is ParagraphBlock pp) };
itemText.Append(GetInlineText(pp.Inline)); tb.FontSize = hb.Level switch { 1 => 22, 2 => 18, 3 => 16, _ => 14 };
} panel.Children.Add(tb);
var tb = new TextBlock { Text = (lb.IsOrdered ? (number++ + ". ") : "• ") + itemText.ToString() }; break;
sp.Children.Add(tb); }
}
case ParagraphBlock pb:
{
var text = GetInlineText(pb.Inline);
var tb = new TextBlock { Text = text, TextWrapping = Avalonia.Media.TextWrapping.Wrap };
panel.Children.Add(tb);
break;
}
case FencedCodeBlock cb:
{
var sb = new StringBuilder();
foreach (var line in cb.Lines.Lines)
{
sb.Append(line.ToString());
}
var codeBox = new TextBox
{
Text = sb.ToString(),
FontFamily = "Consolas, monospace",
IsReadOnly = true,
AcceptsReturn = true
};
panel.Children.Add(codeBox);
break;
}
case ListBlock lb:
{
var sp = new StackPanel { Spacing = 2 };
var number = 1;
foreach (var item in lb)
{
if (item is ListItemBlock lib)
{
var itemText = new StringBuilder();
foreach (var sub in lib)
{
if (sub is ParagraphBlock pp)
itemText.Append(GetInlineText(pp.Inline));
}
var tb = new TextBlock { Text = (lb.IsOrdered ? (number++ + ". ") : "• ") + itemText.ToString() };
sp.Children.Add(tb);
}
}
panel.Children.Add(sp);
break;
}
default:
{
// fallback: raw text
panel.Children.Add(new TextBlock { Text = block.ToString() });
break;
} }
panel.Children.Add(sp);
break;
}
default:
{
// fallback: raw text
panel.Children.Add(new TextBlock { Text = block.ToString() });
break;
} }
} }
}
return panel;
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
return panel; return new Panel();
} }
private static string GetInlineText(ContainerInline? container) private static string GetInlineText(ContainerInline? container)
{ {
if (container == null) return string.Empty; try
var sb = new StringBuilder();
foreach (var inline in container)
{ {
switch (inline) if (container == null) return string.Empty;
var sb = new StringBuilder();
foreach (var inline in container)
{ {
case LiteralInline li: switch (inline)
sb.Append(li.Content.ToString()); {
break; case LiteralInline li:
case EmphasisInline ei: sb.Append(li.Content.ToString());
sb.Append(GetInlineText(ei)); break;
break; case EmphasisInline ei:
case CodeInline ci: sb.Append(GetInlineText(ei));
sb.Append(ci.Content); break;
break; case CodeInline ci:
case LinkInline li: sb.Append(ci.Content);
sb.Append(GetInlineText(li)); break;
break; case LinkInline li:
case LineBreakInline: sb.Append(GetInlineText(li));
sb.Append("\n"); break;
break; case LineBreakInline:
default: sb.Append("\n");
sb.Append(inline.ToString()); break;
break; default:
sb.Append(inline.ToString());
break;
}
} }
}
return sb.ToString(); return sb.ToString();
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
return null;
} }
} }
+6 -3
View File
@@ -13,7 +13,7 @@ public class WikiService
// prefer global wiki storage path if configured // prefer global wiki storage path if configured
if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path)) if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path))
{ {
var cfg = Global._instance.wiki_storage_path; var cfg = PathUtilities.NormalizeFileSystemPath(Global._instance.wiki_storage_path);
WikiRootFullPath = Path.IsPathRooted(cfg) WikiRootFullPath = Path.IsPathRooted(cfg)
? cfg ? cfg
: Path.Combine(Directory.GetCurrentDirectory(), cfg); : Path.Combine(Directory.GetCurrentDirectory(), cfg);
@@ -25,8 +25,11 @@ public class WikiService
wikiRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), wikiRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient", "logofclient",
"wiki"); "wiki");
wikiRoot = PathUtilities.NormalizeFileSystemPath(wikiRoot);
WikiRoot = wikiRoot; WikiRoot = wikiRoot;
WikiRootFullPath = Path.Combine(Directory.GetCurrentDirectory(), wikiRoot); WikiRootFullPath = Path.IsPathRooted(wikiRoot)
? wikiRoot
: Path.Combine(Directory.GetCurrentDirectory(), wikiRoot);
} }
} }
@@ -68,4 +71,4 @@ public class WikiService
if (!File.Exists(path)) return Task.FromResult<string?>(null); if (!File.Exists(path)) return Task.FromResult<string?>(null);
return File.ReadAllTextAsync(path); return File.ReadAllTextAsync(path);
} }
} }
-157
View File
@@ -1,157 +0,0 @@
\documentclass[a4paper]{article}
\usepackage{graphicx}
\usepackage[left=3cm,right=3cm,top=3cm, bottom=3cm]{geometry}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage[headsepline, footsepline]{scrlayer-scrpage}
\usepackage{enumerate}
\usepackage{dsfont}
\usepackage[]{mathtools}
\usepackage[]{mathbbol}
\usepackage{multicol}
\usepackage{enumitem}
\usepackage[hidelinks]{hyperref}
\usepackage[]{circuitikz}
\usepackage{tcolorbox} % Für schöne Boxen
\usetikzlibrary{circuits.logic.IEC}
\usepackage{tikz}
\usepackage{tkz-graph}
\usepackage{listings}
\usepackage{xcolor}
\setlist{itemsep=0.3em, topsep=0.5em, parsep=0pt}
\newcommand{\bpf}[1]{%
\par\vspace{0.8\baselineskip}% Abstand vor der Überschrift
\noindent\textbf{#1}% Fettgedruckte Überschrift
\par\vspace{0.3\baselineskip}% Abstand nach der Überschrift
}
\renewcommand{\contentsname}{Inhaltsverzeichnis}
\renewcommand{\figurename}{Grafik}
\renewcommand{\partname}{Teil}
\renewcommand{\epsilon}{\varepsilon}
\definecolor{darkgrey}{HTML}{232327}
% Zählerdefinition mit AUTOMATISCHEM SUBSECTION-RESET
\newcounter{commoncounter}[subsection]
\renewcommand{\thecommoncounter}{\thesubsection.\arabic{commoncounter}}
% Zähler direkt bei Dokumentstart initialisieren
\AtBeginDocument{\setcounter{commoncounter}{0}}
% BOX-DEFINITIONEN mit korrigierter Zählerlogik
\newtcolorbox{definitionbox}[1][]{
before title={\refstepcounter{commoncounter}}, % KRITISCH: vor dem Titel!
title={Definition \thecommoncounter: #1},
colback=white,
colframe=white!75!darkgrey,
fonttitle=\bfseries,
boxrule=0.6mm,
coltitle=black,
rounded corners,
before skip=10pt,
after skip=10pt
}
\newtcolorbox{examplebox}[1][]{
before title={\refstepcounter{commoncounter}}, % KRITISCH: vor dem Titel!
title={Beispiel \thecommoncounter: #1},
colback=white,
colframe=white!75!orange,
fonttitle=\bfseries,
boxrule=0.6mm,
coltitle=black,
rounded corners,
before skip=10pt,
after skip=10pt
}
\newtcolorbox{satzbox}[1][]{
before title={\refstepcounter{commoncounter}}, % KRITISCH: vor dem Titel!
title={Satz \thecommoncounter: #1},
colback=white,
colframe=white!75!blue,
fonttitle=\bfseries,
boxrule=0.6mm,
coltitle=black,
rounded corners,
before skip=10pt,
after skip=10pt
}
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codeblue}{rgb}{0,0,0.8}
\definecolor{codered}{rgb}{0.8,0,0}
\definecolor{lightgray}{rgb}{0.95,0.95,0.95}
\lstdefinestyle{CSharpStyle}{
language=Python,
basicstyle=\ttfamily\small, % Monospace-Schrift
keywordstyle=\color{blue}\bfseries, % Schlüsselwörter fett und blau
stringstyle=\color{red}, % Strings rot
commentstyle=\color{codegreen}, % Kommentare grün
backgroundcolor=\color{lightgray}, % Hintergrundfarbe
numbers=left, % Zeilennummern links
numbersep=10px, % Abstand zwischen Zeilennummern und Code
numberstyle=\color{gray}\texttt,
stepnumber=1, % Zeilennummerierung Schrittweite 1
frame=single, % Rahmen um den Code
tabsize=4, % Tabulatorgröße
breaklines=true, % Zeilenumbruch aktivieren
captionpos=none,
showstringspaces=false,
xleftmargin=15pt, % Linker Rand für den Code (verschiebt alles nach rechts)
}
% Umgebung für Listings mit Titel und Zähler
\newenvironment{codeexample}[1][]{
\refstepcounter{commoncounter} % Zähler erhöhen
\lstset{
style=CSharpStyle,
caption={Listing \thecommoncounter: #1}, % Titel mit Zähler
label={listing:\thecommoncounter}
}
}{}
\setlength{\parindent}{0pt}
\title{\includegraphics[width=0.3\textwidth]{../assets/icon.png}\vspace{15pt}\\Logof Client\\
Handbuch}
\author{Elias Fierke}
\pagestyle{scrheadings}
\date{Oktober 2025}
\begin{document}
\ohead{Oktober 2025}
\ofoot{Seite {\pagemark} von \pageref{LastPage}}
\ihead{Logof Client Handbuch}
\maketitle
\tableofcontents
\newpage
\part{Einführung}
\newpage
\part{Installation}
\newpage
\part{Adress-Verwaltung}
\label{LastPage}
\end{document}