From 286960429bef530d2731de40d049703e1b1675c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 08:26:26 +0000 Subject: [PATCH 001/962] Bump Microsoft.Extensions.DependencyInjection and Microsoft.NETFramework.ReferenceAssemblies Bumps [Microsoft.Extensions.DependencyInjection](https://github.com/dotnet/runtime) and [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet). These dependencies needed to be updated together. Updates `Microsoft.Extensions.DependencyInjection` from 9.0.1 to 9.0.3 - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v9.0.1...v9.0.3) Updates `Microsoft.NETFramework.ReferenceAssemblies` from 1.0.3 to 1.0.3 - [Commits](https://github.com/Microsoft/dotnet/commits) --- updated-dependencies: - dependency-name: Microsoft.Extensions.DependencyInjection dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.NETFramework.ReferenceAssemblies dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- TestStatements/AppWithPlugin/AppWithPlugin.csproj | 2 +- TestStatements/TestStatements/TestStatements.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TestStatements/AppWithPlugin/AppWithPlugin.csproj b/TestStatements/AppWithPlugin/AppWithPlugin.csproj index e3444f15a..01ee4c7fa 100644 --- a/TestStatements/AppWithPlugin/AppWithPlugin.csproj +++ b/TestStatements/AppWithPlugin/AppWithPlugin.csproj @@ -13,7 +13,7 @@ - + diff --git a/TestStatements/TestStatements/TestStatements.csproj b/TestStatements/TestStatements/TestStatements.csproj index 5f3e2b84e..906e9d906 100644 --- a/TestStatements/TestStatements/TestStatements.csproj +++ b/TestStatements/TestStatements/TestStatements.csproj @@ -58,7 +58,7 @@ - + From 725ce55dbcec2c392df5944c49939fd6f69357b0 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Wed, 10 Dec 2025 19:17:11 +0100 Subject: [PATCH 002/962] Document.Base --- .../DocOdfTest/DocOdfTest.csproj | 21 ++ .../Data/DocumentUtils/DocOdfTest/Program.cs | 29 ++ .../Document.Base/Document.Base.csproj | 8 +- .../Document.Odf/Writing/OdfXmlWriter.cs | 345 ++++++++++++++++++ .../WpfApp1/Views/Frm_W2kMain.xaml | 58 +++ .../WpfApp1/Views/Frm_W2kMain.xaml.cs | 17 + 6 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 CSharpBible/Data/DocumentUtils/DocOdfTest/DocOdfTest.csproj create mode 100644 CSharpBible/Data/DocumentUtils/DocOdfTest/Program.cs create mode 100644 CSharpBible/Data/DocumentUtils/Document.Odf/Writing/OdfXmlWriter.cs create mode 100644 CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml create mode 100644 CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml.cs diff --git a/CSharpBible/Data/DocumentUtils/DocOdfTest/DocOdfTest.csproj b/CSharpBible/Data/DocumentUtils/DocOdfTest/DocOdfTest.csproj new file mode 100644 index 000000000..a1c1c6c17 --- /dev/null +++ b/CSharpBible/Data/DocumentUtils/DocOdfTest/DocOdfTest.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + enable + enable + + + $(TargetFrameworks);net9.0 + + + $(TargetFrameworks);net10.0 + + + + + + + + diff --git a/CSharpBible/Data/DocumentUtils/DocOdfTest/Program.cs b/CSharpBible/Data/DocumentUtils/DocOdfTest/Program.cs new file mode 100644 index 000000000..e86784e6d --- /dev/null +++ b/CSharpBible/Data/DocumentUtils/DocOdfTest/Program.cs @@ -0,0 +1,29 @@ +using Document.Base.Models; +using Document.Odf; + +namespace DocOdfTest; + +internal class Program +{ + static void Main(string[] args) + { + // Neues Dokument erstellen + var doc = new OdfTextDocument(); + + // Überschrift hinzufügen + var h1 = doc.AddHeadline(1); + h1.AppendText("Mein Dokument"); + + // Absatz hinzufügen + var p = doc.AddParagraph(""); + p.AppendText("Dies ist ein "); + p.AddSpan("fetter", EFontStyle.Bold); + p.AppendText(" Text."); + + // Speichern + doc.SaveTo("test.odt"); + + // Laden + doc.LoadFrom("existing.odt"); + } +} diff --git a/CSharpBible/Data/DocumentUtils/Document.Base/Document.Base.csproj b/CSharpBible/Data/DocumentUtils/Document.Base/Document.Base.csproj index f817901a6..1a195279a 100644 --- a/CSharpBible/Data/DocumentUtils/Document.Base/Document.Base.csproj +++ b/CSharpBible/Data/DocumentUtils/Document.Base/Document.Base.csproj @@ -1,10 +1,16 @@  - net8.0;net9.0;net10.0 + net8.0 enable enable preview + + $(TargetFrameworks);net9.0 + + + $(TargetFrameworks);net10.0 + diff --git a/CSharpBible/Data/DocumentUtils/Document.Odf/Writing/OdfXmlWriter.cs b/CSharpBible/Data/DocumentUtils/Document.Odf/Writing/OdfXmlWriter.cs new file mode 100644 index 000000000..1affcaf8e --- /dev/null +++ b/CSharpBible/Data/DocumentUtils/Document.Odf/Writing/OdfXmlWriter.cs @@ -0,0 +1,345 @@ +using System.Xml.Linq; +using Document.Base.Models.Interfaces; +using Document.Odf.Models; + +namespace Document.Odf.Writing; + +/// +/// Generiert ODF-XML-Dokumente aus dem Dokumentmodell. +/// +public static class OdfXmlWriter +{ + /// + /// Erstellt ein vollstndiges content.xml fr ein ODF-Textdokument. + /// + public static XDocument CreateContentXml(OdfSection root) + { + var officeText = new XElement(OdfNamespaces.Office + "text"); + + foreach (var node in root.Nodes) + { + var el = ConvertNode(node); + if (el != null) + officeText.Add(el); + } + + var doc = new XDocument( + new XDeclaration("1.0", "UTF-8", null), + new XElement(OdfNamespaces.Office + "document-content", + new XAttribute(XNamespace.Xmlns + "office", OdfNamespaces.Office), + new XAttribute(XNamespace.Xmlns + "text", OdfNamespaces.Text), + new XAttribute(XNamespace.Xmlns + "style", OdfNamespaces.Style), + new XAttribute(XNamespace.Xmlns + "fo", OdfNamespaces.Fo), + new XAttribute(XNamespace.Xmlns + "xlink", OdfNamespaces.XLink), + new XAttribute(XNamespace.Xmlns + "draw", OdfNamespaces.Draw), + new XAttribute(XNamespace.Xmlns + "table", OdfNamespaces.Table), + new XAttribute(XNamespace.Xmlns + "svg", OdfNamespaces.Svg), + new XAttribute(OdfNamespaces.Office + "version", "1.3"), + CreateAutomaticStyles(root), + new XElement(OdfNamespaces.Office + "body", officeText) + ) + ); + + return doc; + } + + /// + /// Erstellt ein minimales styles.xml. + /// + public static XDocument CreateStylesXml() + { + return new XDocument( + new XDeclaration("1.0", "UTF-8", null), + new XElement(OdfNamespaces.Office + "document-styles", + new XAttribute(XNamespace.Xmlns + "office", OdfNamespaces.Office), + new XAttribute(XNamespace.Xmlns + "style", OdfNamespaces.Style), + new XAttribute(XNamespace.Xmlns + "fo", OdfNamespaces.Fo), + new XAttribute(XNamespace.Xmlns + "text", OdfNamespaces.Text), + new XAttribute(OdfNamespaces.Office + "version", "1.3"), + new XElement(OdfNamespaces.Office + "styles", + CreateDefaultParagraphStyle(), + CreateHeadingStyles() + ) + ) + ); + } + + /// + /// Erstellt ein minimales meta.xml. + /// + public static XDocument CreateMetaXml(string? title = null, string? creator = null) + { + var meta = new XElement(OdfNamespaces.Office + "meta"); + + if (!string.IsNullOrEmpty(title)) + meta.Add(new XElement(OdfNamespaces.Dc + "title", title)); + + if (!string.IsNullOrEmpty(creator)) + meta.Add(new XElement(OdfNamespaces.Dc + "creator", creator)); + + meta.Add(new XElement(OdfNamespaces.Meta + "creation-date", DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"))); + meta.Add(new XElement(OdfNamespaces.Meta + "generator", "Document.Odf")); + + return new XDocument( + new XDeclaration("1.0", "UTF-8", null), + new XElement(OdfNamespaces.Office + "document-meta", + new XAttribute(XNamespace.Xmlns + "office", OdfNamespaces.Office), + new XAttribute(XNamespace.Xmlns + "dc", OdfNamespaces.Dc), + new XAttribute(XNamespace.Xmlns + "meta", OdfNamespaces.Meta), + new XAttribute(OdfNamespaces.Office + "version", "1.3"), + meta + ) + ); + } + + private static XElement CreateAutomaticStyles(OdfSection root) + { + var autoStyles = new XElement(OdfNamespaces.Office + "automatic-styles"); + var usedStyles = new HashSet(); + + CollectUsedStyles(root, usedStyles); + + foreach (var styleName in usedStyles) + { + var style = CreateAutomaticStyle(styleName); + if (style != null) + autoStyles.Add(style); + } + + return autoStyles; + } + + private static void CollectUsedStyles(IDOMElement element, HashSet styles) + { + if (element is OdfSpan span && span.FontStyle != null) + { + var styleName = GetAutoStyleName(span.FontStyle); + if (!string.IsNullOrEmpty(styleName)) + styles.Add(styleName); + } + + foreach (var child in element.Nodes) + { + if (child is IDOMElement childEl) + CollectUsedStyles(childEl, styles); + } + } + + private static XElement? CreateAutomaticStyle(string styleName) + { + var textProps = new XElement(OdfNamespaces.Style + "text-properties"); + bool hasProps = false; + + if (styleName.Contains("Bold")) + { + textProps.Add(new XAttribute(OdfNamespaces.Fo + "font-weight", "bold")); + hasProps = true; + } + if (styleName.Contains("Italic")) + { + textProps.Add(new XAttribute(OdfNamespaces.Fo + "font-style", "italic")); + hasProps = true; + } + if (styleName.Contains("Underline")) + { + textProps.Add(new XAttribute(OdfNamespaces.Style + "text-underline-style", "solid")); + textProps.Add(new XAttribute(OdfNamespaces.Style + "text-underline-width", "auto")); + hasProps = true; + } + if (styleName.Contains("Strikeout")) + { + textProps.Add(new XAttribute(OdfNamespaces.Style + "text-line-through-style", "solid")); + hasProps = true; + } + + if (!hasProps) return null; + + return new XElement(OdfNamespaces.Style + "style", + new XAttribute(OdfNamespaces.Style + "name", styleName), + new XAttribute(OdfNamespaces.Style + "family", "text"), + textProps + ); + } + + private static string? GetAutoStyleName(IDocFontStyle fontStyle) + { + var parts = new List(); + if (fontStyle.Bold) parts.Add("Bold"); + if (fontStyle.Italic) parts.Add("Italic"); + if (fontStyle.Underline) parts.Add("Underline"); + if (fontStyle.Strikeout) parts.Add("Strikeout"); + + return parts.Count > 0 ? "T_" + string.Join("_", parts) : null; + } + + private static XElement CreateDefaultParagraphStyle() + { + return new XElement(OdfNamespaces.Style + "default-style", + new XAttribute(OdfNamespaces.Style + "family", "paragraph"), + new XElement(OdfNamespaces.Style + "paragraph-properties", + new XAttribute(OdfNamespaces.Fo + "margin-top", "0cm"), + new XAttribute(OdfNamespaces.Fo + "margin-bottom", "0.212cm") + ), + new XElement(OdfNamespaces.Style + "text-properties", + new XAttribute(OdfNamespaces.Style + "font-name", "Liberation Serif"), + new XAttribute(OdfNamespaces.Fo + "font-size", "12pt") + ) + ); + } + + private static IEnumerable CreateHeadingStyles() + { + for (int level = 1; level <= 6; level++) + { + var fontSize = level switch + { + 1 => "24pt", + 2 => "18pt", + 3 => "14pt", + 4 => "12pt", + 5 => "10pt", + _ => "10pt" + }; + + yield return new XElement(OdfNamespaces.Style + "style", + new XAttribute(OdfNamespaces.Style + "name", $"Heading_{level}"), + new XAttribute(OdfNamespaces.Style + "family", "paragraph"), + new XAttribute(OdfNamespaces.Style + "parent-style-name", "Heading"), + new XAttribute(OdfNamespaces.Style + "default-outline-level", level.ToString()), + new XElement(OdfNamespaces.Style + "text-properties", + new XAttribute(OdfNamespaces.Fo + "font-size", fontSize), + new XAttribute(OdfNamespaces.Fo + "font-weight", "bold") + ) + ); + } + } + + private static XElement? ConvertNode(IDOMElement node) + { + return node switch + { + OdfParagraph p => ConvertParagraph(p), + OdfHeadline h => ConvertHeadline(h), + OdfTOC toc => ConvertTOC(toc), + _ => null + }; + } + + private static XElement ConvertParagraph(OdfParagraph p) + { + var el = new XElement(OdfNamespaces.Text + "p"); + + if (!string.IsNullOrEmpty(p.StyleName)) + el.Add(new XAttribute(OdfNamespaces.Text + "style-name", p.StyleName)); + + AddInlineContent(el, p); + return el; + } + + private static XElement ConvertHeadline(OdfHeadline h) + { + var el = new XElement(OdfNamespaces.Text + "h", + new XAttribute(OdfNamespaces.Text + "outline-level", h.Level.ToString()), + new XAttribute(OdfNamespaces.Text + "style-name", $"Heading_{h.Level}") + ); + + if (!string.IsNullOrEmpty(h.Id)) + el.Add(new XAttribute(OdfNamespaces.Text + "name", h.Id)); + + AddInlineContent(el, h); + return el; + } + + private static XElement ConvertTOC(OdfTOC toc) + { + var tocSource = new XElement(OdfNamespaces.Text + "table-of-content-source", + new XAttribute(OdfNamespaces.Text + "outline-level", toc.Level.ToString()) + ); + + var tocBody = new XElement(OdfNamespaces.Text + "index-body"); + + foreach (var child in toc.Nodes) + { + if (child is OdfParagraph p) + { + tocBody.Add(ConvertParagraph(p)); + } + } + + return new XElement(OdfNamespaces.Text + "table-of-content", + new XAttribute(OdfNamespaces.Text + "name", toc.Name), + tocSource, + tocBody + ); + } + + private static void AddInlineContent(XElement parent, OdfContentBase content) + { + if (!string.IsNullOrEmpty(content.TextContent)) + parent.Add(new XText(content.TextContent)); + + foreach (var child in content.Nodes) + { + var el = ConvertInlineNode(child); + if (el != null) + parent.Add(el); + } + } + + private static object? ConvertInlineNode(IDOMElement node) + { + return node switch + { + OdfSpan span => ConvertSpan(span), + OdfLineBreak => new XElement(OdfNamespaces.Text + "line-break"), + OdfNbSpace => new XElement(OdfNamespaces.Text + "s"), + OdfTab => new XElement(OdfNamespaces.Text + "tab"), + _ => null + }; + } + + private static XElement ConvertSpan(OdfSpan span) + { + XElement el; + + if (span.IsLink && !string.IsNullOrEmpty(span.Href)) + { + el = new XElement(OdfNamespaces.Text + "a", + new XAttribute(OdfNamespaces.XLink + "href", span.Href), + new XAttribute(OdfNamespaces.XLink + "type", "simple") + ); + } + else + { + el = new XElement(OdfNamespaces.Text + "span"); + + var styleName = GetAutoStyleName(span.FontStyle); + if (!string.IsNullOrEmpty(styleName)) + el.Add(new XAttribute(OdfNamespaces.Text + "style-name", styleName)); + } + + if (!string.IsNullOrEmpty(span.Id)) + { + el.AddFirst(new XElement(OdfNamespaces.Text + "bookmark-start", + new XAttribute(OdfNamespaces.Text + "name", span.Id))); + } + + if (!string.IsNullOrEmpty(span.TextContent)) + el.Add(new XText(span.TextContent)); + + foreach (var child in span.Nodes) + { + var childEl = ConvertInlineNode(child); + if (childEl != null) + el.Add(childEl); + } + + if (!string.IsNullOrEmpty(span.Id)) + { + el.Add(new XElement(OdfNamespaces.Text + "bookmark-end", + new XAttribute(OdfNamespaces.Text + "name", span.Id))); + } + + return el; + } +} diff --git a/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml b/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml new file mode 100644 index 000000000..b1a09f27e --- /dev/null +++ b/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + "); + } + + private void ExportSpeedButton(TSpeedButton speedBtn) + { + var attrs = new List + { + $"Content=\"{EscapeXml(speedBtn.EffectiveCaption)}\"", + GetPositionAttributes(speedBtn), + GetSizeAttributes(speedBtn) + }; + + if (speedBtn.Down) + attrs.Add("IsChecked=\"True\""); + + AppendSingleElement("ToggleButton", attrs, speedBtn.Name); + } + + #endregion + + #region Selection Components + + private void ExportCheckBox(TCheckBox checkBox) + { + var attrs = new List + { + $"Content=\"{EscapeXml(checkBox.Caption)}\"", + GetPositionAttributes(checkBox), + GetSizeAttributes(checkBox) + }; + + if (checkBox.State == CheckBoxState.Checked) + attrs.Add("IsChecked=\"True\""); + else if (checkBox.State == CheckBoxState.Grayed) + attrs.Add("IsChecked=\"{x:Null}\""); + + if (checkBox.AllowGrayed) + attrs.Add("IsThreeState=\"True\""); + + AppendSingleElement("CheckBox", attrs, checkBox.Name); + } + + private void ExportRadioButton(TRadioButton radioBtn) + { + var attrs = new List + { + $"Content=\"{EscapeXml(radioBtn.Caption)}\"", + GetPositionAttributes(radioBtn), + GetSizeAttributes(radioBtn) + }; + + if (radioBtn.Checked) + attrs.Add("IsChecked=\"True\""); + + AppendSingleElement("RadioButton", attrs, radioBtn.Name); + } + + private void ExportComboBox(TComboBox comboBox) + { + var attrs = new List + { + GetPositionAttributes(comboBox), + GetSizeAttributes(comboBox) + }; + + if (comboBox.Style != ComboBoxStyle.DropDownList) + attrs.Add("IsEditable=\"True\""); + if (comboBox.ItemIndex >= 0) + attrs.Add($"SelectedIndex=\"{comboBox.ItemIndex}\""); + + if (comboBox.Items.Count == 0) + { + AppendSingleElement("ComboBox", attrs, comboBox.Name); + } + else + { + AppendLine($""); + _indentLevel++; + foreach (var item in comboBox.Items) + { + AppendLine($""); + } + _indentLevel--; + AppendLine(""); + } + } + + private void ExportListBox(TListBox listBox) + { + var attrs = new List + { + GetPositionAttributes(listBox), + GetSizeAttributes(listBox) + }; + + if (listBox.MultiSelect) + attrs.Add("SelectionMode=\"Multiple\""); + + if (listBox.Items.Count == 0) + { + AppendSingleElement("ListBox", attrs, listBox.Name); + } + else + { + AppendLine($""); + _indentLevel++; + foreach (var item in listBox.Items) + { + AppendLine($""); + } + _indentLevel--; + AppendLine(""); + } + } + + #endregion + + #region Container Components + + private void ExportPanel(TPanel panel) + { + var attrs = new List + { + GetPositionAttributes(panel), + GetSizeAttributes(panel) + }; + + if (panel.Color != Colors.Transparent) + attrs.Add($"Background=\"{ColorToString(panel.Color)}\""); + if (panel.BevelOuter != PanelBevelStyle.None) + attrs.Add("BorderBrush=\"Gray\" BorderThickness=\"1\""); + + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + foreach (var child in panel.Children) + { + ExportComponent(child); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + private void ExportScrollBox(TScrollBox scrollBox) + { + var attrs = new List + { + GetPositionAttributes(scrollBox), + GetSizeAttributes(scrollBox), + "HorizontalScrollBarVisibility=\"Auto\"", + "VerticalScrollBarVisibility=\"Auto\"" + }; + + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + foreach (var child in scrollBox.Children) + { + ExportComponent(child); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + private void ExportGroupBox(TGroupBox groupBox) + { + var attrs = new List + { + $"Header=\"{EscapeXml(groupBox.Caption)}\"", + GetPositionAttributes(groupBox), + GetSizeAttributes(groupBox) + }; + + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + foreach (var child in groupBox.Children) + { + ExportComponent(child); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + private void ExportRadioGroup(TRadioGroup radioGroup) + { + var attrs = new List + { + $"Header=\"{EscapeXml(radioGroup.Caption)}\"", + GetPositionAttributes(radioGroup), + GetSizeAttributes(radioGroup) + }; + + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + for (int i = 0; i < radioGroup.Items.Count; i++) + { + var isChecked = i == radioGroup.ItemIndex ? " IsChecked=\"True\"" : ""; + AppendLine($""); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + private void ExportCheckGroup(TCheckGroup checkGroup) + { + var attrs = new List + { + $"Header=\"{EscapeXml(checkGroup.Caption)}\"", + GetPositionAttributes(checkGroup), + GetSizeAttributes(checkGroup) + }; + + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + for (int i = 0; i < checkGroup.Items.Count; i++) + { + var isChecked = i < checkGroup.Checked.Count && checkGroup.Checked[i] ? " IsChecked=\"True\"" : ""; + AppendLine($""); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + private void ExportPageControl(TPageControl pageControl) + { + var attrs = new List + { + GetPositionAttributes(pageControl), + GetSizeAttributes(pageControl) + }; + + if (pageControl.TabPosition != TabPosition.Top) + { + var placement = pageControl.TabPosition switch + { + TabPosition.Bottom => "Bottom", + TabPosition.Left => "Left", + TabPosition.Right => "Right", + _ => "Top" + }; + attrs.Add($"TabStripPlacement=\"{placement}\""); + } + + AppendLine($""); + _indentLevel++; + + foreach (var child in pageControl.Children.OfType()) + { + ExportTabItem(child); + } + + _indentLevel--; + AppendLine(""); + } + + private void ExportTabItem(TTabSheet tabSheet) + { + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + foreach (var child in tabSheet.Children) + { + ExportComponent(child); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + private void ExportTabSheet(TTabSheet tabSheet) + { + // Standalone TabSheet - export as Border with Canvas + var attrs = new List + { + GetPositionAttributes(tabSheet), + GetSizeAttributes(tabSheet), + "BorderBrush=\"Gray\"", + "BorderThickness=\"1\"" + }; + + AppendLine($""); + _indentLevel++; + AppendLine(""); + _indentLevel++; + + foreach (var child in tabSheet.Children) + { + ExportComponent(child); + } + + _indentLevel--; + AppendLine(""); + _indentLevel--; + AppendLine(""); + } + + #endregion + + #region Other Visual Components + + private void ExportImage(TImage image) + { + var attrs = new List + { + GetPositionAttributes(image), + GetSizeAttributes(image) + }; + + if (image.Stretch) + attrs.Add("Stretch=\"Fill\""); + else if (image.Proportional) + attrs.Add("Stretch=\"Uniform\""); + else + attrs.Add("Stretch=\"None\""); + + // Source would need to be set separately + attrs.Add("Source=\"{Binding ImageSource}\" \""); + + AppendSingleElement("Image", attrs, image.Name); + } + + private void ExportShape(TShape shape) + { + var elementName = shape.ShapeKind switch + { + EShapeType.Ellipse or EShapeType.Circle => "Ellipse", + _ => "Rectangle" + }; + + var attrs = new List + { + GetPositionAttributes(shape), + GetSizeAttributes(shape) + }; + + if (shape.BrushStyleKind != EBrushStyle.Clear) + attrs.Add($"Fill=\"{ColorToString(shape.BrushColor)}\""); + if (shape.PenStyleKind != EPenStyle.Clear) + { + attrs.Add($"Stroke=\"{ColorToString(shape.PenColor)}\""); + attrs.Add($"StrokeThickness=\"{shape.PenWidth}\""); + } + + if (shape.ShapeKind == EShapeType.RoundRect || shape.ShapeKind == EShapeType.RoundSquare) + { + attrs.Add("RadiusX=\"5\" RadiusY=\"5\""); + } + + AppendSingleElement(elementName, attrs, shape.Name); + } + + private void ExportProgressBar(TProgressBar progressBar) + { + var attrs = new List + { + GetPositionAttributes(progressBar), + GetSizeAttributes(progressBar), + $"Minimum=\"{progressBar.Min}\"", + $"Maximum=\"{progressBar.Max}\"", + $"Value=\"{progressBar.Position}\"" + }; + + if (progressBar.Orientation == ProgressBarOrientation.Vertical) + attrs.Add("Orientation=\"Vertical\""); + + AppendSingleElement("ProgressBar", attrs, progressBar.Name); + } + + private void ExportTrackBar(TTrackBar trackBar) + { + var attrs = new List + { + GetPositionAttributes(trackBar), + GetSizeAttributes(trackBar), + $"Minimum=\"{trackBar.Min}\"", + $"Maximum=\"{trackBar.Max}\"", + $"Value=\"{trackBar.Position}\"" + }; + + if (trackBar.Orientation == TrackBarOrientation.Vertical) + attrs.Add("Orientation=\"Vertical\""); + + AppendSingleElement("Slider", attrs, trackBar.Name); + } + + private void ExportSpinEdit(TSpinEdit spinEdit) + { + // WPF doesn't have a native SpinEdit, use TextBox with comment + var attrs = new List + { + $"Text=\"{spinEdit.Value}\"", + GetPositionAttributes(spinEdit), + GetSizeAttributes(spinEdit) + }; + + AppendLine($""); + AppendSingleElement("TextBox", attrs, spinEdit.Name); + } + + private void ExportDataGrid(TDrawGrid drawGrid) + { + var attrs = new List + { + GetPositionAttributes(drawGrid), + GetSizeAttributes(drawGrid) + }; + + AppendLine($""); + AppendSingleElement("DataGrid", attrs, drawGrid.Name); + } + + #endregion + + #region Menu and Toolbar + + private void ExportMainMenu(TMainMenu mainMenu) + { + AppendLine(""); + _indentLevel++; + + foreach (var child in mainMenu.Children.OfType()) + { + ExportMenuItem(child); + } + + _indentLevel--; + AppendLine(""); + } + + private void ExportMenuItem(TMenuItem menuItem) + { + if (menuItem.IsSeparator || menuItem.EffectiveCaption == "-") + { + AppendLine(""); + return; + } + + var header = EscapeXml(menuItem.EffectiveCaption).Replace("&", "_"); + var hasChildren = menuItem.Children.OfType().Any() || menuItem.SubItems.Any(); + + if (!hasChildren) + { + var attrs = new List { $"Header=\"{header}\"" }; + if (!string.IsNullOrEmpty(menuItem.EffectiveShortCut)) + { + attrs.Add($"InputGestureText=\"{FormatShortcut(menuItem.EffectiveShortCut)}\""); + } + AppendSingleElement("MenuItem", attrs, menuItem.Name); + } + else + { + AppendLine($""); + _indentLevel++; + + foreach (var child in menuItem.Children.OfType()) + { + ExportMenuItem(child); + } + foreach (var subItem in menuItem.SubItems) + { + ExportMenuItem(subItem); + } + + _indentLevel--; + AppendLine(""); + } + } + + private void ExportStatusBar(TStatusBar statusBar) + { + var attrs = new List + { + GetPositionAttributes(statusBar), + GetSizeAttributes(statusBar) + }; + + AppendLine($""); + _indentLevel++; + + if (statusBar.SimplePanel) + { + AppendLine($""); + } + else + { + foreach (var panel in statusBar.Panels) + { + AppendLine($""); + } + } + + _indentLevel--; + AppendLine(""); + } + + private void ExportToolBar(TToolBar toolBar) + { + var attrs = new List + { + GetPositionAttributes(toolBar), + GetSizeAttributes(toolBar) + }; + + AppendLine($""); + _indentLevel++; + + foreach (var child in toolBar.Children) + { + if (child is TToolButton toolBtn) + { + ExportToolButton(toolBtn); + } + else + { + ExportComponent(child); + } + } + + _indentLevel--; + AppendLine(""); + } + + private void ExportToolButton(TToolButton toolBtn) + { + if (toolBtn.Style == ToolButtonStyle.Separator) + { + AppendLine(""); + return; + } + + var attrs = new List(); + + if (!string.IsNullOrEmpty(toolBtn.EffectiveHint)) + attrs.Add($"ToolTip=\"{EscapeXml(toolBtn.EffectiveHint)}\""); + + // Add image index as comment + if (toolBtn.EffectiveImageIndex >= 0) + { + AppendLine($""); + } + + AppendSingleElement("Button", attrs, toolBtn.Name); + } + + #endregion + + #region Non-Visual Components + + private void ExportNonVisualComment(LfmComponentBase component) + { + AppendLine($""); + } + + private void ExportUnknownComment(LfmComponentBase component) + { + AppendLine($""); + } + + #endregion + + #region Helper Methods + + private void AppendLine(string line) + { + _sb.Append(new string(' ', _indentLevel * IndentString.Length)); + _sb.AppendLine(line); + } + + private void AppendSingleElement(string elementName, List attributes, string name) + { + var nameAttr = !string.IsNullOrEmpty(name) ? $" x:Name=\"{SanitizeName(name)}\"" : ""; + var attrString = attributes.Count > 0 ? " " + string.Join(" ", attributes) : ""; + AppendLine($"<{elementName}{attrString}{nameAttr} />"); + } + + private static string GetPositionAttributes(LfmComponentBase component) + { + return $"Canvas.Left=\"{component.Left}\" Canvas.Top=\"{component.Top}\""; + } + + private static string GetSizeAttributes(LfmComponentBase component) + { + return $"Width=\"{component.Width}\" Height=\"{component.Height}\""; + } + + private static string ColorToString(Color color) + { + if (color == Colors.Transparent) + return "Transparent"; + if (color.A == 255) + return $"#{color.R:X2}{color.G:X2}{color.B:X2}"; + return $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}"; + } + + private static string EscapeXml(string text) + { + if (string.IsNullOrEmpty(text)) + return string.Empty; + + return text + .Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'"); + } + + private static string SanitizeName(string name) + { + if (string.IsNullOrEmpty(name)) + return "unnamed"; + + // Remove invalid characters for XAML names + var result = new StringBuilder(); + foreach (var c in name) + { + if (char.IsLetterOrDigit(c) || c == '_') + result.Append(c); + } + + var sanitized = result.ToString(); + if (sanitized.Length == 0 || char.IsDigit(sanitized[0])) + sanitized = "_" + sanitized; + + return sanitized; + } + + private static string GetBitBtnIconContent(BitBtnKind kind) => kind switch + { + BitBtnKind.OK => "?", + BitBtnKind.Cancel => "?", + BitBtnKind.Help => "?", + BitBtnKind.Yes => "?", + BitBtnKind.No => "?", + BitBtnKind.Close => "?", + BitBtnKind.Abort => "?", + BitBtnKind.Retry => "??", + BitBtnKind.Ignore => "?", + BitBtnKind.All => "?", + _ => "" + }; + + private static string FormatShortcut(string shortcut) + { + if (string.IsNullOrEmpty(shortcut)) + return string.Empty; + + if (int.TryParse(shortcut, out int keyCode)) + { + var modifiers = new StringBuilder(); + if ((keyCode & 0x4000) != 0) modifiers.Append("Ctrl+"); + if ((keyCode & 0x2000) != 0) modifiers.Append("Shift+"); + if ((keyCode & 0x8000) != 0) modifiers.Append("Alt+"); + + int key = keyCode & 0xFF; + string keyName = key switch + { + >= 65 and <= 90 => ((char)key).ToString(), + >= 48 and <= 57 => ((char)key).ToString(), + 112 => "F1", 113 => "F2", 114 => "F3", 115 => "F4", + 116 => "F5", 117 => "F6", 118 => "F7", 119 => "F8", + 120 => "F9", 121 => "F10", 122 => "F11", 123 => "F12", + _ => $"Key{key}" + }; + + return modifiers.ToString() + keyName; + } + + return shortcut; + } + + #endregion +} From 6615998011510a435f799f02ec92e98dddd8ca9b Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:24:42 +0100 Subject: [PATCH 005/962] AA05_CommandParCalc --- .../BaseLibTests/Helper/ListHelperTests.cs | 67 ++++ .../BaseLibTests/Helper/ObjectHelper2Tests.cs | 45 +++ .../BaseLibTests/Helper/ObjectHelperTests.cs | 285 ++++++++++++++++++ .../BaseLibTests/Model/CRandomTests.cs | 82 +++++ .../BaseLibTests/Model/ConsoleProxyTests.cs | 186 ++++++++++++ .../BaseLibTests/Model/SysTimeTests.cs | 81 +++++ 6 files changed, 746 insertions(+) create mode 100644 Avalonia_Apps/Libraries/BaseLibTests/Helper/ListHelperTests.cs create mode 100644 Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelper2Tests.cs create mode 100644 Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelperTests.cs create mode 100644 Avalonia_Apps/Libraries/BaseLibTests/Model/CRandomTests.cs create mode 100644 Avalonia_Apps/Libraries/BaseLibTests/Model/ConsoleProxyTests.cs create mode 100644 Avalonia_Apps/Libraries/BaseLibTests/Model/SysTimeTests.cs diff --git a/Avalonia_Apps/Libraries/BaseLibTests/Helper/ListHelperTests.cs b/Avalonia_Apps/Libraries/BaseLibTests/Helper/ListHelperTests.cs new file mode 100644 index 000000000..7f49e6103 --- /dev/null +++ b/Avalonia_Apps/Libraries/BaseLibTests/Helper/ListHelperTests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; +using static BaseLib.Helper.TestHelper; + +namespace BaseLib.Helper.Tests; + +[TestClass()] +public class ListHelperTests +{ + [TestMethod()] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 2, 2, new object[] { 0, 1, 2, 3, 4 })] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 4, 0, new object[] { 4, 0, 1, 2, 3 })] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 1, 3, new object[] { 0, 2, 1, 3, 4 })] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 2, 5, new object[] { 0, 1, 3, 4, 2 })] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 1, 6, new object[] { 0, 1, 2, 3, 4 })] + public void MoveItemTest(object[] aoSrc, int iSrc, int iDst, object[] asExp) + { + var aoResult = aoSrc.ToList(); + if (iDst > asExp.Length) + Assert.ThrowsExactly(() => aoResult.MoveItem(iSrc, iDst)); + else + aoResult.MoveItem(iSrc, iDst); + AssertAreEqual(asExp, aoResult.ToArray()); + } + + [TestMethod()] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 2, 2, new object[] { 0, 1, 2, 3, 4 })] + [DataRow(new object[] { 0, 1, 2, 3, 4 }, 4, 0, new object[] { 4, 1, 2, 3, 0 })] + public void SwapTest(object[] aoSrc, int iSrc, int iDst, object[] asExp) + { + aoSrc.Swap(iSrc, iDst); + AssertAreEqual(asExp, aoSrc); + } + + [TestMethod()] + [DataRow(0, -1, new int[] { })] + [DataRow(2, 2, new[] { 2 })] + [DataRow(0, 4, new[] { 0, 1, 2, 3, 4 })] + public void RangeTest(int iSrc, int iDst, int[] aiExp) + { + AssertAreEqual(aiExp, typeof(int).Range(iSrc, iDst)); + } + + [TestMethod()] + [DataRow('2', '6', new char[] { '2', '3', '4', '5', '6' })] + public void RangeTest2(char iSrc, char iDst, char[] aoExp) + { + AssertAreEqual(aoExp, typeof(object).Range(iSrc, iDst)); + } + + [TestMethod()] + [DataRow((byte)0, (byte)4, new byte[] { 0, 1, 2, 3, 4 })] + public void RangeTest3(byte iSrc, byte iDst, byte[] aoExp) + { + AssertAreEqual(aoExp, typeof(object).Range(iSrc, iDst)); + } + + [TestMethod()] + [DataRow(0, -1, new int[] { })] + [DataRow(2, 2, new[] { 2 })] + [DataRow(0, 4, new[] { 0, 1, 2, 3, 4 })] + public void ToTest(int iSrc, int iDst, int[] aoExp) + { + AssertAreEqual(aoExp, iSrc.To(iDst)); + } +} \ No newline at end of file diff --git a/Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelper2Tests.cs b/Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelper2Tests.cs new file mode 100644 index 000000000..4b1e11372 --- /dev/null +++ b/Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelper2Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace BaseLib.Helper.Tests; + +[TestClass()] +public class ObjectHelper2Tests +{ +#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. + private ControlArray _testClass; +#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. + + [TestInitialize] + public void Init() + { + _testClass = new ControlArray(); + } + + [TestMethod()] + public void SetIndexTest() + { + // Act + _testClass.SetIndex(8, 4); + _testClass.SetIndex(2, 1); + // Assert + Assert.AreEqual(2, _testClass.Count); + Assert.AreEqual(8, _testClass[4]); + Assert.AreEqual(2, _testClass[1]); + } + + [TestMethod()] + public void GetIndexTest() + { + // Arrange + var d = (Dictionary)_testClass; + d[5] = 8; + d[1] = null!; + d[2] = 2; + + // Assert + Assert.AreEqual(4, _testClass.GetIndex(8)); + Assert.AreEqual(1, _testClass.GetIndex(2)); + Assert.AreEqual(-1, _testClass.GetIndex(1)); + } +} \ No newline at end of file diff --git a/Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelperTests.cs b/Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelperTests.cs new file mode 100644 index 000000000..05b481c7f --- /dev/null +++ b/Avalonia_Apps/Libraries/BaseLibTests/Helper/ObjectHelperTests.cs @@ -0,0 +1,285 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using BaseLib.Interfaces; +using NSubstitute; + +namespace BaseLib.Helper.Tests; + +[TestClass()] +public class ObjectHelperTests +{ + [TestMethod()] + [DataRow(null, -1)] + [DataRow(1, 1)] + [DataRow(int.MaxValue, int.MaxValue)] + [DataRow(int.MinValue, int.MinValue)] + [DataRow(uint.MaxValue-1, -2)] + [DataRow(2u, 2)] + [DataRow("3", 3)] + [DataRow("4.5", -1)] + [DataRow(5.1f, 5)] + [DataRow(6L, 6)] + [DataRow("DBNull", -1)] + [DataRow("IHasValue", 0)] + [DataRow("_", -1)] + public void AsIntTest(object? obj,int iExp) + { + if (obj is string s) + obj = obj switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "_" => new object(), + _ => obj + }; + // Act + var result = ObjectHelper.AsInt(obj,-1); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(iExp, result); + + } + + [TestMethod()] + [DataRow(null, -1)] + [DataRow(1, 1)] + [DataRow(long.MaxValue, long.MaxValue)] + [DataRow(long.MinValue, long.MinValue)] + [DataRow(ulong.MaxValue - 1, -2)] + [DataRow(2u, 2)] + [DataRow("3", 3)] + [DataRow("4.5", -1)] + [DataRow(5.1f, 5)] + [DataRow(6L, 6)] + [DataRow("DBNull", -1)] + [DataRow("IHasValue", 0)] + [DataRow("_", -1)] + public void AsLongTest(object? obj, long lExp) + { + if (obj is string s) + obj = obj switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "_" => new object(), + _ => obj + }; + // Act + var result = ObjectHelper.AsLong(obj, -1); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(lExp, result); + } + + public enum TestEnum + { + Default = 0, + Value1 = 1, + Value2 = 2 + } + + [TestMethod()] + [DataRow(null, TestEnum.Default)] + [DataRow("Value1", TestEnum.Value1)] + [DataRow("Value2", TestEnum.Value2)] + [DataRow("InvalidValue", TestEnum.Default)] + [DataRow("TestEnum.Value1", TestEnum.Value1)] + [DataRow("IHasValue", TestEnum.Default)] + [DataRow("DBNull", TestEnum.Default)] + [DataRow(1, TestEnum.Value1)] + [DataRow(2, TestEnum.Value2)] + [DataRow(2u, TestEnum.Value2)] + [DataRow(0, TestEnum.Default)] + [DataRow(99, (TestEnum)99)] + [DataRow(-1, (TestEnum)(int)-1)] + [DataRow("_", TestEnum.Default)] + public void AsEnumTest(object? obj, TestEnum expected) + { + if (obj is string s) + obj = obj switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "TestEnum.Value1" => TestEnum.Value1, + + "_" => new object(), + _ => obj + }; + // Act + var result = ObjectHelper.AsEnum(obj); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expected, result); + } + + [TestMethod()] + [DataRow(null, "0001-01-01T00:00:00")] + [DataRow(20231040, "0001-01-01T00:00:00")] + [DataRow(20230010, "0001-01-01T00:00:00")] + [DataRow(0, "0001-01-01T00:00:00")] + [DataRow(20231010, "2023-10-10T00:00:00")] + [DataRow(1000u, "1902-09-26T00:00:00")] + [DataRow(1000, "1902-09-26T00:00:00")] + [DataRow('c', "1999-01-01T00:00:00")] + [DataRow("10-10-2000", "2000-10-10T00:00:00")] + [DataRow("2023-10-10", "2023-10-10T00:00:00")] + [DataRow("10/10/2023", "2023-10-10T00:00:00")] + [DataRow("10-10-2023", "2023-10-10T00:00:00")] + [DataRow("2023-10-10T10:10:10", "2023-10-10T10:10:10")] + [DataRow("InvalidDate", "0001-01-01T00:00:00")] + [DataRow(638000000000000000L, "2022-09-28T22:13:20")] + [DataRow("DBNull", "0001-01-01T00:00:00")] + [DataRow("IHasValue", "0001-01-01T00:00:00")] + [DataRow("_", "0001-01-01T00:00:00")] + public void AsDateTest(object? obj, string expected) + { + if (obj is string s) + obj = s switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "_" => new object(), + _ when s.Contains("2000") && DateTime.TryParse(s, out var dt) => dt, + _ => obj + }; + // Act + var result = ObjectHelper.AsDate(obj); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(DateTime.Parse(expected), result); + } + + [TestMethod()] + [DataRow(null, 0)] + [DataRow(1, 1)] + [DataRow(long.MaxValue, long.MaxValue)] + [DataRow(long.MinValue, long.MinValue)] + [DataRow(ulong.MaxValue , ulong.MaxValue)] + [DataRow(2u, 2)] + [DataRow("3", 3)] + [DataRow("Dog", 0)] + [DataRow("4.5", 4.5)] + [DataRow(5.1f, 5.1f)] + [DataRow(double.MinValue, double.MinValue)] + [DataRow(double.MaxValue, double.MaxValue)] + [DataRow(double.NaN, double.NaN)] + [DataRow(6L, 6)] + [DataRow('7', 48+7)] + [DataRow("DBNull", 0)] + [DataRow("IHasValue", 0)] + [DataRow("_", 0)] + public void AsDoubleTest(object? obj, double dExp) + { + if (obj is string s) + obj = s switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "_" => new object(), + _ => obj + }; + // Act + var result = ObjectHelper.AsDouble(obj); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(dExp, result); + } + + [TestMethod()] + [DataRow(null, false)] + [DataRow(true, true)] + [DataRow(false, false)] + [DataRow(1, true)] + [DataRow(0, false)] + [DataRow("true", true)] + [DataRow("false", false)] + [DataRow("True", true)] + [DataRow("False", false)] + [DataRow("yes", false)] + [DataRow("no", false)] + [DataRow("1", true)] + [DataRow("0", false)] + [DataRow('1', true)] + [DataRow('T', true)] + [DataRow('t', true)] + [DataRow('0', false)] + [DataRow("DBNull", false)] + [DataRow("IHasValue", false)] + [DataRow("_", false)] + public void AsBoolTest(object? obj, bool bExp) + { + if (obj is string s) + obj = obj switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "_" => new object(), + _ => obj + }; + // Act + var result = ObjectHelper.AsBool(obj); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(bExp, result); + } + + [TestMethod()] + [DataRow(null, "00000000-0000-0000-0000-000000000000")] + [DataRow("d3c4a1b2-3f4e-5d6c-7a8b-9c0d1e2f3a4b", "d3c4a1b2-3f4e-5d6c-7a8b-9c0d1e2f3a4b")] + [DataRow("invalid-guid", "00000000-0000-0000-0000-000000000000")] + [DataRow("00000000-0000-0000-0000-000000000000", "00000000-0000-0000-0000-000000000000")] + [DataRow("123456789", "075bcd15-0000-0000-0000-000000000000")] + [DataRow(123456789L, "075bcd15-0000-0000-0000-000000000000")] + [DataRow(123456789u, "075bcd15-0000-0000-0000-000000000000")] + [DataRow("IHasValue", "00000000-0000-0000-0000-000000000000")] + [DataRow("DBNull", "00000000-0000-0000-0000-000000000000")] + [DataRow("EmptyGUID", "00000000-0000-0000-0000-000000000000")] + [DataRow("_", "00000000-0000-0000-0000-000000000000")] + public void AsGUIDTest(object? obj, string expected) + { + if (obj is string s) + obj = obj switch + { + "DBNull" => DBNull.Value, + "IHasValue" => Substitute.For(), + "EmptyGUID" => Guid.Empty, + "_" => new object(), + _ => obj + }; + + // Act + var result = ObjectHelper.AsGUID(obj); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(Guid.Parse(expected), result); + } + + [TestMethod()] + [DataRow(null, false)] + [DataRow(1, true)] + [DataRow("test", true)] + [DataRow("", false)] + [DataRow(0, false)] + public void SetRetTest(object? input, bool expected) + { + // Arrange + bool actionCalled = false; + object? _o = null; + Action action = (obj) => { actionCalled = true; _o = obj; }; + + // Act + var result = ObjectHelper.SetRet(input, action, expected); + + // Assert + Assert.AreEqual(expected, result); + Assert.AreEqual(input, _o); + Assert.AreEqual(true, actionCalled); + } +} \ No newline at end of file diff --git a/Avalonia_Apps/Libraries/BaseLibTests/Model/CRandomTests.cs b/Avalonia_Apps/Libraries/BaseLibTests/Model/CRandomTests.cs new file mode 100644 index 000000000..61baf4b03 --- /dev/null +++ b/Avalonia_Apps/Libraries/BaseLibTests/Model/CRandomTests.cs @@ -0,0 +1,82 @@ +using BaseLib.Models.Interfaces; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static BaseLib.Helper.TestHelper; + +namespace BaseLib.Models.Tests; + +[TestClass()] +public class CRandomTests +{ +#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. + private CRandom _testClass; +#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Erwägen Sie die Deklaration als Nullable. + + [TestInitialize] + public void Init() + { + _testClass = new CRandom(); + _testClass.Seed(0); + } + + [TestMethod()] + public void CRandomTest() + { + Assert.IsNotNull(_testClass); + Assert.IsInstanceOfType(_testClass, typeof(IRandom)); + Assert.IsInstanceOfType(_testClass, typeof(CRandom)); + } + + [TestMethod()] + [DataRow(0, 1, new[] { 0, 0, 0, 0, 0 })] + [DataRow(0, 2, new[] { 1, 1, 1, 1, 0 })] + + public void NextTest(int imin, int iMax,int[] asExp) + { + var aoResult = new int[asExp.Length]; + for (int i = 0; i < asExp.Length; i++) + { + aoResult[i] = _testClass.Next(imin, iMax); + } + AssertAreEqual(asExp, aoResult); + } + + [TestMethod()] + [DataRow(new[] { 0.7262432699679598, 0.8173253595909687, 0.7680226893946634,0.5581611914365372, 0.2060331540210327 })] + public void NextDoubleTest(double[] adExp) + { + var adResult = new double[adExp.Length]; + for (int i = 0; i < adExp.Length; i++) + { + adResult[i] = _testClass.NextDouble(); + } + AssertAreEqual(adExp, adResult); + } + + [TestMethod()] + [DataRow(new[] { 1559595546, 1755192844, 1649316166, 1198642031, 442452829 })] + public void NextIntTest(int[] asExp) + { + var aoResult = new int[asExp.Length]; + for (int i = 0; i < asExp.Length; i++) + { + aoResult[i] = _testClass.NextInt(); + } + AssertAreEqual(asExp, aoResult); + } + + [TestMethod()] + [DataRow(0, new[] { 1559595546, 1755192844, 1649316166, 1198642031, 442452829 })] + [DataRow(1, new[] { 534011718, 237820880, 1002897798, 1657007234, 1412011072 })] + [DataRow(int.MaxValue, new[] { 1559595546, 1755192844, 1649316172, 1198642031, 442452829 })] + + public void SeedTest(int iSeed, int[] asExp) + { + _testClass.Seed(iSeed); + var aoResult = new int[asExp.Length]; + for (int i = 0; i < asExp.Length; i++) + { + aoResult[i] = _testClass.NextInt(); + } + AssertAreEqual(asExp, aoResult); + } +} \ No newline at end of file diff --git a/Avalonia_Apps/Libraries/BaseLibTests/Model/ConsoleProxyTests.cs b/Avalonia_Apps/Libraries/BaseLibTests/Model/ConsoleProxyTests.cs new file mode 100644 index 000000000..b769e5b8f --- /dev/null +++ b/Avalonia_Apps/Libraries/BaseLibTests/Model/ConsoleProxyTests.cs @@ -0,0 +1,186 @@ +using BaseLib.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace BaseLib.Models.Tests; + +[TestClass] +public class ConsoleProxyTests +{ + private ConsoleProxy _sut = null!; + + [TestInitialize] + public void TestInitialize() + { + _sut = new ConsoleProxy(); + } + + [TestMethod] + public void Constructor_ShouldCreateInstance() + { + // Assert + Assert.IsNotNull(_sut); + } + + [TestMethod] + public void ForegroundColor_Get_ShouldReturnConsoleColor() + { + // Act + var result = _sut.ForegroundColor; + + // Assert + Assert.IsInstanceOfType(result, typeof(ConsoleColor)); + } + + [TestMethod] + public void ForegroundColor_Set_ShouldNotThrow() + { + // Arrange + var originalColor = _sut.ForegroundColor; + + // Act & Assert + _sut.ForegroundColor = ConsoleColor.Red; + _sut.ForegroundColor = originalColor; + } + + [TestMethod] + public void BackgroundColor_Get_ShouldReturnConsoleColor() + { + // Act + var result = _sut.BackgroundColor; + + // Assert + Assert.IsInstanceOfType(result, typeof(ConsoleColor)); + } + + [TestMethod] + public void BackgroundColor_Set_ShouldNotThrow() + { + // Arrange + var originalColor = _sut.BackgroundColor; + + // Act & Assert + _sut.BackgroundColor = ConsoleColor.Blue; + _sut.BackgroundColor = originalColor; + } + + [TestMethod] + public void IsOutputRedirected_ShouldReturnBool() + { + // Act + var result = _sut.IsOutputRedirected; + + // Assert + Assert.IsInstanceOfType(result, typeof(bool)); + } + + [TestMethod] + public void KeyAvailable_ShouldReturnBool() + { + // Act + var result = _sut.KeyAvailable; + + // Assert + Assert.IsInstanceOfType(result, typeof(bool)); + } + + [TestMethod] + public void LargestWindowHeight_ShouldReturnInt() + { + // Act + var result = _sut.LargestWindowHeight; + + // Assert + Assert.IsInstanceOfType(result, typeof(int)); + } + + [TestMethod] + public void Title_Get_ShouldReturnString() + { + // Act + var result = _sut.Title; + + // Assert + Assert.IsNotNull(result); + } + + [TestMethod] + public void Title_Set_ShouldNotThrow() + { + // Arrange + var originalTitle = _sut.Title; + + // Act & Assert + _sut.Title = "TestTitle"; + _sut.Title = originalTitle; + } + + [TestMethod] + public void GetCursorPosition_ShouldReturnTuple() + { + // Act + var result = _sut.GetCursorPosition; + + // Assert + Assert.IsNotNull(result); + } + + [TestMethod] + public void SetCursorPosition_ShouldNotThrow() + { + // Arrange + + // Act + var result = _sut.SetCursorPosition; + + //Assert + Assert.IsNotNull(result); + } + + [TestMethod] + public void Write_Char_ShouldNotThrow() + { + // Act & Assert - keine Exception erwartet + _sut.Write('X'); + } + + [TestMethod] + public void Write_String_ShouldNotThrow() + { + // Act & Assert - keine Exception erwartet + _sut.Write("Test"); + } + + [TestMethod] + public void Write_NullString_ShouldNotThrow() + { + // Act & Assert - keine Exception erwartet + _sut.Write((string?)null); + } + + [TestMethod] + public void WriteLine_ShouldNotThrow() + { + // Act & Assert - keine Exception erwartet + _sut.WriteLine("TestLine"); + } + + [TestMethod] + public void WriteLine_Null_ShouldNotThrow() + { + // Act & Assert - keine Exception erwartet + _sut.WriteLine(null); + } + + [TestMethod] + public void Clear_ShouldNotThrow() + { + // Act & Assert - keine Exception erwartet + var m= _sut.Clear; + //Assert + Assert.IsNotNull(m); + } + + // Beep und ReadKey/ReadLine werden nicht getestet, da sie + // auf Benutzereingaben warten oder Audio abspielen +} \ No newline at end of file diff --git a/Avalonia_Apps/Libraries/BaseLibTests/Model/SysTimeTests.cs b/Avalonia_Apps/Libraries/BaseLibTests/Model/SysTimeTests.cs new file mode 100644 index 000000000..abb53320a --- /dev/null +++ b/Avalonia_Apps/Libraries/BaseLibTests/Model/SysTimeTests.cs @@ -0,0 +1,81 @@ +using BaseLib.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace BaseLib.Models.Tests; + +[TestClass] +public class SysTimeTests +{ + private SysTime _sut = null!; + + [TestInitialize] + public void TestInitialize() + { + _sut = new SysTime(); + } + + [TestCleanup] + public void TestCleanup() + { + SysTime.GetNow = () => DateTime.Now; + } + + [TestMethod] + public void Now_ReturnsCurrentDateTime() + { + // Arrange + var expectedDate = new DateTime(2024, 6, 15, 10, 30, 45); + SysTime.GetNow = () => expectedDate; + + // Act + var result = _sut.Now; + + // Assert + Assert.AreEqual(expectedDate, result); + } + + [TestMethod] + public void Today_ReturnsDatePartOnly() + { + // Arrange + var dateWithTime = new DateTime(2024, 6, 15, 10, 30, 45); + var expectedDate = new DateTime(2024, 6, 15); + SysTime.GetNow = () => dateWithTime; + + // Act + var result = _sut.Today; + + // Assert + Assert.AreEqual(expectedDate, result); + } + + [TestMethod] + public void GetNow_DefaultValue_ReturnsDateTimeNow() + { + // Arrange + SysTime.GetNow = () => DateTime.Now; + + // Act + var before = DateTime.Now; + var result = _sut.Now; + var after = DateTime.Now; + + // Assert + Assert.IsTrue(result >= before && result <= after); + } + + [TestMethod] + public void GetNow_CanBeOverridden() + { + // Arrange + var customDate = new DateTime(2000, 1, 1); + SysTime.GetNow = () => customDate; + + // Act + var result = SysTime.GetNow(); + + // Assert + Assert.AreEqual(customDate, result); + } +} \ No newline at end of file From 071d99e14f41d5d1399076e605855b5052366a2e Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:25:12 +0100 Subject: [PATCH 006/962] BaseLib --- .../Libraries/BaseLib/Helper/ListHelper.cs | 147 ++++- .../Libraries/BaseLib/Helper/ObjectHelper.cs | 342 +++++++++- .../Libraries/BaseLib/Helper/StringUtils.cs | 591 +++++++++++++++--- .../Libraries/BaseLib/Interfaces/IConsole.cs | 124 ++++ 4 files changed, 1096 insertions(+), 108 deletions(-) diff --git a/Avalonia_Apps/Libraries/BaseLib/Helper/ListHelper.cs b/Avalonia_Apps/Libraries/BaseLib/Helper/ListHelper.cs index 6a274e268..432f0175c 100644 --- a/Avalonia_Apps/Libraries/BaseLib/Helper/ListHelper.cs +++ b/Avalonia_Apps/Libraries/BaseLib/Helper/ListHelper.cs @@ -9,26 +9,72 @@ // // Copyright © JC-Soft 2023 // -// +// +// Provides extension methods for working with generic lists and arrays, +// including item manipulation, swapping, and range generation utilities. +// // *********************************************************************** using System; using System.Collections.Generic; /// -/// The Helper namespace. +/// The Helper namespace contains utility classes and extension methods +/// that provide common functionality across the BaseLib library. /// namespace BaseLib.Helper; /// -/// Class ListHelper. +/// Provides static extension methods for manipulating generic lists and generating arrays. +/// This class contains helper methods for common list operations such as moving items, +/// swapping elements, and creating ranges of values. /// +/// +/// All methods in this class are implemented as extension methods, allowing them to be +/// called directly on instances or value types. +/// +/// +/// +/// // Moving an item in a list +/// var list = new List<string> { "A", "B", "C", "D" }; +/// list.MoveItem(0, 3); // Result: { "B", "C", "A", "D" } +/// +/// // Swapping items +/// list.Swap(0, 2); // Swaps items at index 0 and 2 +/// +/// // Creating a range +/// int[] range = 1.To(5); // Result: { 1, 2, 3, 4, 5 } +/// +/// public static class ListHelper { - /// Moves the item from source to target. - /// The generic type of the list - /// The list to change. - /// The index of the source. - /// The index of the destination. + /// + /// Moves an item from a source index to a destination index within the list. + /// + /// The type of elements in the list. + /// The list in which to move the item. + /// The zero-based index of the item to move. + /// The zero-based destination index where the item should be placed. + /// + /// + /// If the source and destination indices are equal, no operation is performed. + /// + /// + /// The method handles the index shift that occurs when removing an item before + /// the destination index. If an exception occurs during insertion, the item + /// is restored to its original position before re-throwing the exception. + /// + /// + /// + /// Thrown when or is outside + /// the valid range of indices for the list. + /// + /// + /// + /// var list = new List<int> { 1, 2, 3, 4, 5 }; + /// list.MoveItem(0, 4); // Moves element at index 0 to index 4 + /// // Result: { 2, 3, 4, 1, 5 } + /// + /// public static void MoveItem(this IList list, int iSrc, int iDst) { if (iSrc == iDst) return; @@ -46,18 +92,59 @@ public static void MoveItem(this IList list, int iSrc, int iDst) } /// - /// Exchanges the items. + /// Exchanges (swaps) the positions of two items in the list. /// - /// The generic type of the list - /// The list to change. - /// The index of the first item. - /// The index of the second item. + /// The type of elements in the list. + /// The list containing the items to swap. + /// The zero-based index of the first item to swap. + /// The zero-based index of the second item to swap. + /// + /// If both indices are equal, no operation is performed. + /// Uses tuple deconstruction for efficient in-place swapping. + /// + /// + /// Thrown when or is outside + /// the valid range of indices for the list. + /// + /// + /// + /// var list = new List<string> { "A", "B", "C" }; + /// list.Swap(0, 2); // Swaps "A" and "C" + /// // Result: { "C", "B", "A" } + /// + /// public static void Swap(this IList list, int iItm1, int iItm2) { if (iItm1 == iItm2) return; - (list[iItm1], list[iItm2]) = (list[iItm2],list[iItm1]); + (list[iItm1], list[iItm2]) = (list[iItm2], list[iItm1]); } + /// + /// Creates an array containing a range of values from to inclusive. + /// + /// + /// A value type that implements and can be converted to and from . + /// + /// + /// The instance used as an extension method anchor. This parameter is not used. + /// + /// The starting value of the range (inclusive). + /// The ending value of the range (inclusive). + /// + /// An array of type containing all values from + /// to inclusive. Returns an empty array if + /// is greater than . + /// + /// + /// This method uses internally, so it works best + /// with numeric types that can be represented as integers. + /// + /// + /// + /// int[] range = typeof(int).Range(1, 5); // Result: { 1, 2, 3, 4, 5 } + /// int[] empty = typeof(int).Range(5, 1); // Result: empty array + /// + /// public static T[] Range(this Type _, T v1, T v2) where T : struct, IComparable { if (v1.CompareTo(v2) > 0) @@ -67,6 +154,37 @@ public static T[] Range(this Type _, T v1, T v2) where T : struct, IComparabl result[i] = (T)Convert.ChangeType(Convert.ToInt32(v1) + i, typeof(T)); return result; } + + /// + /// Creates an array containing a range of values from the current value to the specified end value inclusive. + /// + /// + /// A value type that implements and can be converted to and from . + /// + /// The starting value of the range (inclusive). + /// The ending value of the range (inclusive). + /// + /// An array of type containing all values from + /// to inclusive. Returns an empty array if + /// is greater than . + /// + /// + /// + /// This extension method provides a fluent syntax for creating ranges directly from a value. + /// + /// + /// This method uses internally, so it works best + /// with numeric types that can be represented as integers (e.g., , + /// , ). + /// + /// + /// + /// + /// int[] range = 1.To(5); // Result: { 1, 2, 3, 4, 5 } + /// byte[] bytes = ((byte)0).To((byte)3); // Result: { 0, 1, 2, 3 } + /// int[] empty = 10.To(5); // Result: empty array + /// + /// public static T[] To(this T v1, T v2) where T : struct, IComparable { if (v1.CompareTo(v2) > 0) @@ -76,5 +194,4 @@ public static T[] To(this T v1, T v2) where T : struct, IComparable result[i] = (T)Convert.ChangeType(Convert.ToInt32(v1) + i, typeof(T)); return result; } - } diff --git a/Avalonia_Apps/Libraries/BaseLib/Helper/ObjectHelper.cs b/Avalonia_Apps/Libraries/BaseLib/Helper/ObjectHelper.cs index 04832da4d..686e531de 100644 --- a/Avalonia_Apps/Libraries/BaseLib/Helper/ObjectHelper.cs +++ b/Avalonia_Apps/Libraries/BaseLib/Helper/ObjectHelper.cs @@ -1,4 +1,16 @@ - +// *********************************************************************** +// Assembly : BaseLib +// Author : Mir +// Created : 03-27-2023 +// +// Last Modified By : Mir +// Last Modified On : 03-27-2023 +// *********************************************************************** +// +// Copyright © JC-Soft 2025 +// +// +// *********************************************************************** using BaseLib.Interfaces; using System; using System.Collections.Generic; @@ -9,8 +21,54 @@ namespace BaseLib.Helper; +/// +/// Provides extension methods for safe type conversion of instances to various primitive and common types. +/// +/// +/// +/// This static class contains a collection of extension methods that facilitate the conversion of objects +/// to specific types such as , , , , +/// , , and . +/// +/// +/// Each conversion method handles various input types including: +/// +/// Direct type matches (returns the value as-is) +/// String parsing with appropriate format providers +/// interface implementations for recursive value extraction +/// and values (returns default) +/// implementations for standard .NET type conversions +/// +/// +/// public static class ObjectHelper { + /// + /// Converts an object to a 32-bit signed integer. + /// + /// The object to convert. Can be . + /// The default value to return when conversion fails. Defaults to 0. + /// + /// The converted value, or if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already an , returns it directly. + /// If is a , performs an unchecked cast to . + /// If is a , attempts to parse it as an integer. + /// If implements , recursively converts its . + /// If is or , returns . + /// If implements , uses with . + /// + /// + /// + /// + /// int result1 = "42".AsInt(); // Returns 42 + /// int result2 = ((object)null).AsInt(); // Returns 0 + /// int result3 = "invalid".AsInt(-1); // Returns -1 + /// + /// public static int AsInt(this object? obj, int def = default) => obj switch { int i => i, @@ -23,6 +81,31 @@ public static class ObjectHelper _ => def }; + /// + /// Converts an object to a 64-bit signed integer. + /// + /// The object to convert. Can be . + /// The default value to return when conversion fails. Defaults to 0. + /// + /// The converted value, or if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already a , returns it directly. + /// If is a , performs an unchecked cast to . + /// If is a , attempts to parse it as a long integer. + /// If implements , recursively converts its . + /// If is or , returns . + /// If implements , uses with . + /// + /// + /// + /// + /// long result1 = "9223372036854775807".AsLong(); // Returns long.MaxValue + /// long result2 = ((object)null).AsLong(); // Returns 0 + /// + /// public static long AsLong(this object? obj, int def = default) => obj switch { long i => i, @@ -35,7 +118,34 @@ public static class ObjectHelper _ => def }; - + /// + /// Converts an object to an enumeration value of the specified type. + /// + /// The enumeration type to convert to. Must be a value type and an . + /// The object to convert. Can be . + /// + /// The converted enumeration value of type , or if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already of type , returns it directly. + /// If is an within the valid enum range, converts it to the enum value. + /// If is a , attempts to parse it as an enum name. + /// If implements , recursively converts its . + /// If is or , returns . + /// If is a , performs an unchecked cast and converts to enum. + /// If implements , converts to first, then to enum. + /// + /// + /// + /// + /// enum Color { Red, Green, Blue } + /// Color result1 = "Green".AsEnum<Color>(); // Returns Color.Green + /// Color result2 = 1.AsEnum<Color>(); // Returns Color.Green + /// Color result3 = "Invalid".AsEnum<Color>(); // Returns Color.Red (default) + /// + /// public static T AsEnum(this object? obj) where T : struct, Enum => obj switch { T t => t, @@ -51,6 +161,37 @@ string s when Enum.TryParse(s, out var t) => t, _ => default }; + /// + /// Converts an object to a value. + /// + /// The object to convert. Can be . + /// + /// The converted value, or if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already a , returns it directly. + /// If is an in the format YYYYMMDD (e.g., 20231225), parses it as a date. + /// If is an that is 0 or greater than 10000000, returns . + /// If implements , recursively converts its . + /// If is a without dots, attempts to parse using . + /// If is a with dots, attempts to parse using . + /// If is a representing a numeric date (YYYYMMDD), parses accordingly. + /// If is a , treats it as ticks for . + /// If is a , converts from OLE Automation date format. + /// If is a , creates a date with year = char value + 1900. + /// If is , returns . + /// If implements , converts from OLE Automation date format. + /// + /// + /// + /// + /// DateTime result1 = "2023-12-25".AsDate(); // Returns December 25, 2023 + /// DateTime result2 = 20231225.AsDate(); // Returns December 25, 2023 + /// DateTime result3 = ((object)null).AsDate(); // Returns DateTime.MinValue + /// + /// public static DateTime AsDate(this object? obj) => obj switch { DateTime dt => dt, @@ -69,6 +210,35 @@ string s when DateTime.TryParse(s, CultureInfo.CurrentUICulture, DateTimeStyles. _ => default, }; + /// + /// Converts an object to a double-precision floating-point number. + /// + /// The object to convert. Can be . + /// + /// The culture-specific formatting information to use for parsing strings. + /// If , is used. + /// + /// + /// The converted value, or 0.0 if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already a , returns it directly. + /// If is a , attempts to parse it using and the specified culture. + /// If implements , recursively converts its . + /// If is or , returns (0.0). + /// If is a , returns its numeric value. + /// If implements , uses . + /// + /// + /// + /// + /// double result1 = "3.14".AsDouble(); // Returns 3.14 + /// double result2 = "3,14".AsDouble(CultureInfo.GetCultureInfo("de-DE")); // Returns 3.14 + /// double result3 = ((object)null).AsDouble(); // Returns 0.0 + /// + /// public static double AsDouble(this object? obj, CultureInfo? culture = null) => obj switch { double d => d, @@ -82,6 +252,33 @@ string s when double.TryParse(s, NumberStyles.Float, culture ?? CultureInfo.Inva _ => default }; + /// + /// Converts an object to a Boolean value. + /// + /// The object to convert. Can be . + /// + /// The converted value, or if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already a , returns it directly. + /// If implements , recursively converts its . + /// If is or , returns . + /// If is a , attempts to parse it using . + /// If is a equal to "1", returns ; otherwise . + /// If is a , returns for '1', 'T', or 't'. + /// If implements , returns if not equal to 0. + /// + /// + /// + /// + /// bool result1 = "true".AsBool(); // Returns true + /// bool result2 = "1".AsBool(); // Returns true + /// bool result3 = 'T'.AsBool(); // Returns true + /// bool result4 = ((object)null).AsBool(); // Returns false + /// + /// public static bool AsBool(this object? obj) => obj switch { bool x => x, @@ -95,6 +292,32 @@ string s when bool.TryParse(s, out var b) => b, _ => default }; + /// + /// Converts an object to a value. + /// + /// The object to convert. Can be . + /// + /// The converted value, or if conversion is not possible. + /// + /// + /// The conversion is performed using the following priority: + /// + /// If is already a , returns it directly. + /// If implements , recursively converts its . + /// If is or , returns . + /// If is a , attempts to parse it as a GUID. + /// If is a representing an integer, creates a GUID with that integer as the first component. + /// If is a , creates a GUID with that value as the first component. + /// If implements , converts to and creates a GUID with that as the first component. + /// + /// + /// + /// + /// Guid result1 = "550e8400-e29b-41d4-a716-446655440000".AsGUID(); // Returns the parsed GUID + /// Guid result2 = 42.AsGUID(); // Returns GUID with first component = 42 + /// Guid result3 = ((object)null).AsGUID(); // Returns Guid.Empty + /// + /// public static Guid AsGUID(this object? obj) => obj switch { Guid g => g, @@ -109,22 +332,137 @@ string s when Guid.TryParse(s, out var g) => g, _ => default }; + /// + /// Executes an action on an object and returns a specified value. + /// + /// The type of the return value. + /// The type of the object on which the action is performed. + /// The object on which to perform the action. + /// The action to perform on . + /// The value to return after the action is executed. + /// The value after executing the action. + /// + /// This method is useful for performing side effects on an object while returning + /// a value in a fluent or expression-based context. + /// + /// + /// + /// var list = new List<int>(); + /// int count = list.SetRet(l => l.Add(42), 1); // Adds 42 to list, returns 1 + /// + /// public static T SetRet(this T2 obj, Action action, T v) { action(obj); return v; } } + +/// +/// Provides extension methods for dictionary operations with 1-based indexing. +/// +/// +/// This static class contains helper methods for +/// that use 1-based indexing internally while accepting 0-based index parameters. +/// This is useful for compatibility with legacy VB6-style control arrays. +/// public static class ObjectHelper2 { + /// + /// Sets a value in the dictionary at a 1-based internal index corresponding to the given 0-based index. + /// + /// The type of the values in the dictionary. + /// The dictionary to modify. + /// The value to set. + /// The 0-based index. Internally stored at + 1. + /// + /// This method stores the value at key ( + 1) to maintain + /// compatibility with 1-based indexing systems. + /// + /// + /// + /// var dic = new Dictionary<int, string>(); + /// dic.SetIndex("Hello", 0); // Stores "Hello" at key 1 + /// + /// public static void SetIndex(this Dictionary dic, T value, int index) => dic[index + 1] = value; + + /// + /// Gets the 0-based index of a value in the dictionary. + /// + /// The type of the values in the dictionary. + /// The dictionary to search. + /// The value to find. + /// + /// The 0-based index of the value (internal key - 1), or -1 if the value is not found. + /// + /// + /// This method searches for the value using + /// and returns the corresponding 0-based index. + /// + /// + /// + /// var dic = new Dictionary<int, string> { { 1, "Hello" }, { 2, "World" } }; + /// int index = dic.GetIndex("World"); // Returns 1 (key 2 - 1) + /// + /// public static int GetIndex(this Dictionary dic, T value) => dic.Where((itm) => itm.Value?.Equals(value) ?? false) .FirstOrDefault().Key - 1; } + +/// +/// Represents a generic control array that uses 0-based external indexing with 1-based internal storage. +/// +/// The type of elements in the control array. +/// +/// +/// This class extends to provide functionality +/// similar to Visual Basic 6 control arrays. The external indexer uses 0-based indexing, +/// but values are stored internally with 1-based keys. +/// +/// +/// The class implements to support designer initialization +/// scenarios, though the implementation is empty. +/// +/// +/// +/// +/// var controls = new ControlArray<Button>(); +/// controls[1] = new Button(); // Stored at key 2 +/// var button = controls[0]; // Retrieves from key 1 +/// +/// public class ControlArray : Dictionary, ISupportInitialize { + /// + /// Signals the object that initialization is starting. + /// + /// + /// This method is part of the interface + /// and is intentionally left empty as no special initialization logic is required. + /// public void BeginInit() { } + + /// + /// Signals the object that initialization is complete. + /// + /// + /// This method is part of the interface + /// and is intentionally left empty as no special finalization logic is required. + /// public void EndInit() { } + /// + /// Gets the element at the specified 0-based index. + /// + /// The 0-based index of the element to retrieve. + /// The element stored at the internal key ( + 1). + /// + /// Thrown when no element exists at the specified index. + /// + /// + /// This indexer provides 0-based access while internally using 1-based keys, + /// maintaining compatibility with VB6-style control arrays. + /// public new T this[int i] => base[i+1]; } diff --git a/Avalonia_Apps/Libraries/BaseLib/Helper/StringUtils.cs b/Avalonia_Apps/Libraries/BaseLib/Helper/StringUtils.cs index d2a7396a4..aa6497254 100644 --- a/Avalonia_Apps/Libraries/BaseLib/Helper/StringUtils.cs +++ b/Avalonia_Apps/Libraries/BaseLib/Helper/StringUtils.cs @@ -9,7 +9,10 @@ // // Copyright JC-Soft 2023 // -// +// +// Provides a collection of utility extension methods for string manipulation, +// including quoting/unquoting, splitting, formatting, validation, and substring operations. +// // *********************************************************************** using System.Collections.Generic; using System; @@ -18,25 +21,96 @@ namespace BaseLib.Helper; -/// A static class with useful string-routines. +/// +/// A static class providing a comprehensive set of utility extension methods for string manipulation. +/// +/// +/// +/// This class contains various helper methods for common string operations such as: +/// +/// +/// Quoting and unquoting strings (escaping/unescaping special characters) +/// String formatting with parameters +/// Splitting strings by separators with support for quoted sections +/// Tab padding and normalization +/// Substring extraction (Left, Right) +/// Identifier validation +/// Pattern matching (StartswithAny, EndswithAny, ContainsAny) +/// +/// +/// All methods are implemented as extension methods on or related types, +/// allowing for fluent method chaining syntax. +/// +/// +/// +/// +/// // Quoting a multi-line string +/// string quoted = "Hello\nWorld".Quote(); // Returns "Hello\\nWorld" +/// +/// // Getting the first part of a string +/// string first = "Hello World".SFirst(); // Returns "Hello" +/// +/// // Checking if a string is a valid identifier +/// bool valid = "MyVariable".IsValidIdentifyer(); // Returns true +/// +/// public static class StringUtils { - public const string AlphaUpper="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - public const string AlphaLower="abcdefghijklmnopqrstuvwxyz"; - public const string Alpha=AlphaUpper+AlphaLower; - public const string Numeric="0123456789"; - public const string AlphaNumeric=Alpha+Numeric; + /// + /// Contains all uppercase letters of the English alphabet (A-Z). + /// + /// The string "ABCDEFGHIJKLMNOPQRSTUVWXYZ". + public const string AlphaUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /// + /// Contains all lowercase letters of the English alphabet (a-z). + /// + /// The string "abcdefghijklmnopqrstuvwxyz". + public const string AlphaLower = "abcdefghijklmnopqrstuvwxyz"; /// - /// - /// Makes the specified string quotable.
-> by escaping special Characters (Linefeed, NewLine, Tab ...)
- /// - ///
- ///
+ /// Contains all letters of the English alphabet, both uppercase and lowercase. ///
- /// the string . - /// Quoted/escaped string - /// Does the opposite of
In other words: Puts a given text into one line of text.
+ /// The concatenation of and . + public const string Alpha = AlphaUpper + AlphaLower; + + /// + /// Contains all numeric digits (0-9). + /// + /// The string "0123456789". + public const string Numeric = "0123456789"; + + /// + /// Contains all alphanumeric characters (letters and digits). + /// + /// The concatenation of and . + public const string AlphaNumeric = Alpha + Numeric; + + /// + /// Escapes special characters in a string to make it suitable for single-line representation. + /// + /// The input string to quote. Can be null. + /// + /// A string with special characters escaped: + /// + /// Backslash (\) becomes \\ + /// Tab (\t) becomes \t + /// Carriage return (\r) becomes \r + /// Line feed (\n) becomes \n + /// + /// Returns an empty string if the input is null or if an exception occurs. + /// + /// + /// This method is the inverse of . + /// It converts a multi-line text into a single-line representation by escaping control characters. + /// + /// + /// + /// string input = "Line1\nLine2\tTabbed"; + /// string quoted = input.Quote(); // Returns "Line1\\nLine2\\tTabbed" + /// + /// + /// public static string Quote(this string? aStr) { try @@ -49,11 +123,38 @@ public static string Quote(this string? aStr) } catch { return ""; } } - /// Un-quotes the given string.
by un-escaping special characters (Linefeed, Newline, Tab ...)
- /// a string. - /// the unquoted/un-escaped string - /// Does the opposite of
In other words: Takes a given line of text and extracts the (original) text.
- public static string UnQuote(this string? aStr) + + /// + /// Unescapes special character sequences in a string, restoring the original control characters. + /// + /// The input string to unquote. Can be null. + /// + /// A string with escape sequences converted back to their original characters: + /// + /// \\ becomes backslash (\) + /// \t becomes tab character + /// \r becomes carriage return + /// \n becomes line feed + /// + /// Returns an empty string if the input is null. + /// + /// + /// + /// This method is the inverse of . + /// It converts a single-line escaped representation back to its original multi-line form. + /// + /// + /// The method uses a temporary placeholder character (U+0001) to handle nested backslashes correctly. + /// + /// + /// + /// + /// string escaped = "Line1\\nLine2\\tTabbed"; + /// string unquoted = escaped.UnQuote(); // Returns "Line1\nLine2\tTabbed" + /// + /// + /// + public static string UnQuote(this string? aStr) => (aStr ?? "") .Replace("\\\\", "\\\u0001") .Replace("\\t", "\t") @@ -62,22 +163,50 @@ public static string UnQuote(this string? aStr) .Replace("\\\u0001", "\\"); /// - /// Formats the specified string with par. + /// Formats a string using the specified parameters, similar to . /// - /// a string. - /// The par. - /// System.String. - /// + /// The format string containing placeholders like {0}, {1}, etc. + /// An array of objects to format into the string. + /// The formatted string with placeholders replaced by the corresponding parameter values. + /// + /// This is a convenience extension method that wraps , + /// allowing for a more fluent syntax when formatting strings. + /// + /// + /// + /// string template = "Hello, {0}! You have {1} messages."; + /// string result = template.Format("John", 5); // Returns "Hello, John! You have 5 messages." + /// + /// + /// + /// Thrown when the format string is invalid or when there are more placeholders than parameters. + /// public static string Format(this string aStr, params object[] par) => string.Format(aStr, par); /// - /// Gets the first part of the string separated by the separator. + /// Extracts the first part of a string before the specified separator. /// - /// The s. - /// The sep. - /// System.String. - /// + /// The input string to split. + /// The separator string to search for. Defaults to a single space. + /// + /// The portion of the string before the first occurrence of the separator. + /// If the separator is not found, returns the entire original string. + /// + /// + /// This method is useful for parsing delimited strings when only the first element is needed. + /// For the remaining part after the separator, use . + /// + /// + /// + /// string input = "key=value"; + /// string key = input.SFirst("="); // Returns "key" + /// + /// string words = "Hello World Today"; + /// string firstWord = words.SFirst(); // Returns "Hello" + /// + /// + /// public static string SFirst(this string s, string sep = " ") { if (!s.Contains(sep)) return s; @@ -85,24 +214,61 @@ public static string SFirst(this string s, string sep = " ") } /// - /// Gets the other part of the string separated by the separator. + /// Extracts the remaining part of a string after the first occurrence of the specified separator. /// - /// The s. - /// The sep. - /// System.String. - /// + /// The input string to split. + /// The separator string to search for. Defaults to a single space. + /// + /// The portion of the string after the first occurrence of the separator. + /// If the separator is not found, returns an empty string. + /// + /// + /// This method complements for parsing delimited strings. + /// Together, they can be used to iterate through delimited elements. + /// + /// + /// + /// string input = "key=value=extra"; + /// string rest = input.SRest("="); // Returns "value=extra" + /// + /// string words = "Hello World Today"; + /// string remaining = words.SRest(); // Returns "World Today" + /// + /// + /// public static string SRest(this string s, string sep = " ") { if (!s.Contains(sep)) return ""; return s.Substring(s.IndexOf(sep) + 1); } - - /// Pads the tab of the string with spaces. - /// The string. - /// The offset from the start of the line. - /// System.String. - /// + /// + /// Replaces tab characters in a string with the appropriate number of spaces to align to tab stops. + /// + /// The input string containing tab characters. + /// + /// The offset from the start of the line to use for tab stop calculation. + /// Defaults to 0. + /// + /// + /// A string with all tab characters replaced by spaces, aligned to 8-character tab stops. + /// + /// + /// + /// Tab stops are calculated at every 8th character position, taking into account the current + /// position in the line plus the specified offset. + /// + /// + /// This is useful for converting tab-formatted text to fixed-width spacing for display + /// in environments that don't support tab characters. + /// + /// + /// + /// + /// string input = "Name\tAge\tCity"; + /// string padded = input.PadTab(); // Returns "Name Age City" (aligned to 8-char tabs) + /// + /// public static string PadTab(this string s, int offs = 0) { var _s = s.Split('\t'); @@ -119,19 +285,61 @@ public static string PadTab(this string s, int offs = 0) static int TabLen(int l, int o) => l + o + (8 - (l + o) % 8) - o; } - /// Converts to "Normal" case. (first letter to upper- rest to lowercase) - /// The string. - /// System.String. - /// e.G: pEtEr will be converted to Peter. + /// + /// Converts a string to "normal" case, where the first letter is uppercase and all subsequent letters are lowercase. + /// + /// The input string to convert. + /// + /// The string with the first character in uppercase and all remaining characters in lowercase. + /// Returns the original string unchanged if it is null or empty. + /// + /// + /// This method is useful for normalizing names or titles that may have inconsistent casing. + /// Only the first character is capitalized; all other characters are converted to lowercase. + /// + /// + /// + /// string name = "pEtEr"; + /// string normalized = name.ToNormal(); // Returns "Peter" + /// + /// string allCaps = "HELLO"; + /// string result = allCaps.ToNormal(); // Returns "Hello" + /// + /// public static string ToNormal(this string s) => string.IsNullOrEmpty(s) ? s : s.Substring(0, 1).ToUpper() + s.Remove(0, 1).ToLower(); - /// Does a quoted split of the given string. - /// The data. - /// The separator. - /// The quotation mark. - /// List<System.String>. - /// + /// + /// Splits a string by a separator while respecting quoted sections that may contain the separator. + /// + /// The input string to split. + /// The separator string to split by. Defaults to comma (","). + /// The quotation mark string that defines quoted sections. Defaults to double quote ("). + /// + /// A of strings representing the split elements. + /// Quoted sections are preserved as single elements even if they contain the separator. + /// Leading and trailing whitespace is trimmed from each element. + /// + /// + /// + /// This method is particularly useful for parsing CSV data where fields may contain commas + /// enclosed in quotes. The quote marks themselves are removed from the resulting elements. + /// + /// + /// If a quoted section is not properly closed, the remaining content is added as a final element. + /// + /// + /// + /// + /// string csv = "John,\"Doe, Jr.\",25"; + /// List<string> fields = csv.QuotedSplit(); + /// // Returns: ["John", "Doe, Jr.", "25"] + /// + /// string data = "name='John Smith';age='30'"; + /// List<string> parts = data.QuotedSplit(";", "'"); + /// // Returns: ["name=John Smith", "age=30"] + /// + /// public static List QuotedSplit(this string Data, string Separator = ",", string QuoteMark = "\"") { var arPreSplit = Data.Split(new string[] { Separator }, StringSplitOptions.None); @@ -170,14 +378,59 @@ public static List QuotedSplit(this string Data, string Separator = ",", return result; } - public static bool EndswithAny(this string s,params string[] strings) + /// + /// Determines whether the string ends with any of the specified suffixes. + /// + /// The string to check. + /// An array of suffix strings to test against. + /// + /// true if the string ends with any of the specified suffixes as a complete word boundary; + /// otherwise, false. + /// + /// + /// + /// This method checks for word-boundary endings by prepending a space to both the input string + /// and each suffix before comparison. This ensures that "test" does not match "contest" when + /// checking for "test" as an ending. + /// + /// + /// + /// + /// bool result1 = "Hello World".EndswithAny("World", "Test"); // Returns true + /// bool result2 = "contest".EndswithAny("test"); // Returns false (word boundary check) + /// + /// + /// + /// + public static bool EndswithAny(this string s, params string[] strings) { foreach (var item in strings) - if ((" "+s).EndsWith(" "+item)) + if ((" " + s).EndsWith(" " + item)) return true; return false; } + /// + /// Determines whether the string contains any of the specified substrings. + /// + /// The string to search in. + /// An array of substrings to search for. + /// + /// true if the string contains any of the specified substrings; + /// otherwise, false. + /// + /// + /// The search is case-sensitive. The method returns true as soon as + /// the first matching substring is found. + /// + /// + /// + /// bool hasKeyword = "The quick brown fox".ContainsAny("cat", "dog", "fox"); // Returns true + /// bool noMatch = "Hello World".ContainsAny("xyz", "123"); // Returns false + /// + /// + /// + /// public static bool ContainsAny(this string s, params string[] strings) { foreach (var item in strings) @@ -185,69 +438,193 @@ public static bool ContainsAny(this string s, params string[] strings) return true; return false; } + + /// + /// Determines whether the string starts with any of the specified prefixes. + /// + /// The string to check. + /// An array of prefix strings to test against. + /// + /// true if the string starts with any of the specified prefixes as a complete word boundary; + /// otherwise, false. + /// + /// + /// + /// This method checks for word-boundary beginnings by appending a space to both the input string + /// and each prefix before comparison. This ensures that "test" does not match "testing" when + /// checking for "test" as a beginning. + /// + /// + /// + /// + /// bool result1 = "Hello World".StartswithAny("Hello", "Hi"); // Returns true + /// bool result2 = "testing".StartswithAny("test"); // Returns false (word boundary check) + /// + /// + /// + /// public static bool StartswithAny(this string s, params string[] strings) { foreach (var item in strings) - if ((s+" ").StartsWith(item+" ")) + if ((s + " ").StartsWith(item + " ")) return true; return false; } - + /// - /// Prft, ob der angegebene String ein gltiger Bezeichner (Identifier) ist. - /// Ein gltiger Bezeichner beginnt mit einem Grobuchstaben (A-Z) und enthlt nur Buchstaben, Ziffern oder Unterstriche. - /// Leere oder nur aus Leerzeichen bestehende Strings sind ungltig. + /// Determines whether the specified string is a valid identifier according to specific naming rules. /// - /// Der zu prfende String. - /// True, wenn der String ein gltiger Bezeichner ist, sonst false. + /// The string to validate. Can be null. + /// + /// true if the string is a valid identifier; otherwise, false. + /// + /// + /// + /// A valid identifier must meet the following criteria: + /// + /// + /// Cannot be null, empty, or contain only whitespace + /// Must begin with an uppercase letter (A-Z) + /// Can only contain letters (A-Z, a-z), digits (0-9), or underscores (_) + /// + /// + /// Note: The validation is case-insensitive for subsequent characters, but the first character + /// must be an alphabetic character (converted to uppercase for checking). + /// + /// + /// + /// + /// bool valid1 = "MyVariable".IsValidIdentifyer(); // Returns true + /// bool valid2 = "my_variable_123".IsValidIdentifyer(); // Returns true + /// bool invalid1 = "123Variable".IsValidIdentifyer(); // Returns false (starts with digit) + /// bool invalid2 = "my-variable".IsValidIdentifyer(); // Returns false (contains hyphen) + /// bool invalid3 = "".IsValidIdentifyer(); // Returns false (empty) + /// + /// public static bool IsValidIdentifyer(this string? s) { if (string.IsNullOrWhiteSpace(s)) return false; var _s = s!.ToUpper(); if (!AlphaUpper.Contains(_s[0])) return false; foreach (var c in _s) - if (!(AlphaNumeric+"_").Contains(c)) return false; + if (!(AlphaNumeric + "_").Contains(c)) return false; return true; } /// - /// Gibt die ersten iCnt Zeichen des Strings zurck. - /// Bei positivem iCnt werden die ersten iCnt Zeichen geliefert. - /// Bei negativem iCnt werden alle bis auf die letzten |iCnt| Zeichen geliefert. - /// Ist iCnt grer als die Lnge des Strings, wird der gesamte String zurckgegeben. + /// Returns the leftmost characters of a string up to the specified count. /// - /// Der Eingabestring. - /// Anzahl der gewnschten Zeichen (positiv: von links, negativ: bis auf die letzten |iCnt| Zeichen). - /// Der entsprechend gekrzte String. + /// The input string. + /// + /// The number of characters to return. + /// + /// Positive value: Returns the first characters from the left + /// Negative value: Returns all characters except the last || characters + /// + /// + /// + /// A substring containing the specified number of leftmost characters. + /// If exceeds the string length, the entire string is returned. + /// If the result would be negative length, an empty string is returned. + /// + /// + /// This method provides Python-like string slicing behavior for the beginning of strings. + /// + /// + /// + /// string text = "Hello World"; + /// + /// // Positive count - get first N characters + /// string first5 = text.Left(5); // Returns "Hello" + /// string first20 = text.Left(20); // Returns "Hello World" (entire string) + /// + /// // Negative count - exclude last N characters + /// string allButLast3 = text.Left(-3); // Returns "Hello Wo" (excludes "rld") + /// + /// + /// public static string Left(this string data, int iCnt) => iCnt >= 0 ? data.Substring(0, Math.Min(data.Length, iCnt)) : data.Substring(0, Math.Max(0, data.Length + iCnt)); /// - /// Gibt die letzten iCnt Zeichen des Strings zurck. - /// Bei positivem iCnt werden die letzten iCnt Zeichen geliefert. - /// Bei negativem iCnt werden die ersten |iCnt| Zeichen entfernt und der Rest geliefert. - /// Ist iCnt grer als die Lnge des Strings, wird der gesamte String zurckgegeben. + /// Returns the rightmost characters of a string up to the specified count. /// - /// Der Eingabestring. - /// Anzahl der gewnschten Zeichen (positiv: von rechts, negativ: ab dem |iCnt|-ten Zeichen von links). - /// Der entsprechend gekrzte String. + /// The input string. + /// + /// The number of characters to return. + /// + /// Positive value: Returns the last characters from the right + /// Negative value: Returns all characters starting from the ||th position + /// + /// + /// + /// A substring containing the specified number of rightmost characters. + /// If exceeds the string length, the entire string is returned. + /// + /// + /// This method provides Python-like string slicing behavior for the end of strings. + /// + /// + /// + /// string text = "Hello World"; + /// + /// // Positive count - get last N characters + /// string last5 = text.Right(5); // Returns "World" + /// string last20 = text.Right(20); // Returns "Hello World" (entire string) + /// + /// // Negative count - skip first N characters + /// string skipFirst3 = text.Right(-3); // Returns "lo World" (skips "Hel") + /// + /// + /// public static string Right(this string data, int iCnt) => iCnt >= 0 ? data.Substring(Math.Max(0, data.Length - iCnt)) : data.Substring(Math.Min(data.Length, -iCnt)); /// - /// Gibt eine String-Reprsentation des Objekts zurck. - /// Falls das Objekt ein String ist, wird es direkt zurckgegeben. - /// Falls das Objekt das Interface IHasValue implementiert, wird dessen Value als String zurckgegeben. - /// Bei null wird ein leerer String geliefert, ansonsten wird ToString() verwendet. + /// Converts an object to its string representation with special handling for certain types. /// - /// The data. - /// The format.(optional) - /// System.String. - public static string AsString(this object? data,string? format =null) + /// The object to convert. Can be null. + /// + /// An optional format string. Currently unused but reserved for future implementation. + /// + /// + /// A string representation of the object according to the following rules: + /// + /// If is a , it is returned directly + /// If implements , the Value property's string representation is returned + /// If is null, an empty string is returned + /// Otherwise, is called, with null results converted to empty string + /// + /// + /// + /// + /// This method provides a null-safe way to convert objects to strings, with special handling + /// for value wrapper types that implement . + /// + /// + /// The parameter is included for API consistency but is not + /// currently used in the implementation. + /// + /// + /// + /// + /// // String passthrough + /// string s = "Hello".AsString(); // Returns "Hello" + /// + /// // Null handling + /// object? nullObj = null; + /// string empty = nullObj.AsString(); // Returns "" + /// + /// // Object conversion + /// int number = 42; + /// string numStr = number.AsString(); // Returns "42" + /// + /// + public static string AsString(this object? data, string? format = null) => data switch { string s => s, @@ -255,16 +632,49 @@ public static string AsString(this object? data,string? format =null) null => "", object o => o.ToString() ?? "", }; - + /// - /// Copies the elements of the given string array into the specified list of strings, starting at the given offset. - /// If the target list is null, a new list with sufficient capacity is created. - /// Only elements that fit within the bounds of the target list are copied. + /// Copies elements from a string array into a target list of strings at a specified offset. /// /// The source array of strings to copy from. - /// The target list to copy into. If null, a new list is created. - /// The zero-based index in the target list at which copying begins. Can be negative. - /// The target list with the copied elements. + /// + /// The target list to copy into. If null, a new array is created with + /// sufficient capacity to hold all source elements considering the offset. + /// + /// + /// The zero-based index in the target list at which copying begins. + /// Can be negative, which will skip the first || elements of the source array. + /// + /// + /// The target list with the copied elements. If was null, + /// returns the newly created array. + /// + /// + /// + /// Only elements that fit within the bounds of the target list are copied. Elements that would + /// fall outside the target list boundaries (either negative indices or beyond the list count) + /// are silently ignored. + /// + /// + /// When is null, a new string array is created with size + /// Math.Max(0, asData.Length + offs). + /// + /// + /// + /// + /// // Copy into a new array + /// string[] source = { "a", "b", "c" }; + /// IList<string> result = source.IntoString(); // Creates ["a", "b", "c"] + /// + /// // Copy with offset into existing array + /// string[] target = new string[5]; + /// source.IntoString(target, 1); // target becomes [null, "a", "b", "c", null] + /// + /// // Copy with negative offset (skips first element of source) + /// string[] target2 = new string[2]; + /// source.IntoString(target2, -1); // target2 becomes ["b", "c"] + /// + /// public static IList IntoString(this string[] asData, IList? asKont = null, int offs = 0) { asKont ??= new string[Math.Max(0, asData.Length + offs)]; @@ -273,5 +683,4 @@ public static IList IntoString(this string[] asData, IList? asKo asKont[i + offs] = asData[i]; return asKont; } - } diff --git a/Avalonia_Apps/Libraries/BaseLib/Interfaces/IConsole.cs b/Avalonia_Apps/Libraries/BaseLib/Interfaces/IConsole.cs index 30d2bbb15..708334cb3 100644 --- a/Avalonia_Apps/Libraries/BaseLib/Interfaces/IConsole.cs +++ b/Avalonia_Apps/Libraries/BaseLib/Interfaces/IConsole.cs @@ -15,24 +15,148 @@ namespace BaseLib.Interfaces; +/// +/// Defines an abstraction layer for console operations, enabling dependency injection +/// and testability for console-based applications. +/// +/// +/// This interface provides a comprehensive set of console operations including: +/// +/// Color management for foreground and background +/// Window sizing and positioning +/// Input/output operations +/// Cursor positioning +/// Audio feedback capabilities +/// +/// Implementations of this interface can wrap for production use +/// or provide mock implementations for unit testing purposes. +/// public interface IConsole { + /// + /// Gets or sets the foreground color of the console output. + /// + /// + /// A value representing the text color. + /// ConsoleColor ForegroundColor { get; set; } + + /// + /// Gets or sets the background color of the console output. + /// + /// + /// A value representing the background color behind the text. + /// ConsoleColor BackgroundColor { get; set; } + + /// + /// Gets a value indicating whether the console output stream has been redirected. + /// + /// + /// true if the output is redirected to a file or another stream; otherwise, false. + /// bool IsOutputRedirected { get; } + + /// + /// Gets a value indicating whether a key press is available in the input stream. + /// + /// + /// true if a key press is available to be read; otherwise, false. + /// bool KeyAvailable { get; } + + /// + /// Gets the largest possible number of console window rows based on the current font and display resolution. + /// + /// + /// The maximum number of rows that can be displayed in the console window. + /// int LargestWindowHeight { get; } + + /// + /// Gets or sets the title to display in the console window's title bar. + /// + /// + /// A string representing the console window title. + /// string Title { get; set; } + + /// + /// Gets or sets the height of the console window area in rows. + /// + /// + /// The number of rows visible in the console window. + /// int WindowHeight { get; set; } + + /// + /// Gets or sets the width of the console window area in columns. + /// + /// + /// The number of columns visible in the console window. + /// int WindowWidth { get; set; } + /// + /// Plays a beep sound through the console speaker. + /// + /// The frequency of the beep sound in hertz (Hz). Valid range is typically 37 to 32767 Hz. + /// The duration of the beep sound in milliseconds. void Beep(int freq, int len); + + /// + /// Clears the console buffer and corresponding console window of all displayed information. + /// void Clear(); + + /// + /// Gets the current position of the cursor within the console buffer. + /// + /// + /// A tuple containing the Left (column) and Top (row) coordinates of the cursor position, + /// where (0, 0) represents the top-left corner of the console buffer. + /// (int Left, int Top) GetCursorPosition(); + + /// + /// Obtains the next character or function key pressed by the user. + /// + /// + /// A object describing the key pressed, including the character + /// and any modifier keys (Alt, Shift, Control); or null if no key is available. + /// ConsoleKeyInfo? ReadKey(); + + /// + /// Reads the next line of characters from the standard input stream. + /// + /// + /// The next line of characters from the input stream, or an empty string if no input is available. + /// string ReadLine(); + + /// + /// Sets the position of the cursor within the console buffer. + /// + /// The column position of the cursor, where 0 is the leftmost column. + /// The row position of the cursor, where 0 is the topmost row. void SetCursorPosition(int left, int top); + + /// + /// Writes the specified Unicode character to the standard output stream. + /// + /// The character to write to the console. void Write(char ch); + + /// + /// Writes the specified string value to the standard output stream. + /// + /// The string to write. Can be null, in which case nothing is written. void Write(string? st); + + /// + /// Writes the specified string value, followed by the current line terminator, to the standard output stream. + /// + /// The string to write. Can be null or empty. Defaults to an empty string. void WriteLine(string? st = ""); } \ No newline at end of file From 60197eecf0c5f4b453ea79616f208abb600eb1cb Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:45 +0100 Subject: [PATCH 007/962] GenFreeWin3 --- GenFreeWin/GenFreeWin3/App.xaml | 2 +- GenFreeWin/GenFreeWin3/Views/AdresseView.xaml | 2 +- GenFreeWin/GenFreeWin3/Views/MenueControl.xaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GenFreeWin/GenFreeWin3/App.xaml b/GenFreeWin/GenFreeWin3/App.xaml index 0c93fe9f5..c754a8d36 100644 --- a/GenFreeWin/GenFreeWin3/App.xaml +++ b/GenFreeWin/GenFreeWin3/App.xaml @@ -3,7 +3,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:GenFreeWpf" - xmlns:vc2="clr-namespace:MVVM.View.ValueConverter;assembly=MVVM_BaseLib" + xmlns:vc2="clr-namespace:MVVM.Views.ValueConverter;assembly=MVVM_BaseLib" StartupUri="MainWindow.xaml"> diff --git a/GenFreeWin/GenFreeWin3/Views/AdresseView.xaml b/GenFreeWin/GenFreeWin3/Views/AdresseView.xaml index ba92d8606..4571d7ee1 100644 --- a/GenFreeWin/GenFreeWin3/Views/AdresseView.xaml +++ b/GenFreeWin/GenFreeWin3/Views/AdresseView.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:GenFreeWpf.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:vex="clr-namespace:MVVM.View.Extension;assembly=MVVM_BaseLib" + xmlns:vex="clr-namespace:MVVM.Views.Extension;assembly=MVVM_BaseLib" xmlns:vm="clr-namespace:GenFree.ViewModels.Interfaces;assembly=GenFreeUIItfs" DataContext="{vex:IoC2 Type=vm:IAdresseViewModel}" mc:Ignorable="d"> diff --git a/GenFreeWin/GenFreeWin3/Views/MenueControl.xaml b/GenFreeWin/GenFreeWin3/Views/MenueControl.xaml index caa376bf4..8b4235b6f 100644 --- a/GenFreeWin/GenFreeWin3/Views/MenueControl.xaml +++ b/GenFreeWin/GenFreeWin3/Views/MenueControl.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:GenFreeWpf.Views" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mvvmi="clr-namespace:GenFree.ViewModels.Interfaces;assembly=GenFreeUIItfs" - xmlns:vex="clr-namespace:MVVM.View.Extension;assembly=MVVM_BaseLib" + xmlns:vex="clr-namespace:MVVM.Views.Extension;assembly=MVVM_BaseLib" xmlns:viewmodels="clr-namespace:GenFreeWpf.ViewModels" d:DesignHeight="768" d:DesignWidth="1024" From eea1e4372c23fd9092512f30eb244b031225bf24 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:46 +0100 Subject: [PATCH 008/962] MdbBrowser --- GenFreeWin/MdbBrowser/App.xaml.cs | 2 +- GenFreeWin/MdbBrowser/ViewModels/SchemaViewViewModel.cs | 2 +- GenFreeWin/MdbBrowser/ViewModels/TableViewViewModel.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GenFreeWin/MdbBrowser/App.xaml.cs b/GenFreeWin/MdbBrowser/App.xaml.cs index 890bbf1f2..6c67dce50 100644 --- a/GenFreeWin/MdbBrowser/App.xaml.cs +++ b/GenFreeWin/MdbBrowser/App.xaml.cs @@ -4,7 +4,7 @@ using MdbBrowser.ViewModels; using MdbBrowser.ViewModels.Interfaces; using Microsoft.Extensions.DependencyInjection; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Windows; diff --git a/GenFreeWin/MdbBrowser/ViewModels/SchemaViewViewModel.cs b/GenFreeWin/MdbBrowser/ViewModels/SchemaViewViewModel.cs index d8f0952a1..7c4c384aa 100644 --- a/GenFreeWin/MdbBrowser/ViewModels/SchemaViewViewModel.cs +++ b/GenFreeWin/MdbBrowser/ViewModels/SchemaViewViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using MdbBrowser.ViewModels.Interfaces; using MdbBrowser.Models; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Data; using BaseLib.Helper; diff --git a/GenFreeWin/MdbBrowser/ViewModels/TableViewViewModel.cs b/GenFreeWin/MdbBrowser/ViewModels/TableViewViewModel.cs index d2adc074e..c513803d6 100644 --- a/GenFreeWin/MdbBrowser/ViewModels/TableViewViewModel.cs +++ b/GenFreeWin/MdbBrowser/ViewModels/TableViewViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using MdbBrowser.ViewModels.Interfaces; using MdbBrowser.Models; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Collections.Generic; using System.Collections.ObjectModel; From 1f6948c8cb00bd54787c88e5e1879346d0fe747b Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:47 +0100 Subject: [PATCH 009/962] MdbBrowserTests --- GenFreeWin/MdbBrowserTests/AppTests.cs | 2 +- GenFreeWin/MdbBrowserTests/SchemaViewTests.cs | 2 +- GenFreeWin/MdbBrowserTests/Views/TableViewTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GenFreeWin/MdbBrowserTests/AppTests.cs b/GenFreeWin/MdbBrowserTests/AppTests.cs index 9ec946637..8de065fe7 100644 --- a/GenFreeWin/MdbBrowserTests/AppTests.cs +++ b/GenFreeWin/MdbBrowserTests/AppTests.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using MVVM.View.Extension; +using MVVM.Views.Extension; using MdbBrowser.ViewModels.Interfaces; using BaseLib.Helper; diff --git a/GenFreeWin/MdbBrowserTests/SchemaViewTests.cs b/GenFreeWin/MdbBrowserTests/SchemaViewTests.cs index d452a81dc..328cc34fd 100644 --- a/GenFreeWin/MdbBrowserTests/SchemaViewTests.cs +++ b/GenFreeWin/MdbBrowserTests/SchemaViewTests.cs @@ -1,5 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Threading; using Microsoft.Extensions.DependencyInjection; diff --git a/GenFreeWin/MdbBrowserTests/Views/TableViewTests.cs b/GenFreeWin/MdbBrowserTests/Views/TableViewTests.cs index 969563662..f4ff007b3 100644 --- a/GenFreeWin/MdbBrowserTests/Views/TableViewTests.cs +++ b/GenFreeWin/MdbBrowserTests/Views/TableViewTests.cs @@ -2,7 +2,7 @@ using System; using System.Threading; using Microsoft.Extensions.DependencyInjection; -using MVVM.View.Extension; +using MVVM.Views.Extension; using MdbBrowser.ViewModels.Interfaces; using NSubstitute; using BaseLib.Helper; From d42d29133a13c303727b33e65fba79fc5828d523 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:47 +0100 Subject: [PATCH 010/962] MSQBrowser --- GenFreeWin/MSQBrowser/App.xaml.cs | 2 +- GenFreeWin/MSQBrowser/ViewModels/SchemaViewViewModel.cs | 2 +- GenFreeWin/MSQBrowser/ViewModels/TableViewViewModel.cs | 2 +- GenFreeWin/MSQBrowser/Views/DBView.xaml.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GenFreeWin/MSQBrowser/App.xaml.cs b/GenFreeWin/MSQBrowser/App.xaml.cs index aeff762c9..f1c3e036e 100644 --- a/GenFreeWin/MSQBrowser/App.xaml.cs +++ b/GenFreeWin/MSQBrowser/App.xaml.cs @@ -3,7 +3,7 @@ using MSQBrowser.ViewModels; using MSQBrowser.ViewModels.Interfaces; using Microsoft.Extensions.DependencyInjection; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Data.Common; using System.Windows; diff --git a/GenFreeWin/MSQBrowser/ViewModels/SchemaViewViewModel.cs b/GenFreeWin/MSQBrowser/ViewModels/SchemaViewViewModel.cs index 2b553c72f..b17c20bba 100644 --- a/GenFreeWin/MSQBrowser/ViewModels/SchemaViewViewModel.cs +++ b/GenFreeWin/MSQBrowser/ViewModels/SchemaViewViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using MSQBrowser.ViewModels.Interfaces; using MSQBrowser.Models; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Data; using BaseLib.Helper; diff --git a/GenFreeWin/MSQBrowser/ViewModels/TableViewViewModel.cs b/GenFreeWin/MSQBrowser/ViewModels/TableViewViewModel.cs index e0294a0bb..f5817c809 100644 --- a/GenFreeWin/MSQBrowser/ViewModels/TableViewViewModel.cs +++ b/GenFreeWin/MSQBrowser/ViewModels/TableViewViewModel.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using MSQBrowser.ViewModels.Interfaces; using MSQBrowser.Models; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/GenFreeWin/MSQBrowser/Views/DBView.xaml.cs b/GenFreeWin/MSQBrowser/Views/DBView.xaml.cs index 91cf514b0..761710c18 100644 --- a/GenFreeWin/MSQBrowser/Views/DBView.xaml.cs +++ b/GenFreeWin/MSQBrowser/Views/DBView.xaml.cs @@ -2,7 +2,7 @@ using System; using System.Windows.Controls; using CommonDialogs.Interfaces; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System.Security; using BaseLib.Helper; From aff0bc7f3034475a4b130ec74a81689588c020a9 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:48 +0100 Subject: [PATCH 011/962] VBUnObfusicator --- GenFreeWin/VBUnObfusicator/App.xaml.cs | 2 +- GenFreeWin/VBUnObfusicator/ViewModels/CodeUnObFusViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GenFreeWin/VBUnObfusicator/App.xaml.cs b/GenFreeWin/VBUnObfusicator/App.xaml.cs index 7f88cab65..763d3ee59 100644 --- a/GenFreeWin/VBUnObfusicator/App.xaml.cs +++ b/GenFreeWin/VBUnObfusicator/App.xaml.cs @@ -1,4 +1,4 @@ -using MVVM.View.Extension; +using MVVM.Views.Extension; using Microsoft.Extensions.DependencyInjection; using System.Windows; using VBUnObfusicator.Models; diff --git a/GenFreeWin/VBUnObfusicator/ViewModels/CodeUnObFusViewModel.cs b/GenFreeWin/VBUnObfusicator/ViewModels/CodeUnObFusViewModel.cs index 81a527cb4..512d592ed 100644 --- a/GenFreeWin/VBUnObfusicator/ViewModels/CodeUnObFusViewModel.cs +++ b/GenFreeWin/VBUnObfusicator/ViewModels/CodeUnObFusViewModel.cs @@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.Input; using BaseLib.Helper; using MVVM.ViewModel; -using MVVM.View.Extension; +using MVVM.Views.Extension; using VBUnObfusicator.Interfaces.Code; using VBUnObfusicator.Properties; From 28bd85914f6ccb20be0c74f6595dc632db8e8ab2 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:49 +0100 Subject: [PATCH 012/962] VBUnObfusicatorTests --- .../ViewModels/CodeUnObFusViewModelTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GenFreeWin/VBUnObfusicatorTests/ViewModels/CodeUnObFusViewModelTests.cs b/GenFreeWin/VBUnObfusicatorTests/ViewModels/CodeUnObFusViewModelTests.cs index d63315f73..13945f67b 100644 --- a/GenFreeWin/VBUnObfusicatorTests/ViewModels/CodeUnObFusViewModelTests.cs +++ b/GenFreeWin/VBUnObfusicatorTests/ViewModels/CodeUnObFusViewModelTests.cs @@ -1,7 +1,7 @@ using BaseLib.Helper; using CommunityToolkit.Mvvm.Input; using Microsoft.VisualStudio.TestTools.UnitTesting; -using MVVM.View.Extension; +using MVVM.Views.Extension; using System; using System.Collections.Generic; using System.Globalization; From 886caaac2253b1eae9e9c516ad60be3fa6bde3b5 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:27:50 +0100 Subject: [PATCH 013/962] GenFreeWin --- GenFreeWin/Gen_FreeWin.sln | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/GenFreeWin/Gen_FreeWin.sln b/GenFreeWin/Gen_FreeWin.sln index 5c607f9fb..bd2e0e507 100644 --- a/GenFreeWin/Gen_FreeWin.sln +++ b/GenFreeWin/Gen_FreeWin.sln @@ -271,6 +271,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp_net", "..\CSharpBibl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinAhnenNew", "..\WinAhnenNew\WinAhnenNew\WinAhnenNew.csproj", "{A531B6AD-2580-4F20-A371-51C68ACEB3E5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocOdfTest", "..\CSharpBible\Data\DocumentUtils\DocOdfTest\DocOdfTest.csproj", "{1EE28476-F98D-47AF-8E87-CB4CCD187A60}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -789,6 +791,14 @@ Global {A531B6AD-2580-4F20-A371-51C68ACEB3E5}.Release|Any CPU.Build.0 = Release|Any CPU {A531B6AD-2580-4F20-A371-51C68ACEB3E5}.Release|x86.ActiveCfg = Release|Any CPU {A531B6AD-2580-4F20-A371-51C68ACEB3E5}.Release|x86.Build.0 = Release|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Debug|x86.ActiveCfg = Debug|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Debug|x86.Build.0 = Debug|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Release|Any CPU.Build.0 = Release|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Release|x86.ActiveCfg = Release|Any CPU + {1EE28476-F98D-47AF-8E87-CB4CCD187A60}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -861,6 +871,7 @@ Global {D0B84C38-67C1-3A70-EEDD-15FD63F9FC21} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {FD65B95E-DDBD-4048-8BD2-2F4F709445AB} = {2365079F-BDCB-455D-BEB4-9C5D270B2E7A} {A531B6AD-2580-4F20-A371-51C68ACEB3E5} = {2A2E129C-95B6-4192-BA94-D9C5A249AB43} + {1EE28476-F98D-47AF-8E87-CB4CCD187A60} = {E0561A19-1682-4804-878F-24D4A1CB05C4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3B766B89-B32C-4777-9290-8BF7CEC401B5} From 2882703f49baa18ccd002eebdf87d2325745935b Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 00:28:30 +0100 Subject: [PATCH 014/962] Document.Odf --- .../Document.Odf/OdfTextDocument.cs | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/CSharpBible/Data/DocumentUtils/Document.Odf/OdfTextDocument.cs b/CSharpBible/Data/DocumentUtils/Document.Odf/OdfTextDocument.cs index cc92aeba1..e2f1f6fd1 100644 --- a/CSharpBible/Data/DocumentUtils/Document.Odf/OdfTextDocument.cs +++ b/CSharpBible/Data/DocumentUtils/Document.Odf/OdfTextDocument.cs @@ -74,8 +74,7 @@ public bool SaveTo(Stream sOutputStream, object? options = null) var contentEntry = zip.CreateEntry("content.xml"); using (var stream = contentEntry.Open()) { - var xdoc = new System.Xml.Linq.XDocument(); - // Document.Odf.Serialization.OdfBlockSerializer.Serialize(this, xdoc); + var xdoc = CreateContentXDocument(); xdoc.Save(stream); } @@ -132,7 +131,8 @@ public bool LoadFrom(Stream sInputStream, object? options = null) private Models.OdfSection EnsureRoot() => Root as Models.OdfSection ?? throw new InvalidOperationException("Root ist nicht OdfSection"); - private static System.Xml.Linq.XDocument CreateManifest() + + private static System.Xml.Linq.XDocument CreateManifest() { System.Xml.Linq.XNamespace manifestNs = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"; return new System.Xml.Linq.XDocument( @@ -148,4 +148,59 @@ private static System.Xml.Linq.XDocument CreateManifest() ) ); } + + private System.Xml.Linq.XDocument CreateContentXDocument() + { + // TODO: Anpassung an tatsächliches ODF-Modell der Anwendung + var officeNs = System.Xml.Linq.XNamespace.Get("urn:oasis:names:tc:opendocument:xmlns:office:1.0"); + var textNs = System.Xml.Linq.XNamespace.Get("urn:oasis:names:tc:opendocument:xmlns:text:1.0"); + + var rootSection = EnsureRoot(); + + var bodyElement = new System.Xml.Linq.XElement(officeNs + "body"); + var textElement = new System.Xml.Linq.XElement(officeNs + "text"); + bodyElement.Add(textElement); + + foreach (var element in rootSection.Enumerate()) + { + if (element is IDocHeadline headline) + { + var h = new System.Xml.Linq.XElement(textNs + "h", + new System.Xml.Linq.XAttribute(textNs + "outline-level", headline.Level)); + + if (!string.IsNullOrEmpty(headline.Id)) + h.Add(new System.Xml.Linq.XAttribute("xml:id", headline.Id)); + + // headline.Text gibt es nicht, stattdessen TextContent verwenden + if (!string.IsNullOrEmpty(headline.TextContent)) + h.Add(new System.Xml.Linq.XText(headline.TextContent)); + + textElement.Add(h); + } + else if (element is IDocParagraph paragraph) + { + var p = new System.Xml.Linq.XElement(textNs + "p"); + + // StyleName gibt es nicht im Interface, daher entfernt + // if (!string.IsNullOrEmpty(paragraph.StyleName)) + // p.Add(new System.Xml.Linq.XAttribute(textNs + "style-name", paragraph.StyleName)); + + if (!string.IsNullOrEmpty(paragraph.TextContent)) + p.Add(new System.Xml.Linq.XText(paragraph.TextContent)); + + textElement.Add(p); + } + // Weitere Elementtypen hier bei Bedarf abbilden + } + + var doc = new System.Xml.Linq.XDocument( + new System.Xml.Linq.XDeclaration("1.0", "UTF-8", null), + new System.Xml.Linq.XElement(officeNs + "document-content", + new System.Xml.Linq.XAttribute(System.Xml.Linq.XNamespace.Xmlns + "office", officeNs), + new System.Xml.Linq.XAttribute(System.Xml.Linq.XNamespace.Xmlns + "text", textNs), + bodyElement) + ); + + return doc; + } } From ee7fc1eecfbb3a618f48f0b0e14f85cc8ffe8c10 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 02:43:03 +0100 Subject: [PATCH 015/962] AsteroidsModernEngine.Tests --- .../Games/Arkanoid.Base/Arkanoid.Base.csproj | 13 ++ CSharpBible/Games/Arkanoid.Base/GameEngine.cs | 116 ++++++++++++++ .../Games/Arkanoid.Base/Models/GameObjects.cs | 46 ++++++ .../Games/Arkanoid.Cons/Arkanoid.Cons.csproj | 16 ++ CSharpBible/Games/Arkanoid.Cons/Program.cs | 149 ++++++++++++++++++ .../AsteroidsModernEngine.Tests.csproj | 4 +- 6 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 CSharpBible/Games/Arkanoid.Base/Arkanoid.Base.csproj create mode 100644 CSharpBible/Games/Arkanoid.Base/GameEngine.cs create mode 100644 CSharpBible/Games/Arkanoid.Base/Models/GameObjects.cs create mode 100644 CSharpBible/Games/Arkanoid.Cons/Arkanoid.Cons.csproj create mode 100644 CSharpBible/Games/Arkanoid.Cons/Program.cs diff --git a/CSharpBible/Games/Arkanoid.Base/Arkanoid.Base.csproj b/CSharpBible/Games/Arkanoid.Base/Arkanoid.Base.csproj new file mode 100644 index 000000000..c60e65135 --- /dev/null +++ b/CSharpBible/Games/Arkanoid.Base/Arkanoid.Base.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/CSharpBible/Games/Arkanoid.Base/GameEngine.cs b/CSharpBible/Games/Arkanoid.Base/GameEngine.cs new file mode 100644 index 000000000..4641a7931 --- /dev/null +++ b/CSharpBible/Games/Arkanoid.Base/GameEngine.cs @@ -0,0 +1,116 @@ +using Arkanoid.Base.Models; + +namespace Arkanoid.Base; + +public class GameEngine +{ + public GameState State { get; } = new(); + + public GameEngine() + { + CreateDefaultLevel(); + } + + public void MovePaddle(float direction, float deltaTime) + { + var dx = State.Paddle.Speed * direction * deltaTime; + var newX = Math.Clamp(State.Paddle.Position.X + dx, 0, State.FieldWidth - State.Paddle.Width); + State.Paddle.Position = new Vector2(newX, State.Paddle.Position.Y); + } + + public void Update(float deltaTime) + { + if (State.IsGameOver) return; + + State.Ball.Position = new Vector2( + State.Ball.Position.X + State.Ball.Velocity.X * deltaTime, + State.Ball.Position.Y + State.Ball.Velocity.Y * deltaTime); + + HandleCollisions(); + } + + private void HandleCollisions() + { + var b = State.Ball; + + // walls + if (b.Position.X - b.Radius < 0 || b.Position.X + b.Radius > State.FieldWidth) + State.Ball.Velocity = new Vector2(-b.Velocity.X, b.Velocity.Y); + if (b.Position.Y - b.Radius < 0) + State.Ball.Velocity = new Vector2(b.Velocity.X, -b.Velocity.Y); + if (b.Position.Y - b.Radius > State.FieldHeight) + { + State.Lives--; + if (State.Lives <= 0) State.IsGameOver = true; + ResetBallAndPaddle(); + return; + } + + // paddle (simple AABB) + var p = State.Paddle; + if (b.Position.Y + b.Radius >= p.Position.Y && + b.Position.Y - b.Radius <= p.Position.Y + 1 && + b.Position.X >= p.Position.X && + b.Position.X <= p.Position.X + p.Width && + b.Velocity.Y > 0) + { + State.Ball.Velocity = new Vector2(b.Velocity.X, -Math.Abs(b.Velocity.Y)); + } + + // bricks + foreach (var brick in State.Bricks.ToList()) + { + if (brick.IsDestroyed) continue; + + if (b.Position.X + b.Radius < brick.Position.X || + b.Position.X - b.Radius > brick.Position.X + brick.Width || + b.Position.Y + b.Radius < brick.Position.Y || + b.Position.Y - b.Radius > brick.Position.Y + brick.Height) + continue; + + State.Ball.Velocity = new Vector2(b.Velocity.X, -b.Velocity.Y); + + if (brick.Type != BrickType.Unbreakable) + { + brick.HitPoints--; + if (brick.IsDestroyed) + { + State.Score += 10; + } + } + } + + if (State.Bricks.All(br => br.IsDestroyed)) + State.IsGameOver = true; + } + + private void ResetBallAndPaddle() + { + State.Paddle.Position = new Vector2(State.FieldWidth / 2 - State.Paddle.Width / 2, State.FieldHeight - 2); + State.Ball.Position = new Vector2(State.FieldWidth / 2, State.FieldHeight / 2); + State.Ball.Velocity = new Vector2(15, -20); + } + + private void CreateDefaultLevel() + { + State.Bricks.Clear(); + var rows = 5; + var cols = 10; + var brickWidth = State.FieldWidth / cols; + for (var y = 0; y < rows; y++) + { + for (var x = 0; x < cols; x++) + { + State.Bricks.Add(new Brick + { + Position = new Vector2(x * brickWidth, 2 + y), + Width = brickWidth - 0.5f, + Height = 1, + Type = BrickType.Normal, + HitPoints = 1 + }); + } + } + ResetBallAndPaddle(); + } +} diff --git a/CSharpBible/Games/Arkanoid.Base/Models/GameObjects.cs b/CSharpBible/Games/Arkanoid.Base/Models/GameObjects.cs new file mode 100644 index 000000000..bf3b014bf --- /dev/null +++ b/CSharpBible/Games/Arkanoid.Base/Models/GameObjects.cs @@ -0,0 +1,46 @@ +namespace Arkanoid.Base.Models; + +public enum BrickType +{ + Normal, + Strong, + Unbreakable +} + +public record Vector2(float X, float Y); + +public class Paddle +{ + public Vector2 Position { get; set; } = new(10, 20); + public float Width { get; set; } = 6f; + public float Speed { get; set; } = 25f; // units per second +} + +public class Ball +{ + public Vector2 Position { get; set; } = new(10, 10); + public Vector2 Velocity { get; set; } = new(10, -10); // units per second + public float Radius { get; set; } = 0.5f; +} + +public class Brick +{ + public Vector2 Position { get; set; } + public float Width { get; set; } = 3f; + public float Height { get; set; } = 1f; + public BrickType Type { get; set; } + public int HitPoints { get; set; } = 1; + public bool IsDestroyed => HitPoints <= 0 && Type != BrickType.Unbreakable; +} + +public class GameState +{ + public Paddle Paddle { get; } = new(); + public Ball Ball { get; } = new(); + public List Bricks { get; } = new(); + public int Score { get; set; } + public int Lives { get; set; } = 3; + public float FieldWidth { get; set; } = 80; + public float FieldHeight { get; set; } = 40; + public bool IsGameOver { get; set; } +} diff --git a/CSharpBible/Games/Arkanoid.Cons/Arkanoid.Cons.csproj b/CSharpBible/Games/Arkanoid.Cons/Arkanoid.Cons.csproj new file mode 100644 index 000000000..2709085c9 --- /dev/null +++ b/CSharpBible/Games/Arkanoid.Cons/Arkanoid.Cons.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/CSharpBible/Games/Arkanoid.Cons/Program.cs b/CSharpBible/Games/Arkanoid.Cons/Program.cs new file mode 100644 index 000000000..2f67576ab --- /dev/null +++ b/CSharpBible/Games/Arkanoid.Cons/Program.cs @@ -0,0 +1,149 @@ +using Arkanoid.Base; +using ConsoleDisplay.View; +using ConsoleLib; +using ConsoleLib.Interfaces; + +namespace Arkanoid.Cons +{ + internal class Program + { + static void Main(string[] args) + { + Console.CursorVisible = false; + var engine = new GameEngine(); + + var displayWidth = (int)engine.State.FieldWidth; + var displayHeight = (int)engine.State.FieldHeight; + var display = new Display(0, 0, displayWidth, displayHeight); + + // ExtendedConsole initialisieren, um echte Key-/Maus-Events zu bekommen + var extConsole = new ExtendedConsole(); + + bool leftDown = false; + bool rightDown = false; + bool useMouse = true; + + float paddleVelocity = 0f; + const float paddleAcceleration = 60f; + const float paddleFriction = 40f; + + // Key-Events (KeyDown/KeyUp) + extConsole.KeyEvent += (s, e) => + { + // Virtuelle Keycodes der Pfeiltasten + const ushort VK_LEFT = 0x25; + const ushort VK_RIGHT = 0x27; + + if (e.usKeyCode == VK_LEFT) + { + leftDown = e.bKeyDown; + if (e.bKeyDown) rightDown = false; + e.Handled = true; + } + else if (e.usKeyCode == VK_RIGHT) + { + rightDown = e.bKeyDown; + if (e.bKeyDown) leftDown = false; + e.Handled = true; + } + else if (e.usKeyCode == (ushort)ConsoleKey.Escape && e.bKeyDown) + { + // ESC zum Beenden + extConsole.Stop(); + engine.State.IsGameOver = true; + e.Handled = true; + } + }; + + // Maussteuerung: Paddle per Maus-X-Position bewegen + extConsole.MouseEvent += (s, me) => + { + if (!useMouse) return; + + if (me.MouseMoved || me.ButtonEvent) + { + var mouseX = me.MousePos.X; + // Paddle-Mittelpunkt an Maus-X ausrichten + var newX = mouseX - engine.State.Paddle.Width / 2f; + newX = Math.Clamp(newX, 0, engine.State.FieldWidth - engine.State.Paddle.Width); + engine.State.Paddle.Position = new Arkanoid.Base.Models.Vector2(newX, engine.State.Paddle.Position.Y); + me.Handled = true; + } + }; + + var lastTime = DateTime.UtcNow; + + while (!engine.State.IsGameOver) + { + var now = DateTime.UtcNow; + var delta = (float)(now - lastTime).TotalSeconds; + if (delta <= 0f) delta = 0.001f; + lastTime = now; + + // Tastatursteuerung (wenn Maus nicht benutzt wird) + if (!useMouse) + { + float moveDir = 0f; + if (leftDown && !rightDown) moveDir = -1f; + else if (rightDown && !leftDown) moveDir = 1f; + else + { + // Reibung, wenn keine Richtung gehalten wird + if (paddleVelocity > 0) + { + paddleVelocity -= paddleFriction * delta; + if (paddleVelocity < 0) paddleVelocity = 0; + } + else if (paddleVelocity < 0) + { + paddleVelocity += paddleFriction * delta; + if (paddleVelocity > 0) paddleVelocity = 0; + } + } + + if (moveDir != 0f) + { + paddleVelocity += moveDir * paddleAcceleration * delta; + } + + if (paddleVelocity != 0f) + { + var direction = Math.Sign(paddleVelocity); + engine.MovePaddle(direction, delta); + } + } + + engine.Update(delta); + + // render + display.Clear(); + + // paddle + for (int x = 0; x < (int)engine.State.Paddle.Width; x++) + { + display.PutPixel((int)(engine.State.Paddle.Position.X + x), (int)engine.State.Paddle.Position.Y, ConsoleColor.Green); + } + + // ball + display.PutPixel((int)engine.State.Ball.Position.X, (int)engine.State.Ball.Position.Y, ConsoleColor.Yellow); + + // bricks + foreach (var brick in engine.State.Bricks) + { + if (brick.IsDestroyed) continue; + for (int bx = 0; bx < (int)brick.Width; bx++) + { + display.PutPixel((int)(brick.Position.X + bx), (int)brick.Position.Y, ConsoleColor.Red); + } + } + + display.Update(); + + Thread.Sleep(10); + } + + Console.ResetColor(); + Console.CursorVisible = true; + } + } +} diff --git a/CSharpBible/Games/AsteroidsModernEngine.Tests/AsteroidsModernEngine.Tests.csproj b/CSharpBible/Games/AsteroidsModernEngine.Tests/AsteroidsModernEngine.Tests.csproj index 69a877585..79559d9db 100644 --- a/CSharpBible/Games/AsteroidsModernEngine.Tests/AsteroidsModernEngine.Tests.csproj +++ b/CSharpBible/Games/AsteroidsModernEngine.Tests/AsteroidsModernEngine.Tests.csproj @@ -7,8 +7,8 @@ enable - - + + all From 4bf33a2ee644cb26380ea856fbf901d05f6b52f4 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 02:43:03 +0100 Subject: [PATCH 016/962] Games --- .../AsteroidsModernEngine.csproj | 2 +- .../AsteroidsModernUI.csproj | 4 +- CSharpBible/Games/BoxFlight/BoxFlight.csproj | 2 +- .../Games/CreateCards/CreateCards.csproj | 2 +- CSharpBible/Games/CreateCards/Model/Card.cs | 162 +++++- .../Games/CreateCards/Model/CardDrawDef.cs | 7 +- .../DetectiveGame.Console.csproj | 2 +- .../DetectiveGame.Tests.csproj | 4 +- .../Games/Galaxia_UI/Galaxia_UI.csproj | 2 +- CSharpBible/Games/Games.sln | 16 + .../HexaBan_Console/HexaBan_Console.csproj | 2 +- .../HexaBan_Console/HexaBan_ConsoleLnx.csproj | 2 +- .../HexaBan_Console/HexaBan_ConsoleWin.csproj | 2 +- .../MidiSwing.MVVM/Models/MusicGenerator.cs | 460 ++++++++++++------ CSharpBible/Games/Snake.WPF/Snake.WPF.csproj | 2 +- .../Games/Snake_Console/Snake_Console.csproj | 2 +- .../Games/Treppen.Base/Treppen.Base.csproj | 4 +- .../Treppen.BaseTests.csproj | 4 +- .../Games/Treppen.WPF/Treppen.WPF.csproj | 2 +- CSharpBible/Games/VTileEdit/VTileEdit.csproj | 2 +- CSharpBible/Games/VectorGfx/VectorGfx.csproj | 2 +- .../Games/VectorGfx2/VectorGfx2.csproj | 2 +- .../Werner_Flaschbier_Console.csproj | 2 +- 23 files changed, 490 insertions(+), 201 deletions(-) diff --git a/CSharpBible/Games/AsteroidsModernEngine/AsteroidsModernEngine.csproj b/CSharpBible/Games/AsteroidsModernEngine/AsteroidsModernEngine.csproj index 6acc27ca0..94b5d2e0c 100644 --- a/CSharpBible/Games/AsteroidsModernEngine/AsteroidsModernEngine.csproj +++ b/CSharpBible/Games/AsteroidsModernEngine/AsteroidsModernEngine.csproj @@ -17,6 +17,6 @@ - + diff --git a/CSharpBible/Games/AsteroidsModernUI/AsteroidsModernUI.csproj b/CSharpBible/Games/AsteroidsModernUI/AsteroidsModernUI.csproj index ce4dcdcc0..e290d4767 100644 --- a/CSharpBible/Games/AsteroidsModernUI/AsteroidsModernUI.csproj +++ b/CSharpBible/Games/AsteroidsModernUI/AsteroidsModernUI.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/CSharpBible/Games/BoxFlight/BoxFlight.csproj b/CSharpBible/Games/BoxFlight/BoxFlight.csproj index bf9bb1374..3b65b75cd 100644 --- a/CSharpBible/Games/BoxFlight/BoxFlight.csproj +++ b/CSharpBible/Games/BoxFlight/BoxFlight.csproj @@ -9,6 +9,6 @@ - + \ No newline at end of file diff --git a/CSharpBible/Games/CreateCards/CreateCards.csproj b/CSharpBible/Games/CreateCards/CreateCards.csproj index 90887b382..c928d747a 100644 --- a/CSharpBible/Games/CreateCards/CreateCards.csproj +++ b/CSharpBible/Games/CreateCards/CreateCards.csproj @@ -7,7 +7,7 @@ - + diff --git a/CSharpBible/Games/CreateCards/Model/Card.cs b/CSharpBible/Games/CreateCards/Model/Card.cs index 47f853d43..cfa92dc46 100644 --- a/CSharpBible/Games/CreateCards/Model/Card.cs +++ b/CSharpBible/Games/CreateCards/Model/Card.cs @@ -40,18 +40,14 @@ public class Card new PointF(0.3f, 0.375f), new PointF(0.7f, 0.375f), new PointF(0.3f, 0.625f), new PointF(0.7f, 0.625f), new PointF(0.5f, 0.3125f),new PointF(0.5f, 0.6825f) } } }, { CardValues.Jack ,new("J"){ - PntSuits=new[]{ new PointF(0.7f, 0.25f), new PointF(0.7f, 0.35f), new PointF(0.7f, 0.45f), new PointF(0.7f, 0.55f),new PointF(0.7f, 0.65f), - new PointF(0.6f, 0.75f),new PointF(0.5f, 0.75f),new PointF(0.4f, 0.75f), new PointF(0.3f, 0.65f) } } }, + CustomDraw = DrawJack + }}, { CardValues.Queen ,new("Q"){ - PntSuits=new[]{ new PointF(0.3f, 0.35f), new PointF(0.3f, 0.45f), new PointF(0.3f, 0.55f),new PointF(0.3f, 0.65f), - new PointF(0.7f, 0.35f), new PointF(0.7f, 0.45f), new PointF(0.7f, 0.55f), new PointF(0.7f, 0.75f), - new PointF(0.4f, 0.3f), new PointF(0.5f, 0.25f),new PointF(0.6f, 0.3f), - new PointF(0.4f, 0.7f), new PointF(0.5f, 0.75f),new PointF(0.6f, 0.65f), - new PointF(0.5f, 0.55f) } } }, + CustomDraw = DrawQueen + }}, { CardValues.King ,new("K"){ - PntSuits=new[]{ new PointF(0.3f, 0.25f), new PointF(0.3f, 0.35f), new PointF(0.3f, 0.45f), new PointF(0.3f, 0.55f),new PointF(0.3f, 0.75f), - new PointF(0.3f, 0.65f), new PointF(0.4f, 0.55f), new PointF(0.5f, 0.45f), new PointF(0.6f, 0.35f), new PointF(0.7f, 0.25f), - new PointF(0.566667f, 0.55f), new PointF(0.63333f, 0.65f), new PointF(0.7f, 0.75f) } } }, + CustomDraw = DrawKing + }}, }; public string Suit { get; set; } public CardValues Value { get; set; } @@ -86,13 +82,20 @@ public void DrawCard(int width, int height, Graphics g) if (DrawDef.TryGetValue(Value, out var drawDef)) { - foreach (var pnt in drawDef.PntVals) - DrawText(rect, drawDef.PrintValue, drawDef.ValSize, pnt, g); - foreach (var pnt in drawDef.PntSVals) - DrawText(rect, Suit, drawDef.ValSize, pnt, g); - foreach (var pnt in drawDef.PntSuits) - DrawText(rect, Suit, drawDef.SuitSize, pnt, g); - + if (drawDef.CustomDraw != null) + { + // Face card or custom layout + drawDef.CustomDraw(g, rect, Color); + } + else + { + foreach (var pnt in drawDef.PntVals) + DrawText(rect, drawDef.PrintValue, drawDef.ValSize, pnt, g); + foreach (var pnt in drawDef.PntSVals) + DrawText(rect, Suit, drawDef.ValSize, pnt, g); + foreach (var pnt in drawDef.PntSuits) + DrawText(rect, Suit, drawDef.SuitSize, pnt, g); + } } void DrawText(Rectangle rect, string sP, double fSize, PointF fpOffs, Graphics g) @@ -108,6 +111,131 @@ void DrawText(Rectangle rect, string sP, double fSize, PointF fpOffs, Graphics g } } + private static void DrawJack(Graphics g, Rectangle rect, Color color) + { + // Simple stylized Jack vector drawing + RectangleF inner = RectangleF.Inflate(rect, -rect.Width * 0.15f, -rect.Height * 0.2f); + using var bodyBrush = new SolidBrush(Color.FromArgb(220, color)); + using var outlinePen = new Pen(Color.Black, rect.Width / 150f); + + // Body + RectangleF body = new RectangleF(inner.X + inner.Width * 0.25f, inner.Y + inner.Height * 0.35f, + inner.Width * 0.5f, inner.Height * 0.5f); + g.FillRectangle(bodyBrush, body); + g.DrawRectangle(outlinePen, body.X, body.Y, body.Width, body.Height); + + // Head + float headRadius = inner.Width * 0.18f; + PointF headCenter = new PointF(inner.X + inner.Width * 0.5f, inner.Y + inner.Height * 0.22f); + RectangleF head = new RectangleF(headCenter.X - headRadius, headCenter.Y - headRadius, + headRadius * 2, headRadius * 2); + using var headBrush = new SolidBrush(Color.Beige); + g.FillEllipse(headBrush, head); + g.DrawEllipse(outlinePen, head); + + // Cap + RectangleF cap = new RectangleF(head.X - headRadius * 0.3f, head.Y - headRadius * 0.6f, + head.Width * 1.3f, head.Height * 0.6f); + using var capBrush = new SolidBrush(color); + g.FillRectangle(capBrush, cap); + g.DrawRectangle(outlinePen, cap.X, cap.Y, cap.Width, cap.Height); + } + + private static void DrawQueen(Graphics g, Rectangle rect, Color color) + { + // Simple stylized Queen vector drawing + RectangleF inner = RectangleF.Inflate(rect, -rect.Width * 0.15f, -rect.Height * 0.2f); + using var bodyBrush = new SolidBrush(Color.FromArgb(200, color)); + using var outlinePen = new Pen(Color.Black, rect.Width / 150f); + + // Dress (triangle) + PointF p1 = new PointF(inner.X + inner.Width * 0.5f, inner.Bottom); + PointF p2 = new PointF(inner.X + inner.Width * 0.2f, inner.Y + inner.Height * 0.45f); + PointF p3 = new PointF(inner.X + inner.Width * 0.8f, inner.Y + inner.Height * 0.45f); + PointF[] dress = { p1, p2, p3 }; + g.FillPolygon(bodyBrush, dress); + g.DrawPolygon(outlinePen, dress); + + // Upper body + RectangleF body = new RectangleF(inner.X + inner.Width * 0.35f, inner.Y + inner.Height * 0.32f, + inner.Width * 0.3f, inner.Height * 0.2f); + g.FillRectangle(bodyBrush, body); + g.DrawRectangle(outlinePen, body.X, body.Y, body.Width, body.Height); + + // Head + float headRadius = inner.Width * 0.17f; + PointF headCenter = new PointF(inner.X + inner.Width * 0.5f, inner.Y + inner.Height * 0.18f); + RectangleF head = new RectangleF(headCenter.X - headRadius, headCenter.Y - headRadius, + headRadius * 2, headRadius * 2); + using var headBrush = new SolidBrush(Color.Beige); + g.FillEllipse(headBrush, head); + g.DrawEllipse(outlinePen, head); + + // Crown + float crownHeight = headRadius * 0.8f; + PointF c1 = new PointF(head.X, head.Y); + PointF c2 = new PointF(head.Right, head.Y); + PointF c3 = new PointF(head.X + head.Width * 0.2f, head.Y - crownHeight); + PointF c4 = new PointF(head.X + head.Width * 0.5f, head.Y - crownHeight * 1.2f); + PointF c5 = new PointF(head.X + head.Width * 0.8f, head.Y - crownHeight); + PointF[] crown = { c1, c3, c4, c5, c2 }; + using var crownBrush = new SolidBrush(color); + g.FillPolygon(crownBrush, crown); + g.DrawPolygon(outlinePen, crown); + } + + private static void DrawKing(Graphics g, Rectangle rect, Color color) + { + // Simple stylized King vector drawing + RectangleF inner = RectangleF.Inflate(rect, -rect.Width * 0.15f, -rect.Height * 0.2f); + using var robeBrush = new SolidBrush(Color.FromArgb(230, color)); + using var outlinePen = new Pen(Color.Black, rect.Width / 150f); + + // Robe (rounded rectangle) + RectangleF robe = new RectangleF(inner.X + inner.Width * 0.25f, inner.Y + inner.Height * 0.4f, + inner.Width * 0.5f, inner.Height * 0.5f); + using (GraphicsPath robePath = new GraphicsPath()) + { + float r = robe.Width * 0.2f; + robePath.AddArc(robe.X, robe.Y, r, r, 180, 90); + robePath.AddArc(robe.Right - r, robe.Y, r, r, 270, 90); + robePath.AddArc(robe.Right - r, robe.Bottom - r, r, r, 0, 90); + robePath.AddArc(robe.X, robe.Bottom - r, r, r, 90, 90); + robePath.CloseFigure(); + g.FillPath(robeBrush, robePath); + g.DrawPath(outlinePen, robePath); + } + + // Head + float headRadius = inner.Width * 0.18f; + PointF headCenter = new PointF(inner.X + inner.Width * 0.5f, inner.Y + inner.Height * 0.24f); + RectangleF head = new RectangleF(headCenter.X - headRadius, headCenter.Y - headRadius, + headRadius * 2, headRadius * 2); + using var headBrush = new SolidBrush(Color.Beige); + g.FillEllipse(headBrush, head); + g.DrawEllipse(outlinePen, head); + + // Beard + RectangleF beard = new RectangleF(head.X + head.Width * 0.2f, headCenter.Y, + head.Width * 0.6f, headRadius * 0.9f); + using var beardBrush = new SolidBrush(Color.FromArgb(160, 120, 80)); + g.FillEllipse(beardBrush, beard); + g.DrawEllipse(outlinePen, beard); + + // Crown (more geometric) + float crownHeight = headRadius * 0.9f; + PointF k1 = new PointF(head.X, head.Y); + PointF k2 = new PointF(head.Right, head.Y); + PointF k3 = new PointF(head.X + head.Width * 0.15f, head.Y - crownHeight); + PointF k4 = new PointF(head.X + head.Width * 0.35f, head.Y - crownHeight * 1.2f); + PointF k5 = new PointF(head.X + head.Width * 0.65f, head.Y - crownHeight * 1.2f); + PointF k6 = new PointF(head.X + head.Width * 0.85f, head.Y - crownHeight); + PointF[] crown = { k1, k3, k4, k5, k6, k2 }; + using var crownBrush = new SolidBrush(color); + g.FillPolygon(crownBrush, crown); + g.DrawPolygon(outlinePen, crown); + } + private GraphicsPath RoundedRectangle(Rectangle r, int d) { GraphicsPath path = new GraphicsPath(); diff --git a/CSharpBible/Games/CreateCards/Model/CardDrawDef.cs b/CSharpBible/Games/CreateCards/Model/CardDrawDef.cs index d1ed02f54..03ea443b3 100644 --- a/CSharpBible/Games/CreateCards/Model/CardDrawDef.cs +++ b/CSharpBible/Games/CreateCards/Model/CardDrawDef.cs @@ -1,4 +1,5 @@ -using System.Drawing; +using System; +using System.Drawing; namespace CreateCards.Model { @@ -12,6 +13,10 @@ public class CardDrawDef public PointF[] PntVals = new[] { new PointF(hOffs, vOffs), new PointF(hOffs, 1f-vOffs), new PointF(1f-hOffs, vOffs), new PointF(1f-hOffs, 1f- vOffs) }; public PointF[] PntSVals = new[] { new PointF(hOffs, vOffs*2), new PointF(hOffs, 1f - vOffs*2), new PointF(1f-hOffs, vOffs*2), new PointF(1f-hOffs, 1f - vOffs*2) }; public PointF[] PntSuits = { }; + + // Optional custom drawing callback for face cards or other special layouts + public Action? CustomDraw; + public CardDrawDef(string _pv,double _ss=0.2d) { PrintValue = _pv; SuitSize = _ss; } diff --git a/CSharpBible/Games/DetectiveGame.Console/DetectiveGame.Console.csproj b/CSharpBible/Games/DetectiveGame.Console/DetectiveGame.Console.csproj index 874cdeb66..d4fee8a1b 100644 --- a/CSharpBible/Games/DetectiveGame.Console/DetectiveGame.Console.csproj +++ b/CSharpBible/Games/DetectiveGame.Console/DetectiveGame.Console.csproj @@ -16,6 +16,6 @@ - + \ No newline at end of file diff --git a/CSharpBible/Games/DetectiveGame.Tests/DetectiveGame.Tests.csproj b/CSharpBible/Games/DetectiveGame.Tests/DetectiveGame.Tests.csproj index 16fd768dc..9682ce1f5 100644 --- a/CSharpBible/Games/DetectiveGame.Tests/DetectiveGame.Tests.csproj +++ b/CSharpBible/Games/DetectiveGame.Tests/DetectiveGame.Tests.csproj @@ -5,8 +5,8 @@ enable - - + + all diff --git a/CSharpBible/Games/Galaxia_UI/Galaxia_UI.csproj b/CSharpBible/Games/Galaxia_UI/Galaxia_UI.csproj index 8153d6a73..dc5639a21 100644 --- a/CSharpBible/Games/Galaxia_UI/Galaxia_UI.csproj +++ b/CSharpBible/Games/Galaxia_UI/Galaxia_UI.csproj @@ -15,7 +15,7 @@ - + diff --git a/CSharpBible/Games/Games.sln b/CSharpBible/Games/Games.sln index 8de889e0b..25e20fe20 100644 --- a/CSharpBible/Games/Games.sln +++ b/CSharpBible/Games/Games.sln @@ -167,6 +167,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MidiSwing.MVVM", "MidiSwing EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snake.WPF", "Snake.WPF\Snake.WPF.csproj", "{AF5CE3DF-B2EE-4526-95B5-96280CD6F0F7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Arkanoid", "Arkanoid", "{DAA48107-9A7D-4186-8AD0-2316D9B027CC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arkanoid.Base", "Arkanoid.Base\Arkanoid.Base.csproj", "{EACFD7D3-FC23-48A0-A25C-8E0B116860A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arkanoid.Cons", "Arkanoid.Cons\Arkanoid.Cons.csproj", "{348EFF6D-2854-486E-A594-2330C26AE3BA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -421,6 +427,14 @@ Global {AF5CE3DF-B2EE-4526-95B5-96280CD6F0F7}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF5CE3DF-B2EE-4526-95B5-96280CD6F0F7}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF5CE3DF-B2EE-4526-95B5-96280CD6F0F7}.Release|Any CPU.Build.0 = Release|Any CPU + {EACFD7D3-FC23-48A0-A25C-8E0B116860A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EACFD7D3-FC23-48A0-A25C-8E0B116860A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EACFD7D3-FC23-48A0-A25C-8E0B116860A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EACFD7D3-FC23-48A0-A25C-8E0B116860A0}.Release|Any CPU.Build.0 = Release|Any CPU + {348EFF6D-2854-486E-A594-2330C26AE3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {348EFF6D-2854-486E-A594-2330C26AE3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {348EFF6D-2854-486E-A594-2330C26AE3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {348EFF6D-2854-486E-A594-2330C26AE3BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -472,6 +486,8 @@ Global {19CE8BB5-06C5-152E-4FAA-C5347526E828} = {E2FA6ACF-7A86-453B-9342-799EB5280461} {495BD0C6-17EC-40C5-89A0-74EC258246EE} = {E2FA6ACF-7A86-453B-9342-799EB5280461} {AF5CE3DF-B2EE-4526-95B5-96280CD6F0F7} = {8E0BEE1E-8100-46C8-AB90-74EA8A5E96C5} + {EACFD7D3-FC23-48A0-A25C-8E0B116860A0} = {DAA48107-9A7D-4186-8AD0-2316D9B027CC} + {348EFF6D-2854-486E-A594-2330C26AE3BA} = {DAA48107-9A7D-4186-8AD0-2316D9B027CC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {51252E6F-E712-436A-B391-1C5243F65ABE} diff --git a/CSharpBible/Games/HexaBan_Console/HexaBan_Console.csproj b/CSharpBible/Games/HexaBan_Console/HexaBan_Console.csproj index 843a06dc8..0f502f1e1 100644 --- a/CSharpBible/Games/HexaBan_Console/HexaBan_Console.csproj +++ b/CSharpBible/Games/HexaBan_Console/HexaBan_Console.csproj @@ -18,6 +18,6 @@ - + diff --git a/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleLnx.csproj b/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleLnx.csproj index a1746f9c2..f0b5a629f 100644 --- a/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleLnx.csproj +++ b/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleLnx.csproj @@ -12,7 +12,7 @@ - + diff --git a/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleWin.csproj b/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleWin.csproj index 558e581bc..0b4a9d1c0 100644 --- a/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleWin.csproj +++ b/CSharpBible/Games/HexaBan_Console/HexaBan_ConsoleWin.csproj @@ -12,7 +12,7 @@ - + diff --git a/CSharpBible/Games/MidiSwing.MVVM/Models/MusicGenerator.cs b/CSharpBible/Games/MidiSwing.MVVM/Models/MusicGenerator.cs index 68c8892a3..f43483133 100644 --- a/CSharpBible/Games/MidiSwing.MVVM/Models/MusicGenerator.cs +++ b/CSharpBible/Games/MidiSwing.MVVM/Models/MusicGenerator.cs @@ -16,12 +16,8 @@ public class MusicGenerator private MidiOut _midi; private readonly object _midiSync = new(); - // Platzhalter für MIDI-Ausgabegerät aus der gewählten Bibliothek (z.B. NAudio) - // private NAudio.Midi.MidiOut midiOut; - public MusicGenerator() { - // Initialisierung des MIDI-Ports (z.B. Port 0) _channel = Math.Clamp(2, 1, 16); try { @@ -36,111 +32,190 @@ public MusicGenerator() } } - /* - PSEUDOCODE (Plan): - - Ziel: Die bestehende Swing-Melodie um eine Begleitung (Akkord-Comping) und Schlagzeug (Hi-Hat, Kick, Snare) erweitern. - - Kanäle: - - Melodie: vorhandener Kanal _channel (z.B. 2), Instrument: Tenor Sax (GM 67). - - Begleitung: eigener Kanal (z.B. 4), Instrument: Jazz-Gitarre (GM 27). - - Drums: GM-Drum-Kanal 10 (Percussion; Patch egal). - - Timing: - - BPM definieren (z.B. 120). - - beatMs = 60000 / BPM. - - Swing-Achtel: long = 2/3 beat, short = 1/3 beat. - - Melodie: - - Vorhandene Notenliste weiterverwenden. - - Nacheinander NoteOn -> Delay(duration) -> NoteOff -> kurze Pause. - - Begleitung (Comping): - - Einfache 4-taktige Akkordfolge passend zur Melodie (Cmaj7 | Dm7 | G7 | Cmaj7). - - Pro Takt zwei kurze Stabs auf 2 und 4 (je ~140 ms), restliche Zeit Pause. - - Akkordtöne im mittleren Register, Blockakkord anspielen, nach Länge NoteOff für alle. - - Drums: - - Hi-Hat (42) auf geswingten Achteln: On-Beat (stärker), Off-Beat (leiser). - - Kick (36) auf 1 und 3, Snare (38) auf 2 und 4. - - Jeder Schlag kurzer Impuls (NoteOff nach ~20–30 ms). - - Nebenläufigkeit: - - Melodie, Begleitung und Drums als drei Tasks parallel starten, am Ende await Task.WhenAll. - - Thread-Safety: alle MIDI-Sends über Lock synchronisieren. - - Fehlerbehandlung: - - try/catch um Gesamtablauf; TaskCanceledException ignorieren. - - Bei fehlendem MIDI (_midi == null) früh beenden. - */ + // Basic lengths in eighth-note units to keep rhythm handling simple + private enum NoteLength + { + Eighth = 1, + Quarter = 2, + Half = 4, + Whole = 8 + } - public async Task PlaySwingMelody() + private sealed class MelodyNote + { + public int Pitch { get; set; } + public NoteLength Length { get; set; } + public bool Accent { get; set; } + } + + private List GenerateBaseMelody(int root = 60) { - // Plan (Pseudocode): - // - Ein einziger Timing-Task steuert alle Events deterministisch. - // - Vorbereitungen: - // - Kanäle/Velocity/BPM/Swing berechnen. - // - Patches setzen. - // - Timeline aufbauen: - // - Drums: Für 4 Takte pro Beat On-Beat (Hi-Hat, Kick/Snare), danach Swing-Off-Beat. - // - Begleitung: Pro Takt Stabs auf 2 und 4 (140 ms) mit Akkordtönen. - // - Melodie: Sequenz der Noten nacheinander mit gegebenen Dauer und kleiner Pause. - // - Ein einziger Ablauf: - // - Ereignisse werden nacheinander mit Task.Delay ausgeführt, Reihenfolge durch Zeitstempel. - // - NoteOn/NoteOff/DrumHits mit Lock senden. - // - Fehlerbehandlung: try/catch um Gesamtablauf, bei fehlendem MIDI früh beenden. + // Simple minor-blues flavored motif around root + var scale = new[] { 0, 2, 4, 5, 7,9, 10, 12 }; + int Degree(int i) => root + scale[i]; + var motif = new List + { + new() { Pitch = Degree(0), Length = NoteLength.Quarter, Accent = true }, + new() { Pitch = Degree(1), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(2), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(3), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(4), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(5), Length = NoteLength.Quarter, Accent = false }, + new() { Pitch = Degree(6), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(7), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(5), Length = NoteLength.Quarter, Accent = false }, + new() { Pitch = Degree(7), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(2), Length = NoteLength.Eighth, Accent = false }, + new() { Pitch = Degree(0), Length = NoteLength.Quarter, Accent = false } + }; + + var phrase = new List(); + + // Bar 1: original motif + phrase.AddRange(motif); + + // Bar 2: up a second + phrase.AddRange(motif.Select(n => new MelodyNote + { + Pitch = n.Pitch + 2, + Length = n.Length, + Accent = n.Accent + })); + + // Bar 3: down a minor third + phrase.AddRange(motif.Select(n => new MelodyNote + { + Pitch = n.Pitch - 3, + Length = n.Length, + Accent = n.Accent + })); + + // Bar 4: original but with a held note at the end + phrase.AddRange(motif.Take(motif.Count - 1)); + phrase.Add(new MelodyNote + { + Pitch = Degree(0), + Length = NoteLength.Half, + Accent = true + }); + + return phrase; + } + + private List CreateVariation(List baseMelody, int semitoneShift, bool addSyncopation) + { + var rnd = new Random(); + var result = new List(); + + foreach (var note in baseMelody) + { + var pitch = note.Pitch + semitoneShift; + + // Occasional neighbor note for variation + if (rnd.NextDouble() < 0.2) + { + int neighbor = pitch + (rnd.Next(2) == 0 ? -1 : 1); + result.Add(new MelodyNote + { + Pitch = neighbor, + Length = NoteLength.Eighth, + Accent = false + }); + + result.Add(new MelodyNote + { + Pitch = pitch, + Length = note.Length == NoteLength.Quarter ? NoteLength.Eighth : note.Length, + Accent = note.Accent + }); + + continue; + } + + var len = note.Length; + if (addSyncopation && len == NoteLength.Quarter && rnd.NextDouble() < 0.4) + { + // Split quarter into two eighths + result.Add(new MelodyNote + { + Pitch = pitch, + Length = NoteLength.Eighth, + Accent = note.Accent + }); + result.Add(new MelodyNote + { + Pitch = pitch, + Length = NoteLength.Eighth, + Accent = false + }); + } + else + { + result.Add(new MelodyNote + { + Pitch = pitch, + Length = len, + Accent = note.Accent + }); + } + } + + return result; + } + + private List GetMainChords() + { + // Rough C minor → F7 → G7 → Cmin pattern + return new List + { + new[] { 48, 51, 55, 58 }, // Cmin7 + new[] { 41, 45, 48, 52 }, // F7 + new[] { 43, 47, 50, 53 }, // G7 + new[] { 48, 51, 55, 58 } // Cmin7 + }; + } + + public async Task PlaySwingMelody() + { if (_midi == null) { Console.WriteLine("Kein MIDI-Ausgabegerät verfügbar."); return; } - const int drumsChannel = 10; // GM-Drums - const int compChannel = 4; // Begleitung - const int melodyVelocity = 100; - const int compVelocity = 80; - const int hiHatVelocityOn = 70; - const int hiHatVelocityOff = 55; - const int kickVelocity = 90; - const int snareVelocity = 95; + const int drumsChannel = 10; + const int bassChannel = 3; + const int compChannel = 4; + const int leadChannel = 2; + + const int leadPatch = 67; // Tenor Sax + const int compPatch = 27; // Jazz Guitar + const int bassPatch = 33; // Acoustic Bass int bpm = 120; int beatMs = 60000 / bpm; - int swingLong = (int)Math.Round(beatMs * 2.0 / 3.0); - int swingShort = beatMs - swingLong; try { - // Instrumente setzen - SetPatch(67, _channel); // Tenor Sax - SetPatch(27, compChannel); // Jazz-Gitarre (GM 27) - - // Beispiel-Melodie mit Swing-Rhythmus (Triolen-basiert) - var melody = new List<(int note, int duration)> - { - (60, 2), (62, 1), (64, 2), (62, 1), // C D E D - (60, 6), // C - (65, 2), (64, 1), (62, 2), (60, 1), // F E D C - (59, 6), // H (B) - (60, 2), (62, 1), (64, 2), (62, 1), // C D E D - (60, 6), // C - (65, 2), (64, 1), (62, 2), (60, 1), // F E D C - (59, 6), // H (B) - }; - - // 4-taktige Akkordfolge (Cmaj7 | Dm7 | G7 | Cmaj7) - var chords = new List - { - new[] { 48, 52, 55, 59 }, // Cmaj7: C E G B (C3 E3 G3 B3) - new[] { 50, 53, 57, 60 }, // Dm7: D F A C - new[] { 43, 47, 50, 53 }, // G7: G B D F (tiefer für Klarheit) - new[] { 48, 52, 55, 59 } // Cmaj7 - }; + SetPatch(leadPatch, leadChannel); + SetPatch(compPatch, compChannel); + SetPatch(bassPatch, bassChannel); - // Timeline-Event-Struktur var timeline = new List<(int atMs, Action action)>(); - // Hilfsfunktionen zum Hinzufügen - void AddNoteOn(int atMs, int note, int vel, int ch) => timeline.Add((atMs, () => NoteOn(note, vel, ch))); - void AddNoteOff(int atMs, int note, int ch) => timeline.Add((atMs, () => NoteOff(note, ch))); - void AddDrumHit(int atMs, int midiNote, int vel, int ch, int lenMs) + void AddNoteOn(int atMs, int note, int vel, int ch) => + timeline.Add((atMs, () => NoteOn(note, vel, ch))); + + void AddNoteOff(int atMs, int note, int ch) => + timeline.Add((atMs, () => NoteOff(note, ch))); + + void AddDrumHit(int atMs, int midiNote, int vel, int lenMs) { - timeline.Add((atMs, () => NoteOn(midiNote, vel, ch))); - timeline.Add((atMs + lenMs, () => NoteOff(midiNote, ch))); + timeline.Add((atMs, () => NoteOn(midiNote, vel, drumsChannel))); + timeline.Add((atMs + lenMs, () => NoteOff(midiNote, drumsChannel))); } + void AddChordStab(int atMs, IEnumerable notes, int vel, int ch, int lenMs) { foreach (var n in notes) @@ -150,63 +225,161 @@ void AddChordStab(int atMs, IEnumerable notes, int vel, int ch, int lenMs) } } - // Gesamtdauer: 4 Takte à 4 Schläge - int bars = 4; - int barLenMs = beatMs * 4; - int totalLenMs = bars * barLenMs; + int TimeBeatsToMs(double beats) => (int)Math.Round(beats * beatMs); + + // Build musical material + var baseMelody = GenerateBaseMelody(60); + var variation1 = CreateVariation(baseMelody, 2, addSyncopation: true); + var variation2 = CreateVariation(baseMelody, -3, addSyncopation: true); + var chords = GetMainChords(); + + int sectionStartMs = 0; - // Drums: On-Beat + Swing Off-Beat - for (int bar = 0; bar < bars; bar++) + // Intro: 4 bars, bass + light drums, short walk-in + int introBars = 4; + for (int bar = 0; bar < introBars; bar++) { - int barStart = bar * barLenMs; + int barStart = sectionStartMs + bar * beatMs * 4; + + int[] bassPattern = { 36, 43, 36, 43 }; // C2, G2 for (int beat = 0; beat < 4; beat++) { - int beatStart = barStart + beat * beatMs; - - // On-Beat: Hi-Hat - AddDrumHit(beatStart, 42, hiHatVelocityOn, drumsChannel, 25); + int t = barStart + beat * beatMs; + int note = bassPattern[beat % bassPattern.Length]; + AddNoteOn(t, note, 85, bassChannel); + AddNoteOff(t + TimeBeatsToMs(0.9), note, bassChannel); - // Kick auf 1 und 3, Snare auf 2 und 4 - if (beat == 0 || beat == 2) + if (beat == 1 || beat == 3) { - AddDrumHit(beatStart, 36, kickVelocity, drumsChannel, 25); + AddDrumHit(t, 42, 70, 30); // light hi-hat on 2 and 4 } + } + } + + sectionStartMs += introBars * beatMs * 4; + + // Helper to add one groove bar (drums + comp + bass) + void AddGrooveBar(int barIndex, int startMs) + { + int chordIndex = barIndex % chords.Count; + var chord = chords[chordIndex]; + + for (int beat = 0; beat < 4; beat++) + { + int beatStart = startMs + beat * beatMs; + + // Hi-hat on each beat + AddDrumHit(beatStart, 42, 75, 25); + + // Kick/snare + if (beat == 0 || beat == 2) + AddDrumHit(beatStart, 36, 90, 30); // kick else - { - AddDrumHit(beatStart, 38, snareVelocity, drumsChannel, 25); - } + AddDrumHit(beatStart, 38, 95, 30); // snare + + // Off-beat hi-hat (swing) + int off = beatStart + (int)Math.Round(beatMs * 2.0 / 3.0); + AddDrumHit(off, 42, 60, 25); + } - // Off-Beat (Swing) - int offBeat = beatStart + swingLong; - AddDrumHit(offBeat, 42, hiHatVelocityOff, drumsChannel, 25); + // Comp stabs on 2 and 4 + int beat2 = startMs + beatMs; + int beat4 = startMs + 3 * beatMs; + AddChordStab(beat2, chord, 75, compChannel, 140); + AddChordStab(beat4, chord, 80, compChannel, 160); + + // Simple bass walking + int[] bassLine = { chord[0], chord[2], chord[2] + 2, chord[3] }; + for (int beat = 0; beat < 4; beat++) + { + int t = startMs + beat * beatMs; + int n = bassLine[beat % bassLine.Length] - 12; + AddNoteOn(t, n, 80, bassChannel); + AddNoteOff(t + TimeBeatsToMs(0.9), n, bassChannel); } } - // Begleitung: Stabs auf 2 und 4 - const int stabLen = 125; - for (int bar = 0; bar < chords.Count; bar++) + int barLenMs = beatMs * 4; + + // Main A section: 8 bars of groove + int aSectionBars = 8; + for (int bar = 0; bar < aSectionBars; bar++) { - int barStart = bar * barLenMs; - var chord = chords[bar]; + int barStart = sectionStartMs + bar * barLenMs; + AddGrooveBar(bar, barStart); + } - int beat2 = barStart + beatMs; // Schlag 2 - int beat4 = barStart + beatMs * 3; // Schlag 4 + // Helper to place a melody line on the lead channel + void PlaceMelody(List melody, int startMs, int channel, int baseVelocity) + { + double beatPos = 0; + foreach (var n in melody) + { + int start = startMs + TimeBeatsToMs(beatPos); + double lenBeats = n.Length switch + { + NoteLength.Eighth => 0.5, + NoteLength.Quarter => 1.0, + NoteLength.Half => 2.0, + NoteLength.Whole => 4.0, + _ => 1.0 + }; + + int vel = baseVelocity + (n.Accent ? 15 : 0); + AddNoteOn(start, n.Pitch, vel, channel); + AddNoteOff(start + TimeBeatsToMs(lenBeats * 0.9), n.Pitch, channel); + + beatPos += lenBeats; + } + } + + int melodyStart = sectionStartMs; + PlaceMelody(baseMelody, melodyStart, leadChannel, 100); + + int baseMelodyLenBeats = baseMelody.Sum(m => m.Length switch + { + NoteLength.Eighth => 1, + NoteLength.Quarter => 2, + NoteLength.Half => 4, + NoteLength.Whole => 8, + _ => 2 + }); + int baseMelodyLenMs = TimeBeatsToMs(baseMelodyLenBeats / 2.0); // convert eighth units back to beats + + PlaceMelody(baseMelody, melodyStart + baseMelodyLenMs, leadChannel, 105); + + sectionStartMs += aSectionBars * barLenMs; - AddChordStab(beat2, chord, compVelocity, compChannel, stabLen); - AddChordStab(beat4, chord, compVelocity, compChannel, stabLen); + // A' section: variation1 over same groove + int a2Bars = 8; + for (int bar = 0; bar < a2Bars; bar++) + { + int barStart = sectionStartMs + bar * barLenMs; + AddGrooveBar(bar, barStart); } - // Melodie: nacheinander ohne weitere Parallelität - // Start der Melodie am Beginn - int t = 0; - foreach (var (note, duration) in melody) + PlaceMelody(variation1, sectionStartMs, leadChannel, 105); + sectionStartMs += a2Bars * barLenMs; + + // B section: variation2, denser comping + int bBars = 8; + for (int bar = 0; bar < bBars; bar++) { - AddNoteOn(t , note, melodyVelocity, _channel); - AddNoteOff(t + duration* beatMs / 3-25, note, _channel); - t += duration* beatMs / 3; // kleine Pause zwischen den Noten + int barStart = sectionStartMs + bar * barLenMs; + AddGrooveBar(bar, barStart); + + int chordIndex = bar % chords.Count; + var chord = chords[chordIndex]; + int off2 = barStart + beatMs + beatMs / 2; + int off4 = barStart + 3 * beatMs + beatMs / 2; + AddChordStab(off2, chord, 85, compChannel, 100); + AddChordStab(off4, chord, 90, compChannel, 120); } - // Sortierte Ausführung nach Zeitstempel + PlaceMelody(variation2, sectionStartMs, leadChannel, 110); + sectionStartMs += bBars * barLenMs; + + // Play timeline in order timeline.Sort((a, b) => a.atMs.CompareTo(b.atMs)); int currentTime = 0; @@ -222,26 +395,15 @@ void AddChordStab(int atMs, IEnumerable notes, int vel, int ch, int lenMs) action(); } - // Sicherstellen, dass Restzeit abgewartet wird, falls Melodie kürzer ist als Gesamt-Arrangement - if (currentTime < totalLenMs) - { - await Task.Delay(totalLenMs - currentTime); - } + await Task.Delay(1000); } catch (TaskCanceledException) { - // Ignorieren, falls später Abbruch hinzugefügt wird } catch (Exception ex) { Console.WriteLine($"Fehler beim Abspielen: {ex.Message}"); } - finally - { - // Gerät schließen (falls gewünscht) - // midiOut.Close(); - // midiOut.Dispose(); - } } private void NoteOn(int noteNumber, int velocity = 100, int channel = -1) @@ -284,26 +446,4 @@ private void SetPatch(int program, int channel) _midi.Send(MidiMessage.ChangePatch(program, channel).RawData); } } - - private async Task PlayChordStabAsync(IEnumerable notes, int velocity, int lengthMs, int channel) - { - foreach (var n in notes) - { - NoteOn(n, velocity, channel); - } - - await Task.Delay(lengthMs); - - foreach (var n in notes) - { - NoteOff(n, channel); - } - } - - private async Task DrumHitAsync(int midiNote, int velocity, int channel) - { - NoteOn(midiNote, velocity, channel); - await Task.Delay(25); - NoteOff(midiNote, channel); - } } diff --git a/CSharpBible/Games/Snake.WPF/Snake.WPF.csproj b/CSharpBible/Games/Snake.WPF/Snake.WPF.csproj index 166c9621a..e69f92c4e 100644 --- a/CSharpBible/Games/Snake.WPF/Snake.WPF.csproj +++ b/CSharpBible/Games/Snake.WPF/Snake.WPF.csproj @@ -14,7 +14,7 @@ - + diff --git a/CSharpBible/Games/Snake_Console/Snake_Console.csproj b/CSharpBible/Games/Snake_Console/Snake_Console.csproj index 7ff8c84c1..2e42e4db3 100644 --- a/CSharpBible/Games/Snake_Console/Snake_Console.csproj +++ b/CSharpBible/Games/Snake_Console/Snake_Console.csproj @@ -12,7 +12,7 @@ - + diff --git a/CSharpBible/Games/Treppen.Base/Treppen.Base.csproj b/CSharpBible/Games/Treppen.Base/Treppen.Base.csproj index 58331ef09..dc5b59561 100644 --- a/CSharpBible/Games/Treppen.Base/Treppen.Base.csproj +++ b/CSharpBible/Games/Treppen.Base/Treppen.Base.csproj @@ -17,8 +17,8 @@ $(TargetFrameworks);net10.0 - - + + diff --git a/CSharpBible/Games/Treppen.BaseTests/Treppen.BaseTests.csproj b/CSharpBible/Games/Treppen.BaseTests/Treppen.BaseTests.csproj index e9d9a2972..c528f6d28 100644 --- a/CSharpBible/Games/Treppen.BaseTests/Treppen.BaseTests.csproj +++ b/CSharpBible/Games/Treppen.BaseTests/Treppen.BaseTests.csproj @@ -11,8 +11,8 @@ - - + + diff --git a/CSharpBible/Games/Treppen.WPF/Treppen.WPF.csproj b/CSharpBible/Games/Treppen.WPF/Treppen.WPF.csproj index a45658223..309ea60bb 100644 --- a/CSharpBible/Games/Treppen.WPF/Treppen.WPF.csproj +++ b/CSharpBible/Games/Treppen.WPF/Treppen.WPF.csproj @@ -10,7 +10,7 @@ - + diff --git a/CSharpBible/Games/VTileEdit/VTileEdit.csproj b/CSharpBible/Games/VTileEdit/VTileEdit.csproj index 75010a222..526511621 100644 --- a/CSharpBible/Games/VTileEdit/VTileEdit.csproj +++ b/CSharpBible/Games/VTileEdit/VTileEdit.csproj @@ -10,7 +10,7 @@ - + diff --git a/CSharpBible/Games/VectorGfx/VectorGfx.csproj b/CSharpBible/Games/VectorGfx/VectorGfx.csproj index 0aabc9dd8..28391a76e 100644 --- a/CSharpBible/Games/VectorGfx/VectorGfx.csproj +++ b/CSharpBible/Games/VectorGfx/VectorGfx.csproj @@ -18,7 +18,7 @@ - + diff --git a/CSharpBible/Games/VectorGfx2/VectorGfx2.csproj b/CSharpBible/Games/VectorGfx2/VectorGfx2.csproj index 0aabc9dd8..28391a76e 100644 --- a/CSharpBible/Games/VectorGfx2/VectorGfx2.csproj +++ b/CSharpBible/Games/VectorGfx2/VectorGfx2.csproj @@ -18,7 +18,7 @@ - + diff --git a/CSharpBible/Games/Werner_Flaschbier/Werner_Flaschbier_Console.csproj b/CSharpBible/Games/Werner_Flaschbier/Werner_Flaschbier_Console.csproj index de82b48b4..8dacd61e2 100644 --- a/CSharpBible/Games/Werner_Flaschbier/Werner_Flaschbier_Console.csproj +++ b/CSharpBible/Games/Werner_Flaschbier/Werner_Flaschbier_Console.csproj @@ -24,7 +24,7 @@ - + From da139ed2400ddf1457e443db60a0d11e1693b357 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Fri, 12 Dec 2025 02:43:04 +0100 Subject: [PATCH 017/962] Common --- CSharpBible/Libraries/BaseLib/BaseLib.csproj | 2 +- CSharpBible/Libraries/MVVM_BaseLib/MVVM_BaseLib.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CSharpBible/Libraries/BaseLib/BaseLib.csproj b/CSharpBible/Libraries/BaseLib/BaseLib.csproj index 2baaed6f9..41c6e14a9 100644 --- a/CSharpBible/Libraries/BaseLib/BaseLib.csproj +++ b/CSharpBible/Libraries/BaseLib/BaseLib.csproj @@ -30,6 +30,6 @@ - + \ No newline at end of file diff --git a/CSharpBible/Libraries/MVVM_BaseLib/MVVM_BaseLib.csproj b/CSharpBible/Libraries/MVVM_BaseLib/MVVM_BaseLib.csproj index 36909575d..6371ffcf7 100644 --- a/CSharpBible/Libraries/MVVM_BaseLib/MVVM_BaseLib.csproj +++ b/CSharpBible/Libraries/MVVM_BaseLib/MVVM_BaseLib.csproj @@ -32,7 +32,7 @@ - + From b2a679101524b9453e720d373576a59af15c826b Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:10:19 +0100 Subject: [PATCH 018/962] Analyzer1 --- Transpiler_pp/CONTRIBUTING.md | 26 +++++ .../Trnsp.Show.Lfm.Tests/SampleTests.cs | 29 +++++ .../Services/LfmObjectResolverTests.cs | 103 +++++++++++++++++ .../Trnsp.Show.Lfm.Tests.csproj | 25 +++++ .../Services/Interfaces/IComponentFactory.cs | 20 ++++ .../Services/Interfaces/ILfmParserService.cs | 23 ++++ .../Services/Interfaces/IObjectResolver.cs | 10 ++ .../Services/Interfaces/IXamlExporter.cs | 20 ++++ .../Services/LfmObjectResolver.cs | 97 ++++++++++++++++ .../Services/XamlExporter.Button.cs | 74 +++++++++++++ .../Services/XamlExporter.Edit.cs | 78 +++++++++++++ .../Services/XamlExporter.Label.cs | 55 +++++++++ .../Services/XamlExporter.Selection.cs | 104 ++++++++++++++++++ .../Trnsp.Show.Pas.Tests/MSTestSettings.cs | 1 + Transpiler_pp/Trnsp.Show.Pas.Tests/Test1.cs | 17 +++ .../Trnsp.Show.Pas.Tests.csproj | 32 ++++++ Transpiler_pp/Trnsp.Show.Pas/App.xaml | 9 ++ Transpiler_pp/Trnsp.Show.Pas/App.xaml.cs | 14 +++ Transpiler_pp/Trnsp.Show.Pas/AssemblyInfo.cs | 10 ++ Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml | 12 ++ .../Trnsp.Show.Pas/MainWindow.xaml.cs | 24 ++++ .../Trnsp.Show.Pas/Trnsp.Show.Pas.csproj | 28 +++++ 22 files changed, 811 insertions(+) create mode 100644 Transpiler_pp/CONTRIBUTING.md create mode 100644 Transpiler_pp/Trnsp.Show.Lfm.Tests/SampleTests.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm.Tests/Trnsp.Show.Lfm.Tests.csproj create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IComponentFactory.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/ILfmParserService.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IObjectResolver.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IXamlExporter.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/LfmObjectResolver.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Button.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Edit.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Label.cs create mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Selection.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/MSTestSettings.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/Test1.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/Trnsp.Show.Pas.Tests.csproj create mode 100644 Transpiler_pp/Trnsp.Show.Pas/App.xaml create mode 100644 Transpiler_pp/Trnsp.Show.Pas/App.xaml.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/AssemblyInfo.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml create mode 100644 Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/Trnsp.Show.Pas.csproj diff --git a/Transpiler_pp/CONTRIBUTING.md b/Transpiler_pp/CONTRIBUTING.md new file mode 100644 index 000000000..ca73712c5 --- /dev/null +++ b/Transpiler_pp/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing Guidelines + +## Coding Standards & Patterns + +### Architecture +- **MVVM (Model-View-ViewModel):** We strictly follow the MVVM pattern for UI applications. +- **Dependency Injection (DI):** Use DI for loose coupling and testability. +- **File Structure:** Adhere to the "One Class per File" rule. +- **Strict Separation of Concerns:** Each class should have a single responsibility. +- **Strict Separation of Code and generated Stuff:** The code you write should be separate from any generated code. +- **I18n/L10n:** All user-facing strings must be localized. + +### Testing & TDD +- **Test-Driven Development (TDD):** Tests must be created *before* the implementation. +- **Frameworks:** + - **Testing Framework:** MSTestv4 + - **Mocking:** NSubstitute + - **MVVM Support:** CommunityToolkit.Mvvm + +### Build Configuration +- **Warnings as Errors:** `TreatWarningsAsErrors` is enabled. Ensure your code compiles without warnings. + +## Workflow +1. Create a failing test (Red). +2. Implement the minimal code to pass the test (Green). +3. Refactor. diff --git a/Transpiler_pp/Trnsp.Show.Lfm.Tests/SampleTests.cs b/Transpiler_pp/Trnsp.Show.Lfm.Tests/SampleTests.cs new file mode 100644 index 000000000..8a3fb1a01 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm.Tests/SampleTests.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; + +namespace MSTestv4.Tests; + +public interface IMathService +{ + int Add(int a, int b); +} + +[TestClass] +public class SampleTests +{ + [TestMethod] + [DataRow(1, 2, 3)] + [DataRow(-1, 1, 0)] + [DataRow(10, 5, 15)] + public void Add_UsesService(int a, int b, int expected) + { + var mathService = Substitute.For(); + mathService.Add(a, b).Returns(expected); + + var result = mathService.Add(a, b); + + Assert.AreEqual(expected, result); + mathService.Received(1).Add(a, b); + } +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs b/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs new file mode 100644 index 000000000..e413f9556 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs @@ -0,0 +1,103 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.Marshalling; +using System.Text; +using System.Threading.Tasks; +using TranspilerLib.Pascal.Models; +using Trnsp.Show.Lfm.Models.Components; + +namespace Trnsp.Show.Lfm.Services.Tests; + +[TestClass] +public class LfmObjectResolverTests +{ + private sealed class TestComponent : LfmComponentBase + { + } + + private sealed class TestAction : TAction + { + public TestAction() + { + Caption = "TestCaption"; + Hint = "TestHint"; + } + } + + [TestMethod] + public void ResolveOrDefer_ResolvesAlreadyRegisteredObject() + { + var resolver = new LfmObjectResolver(); + LfmComponentBase.ObjectResolver = resolver; + + var action = new TestAction(); + resolver.RegisterObject("TestAction", action); + + var comp = new TestComponent(); + comp.ApplyProperties(new LfmObject + { + Name = "Comp1", + TypeName = "TButton", + Properties = + { + new() { Name = "Action", Value = "TestAction" } + } + }); + + Assert.AreSame(action, comp.LinkedAction.TryGetTarget(out var t)?t:t); + Assert.AreEqual("TestCaption", comp.EffectiveCaption); + Assert.AreEqual("TestHint", comp.EffectiveHint); + } + + [TestMethod] + public void ResolveOrDefer_ResolvesDeferredObject() + { + var resolver = new LfmObjectResolver(); + LfmComponentBase.ObjectResolver = resolver; + + var comp = new TestComponent(); + comp.ApplyProperties(new LfmObject + { + Name = "Comp1", + TypeName = "TButton", + Properties = + { + new() { Name = "Action", Value = "TestAction" } + } + }); + + Assert.IsNull(comp.LinkedAction); + + var action = new TestAction(); + resolver.RegisterObject("TestAction", action); + + Assert.AreSame(action, comp.LinkedAction.TryGetTarget(out var t) ? t : t); + Assert.AreEqual("TestCaption", comp.EffectiveCaption); + Assert.AreEqual("TestHint", comp.EffectiveHint); + } + + [TestMethod] + public void RegisterObject_RegistersComponentItself() + { + var resolver = new LfmObjectResolver(); + LfmComponentBase.ObjectResolver = resolver; + + var comp = new TestComponent(); + comp.ApplyProperties(new LfmObject + { + Name = "Comp1", + TypeName = "TButton" + }); + + resolver.ResolveOrDefer( + "Comp1", + new object(), + (resolved) => + { + Assert.AreSame(comp, resolved); + }); + } + +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm.Tests/Trnsp.Show.Lfm.Tests.csproj b/Transpiler_pp/Trnsp.Show.Lfm.Tests/Trnsp.Show.Lfm.Tests.csproj new file mode 100644 index 000000000..f9c66ef3a --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm.Tests/Trnsp.Show.Lfm.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0-windows + false + true + Trnsp.Show.Lfm + + + + + $(TargetFrameworks);net9.0-windows + + + $(TargetFrameworks);net10.0-windows + + + + + + + + + + \ No newline at end of file diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IComponentFactory.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IComponentFactory.cs new file mode 100644 index 000000000..6bbe391c7 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IComponentFactory.cs @@ -0,0 +1,20 @@ +using Trnsp.Show.Lfm.Models.Components; +using TranspilerLib.Pascal.Models; + +namespace Trnsp.Show.Lfm.Services; + +/// +/// Factory interface for creating LFM components. +/// +public interface IComponentFactory +{ + /// + /// Creates a component from an LfmObject. + /// + LfmComponentBase CreateComponent(LfmObject lfmObject); + + /// + /// Creates a component tree from a root LfmObject. + /// + LfmComponentBase? CreateComponentTree(LfmObject? rootObject); +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/ILfmParserService.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/ILfmParserService.cs new file mode 100644 index 000000000..a715cc009 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/ILfmParserService.cs @@ -0,0 +1,23 @@ +using TranspilerLib.Pascal.Models; + +namespace Trnsp.Show.Lfm.Services.Interfaces; + +/// +/// Service interface for parsing LFM files. +/// +public interface ILfmParserService +{ + /// + /// Parses the LFM content and returns the root object. + /// + /// The LFM file content as string. + /// The parsed LfmObject or null if parsing fails. + LfmObject? Parse(string content); + + /// + /// Loads and parses an LFM file from the given path. + /// + /// The path to the LFM file. + /// The parsed LfmObject or null if loading/parsing fails. + LfmObject? LoadFromFile(string filePath); +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IObjectResolver.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IObjectResolver.cs new file mode 100644 index 000000000..972d7efa5 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IObjectResolver.cs @@ -0,0 +1,10 @@ +using System; + +namespace Trnsp.Show.Lfm.Services.Interfaces +{ + public interface IObjectResolver + { + void RegisterObject(string name, object instance); + void ResolveOrDefer(string name, object requestingComponent, Action linkAction); + } +} \ No newline at end of file diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IXamlExporter.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IXamlExporter.cs new file mode 100644 index 000000000..57f86f8c3 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/Interfaces/IXamlExporter.cs @@ -0,0 +1,20 @@ +using System.Text; +using Trnsp.Show.Lfm.Models.Components; + +namespace Trnsp.Show.Lfm.Services.Interfaces; + +/// +/// Interface for exporting LFM components to XAML. +/// +public interface IXamlExporter +{ + /// + /// Exports a component tree to XAML Window markup. + /// + string ExportToXaml(LfmComponentBase component); + + /// + /// Exports a component tree to XAML and saves it to a file. + /// + void ExportToFile(LfmComponentBase component, string filePath); +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/LfmObjectResolver.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/LfmObjectResolver.cs new file mode 100644 index 000000000..3dd6c68f4 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/LfmObjectResolver.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using Trnsp.Show.Lfm.Services.Interfaces; + +namespace Trnsp.Show.Lfm.Services; + +/// +/// Verwaltet benannte Objekte und deren verzgerte Auflsung. +/// Vermeidet zirkulre Referenzen durch Verwendung von WeakReference. +/// +public sealed class LfmObjectResolver : IObjectResolver +{ + private readonly Dictionary> _resolvedObjects = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> _unresolvedLinks = new(StringComparer.OrdinalIgnoreCase); + + private readonly object _syncRoot = new(); + + private sealed class ReverseLink + { + public WeakReference TargetComponent { get; } + public Action LinkAction { get; } + + public ReverseLink(object targetComponent, Action linkAction) + { + TargetComponent = new WeakReference(targetComponent); + LinkAction = linkAction; + } + } + + /// + /// Registriert ein Objekt unter einem Namen und versucht, + /// eventuell ausstehende Referenzen aufzulsen. + /// + public void RegisterObject(string name, object instance) + { + if (string.IsNullOrWhiteSpace(name) || instance is null) + { + return; + } + + lock (_syncRoot) + { + _resolvedObjects[name] = new WeakReference(instance); + + if (_unresolvedLinks.TryGetValue(name, out var links)) + { + foreach (var link in links) + { + if (!link.TargetComponent.TryGetTarget(out var target)) + { + continue; + } + + link.LinkAction(instance); + } + + _unresolvedLinks.Remove(name); + } + } + } + + /// + /// Versucht ein Objekt zu holen. Falls es (noch) nicht existiert, + /// wird der Link als "unresolved" gespeichert. + /// + /// Gesuchter Objektname. + /// Objekt, das die Referenz bentigt. + /// + /// Aktion, die ausgefhrt wird, wenn das Objekt gefunden/registriert wird. + /// Parameter: (requestingComponent, resolvedObject). + /// + public void ResolveOrDefer(string name, object requestingComponent, Action linkAction) + { + if (string.IsNullOrWhiteSpace(name) || requestingComponent is null || linkAction is null) + { + return; + } + + lock (_syncRoot) + { + if (_resolvedObjects.TryGetValue(name, out var weak) + && weak.TryGetTarget(out var instance)) + { + linkAction(instance); + return; + } + + if (!_unresolvedLinks.TryGetValue(name, out var links)) + { + links = []; + _unresolvedLinks[name] = links; + } + + links.Add(new ReverseLink(requestingComponent, linkAction)); + } + } +} \ No newline at end of file diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Button.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Button.cs new file mode 100644 index 000000000..4b273e5b1 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Button.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Trnsp.Show.Lfm.Models.Components; + +namespace Trnsp.Show.Lfm.Services; + +public partial class XamlExporter +{ + private void ExportButton(TButton button) + { + var attrs = new List + { + $"Content=\"{EscapeXml(button.EffectiveCaption)}\"", + GetPositionAttributes(button), + GetSizeAttributes(button) + }; + + if (button.Default) + attrs.Add("IsDefault=\"True\""); + + AppendSingleElement("Button", attrs, button.Name); + } + + private void ExportBitBtn(TBitBtn bitBtn) + { + var attrs = new List + { + GetPositionAttributes(bitBtn), + GetSizeAttributes(bitBtn) + }; + + AppendLine($""); + } + + private void ExportSpeedButton(TSpeedButton speedBtn) + { + var attrs = new List + { + $"Content=\"{EscapeXml(speedBtn.EffectiveCaption)}\"", + GetPositionAttributes(speedBtn), + GetSizeAttributes(speedBtn) + }; + + if (speedBtn.Down) + attrs.Add("IsChecked=\"True\""); + + AppendSingleElement("ToggleButton", attrs, speedBtn.Name); + } + +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Edit.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Edit.cs new file mode 100644 index 000000000..6b1f2002d --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Edit.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Trnsp.Show.Lfm.Models.Components; + +namespace Trnsp.Show.Lfm.Services; + +public partial class XamlExporter +{ + private void ExportEdit(TEdit edit) + { + var attrs = new List + { + $"Text=\"{EscapeXml(edit.Caption)}\"", + GetPositionAttributes(edit), + GetSizeAttributes(edit) + }; + + if (edit.ReadOnly) + attrs.Add("IsReadOnly=\"True\""); + if (edit.MaxLength > 0) + attrs.Add($"MaxLength=\"{edit.MaxLength}\""); + + AppendSingleElement("TextBox", attrs, edit.Name); + } + + private void ExportLabeledEdit(TLabeledEdit labeledEdit) + { + // Export as StackPanel with Label and TextBox + var attrs = new List + { + GetPositionAttributes(labeledEdit), + GetSizeAttributes(labeledEdit), + $"Orientation=\"{(labeledEdit.LabelPosKind == ELabelPosition.Left || labeledEdit.LabelPosKind == ELabelPosition.Right ? "Horizontal" : "Vertical")}\"" + }; + + AppendLine($""); + _indentLevel++; + + if (labeledEdit.LabelPosKind == ELabelPosition.Above || labeledEdit.LabelPosKind == ELabelPosition.Left) + { + AppendLine($""); + } + + private void ExportMemo(TMemo memo) + { + var attrs = new List + { + $"Text=\"{EscapeXml(memo.Caption)}\"", + GetPositionAttributes(memo), + GetSizeAttributes(memo), + "AcceptsReturn=\"True\"", + "AcceptsTab=\"True\"" + }; + + if (memo.WordWrap) + attrs.Add("TextWrapping=\"Wrap\""); + if (memo.ScrollBars == ScrollStyle.Vertical || memo.ScrollBars == ScrollStyle.Both) + attrs.Add("VerticalScrollBarVisibility=\"Auto\""); + if (memo.ScrollBars == ScrollStyle.Horizontal || memo.ScrollBars == ScrollStyle.Both) + attrs.Add("HorizontalScrollBarVisibility=\"Auto\""); + + AppendSingleElement("TextBox", attrs, memo.Name); + } + +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Label.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Label.cs new file mode 100644 index 000000000..a9b4bf983 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Label.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using Trnsp.Show.Lfm.Models.Components; + +namespace Trnsp.Show.Lfm.Services; + +public partial class XamlExporter +{ + + private void ExportLabel(TLabel label) + { + var attrs = new List + { + $"Content=\"{EscapeXml(label.Caption)}\"", + GetPositionAttributes(label), + GetSizeAttributes(label) + }; + + if (label.FontSize != 12) + attrs.Add($"FontSize=\"{label.FontSize}\""); + if (!string.IsNullOrEmpty(label.FontName) && label.FontName != "Segoe UI") + attrs.Add($"FontFamily=\"{label.FontName}\""); + if (label.FontColor != Colors.Black) + attrs.Add($"Foreground=\"{ColorToString(label.FontColor)}\""); + if (!label.Transparent && label.Color != Colors.Transparent) + attrs.Add($"Background=\"{ColorToString(label.Color)}\""); + + AppendSingleElement("Label", attrs, label.Name); + } + + private void ExportStaticText(TStaticText staticText) + { + var attrs = new List + { + GetPositionAttributes(staticText), + GetSizeAttributes(staticText) + }; + + if (staticText.StaticBorderStyleKind != EStaticBorderStyle.None) + { + attrs.Add("BorderBrush=\"Gray\""); + attrs.Add("BorderThickness=\"1\""); + } + + AppendLine($""); + _indentLevel++; + AppendLine($""); + _indentLevel--; + AppendLine(""); + } +} diff --git a/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Selection.cs b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Selection.cs new file mode 100644 index 000000000..f0c6e53e1 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Lfm/Services/XamlExporter.Selection.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Trnsp.Show.Lfm.Models.Components; + +namespace Trnsp.Show.Lfm.Services; + +public partial class XamlExporter +{ + private void ExportCheckBox(TCheckBox checkBox) + { + var attrs = new List + { + $"Content=\"{EscapeXml(checkBox.Caption)}\"", + GetPositionAttributes(checkBox), + GetSizeAttributes(checkBox) + }; + + if (checkBox.State == CheckBoxState.Checked) + attrs.Add("IsChecked=\"True\""); + else if (checkBox.State == CheckBoxState.Grayed) + attrs.Add("IsChecked=\"{x:Null}\""); + + if (checkBox.AllowGrayed) + attrs.Add("IsThreeState=\"True\""); + + AppendSingleElement("CheckBox", attrs, checkBox.Name); + } + + private void ExportRadioButton(TRadioButton radioBtn) + { + var attrs = new List + { + $"Content=\"{EscapeXml(radioBtn.Caption)}\"", + GetPositionAttributes(radioBtn), + GetSizeAttributes(radioBtn) + }; + + if (radioBtn.Checked) + attrs.Add("IsChecked=\"True\""); + + AppendSingleElement("RadioButton", attrs, radioBtn.Name); + } + + private void ExportComboBox(TComboBox comboBox) + { + var attrs = new List + { + GetPositionAttributes(comboBox), + GetSizeAttributes(comboBox) + }; + + if (comboBox.Style != ComboBoxStyle.DropDownList) + attrs.Add("IsEditable=\"True\""); + if (comboBox.ItemIndex >= 0) + attrs.Add($"SelectedIndex=\"{comboBox.ItemIndex}\""); + + if (comboBox.Items.Count == 0) + { + AppendSingleElement("ComboBox", attrs, comboBox.Name); + } + else + { + AppendLine($""); + _indentLevel++; + foreach (var item in comboBox.Items) + { + AppendLine($""); + } + _indentLevel--; + AppendLine(""); + } + } + + private void ExportListBox(TListBox listBox) + { + var attrs = new List + { + GetPositionAttributes(listBox), + GetSizeAttributes(listBox) + }; + + if (listBox.MultiSelect) + attrs.Add("SelectionMode=\"Multiple\""); + + if (listBox.Items.Count == 0) + { + AppendSingleElement("ListBox", attrs, listBox.Name); + } + else + { + AppendLine($""); + _indentLevel++; + foreach (var item in listBox.Items) + { + AppendLine($""); + } + _indentLevel--; + AppendLine(""); + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/MSTestSettings.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/MSTestSettings.cs new file mode 100644 index 000000000..aaf278c84 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/Test1.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/Test1.cs new file mode 100644 index 000000000..5598cfeab --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/Test1.cs @@ -0,0 +1,17 @@ +namespace Trnsp.Show.Pas.Tests +{ + [TestClass] + public sealed class Test1 + { + [TestInitialize] + public void TestInit() + { + // This method is called before each test method. + } + + [TestMethod] + public void TestMethod1() + { + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/Trnsp.Show.Pas.Tests.csproj b/Transpiler_pp/Trnsp.Show.Pas.Tests/Trnsp.Show.Pas.Tests.csproj new file mode 100644 index 000000000..2e28becb1 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/Trnsp.Show.Pas.Tests.csproj @@ -0,0 +1,32 @@ + + + + net8.0-windows + false + true + Trnsp.Show.Pas + + + + + $(TargetFrameworks);net9.0-windows + + + $(TargetFrameworks);net10.0-windows + + + + + + + + + + + + + + + + + diff --git a/Transpiler_pp/Trnsp.Show.Pas/App.xaml b/Transpiler_pp/Trnsp.Show.Pas/App.xaml new file mode 100644 index 000000000..9b4327029 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Transpiler_pp/Trnsp.Show.Pas/App.xaml.cs b/Transpiler_pp/Trnsp.Show.Pas/App.xaml.cs new file mode 100644 index 000000000..238f7a5eb --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/App.xaml.cs @@ -0,0 +1,14 @@ +using System.Configuration; +using System.Data; +using System.Windows; + +namespace Transp.Show.Pas +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } + +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/AssemblyInfo.cs b/Transpiler_pp/Trnsp.Show.Pas/AssemblyInfo.cs new file mode 100644 index 000000000..b0ec82757 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml new file mode 100644 index 000000000..9c34f3e8a --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs new file mode 100644 index 000000000..18c334772 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs @@ -0,0 +1,24 @@ +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Transp.Show.Pas +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Transpiler_pp/Trnsp.Show.Pas/Trnsp.Show.Pas.csproj b/Transpiler_pp/Trnsp.Show.Pas/Trnsp.Show.Pas.csproj new file mode 100644 index 000000000..57a389597 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/Trnsp.Show.Pas.csproj @@ -0,0 +1,28 @@ + + + + + WinExe + net8.0-windows + true + + + + + $(TargetFrameworks);net9.0-windows + + + $(TargetFrameworks);net10.0-windows + + + + + + + + + + + + + From 8927c9264d462a685293413b2849612c4c5afb42 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:10:28 +0100 Subject: [PATCH 019/962] Trnsp.Show.Lfm --- Transpiler_pp/Trnsp.Show.Lfm/App.xaml | 13 +- Transpiler_pp/Trnsp.Show.Lfm/App.xaml.cs | 2 + Transpiler_pp/Trnsp.Show.Lfm/MainWindow.xaml | 7 + .../Models/Components/LfmComponentBase.cs | 131 +++--- .../Models/Components/TBitBtn.cs | 21 +- .../Models/Components/TBitmap.cs | 116 ++++- .../Trnsp.Show.Lfm/Models/Components/TForm.cs | 6 + .../Models/Components/TImageList.cs | 53 ++- .../Trnsp.Show.Lfm/Models/Components/TMenu.cs | 37 +- .../Models/Components/TToolBar.cs | 25 +- .../Trnsp.Show.Lfm/Services/ActionResolver.cs | 118 ----- .../Services/ComponentFactory.cs | 28 +- .../Services/ComponentRenderer.cs | 231 ++++------ .../Services/IComponentFactory.cs | 20 - .../Services/ILfmParserService.cs | 23 - .../Trnsp.Show.Lfm/Services/IXamlExporter.cs | 20 - .../Services/LfmParserService.cs | 1 + .../Trnsp.Show.Lfm/Services/XamlExporter.cs | 413 +++++------------- .../ViewModels/MainViewModel.cs | 71 +-- 19 files changed, 518 insertions(+), 818 deletions(-) delete mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/ActionResolver.cs delete mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/IComponentFactory.cs delete mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/ILfmParserService.cs delete mode 100644 Transpiler_pp/Trnsp.Show.Lfm/Services/IXamlExporter.cs diff --git a/Transpiler_pp/Trnsp.Show.Lfm/App.xaml b/Transpiler_pp/Trnsp.Show.Lfm/App.xaml index 7f8886e4f..750b1f442 100644 --- a/Transpiler_pp/Trnsp.Show.Lfm/App.xaml +++ b/Transpiler_pp/Trnsp.Show.Lfm/App.xaml @@ -1,8 +1,7 @@ - - - - + + diff --git a/Transpiler_pp/Trnsp.Show.Lfm/App.xaml.cs b/Transpiler_pp/Trnsp.Show.Lfm/App.xaml.cs index 4f167da6d..1b045c3f3 100644 --- a/Transpiler_pp/Trnsp.Show.Lfm/App.xaml.cs +++ b/Transpiler_pp/Trnsp.Show.Lfm/App.xaml.cs @@ -2,6 +2,7 @@ using System.Windows; using Microsoft.Extensions.DependencyInjection; using Trnsp.Show.Lfm.Services; +using Trnsp.Show.Lfm.Services.Interfaces; using Trnsp.Show.Lfm.ViewModels; namespace Trnsp.Show.Lfm; @@ -37,6 +38,7 @@ private static void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // ViewModels services.AddTransient(); diff --git a/Transpiler_pp/Trnsp.Show.Lfm/MainWindow.xaml b/Transpiler_pp/Trnsp.Show.Lfm/MainWindow.xaml index 180da8490..a57876f62 100644 --- a/Transpiler_pp/Trnsp.Show.Lfm/MainWindow.xaml +++ b/Transpiler_pp/Trnsp.Show.Lfm/MainWindow.xaml @@ -54,6 +54,13 @@ + + "); - } - - private void ExportSpeedButton(TSpeedButton speedBtn) - { - var attrs = new List - { - $"Content=\"{EscapeXml(speedBtn.EffectiveCaption)}\"", - GetPositionAttributes(speedBtn), - GetSizeAttributes(speedBtn) - }; - - if (speedBtn.Down) - attrs.Add("IsChecked=\"True\""); - - AppendSingleElement("ToggleButton", attrs, speedBtn.Name); - } - - #endregion - - #region Selection Components - - private void ExportCheckBox(TCheckBox checkBox) - { - var attrs = new List - { - $"Content=\"{EscapeXml(checkBox.Caption)}\"", - GetPositionAttributes(checkBox), - GetSizeAttributes(checkBox) - }; - - if (checkBox.State == CheckBoxState.Checked) - attrs.Add("IsChecked=\"True\""); - else if (checkBox.State == CheckBoxState.Grayed) - attrs.Add("IsChecked=\"{x:Null}\""); - - if (checkBox.AllowGrayed) - attrs.Add("IsThreeState=\"True\""); - - AppendSingleElement("CheckBox", attrs, checkBox.Name); - } - - private void ExportRadioButton(TRadioButton radioBtn) - { - var attrs = new List - { - $"Content=\"{EscapeXml(radioBtn.Caption)}\"", - GetPositionAttributes(radioBtn), - GetSizeAttributes(radioBtn) - }; - - if (radioBtn.Checked) - attrs.Add("IsChecked=\"True\""); - - AppendSingleElement("RadioButton", attrs, radioBtn.Name); - } - - private void ExportComboBox(TComboBox comboBox) - { - var attrs = new List - { - GetPositionAttributes(comboBox), - GetSizeAttributes(comboBox) - }; - - if (comboBox.Style != ComboBoxStyle.DropDownList) - attrs.Add("IsEditable=\"True\""); - if (comboBox.ItemIndex >= 0) - attrs.Add($"SelectedIndex=\"{comboBox.ItemIndex}\""); - - if (comboBox.Items.Count == 0) - { - AppendSingleElement("ComboBox", attrs, comboBox.Name); - } - else - { - AppendLine($""); - _indentLevel++; - foreach (var item in comboBox.Items) - { - AppendLine($""); - } - _indentLevel--; - AppendLine(""); - } - } - - private void ExportListBox(TListBox listBox) - { - var attrs = new List - { - GetPositionAttributes(listBox), - GetSizeAttributes(listBox) - }; - - if (listBox.MultiSelect) - attrs.Add("SelectionMode=\"Multiple\""); - - if (listBox.Items.Count == 0) - { - AppendSingleElement("ListBox", attrs, listBox.Name); - } - else - { - AppendLine($""); - _indentLevel++; - foreach (var item in listBox.Items) - { - AppendLine($""); - } - _indentLevel--; - AppendLine(""); - } - } - - #endregion - #region Container Components private void ExportPanel(TPanel panel) @@ -742,8 +487,8 @@ private void ExportImage(TImage image) else attrs.Add("Stretch=\"None\""); - // Source would need to be set separately - attrs.Add("Source=\"{Binding ImageSource}\" \""); + // Source would need to be set separately (step 2 will adjust this) + attrs.Add("Source=\"{Binding ImageSource}\" "); AppendSingleElement("Image", attrs, image.Name); } @@ -842,7 +587,7 @@ private void ExportDataGrid(TDrawGrid drawGrid) #region Menu and Toolbar - private void ExportMainMenu(TMainMenu mainMenu) + private void ExportMainMenu(TMainMenu mainMenu, string? imagesDirectory) { AppendLine(""); _indentLevel++; @@ -958,7 +703,7 @@ private void ExportToolButton(TToolButton toolBtn) } var attrs = new List(); - + if (!string.IsNullOrEmpty(toolBtn.EffectiveHint)) attrs.Add($"ToolTip=\"{EscapeXml(toolBtn.EffectiveHint)}\""); @@ -973,20 +718,88 @@ private void ExportToolButton(TToolButton toolBtn) #endregion - #region Non-Visual Components - private void ExportNonVisualComment(LfmComponentBase component) + #region Image Export Helpers + + private static void ExportImagesRecursive(LfmComponentBase component, string imagesDirectory) { - AppendLine($""); + if (string.IsNullOrEmpty(imagesDirectory)) + return; + + // Export image for TImage/TPaintBox + if (component is TImage image && image.ImageSource is not null) + { + Directory.CreateDirectory(imagesDirectory); + var fileName = SanitizeName(string.IsNullOrEmpty(image.Name) ? "Image" : image.Name) + ".png"; + var fullPath = Path.Combine(imagesDirectory, fileName); + SaveImageSourceAsPng(image.ImageSource, fullPath); + } + + // Export combined bitmap for TImageList (the strip); individual images could be added later if needed + if (component is TImageList imageList && imageList.Bitmap?.ImageSource is not null) + { + Directory.CreateDirectory(imagesDirectory); + var fileName = SanitizeName(string.IsNullOrEmpty(imageList.Name) ? "ImageList" : imageList.Name) + ".png"; + var fullPath = Path.Combine(imagesDirectory, fileName); + SaveImageSourceAsPng(imageList.Bitmap.ImageSource, fullPath); + } + + // Recurse into children for containers/forms + switch (component) + { + case TForm form: + foreach (var child in form.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + case TScrollBox scrollBox: + foreach (var child in scrollBox.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + case TPanel panel: + foreach (var child in panel.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + case TGroupBox groupBox: + foreach (var child in groupBox.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + case TPageControl pageControl: + foreach (var child in pageControl.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + case TTabSheet tabSheet: + foreach (var child in tabSheet.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + case TToolBar toolBar: + foreach (var child in toolBar.Children) + ExportImagesRecursive(child, imagesDirectory); + break; + } } - private void ExportUnknownComment(LfmComponentBase component) + private static void SaveImageSourceAsPng(ImageSource source, string fullPath) { - AppendLine($""); + if (source is not System.Windows.Media.Imaging.BitmapSource bitmapSource) + return; + + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); + + using var stream = File.Create(fullPath); + encoder.Save(stream); } #endregion + #region Non-Visual Components + + private void ExportNonVisualComment(LfmComponentBase component) => AppendLine($""); + + private void ExportUnknownComment(LfmComponentBase component) => AppendLine($""); + + #endregion + #region Helper Methods private void AppendLine(string line) @@ -1002,15 +815,9 @@ private void AppendSingleElement(string elementName, List attributes, st AppendLine($"<{elementName}{attrString}{nameAttr} />"); } - private static string GetPositionAttributes(LfmComponentBase component) - { - return $"Canvas.Left=\"{component.Left}\" Canvas.Top=\"{component.Top}\""; - } + private static string GetPositionAttributes(LfmComponentBase component) => $"Canvas.Left=\"{component.Left}\" Canvas.Top=\"{component.Top}\""; - private static string GetSizeAttributes(LfmComponentBase component) - { - return $"Width=\"{component.Width}\" Height=\"{component.Height}\""; - } + private static string GetSizeAttributes(LfmComponentBase component) => $"Width=\"{component.Width}\" Height=\"{component.Height}\""; private static string ColorToString(Color color) { diff --git a/Transpiler_pp/Trnsp.Show.Lfm/ViewModels/MainViewModel.cs b/Transpiler_pp/Trnsp.Show.Lfm/ViewModels/MainViewModel.cs index a78a6530b..d96fb837a 100644 --- a/Transpiler_pp/Trnsp.Show.Lfm/ViewModels/MainViewModel.cs +++ b/Transpiler_pp/Trnsp.Show.Lfm/ViewModels/MainViewModel.cs @@ -8,6 +8,7 @@ using Microsoft.Win32; using Trnsp.Show.Lfm.Models.Components; using Trnsp.Show.Lfm.Services; +using Trnsp.Show.Lfm.Services.Interfaces; namespace Trnsp.Show.Lfm.ViewModels; @@ -18,6 +19,7 @@ public partial class MainViewModel : ObservableObject { private readonly ILfmParserService _parserService; private readonly IComponentFactory _componentFactory; + private readonly IXamlExporter _xamlExporter; [ObservableProperty] private string _title = "LFM Viewer"; @@ -54,10 +56,11 @@ public partial class MainViewModel : ObservableObject public ObservableCollection RootObjects { get; } = []; - public MainViewModel(ILfmParserService parserService, IComponentFactory componentFactory) + public MainViewModel(ILfmParserService parserService, IComponentFactory componentFactory, IXamlExporter xamlExporter) { _parserService = parserService; _componentFactory = componentFactory; + _xamlExporter = xamlExporter; } [RelayCommand] @@ -157,34 +160,19 @@ private void ParseText() } [RelayCommand] - private void ExpandAll() - { - SetExpanded(RootObjects, true); - } + private void ExpandAll() => SetExpanded(RootObjects, true); [RelayCommand] - private void CollapseAll() - { - SetExpanded(RootObjects, false); - } + private void CollapseAll() => SetExpanded(RootObjects, false); [RelayCommand] - private void ZoomIn() - { - Zoom = Math.Min(4.0, Zoom + 0.25); - } + private void ZoomIn() => Zoom = Math.Min(4.0, Zoom + 0.25); [RelayCommand] - private void ZoomOut() - { - Zoom = Math.Max(0.25, Zoom - 0.25); - } + private void ZoomOut() => Zoom = Math.Max(0.25, Zoom - 0.25); [RelayCommand] - private void ZoomReset() - { - Zoom = 1.0; - } + private void ZoomReset() => Zoom = 1.0; private static void SetExpanded(IEnumerable objects, bool expanded) { @@ -195,13 +183,42 @@ private static void SetExpanded(IEnumerable objects, bool ex } } - private static int CountObjects(TranspilerLib.Pascal.Models.LfmObject obj) + [RelayCommand] + private void ExportXaml() { - return 1 + obj.Children.Sum(CountObjects); - } + try + { + if (DesignerRoot == null) + { + StatusMessage = "Kein XAML exportierbar kein Designerinhalt vorhanden."; + return; + } - private static int CountProperties(TranspilerLib.Pascal.Models.LfmObject obj) - { - return obj.Properties.Count + obj.Children.Sum(CountProperties); + var saveDialog = new SaveFileDialog + { + Filter = "XAML Dateien (*.xaml)|*.xaml|Alle Dateien (*.*)|*.*", + Title = "XAML exportieren", + FileName = string.IsNullOrWhiteSpace(FilePath) + ? "Export.xaml" + : Path.ChangeExtension(Path.GetFileName(FilePath), ".xaml") + }; + + if (saveDialog.ShowDialog() != true) + { + return; + } + + _xamlExporter.ExportToFile(DesignerRoot, saveDialog.FileName); + + StatusMessage = $"XAML exportiert: {Path.GetFileName(saveDialog.FileName)}"; + } + catch (Exception ex) + { + StatusMessage = $"Fehler beim XAML-Export: {ex.Message}"; + } } + + private static int CountObjects(TranspilerLib.Pascal.Models.LfmObject obj) => 1 + obj.Children.Sum(CountObjects); + + private static int CountProperties(TranspilerLib.Pascal.Models.LfmObject obj) => obj.Properties.Count + obj.Children.Sum(CountProperties); } From d41bff2bf2860493c93a7c23f3baee295f444e6b Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:10:31 +0100 Subject: [PATCH 020/962] Transpiler_pp --- Transpiler_pp/Transpiler.props | 2 ++ Transpiler_pp/Transpiler.sln | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/Transpiler_pp/Transpiler.props b/Transpiler_pp/Transpiler.props index f1e12eb70..2f5d95cf4 100644 --- a/Transpiler_pp/Transpiler.props +++ b/Transpiler_pp/Transpiler.props @@ -5,10 +5,12 @@ $(UpDir)\..\obj\$(MSBuildProjectName)\ 12.0 enable + true NULLABLE disable JC-Soft Joe Care Copyright © JC-Soft 2024 + true \ No newline at end of file diff --git a/Transpiler_pp/Transpiler.sln b/Transpiler_pp/Transpiler.sln index 46d7a2086..39d62b37f 100644 --- a/Transpiler_pp/Transpiler.sln +++ b/Transpiler_pp/Transpiler.sln @@ -5,6 +5,7 @@ VisualStudioVersion = 18.0.11201.2 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Projektmappenelemente", "Projektmappenelemente", "{FB9836CD-815C-4901-B10D-8CCF99B18E30}" ProjectSection(SolutionItems) = preProject + CONTRIBUTING.md = CONTRIBUTING.md ..\Gen_FreeWin\GenFreeWin.props = ..\Gen_FreeWin\GenFreeWin.props Transpiler.props = Transpiler.props EndProjectSection @@ -54,6 +55,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cmd", "cmd", "{0E2C6B7C-A65 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trnsp.Show.Lfm", "Trnsp.Show.Lfm\Trnsp.Show.Lfm.csproj", "{AFFF4BB6-EFBC-4077-AC1B-D2B8869476B8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trnsp.Show.Lfm.Tests", "Trnsp.Show.Lfm.Tests\Trnsp.Show.Lfm.Tests.csproj", "{254C22CE-920A-AED1-2EA5-C96DB6A09864}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trnsp.Show.Pas.Tests", "Trnsp.Show.Pas.Tests\Trnsp.Show.Pas.Tests.csproj", "{53F850C8-1AAB-4FDE-9474-DFCF998A53C9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trnsp.Show.Pas", "Trnsp.Show.Pas\Trnsp.Show.Pas.csproj", "{6DC12DCF-BAA7-66F1-405F-3B2AD554496C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -256,6 +263,42 @@ Global {AFFF4BB6-EFBC-4077-AC1B-D2B8869476B8}.Release|Mixed Platforms.Build.0 = Release|Any CPU {AFFF4BB6-EFBC-4077-AC1B-D2B8869476B8}.Release|x86.ActiveCfg = Release|Any CPU {AFFF4BB6-EFBC-4077-AC1B-D2B8869476B8}.Release|x86.Build.0 = Release|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Debug|Any CPU.Build.0 = Debug|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Debug|x86.ActiveCfg = Debug|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Debug|x86.Build.0 = Debug|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Release|Any CPU.ActiveCfg = Release|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Release|Any CPU.Build.0 = Release|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Release|x86.ActiveCfg = Release|Any CPU + {254C22CE-920A-AED1-2EA5-C96DB6A09864}.Release|x86.Build.0 = Release|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Debug|x86.Build.0 = Debug|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Release|Any CPU.Build.0 = Release|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Release|x86.ActiveCfg = Release|Any CPU + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9}.Release|x86.Build.0 = Release|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Debug|x86.Build.0 = Debug|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Release|Any CPU.Build.0 = Release|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Release|x86.ActiveCfg = Release|Any CPU + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -277,6 +320,9 @@ Global {1CCC7458-4422-4370-8540-3BE92F479BEA} = {5481B009-5D62-45A8-9DD3-B9E6792B0C0E} {DD21FC5F-2FFF-4BD6-847B-7B5006DB3389} = {5481B009-5D62-45A8-9DD3-B9E6792B0C0E} {AFFF4BB6-EFBC-4077-AC1B-D2B8869476B8} = {5481B009-5D62-45A8-9DD3-B9E6792B0C0E} + {254C22CE-920A-AED1-2EA5-C96DB6A09864} = {5481B009-5D62-45A8-9DD3-B9E6792B0C0E} + {53F850C8-1AAB-4FDE-9474-DFCF998A53C9} = {5481B009-5D62-45A8-9DD3-B9E6792B0C0E} + {6DC12DCF-BAA7-66F1-405F-3B2AD554496C} = {5481B009-5D62-45A8-9DD3-B9E6792B0C0E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {44374C4A-6FF4-4900-8BB8-964A6A2E0338} From b224739a0fa7552b5192e9d9e742e05350e34168 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:27 +0100 Subject: [PATCH 021/962] Analyzer1 --- .../Services/FileServiceTests.cs | 16 +++++ .../Services/PascalParserServiceTests.cs | 32 ++++++++++ .../ViewModels/CodeBlockNodeTests.cs | 58 ++++++++++++++++++ .../ViewModels/MainViewModelTests.cs | 59 +++++++++++++++++++ .../Trnsp.Show.Pas/Services/FileService.cs | 29 +++++++++ .../Trnsp.Show.Pas/Services/IFileService.cs | 8 +++ .../Services/IPascalParserService.cs | 9 +++ .../Services/PascalParserService.cs | 15 +++++ .../ViewModels/CodeBlockNode.cs | 26 ++++++++ .../ViewModels/MainViewModel.cs | 40 +++++++++++++ 10 files changed, 292 insertions(+) create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/Services/FileServiceTests.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/Services/PascalParserServiceTests.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/CodeBlockNodeTests.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/Services/FileService.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/Services/IFileService.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/Services/IPascalParserService.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/Services/PascalParserService.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/ViewModels/CodeBlockNode.cs create mode 100644 Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/Services/FileServiceTests.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/Services/FileServiceTests.cs new file mode 100644 index 000000000..e5e3aeb1e --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/Services/FileServiceTests.cs @@ -0,0 +1,16 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Trnsp.Show.Pas.Services; + +namespace Trnsp.Show.Pas.Tests.Services +{ + [TestClass] + public class FileServiceTests + { + [TestMethod] + public void FileService_Implements_IFileService() + { + var service = new FileService(); + Assert.IsInstanceOfType(service, typeof(IFileService)); + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/Services/PascalParserServiceTests.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/Services/PascalParserServiceTests.cs new file mode 100644 index 000000000..01bbd4638 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/Services/PascalParserServiceTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Trnsp.Show.Pas.Services; +using TranspilerLib.Interfaces.Code; + +namespace Trnsp.Show.Pas.Tests.Services +{ + [TestClass] + public class PascalParserServiceTests + { + [TestMethod] + public void PascalParserService_Implements_IPascalParserService() + { + var service = new PascalParserService(); + Assert.IsInstanceOfType(service, typeof(IPascalParserService)); + } + + [TestMethod] + public void Parse_ReturnsCodeBlock() + { + // Arrange + var service = new PascalParserService(); + string code = "program Test; begin end."; + + // Act + var result = service.Parse(code); + + // Assert + Assert.IsNotNull(result); + Assert.IsInstanceOfType(result, typeof(ICodeBlock)); + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/CodeBlockNodeTests.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/CodeBlockNodeTests.cs new file mode 100644 index 000000000..355205b67 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/CodeBlockNodeTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using TranspilerLib.Data; +using TranspilerLib.Interfaces.Code; +using Trnsp.Show.Pas.ViewModels; + +namespace Trnsp.Show.Pas.Tests.ViewModels +{ + [TestClass] + public class CodeBlockNodeTests + { + [TestMethod] + public void Constructor_SetsProperties() + { + // Arrange + var codeBlock = Substitute.For(); + codeBlock.Name.Returns("TestBlock"); + codeBlock.Type.Returns(CodeBlockType.Operation); + codeBlock.Code.Returns("x := 1;"); + codeBlock.SubBlocks.Returns(new List()); + + // Act + var node = new CodeBlockNode(codeBlock); + + // Assert + Assert.AreEqual("TestBlock", node.Name); + Assert.AreEqual(CodeBlockType.Operation, node.Type); + Assert.AreEqual("x := 1;", node.Code); +#pragma warning disable MSTEST0037 // Use Assert.IsEmpty + Assert.AreEqual(0, node.Children.Count); +#pragma warning restore MSTEST0037 + } + + [TestMethod] + public void Constructor_CreatesChildrenRecursively() + { + // Arrange + var childBlock = Substitute.For(); + childBlock.Name.Returns("Child"); + childBlock.SubBlocks.Returns(new List()); + + var parentBlock = Substitute.For(); + parentBlock.Name.Returns("Parent"); + parentBlock.SubBlocks.Returns(new List { childBlock }); + + // Act + var node = new CodeBlockNode(parentBlock); + + // Assert +#pragma warning disable MSTEST0037 // Use Assert.HasCount + Assert.AreEqual(1, node.Children.Count); +#pragma warning restore MSTEST0037 + Assert.AreEqual("Child", node.Children.First().Name); + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs new file mode 100644 index 000000000..cf8e6c6ab --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs @@ -0,0 +1,59 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +using System.Collections.Generic; +using Trnsp.Show.Pas.Services; +using Trnsp.Show.Pas.ViewModels; +using TranspilerLib.Interfaces.Code; + +namespace Trnsp.Show.Pas.Tests.ViewModels +{ + [TestClass] + public class MainViewModelTests + { + private MainViewModel _testViewModel = null!; + private IFileService _fileService = null!; + private IPascalParserService _parserService = null!; + + [TestInitialize] + public void Init() + { + _fileService = Substitute.For(); + _parserService = Substitute.For(); + _testViewModel = new MainViewModel(_fileService, _parserService); + } + + [TestMethod] + public void SetupTest() + { + Assert.IsNotNull(_testViewModel); + Assert.IsInstanceOfType(_testViewModel, typeof(MainViewModel)); + Assert.IsInstanceOfType(_testViewModel, typeof(ObservableObject)); + } + + [TestMethod] + public void LoadFileCommand_ExecutesCorrectly() + { + // Arrange + string filePath = "test.pas"; + string fileContent = "program Test; begin end."; + var codeBlock = Substitute.For(); + codeBlock.SubBlocks.Returns(new List()); + + _fileService.OpenFileDialog(Arg.Any(), Arg.Any()).Returns(filePath); + _fileService.ReadAllText(filePath).Returns(fileContent); + _parserService.Parse(fileContent).Returns(codeBlock); + + // Act + _testViewModel.LoadFileCommand.Execute(null); + + // Assert + _fileService.Received(1).OpenFileDialog(Arg.Any(), Arg.Any()); + _fileService.Received(1).ReadAllText(filePath); + _parserService.Received(1).Parse(fileContent); +#pragma warning disable MSTEST0037 // Use Assert.HasCount + Assert.AreEqual(1, _testViewModel.RootNodes.Count); +#pragma warning restore MSTEST0037 + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/Services/FileService.cs b/Transpiler_pp/Trnsp.Show.Pas/Services/FileService.cs new file mode 100644 index 000000000..4c93238e8 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/Services/FileService.cs @@ -0,0 +1,29 @@ +using System.IO; +using Microsoft.Win32; + +namespace Trnsp.Show.Pas.Services +{ + public class FileService : IFileService + { + public string? OpenFileDialog(string title, string filter) + { + var dialog = new OpenFileDialog + { + Title = title, + Filter = filter + }; + + if (dialog.ShowDialog() == true) + { + return dialog.FileName; + } + + return null; + } + + public string ReadAllText(string path) + { + return File.ReadAllText(path); + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/Services/IFileService.cs b/Transpiler_pp/Trnsp.Show.Pas/Services/IFileService.cs new file mode 100644 index 000000000..ac25fa9b9 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/Services/IFileService.cs @@ -0,0 +1,8 @@ +namespace Trnsp.Show.Pas.Services +{ + public interface IFileService + { + string? OpenFileDialog(string title, string filter); + string ReadAllText(string path); + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/Services/IPascalParserService.cs b/Transpiler_pp/Trnsp.Show.Pas/Services/IPascalParserService.cs new file mode 100644 index 000000000..01642602d --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/Services/IPascalParserService.cs @@ -0,0 +1,9 @@ +using TranspilerLib.Interfaces.Code; + +namespace Trnsp.Show.Pas.Services +{ + public interface IPascalParserService + { + ICodeBlock Parse(string code); + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/Services/PascalParserService.cs b/Transpiler_pp/Trnsp.Show.Pas/Services/PascalParserService.cs new file mode 100644 index 000000000..450e987e2 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/Services/PascalParserService.cs @@ -0,0 +1,15 @@ +using TranspilerLib.Interfaces.Code; +using TranspilerLib.Pascal.Models.Scanner; + +namespace Trnsp.Show.Pas.Services +{ + public class PascalParserService : IPascalParserService + { + public ICodeBlock Parse(string code) + { + var parser = new PasCode(); + parser.OriginalCode = code; + return parser.Parse(); + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/ViewModels/CodeBlockNode.cs b/Transpiler_pp/Trnsp.Show.Pas/ViewModels/CodeBlockNode.cs new file mode 100644 index 000000000..98e9ebe6a --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/ViewModels/CodeBlockNode.cs @@ -0,0 +1,26 @@ +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using TranspilerLib.Data; +using TranspilerLib.Interfaces.Code; + +namespace Trnsp.Show.Pas.ViewModels +{ + public partial class CodeBlockNode : ObservableObject + { + private readonly ICodeBlock _codeBlock; + + public string Name => _codeBlock.Name; + public CodeBlockType Type => _codeBlock.Type; + public string Code => _codeBlock.Code; + public ObservableCollection Children { get; } = new(); + + public CodeBlockNode(ICodeBlock codeBlock) + { + _codeBlock = codeBlock; + foreach (var child in _codeBlock.SubBlocks) + { + Children.Add(new CodeBlockNode(child)); + } + } + } +} diff --git a/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs b/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs new file mode 100644 index 000000000..0804e5c18 --- /dev/null +++ b/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs @@ -0,0 +1,40 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using System.Collections.ObjectModel; +using Trnsp.Show.Pas.Services; + +namespace Trnsp.Show.Pas.ViewModels +{ + public partial class MainViewModel : ObservableObject + { + private readonly IFileService _fileService; + private readonly IPascalParserService _parserService; + + public ObservableCollection RootNodes { get; } = new(); + + public IRelayCommand LoadFileCommand { get; } + + public MainViewModel(IFileService fileService, IPascalParserService parserService) + { + _fileService = fileService; + _parserService = parserService; + LoadFileCommand = new RelayCommand(LoadFile); + } + + public MainViewModel() : this(new FileService(), new PascalParserService()) + { + } + + private void LoadFile() + { + var filePath = _fileService.OpenFileDialog("Open Pascal File", "Pascal Files (*.pas;*.pp)|*.pas;*.pp|All Files (*.*)|*.*"); + if (filePath != null) + { + var content = _fileService.ReadAllText(filePath); + var rootBlock = _parserService.Parse(content); + RootNodes.Clear(); + RootNodes.Add(new CodeBlockNode(rootBlock)); + } + } + } +} From 71f26075411ff39c83a3da40b98f0afb1893736f Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:29 +0100 Subject: [PATCH 022/962] TranspilerLib --- Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml | 2 +- Transpiler_pp/TranspilerLib/Interfaces/Code/ICodeBlock.cs | 4 ++-- Transpiler_pp/TranspilerLib/Models/Scanner/CodeBlock.cs | 6 +++--- Transpiler_pp/TranspilerLib/Models/Scanner/CodeBuilder.cs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml b/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml index 2896dab79..8eda1e5ff 100644 --- a/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml +++ b/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml @@ -253,7 +253,7 @@ Extends the Extends the - + diff --git a/Transpiler_pp/TranspilerLib/Interfaces/Code/ICodeBlock.cs b/Transpiler_pp/TranspilerLib/Interfaces/Code/ICodeBlock.cs index 8aae5f10c..d3442f7ae 100644 --- a/Transpiler_pp/TranspilerLib/Interfaces/Code/ICodeBlock.cs +++ b/Transpiler_pp/TranspilerLib/Interfaces/Code/ICodeBlock.cs @@ -22,7 +22,7 @@ namespace TranspilerLib.Interfaces.Code; /// Extends the /// Extends the /// -/// +/// /// public interface ICodeBlock : IHasParents, IEquatable { @@ -65,7 +65,7 @@ public interface ICodeBlock : IHasParents, IEquatable /// Gets the sources. /// /// The sources. - IList?> Sources { get; } + IList> Sources { get; } // new ICodeBlock? Parent { get; set; } /// /// Gets the next. diff --git a/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBlock.cs b/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBlock.cs index a0aba9ab8..77880a4a1 100644 --- a/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBlock.cs +++ b/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBlock.cs @@ -129,7 +129,7 @@ public class CodeBlock : ICodeBlock /// /// List of weak references to source blocks. [IgnoreDataMember] - public virtual IList?> Sources { get; private set; } = new List?>(); + public virtual IList> Sources { get; private set; } = new List>(); /// /// Gets or sets the index-paths for each source referring to this block. @@ -247,7 +247,7 @@ public CodeBlock() else if (item2 != null && v >= item2.SubBlocks.Count || v < 0) { item2 = null; break; } else - item2 = item2.SubBlocks[v]; + item2 = item2!.SubBlocks[v]; } return item2; } @@ -392,7 +392,7 @@ public bool DeleteSubBlocks(int iSrc, int cnt) { (c = SubBlocks[iSrc]).Parent = null; if (c.Destination != null && c.Destination.TryGetTarget(out var target)) - foreach (var item in target.Sources) + foreach (var item in target!.Sources) if (item.TryGetTarget(out var source)) if (source == c) { diff --git a/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBuilder.cs b/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBuilder.cs index e103d2c10..2a7b39f49 100644 --- a/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBuilder.cs +++ b/Transpiler_pp/TranspilerLib/Models/Scanner/CodeBuilder.cs @@ -71,16 +71,16 @@ public virtual void OnToken(TokenData tokenData, ICodeBuilderData data) { // Ein Level höher als das Token: wir hängen als Geschwister an den Parent int i when i == tokenData.Level + 1 - => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock.Parent, tokenData.Pos), + => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock.Parent!, tokenData.Pos), // Gleiches Level: Kind des aktuellen Blocks int i when i == tokenData.Level => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock, tokenData.Pos), // Zwei Levels höher: wir steigen zwei Ebenen auf int i when i == tokenData.Level + 2 - => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock.Parent.Parent, tokenData.Pos), + => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock.Parent!.Parent!, tokenData.Pos), // Drei Levels höher (Sonderfall) – vermutlich fehlerhafte Struktur aber aktuell zugelassen int i when i == tokenData.Level + 3 - => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock.Parent.Parent.Parent, tokenData.Pos), + => NewCodeBlock($"{tokenData.type}", tokenData.type, tokenData.Code, data.actualBlock.Parent!.Parent!.Parent!, tokenData.Pos), _ => throw new NotImplementedException() }; From 650b5e7201aec24c75fd1995d404caf0bec9bdc6 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:29 +0100 Subject: [PATCH 023/962] TranspilerLib.CSharp --- .../TranspilerLib.CSharp/Models/Scanner/CodeOptimizer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Transpiler_pp/TranspilerLib.CSharp/Models/Scanner/CodeOptimizer.cs b/Transpiler_pp/TranspilerLib.CSharp/Models/Scanner/CodeOptimizer.cs index 0f19fb16d..5713aac75 100644 --- a/Transpiler_pp/TranspilerLib.CSharp/Models/Scanner/CodeOptimizer.cs +++ b/Transpiler_pp/TranspilerLib.CSharp/Models/Scanner/CodeOptimizer.cs @@ -206,18 +206,19 @@ private void TestForEasyWhileLoops(ICodeBlock item) while (c?.Next is ICodeBlock next && c.Type is CodeBlockType.LComment or CodeBlockType.Comment) c = next; - if (c.Next is ICodeBlock next2 + if (c?.Next is ICodeBlock next2 && c.Code.StartsWith("num =")) c = next2; // move Instructions to While-Goto - if (item.Parent.MoveSubBlocks(c.Index, source, ifItem.Index - c.Index)) + if (item.Parent?.MoveSubBlocks(c!.Index, source, ifItem.Index - c.Index) ?? false) { ifItem.Code = ifItem.Code.Replace("if", "while"); // Delete goto DeleteItem(source); if (ifItem.Next is ICodeBlock elseItem && elseItem.Type == CodeBlockType.Operation - && elseItem.Code == "else") + && elseItem.Code == "else" + && elseItem.Next != null) { elseItem.MoveSubBlocks(1, elseItem.Next, elseItem.SubBlocks.Count - 2); DeleteItem(elseItem); From eb077e8692174634edb81b6cf0dccaffe41e66f4 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:29 +0100 Subject: [PATCH 024/962] TranspilerLib.IEC --- Transpiler_pp/TranspilerLib.IEC/Models/ExtOutput.cs | 4 ++-- .../TranspilerLib.IEC/Models/Scanner/IECCodeBlock.cs | 2 +- .../TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Transpiler_pp/TranspilerLib.IEC/Models/ExtOutput.cs b/Transpiler_pp/TranspilerLib.IEC/Models/ExtOutput.cs index a3bd06fe2..1d6682697 100644 --- a/Transpiler_pp/TranspilerLib.IEC/Models/ExtOutput.cs +++ b/Transpiler_pp/TranspilerLib.IEC/Models/ExtOutput.cs @@ -195,13 +195,13 @@ private static E_State AttribStateMachine(E_State eState, string sAttrName, obje case (E_State.POU, scAttrPouType) when sName.ToLower() == scNodePou && dData.ContainsKey(scAttrName): dData[scAttrPouType] = sAttrValue.ToString(); - if (sAttrValue.ToString().ToLower() == "function") + if (sAttrValue?.ToString()?.ToLower() == "function") { _out($"FUNCTION {dData[scAttrName]} : "); eState = E_State.Function; } else - _out($"{sAttrValue.ToString().ToUpper()} {dData[scAttrName]};"); + _out($"{sAttrValue?.ToString()?.ToUpper()} {dData[scAttrName]};"); break; } diff --git a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBlock.cs b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBlock.cs index 78465af09..4aab2f5e2 100644 --- a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBlock.cs +++ b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBlock.cs @@ -59,7 +59,7 @@ string ToCode(int indent = 2) return $" {Code}"; // Standardformatierung (Einrückung abhängig vom Blocktyp) else - return $"{new string(' ', Type is CodeBlockType.Block or CodeBlockType.Label ? indent - 4 : 1)}{Code}{codeComment}{(SubBlocks.Count > 0 ? "\r\n" : string.Empty)}{subCode}"; + return $"{new string(' ', Type is CodeBlockType.Block or CodeBlockType.Label ? indent - 4 : 1)}{Code}{codeComment}{(SubBlocks?.Count > 0 ? "\r\n" : string.Empty)}{subCode}"; } /// diff --git a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs index fa42a70fe..ddbd177c5 100644 --- a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs +++ b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs @@ -93,7 +93,7 @@ private void BuildFunction(TokenData tokenData, ICodeBuilderData data) var td = tokenData; td.type = CodeBlockType.Block; td.Level = tokenData.Level - 1; - while (data.actualBlock.Level > tokenData.Level) + while (data.actualBlock?.Level > tokenData.Level) data.actualBlock = data.actualBlock.Parent; base.OnToken(td, data); } From 8699ca9e024fcae50d3e0db9355de0af10b6e5b3 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:30 +0100 Subject: [PATCH 025/962] TranspilerLib.Pascal.Tests --- .../Models/LfmObjectBuilderTests.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmObjectBuilderTests.cs b/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmObjectBuilderTests.cs index f2505d79c..5676ee0bc 100644 --- a/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmObjectBuilderTests.cs +++ b/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmObjectBuilderTests.cs @@ -63,7 +63,7 @@ public void Build_SimpleObject_ReturnsLfmObject() Assert.AreEqual("Form1", result.Name); Assert.AreEqual("TForm1", result.TypeName); Assert.IsFalse(result.IsInherited); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); Assert.AreEqual("Left", result.Properties[0].Name); Assert.AreEqual(100, result.Properties[0].Value); } @@ -103,7 +103,7 @@ public void Build_ObjectWithStringProperty_ParsesStringCorrectly() // Assert Assert.IsNotNull(result); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); Assert.AreEqual("Caption", result.Properties[0].Name); Assert.AreEqual("Hello World", result.Properties[0].Value); } @@ -124,9 +124,9 @@ public void Build_ObjectWithBooleanProperty_ParsesBooleanCorrectly() // Assert Assert.IsNotNull(result); - Assert.AreEqual(2, result.Properties.Count); - Assert.AreEqual(true, result.Properties[0].Value); - Assert.AreEqual(false, result.Properties[1].Value); + Assert.HasCount(2, result.Properties); + Assert.IsTrue((bool?)result.Properties[0].Value); + Assert.IsFalse((bool?)result.Properties[1].Value); } [TestMethod] @@ -148,13 +148,13 @@ public void Build_ObjectWithNestedObject_ParsesChildrenCorrectly() // Assert Assert.IsNotNull(result); Assert.AreEqual("Form1", result.Name); - Assert.AreEqual(1, result.Properties.Count); - Assert.AreEqual(1, result.Children.Count); + Assert.HasCount(1, result.Properties); + Assert.HasCount(1, result.Children); var child = result.Children[0]; Assert.AreEqual("Button1", child.Name); Assert.AreEqual("TButton", child.TypeName); - Assert.AreEqual(1, child.Properties.Count); + Assert.HasCount(1, child.Properties); Assert.AreEqual("Caption", child.Properties[0].Name); Assert.AreEqual("Click", child.Properties[0].Value); } @@ -174,12 +174,12 @@ public void Build_ObjectWithSetProperty_ParsesSetCorrectly() // Assert Assert.IsNotNull(result); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); Assert.AreEqual(LfmPropertyType.Set, result.Properties[0].PropertyType); var setItems = result.Properties[0].Value as List; Assert.IsNotNull(setItems); - Assert.AreEqual(2, setItems.Count); + Assert.HasCount(2, setItems); Assert.AreEqual("biMinimize", setItems[0]); Assert.AreEqual("biMaximize", setItems[1]); } @@ -199,7 +199,7 @@ public void Build_ObjectWithNegativeNumber_ParsesNegativeCorrectly() // Assert Assert.IsNotNull(result); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); Assert.AreEqual("Min", result.Properties[0].Name); Assert.AreEqual(-1000, result.Properties[0].Value); } @@ -219,7 +219,7 @@ public void Build_ObjectWithIdentifierProperty_ParsesIdentifierCorrectly() // Assert Assert.IsNotNull(result); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); Assert.AreEqual("BorderStyle", result.Properties[0].Name); Assert.AreEqual("bsToolWindow", result.Properties[0].Value); } @@ -239,7 +239,7 @@ public void Build_ObjectWithQualifiedPropertyName_ParsesQualifiedNameCorrectly() // Assert Assert.IsNotNull(result); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); Assert.AreEqual("Font.Name", result.Properties[0].Name); Assert.AreEqual("Arial", result.Properties[0].Value); } @@ -267,7 +267,7 @@ public void Build_ObjectWithItemListProperty_ParsesItemListCorrectly() Assert.IsNotNull(result); Assert.AreEqual("StatusBar1", result.Name); Assert.AreEqual("TStatusBar", result.TypeName); - Assert.AreEqual(1, result.Properties.Count); + Assert.HasCount(1, result.Properties); var panelsProperty = result.Properties[0]; Assert.AreEqual("Panels", panelsProperty.Name); @@ -275,15 +275,15 @@ public void Build_ObjectWithItemListProperty_ParsesItemListCorrectly() var items = panelsProperty.Value as List; Assert.IsNotNull(items); - Assert.AreEqual(2, items.Count); + Assert.HasCount(2, items); // Check first item - Assert.AreEqual(1, items[0].Properties.Count); + Assert.HasCount(1, items[0].Properties); Assert.AreEqual("Width", items[0].Properties[0].Name); Assert.AreEqual(50, items[0].Properties[0].Value); // Check second item - Assert.AreEqual(1, items[1].Properties.Count); + Assert.HasCount(1, items[1].Properties); Assert.AreEqual("Width", items[1].Properties[0].Name); Assert.AreEqual(80, items[1].Properties[0].Value); } From c81a0e2f2737a33e010d38d7728aa22b6db3c2ae Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:30 +0100 Subject: [PATCH 026/962] TranspilerLibTests --- .../TranspilerLibTests/Models/Scanner/CSTokenHandlerTests.cs | 2 ++ Transpiler_pp/TranspilerLibTests/Models/Scanner/IECCodeTests.cs | 2 ++ .../TranspilerLibTests/Models/Scanner/IECTokenHandlerTests.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSTokenHandlerTests.cs b/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSTokenHandlerTests.cs index be361e797..d7ac3e87b 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSTokenHandlerTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSTokenHandlerTests.cs @@ -9,7 +9,9 @@ namespace TranspilerLib.Models.Scanner.Tests [TestClass()] public class CSTokenHandlerTests { +#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. ITokenHandler testHandler; +#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. [TestInitialize] public void Init() diff --git a/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECCodeTests.cs b/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECCodeTests.cs index ee2683cfc..7218b127e 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECCodeTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECCodeTests.cs @@ -12,7 +12,9 @@ namespace TranspilerLib.Models.Scanner.Tests; [TestClass] public class IECCodeTests : TestBase { +#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. private IECCode _testClass; +#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. public static IEnumerable TestTokenizeList => [ diff --git a/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECTokenHandlerTests.cs b/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECTokenHandlerTests.cs index af2b4d0fa..692742745 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECTokenHandlerTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/Scanner/IECTokenHandlerTests.cs @@ -9,7 +9,9 @@ namespace TranspilerLib.Models.Scanner.Tests [TestClass()] public class IECTokenHandlerTests { +#pragma warning disable CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. ITokenHandler testHandler; +#pragma warning restore CS8618 // Ein Non-Nullable-Feld muss beim Beenden des Konstruktors einen Wert ungleich NULL enthalten. Fügen Sie ggf. den „erforderlichen“ Modifizierer hinzu, oder deklarieren Sie den Modifizierer als NULL-Werte zulassend. [TestInitialize] public void Init() From 161958b7671bb6f760cb4251ca4c827fe9d62861 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 08:44:32 +0100 Subject: [PATCH 027/962] Transpiler_pp --- Transpiler_pp/Transpiler.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Transpiler_pp/Transpiler.props b/Transpiler_pp/Transpiler.props index 2f5d95cf4..d0542248a 100644 --- a/Transpiler_pp/Transpiler.props +++ b/Transpiler_pp/Transpiler.props @@ -5,7 +5,7 @@ $(UpDir)\..\obj\$(MSBuildProjectName)\ 12.0 enable - true + NULLABLE disable JC-Soft From f3b0d4318bf3e1d7594ce860ae2968f48dde9938 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 10:59:17 +0100 Subject: [PATCH 028/962] DataAnalysis.Core --- .../Models/AnalysisAggregateProfile.cs | 61 ++++++++++++++++--- .../DataAnalysis.Core/Models/AnalysisQuery.cs | 39 ++++++------ .../DataAnalysis.Core/Models/DimensionKind.cs | 14 +++-- .../Models/Filters/FilterCompiler.cs | 12 ++-- .../Models/Filters/FilterDefinitions.cs | 4 +- .../DataAnalysis.Core/Models/QueryBuilder.cs | 16 ++++- 6 files changed, 106 insertions(+), 40 deletions(-) diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisAggregateProfile.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisAggregateProfile.cs index 31ff305ec..17ac7408b 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisAggregateProfile.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisAggregateProfile.cs @@ -1,3 +1,4 @@ +using ClosedXML.Excel; using System.Collections.Generic; namespace DataAnalysis.Core.Models; @@ -17,36 +18,80 @@ public sealed class AnalysisAggregateProfile { Queries = new List { + // SW-ProgID new AnalysisQuery { Title = "Severity", Dimensions = new [] { DimensionKind.Severity } }, + new AnalysisQuery { Title = "Source x ProgID", Dimensions = new [] { DimensionKind.Source, DimensionKind.ProgID }, Grouped = true }, new AnalysisQuery { Title = "Source", Filter = new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, Dimensions = new [] { DimensionKind.Source }, TopN =300 }, - new AnalysisQuery { Title = "Hour x Source", Dimensions = new [] { DimensionKind.Hour, DimensionKind.Source },Filter = new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, }, + new AnalysisQuery { Title = "Hour x Source", Dimensions = new [] { DimensionKind.Hour, DimensionKind.Source }, + Filter = new GroupFilterDefinition { Mode="And", Type = "group", Filters = [ + new ValueFilterDefinition { Field = "PS", Type = "value", Operator = FilterOperator.Eq, Value = "1" }, + new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, + ] } + }, new AnalysisQuery { Title = "Top-Events", Dimensions = new [] { DimensionKind.MessageNormalized }, TopN =100 }, - new AnalysisQuery { Title = "Top-Events2", Dimensions = new [] { DimensionKind.MessageNormalized }, TopN =30, Filter = new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, }, + new AnalysisQuery { Title = "Top-Errors", Dimensions = new [] { DimensionKind.MessageNormalized }, TopN =30, + Filter = new GroupFilterDefinition { Mode="And", Type = "group", Filters = [ + new ValueFilterDefinition { Field = "PS", Type = "value", Operator = FilterOperator.Eq, Value = "1" }, + new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, + ] } + }, new AnalysisQuery { Title = "Severity x Source", Dimensions = new [] { DimensionKind.Source, DimensionKind.Severity }, Columns = Enum.GetValues().Select(s => s.ToString()).ToArray() }, new AnalysisQuery { Title = "Events x Source (Top50)", Dimensions = new [] { DimensionKind.Source, DimensionKind.MessageNormalized }, TopN =300}, - new AnalysisQuery { Title = "Error Cluster", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 30, IsDBScan = true, DbEps = 2.0, DbMinPts = 3, Filter = new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, }, - new AnalysisQuery { Title = "Max-G Cluster (1.0)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 30, IsDBScan = true, DbEps = 1.0, DbMinPts = 3, Filter = new GroupFilterDefinition { Mode="And", Type = "group", + new AnalysisQuery { Title = "Events x ProgID", Dimensions = new [] { DimensionKind.ProgID, DimensionKind.MessageNormalized }, Grouped = true , + Filter = new ValueFilterDefinition { Field = "PS", Type = "value", Operator = FilterOperator.Eq, Value = "1" } }, + new AnalysisQuery { Title = "Error Cluster", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 30, IsDBScan = true, DbEps = 2.0, DbMinPts = 3, + Filter = new GroupFilterDefinition { Mode="And", Type = "group", Filters = [ + new ValueFilterDefinition { Field = "PS", Type = "value", Operator = FilterOperator.Eq, Value = "1" }, + new ValueFilterDefinition { Field = "Severity", Type = "Enum", Operator = FilterOperator.Le, Value = "Error" }, + ] } + }, + new AnalysisQuery { Title = "Max-G Cluster (1.0)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 30, IsDBScan = true, DbEps = 1.0, DbMinPts = 3, + Filter = new GroupFilterDefinition { Mode="And", Type = "group", Filters=[ new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.Eq, Value = "Max. G-Force" }, new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" } ] }, }, - new AnalysisQuery { Title = "Max-G Cluster (0.5)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 50, IsDBScan = true, DbEps = 0.5, DbMinPts = 3, Filter = + new AnalysisQuery { Title = "Max-G Cluster (0.5)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 50, IsDBScan = true, DbEps = 0.5, DbMinPts = 3, Filter = new GroupFilterDefinition { Mode="And", Type = "group", - Filters=[ + Filters=[ new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.Eq, Value = "Max. G-Force" }, new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" } ] }, }, + new AnalysisQuery { Title = "Max-G Cluster (0.5) > 1g", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 50, IsDBScan = true, DbEps = 0.5, DbMinPts = 3, Filter = + new GroupFilterDefinition { Mode="And", Type = "group", + Filters=[ + new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.Eq, Value = "Max. G-Force" }, + new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" }, + new GroupFilterDefinition { Mode="Or", Type = "group", + Filters=[ + new ValueFilterDefinition { Field = "U", Type = "value", Operator = FilterOperator.Ge, Value = "1" }, + new ValueFilterDefinition { Field = "U", Type = "value", Operator = FilterOperator.Le, Value = "-1" }] } + ] }, }, + new AnalysisQuery { Title = "SSCU Cluster (0.5)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 30, IsDBScan = true, DbEps = 0.5, DbMinPts = 3, Filter = new GroupFilterDefinition { Mode="And", Type = "group", Filters=[ new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.StartsWith, Value = "SSCU" }, new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" }, - ] + ] }, }, new AnalysisQuery { Title = "SSCU Cluster (0.25)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 50, IsDBScan = true, DbEps = 0.25, DbMinPts = 3, Filter = - new GroupFilterDefinition { Mode="And", Type = "group", Filters=[ + new GroupFilterDefinition { Mode="And", Type = "group", Filters=[ new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.StartsWith, Value = "SSCU" }, new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" }, ] }, + }, + new AnalysisQuery { Title = "Scanner Front Err (0.25)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 30, IsDBScan = true, DbEps = 0.25, DbMinPts = 3, Filter = + new GroupFilterDefinition { Mode="And", Type = "group", Filters=[ + new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.StartsWith, Value = "Front Scanner" }, + new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" }, + ] + }, }, + new AnalysisQuery { Title = "Scanner Rear Err (0.25)", Dimensions = new [] { DimensionKind.X, DimensionKind.Y }, TopN = 50, IsDBScan = true, DbEps = 0.25, DbMinPts = 3, Filter = + new GroupFilterDefinition { Mode="And", Type = "group", Filters=[ + new ValueFilterDefinition { Field = "Message", Type = "String", Operator = FilterOperator.StartsWith, Value = "Rear Scanner" }, + new ValueFilterDefinition { Field = "X", Type = "value", Operator = FilterOperator.Gt, Value = "10" }, + ] }, } + } }; } diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisQuery.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisQuery.cs index 9d62fbf87..8ec422351 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisQuery.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/AnalysisQuery.cs @@ -4,28 +4,31 @@ namespace DataAnalysis.Core.Models; public sealed class AnalysisQuery { - // Anzeigename fr den Export - public required string Title { get; init; } + // Anzeigename fr den Export + public required string Title { get; init; } - // Liste der Dimensionen:1D => Serie,2D => Kreuztabelle - public required IReadOnlyList Dimensions { get; init; } + // Liste der Dimensionen:1D => Serie,2D => Kreuztabelle + public required IReadOnlyList Dimensions { get; init; } - // Optional: feste Spalten fr2D (z. B. Reihenfolge der Severity-Spalten) - public IReadOnlyList? Columns { get; init; } + // Optional: feste Spalten fr2D (z. B. Reihenfolge der Severity-Spalten) + public IReadOnlyList? Columns { get; init; } - // Optional: TopN-Begrenzung fr1D-Serien - public int? TopN { get; init; } + // Optional: TopN-Begrenzung fr1D-Serien + public int? TopN { get; init; } - // Optional: TopN pro Zeile fr2D-Kreuztabellen - public int? RowTopN { get; init; } + // Optional: TopN pro Zeile fr2D-Kreuztabellen + public int? RowTopN { get; init; } - // Optional: DBSCAN-Clustering der Punkte (bei2D X/Y) - public bool IsDBScan { get; init; } = false; - // Optional: Radius (Epsilon) fr DBSCAN (gleiche Einheit wie X/Y) - public double? DbEps { get; init; } - // Optional: Mindestanzahl Punkte pro Cluster - public int? DbMinPts { get; init; } + // Optional: DBSCAN-Clustering der Punkte (bei2D X/Y) + public bool IsDBScan { get; init; } = false; + // Optional: Radius (Epsilon) fr DBSCAN (gleiche Einheit wie X/Y) + public double? DbEps { get; init; } + // Optional: Mindestanzahl Punkte pro Cluster + public int? DbMinPts { get; init; } - // Optional: Auswertungsspezifischer Filter (DTO, serialisierbar) - public FilterDefinition? Filter { get; init; } + // Optional: Auswertungsspezifischer Filter (DTO, serialisierbar) + public FilterDefinition? Filter { get; init; } + + // Optional: Gruppierte Ausgabe (fr2D-Tabellen) + public bool Grouped { get; init; } } diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/DimensionKind.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/DimensionKind.cs index 30f2a0758..3df695e29 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/DimensionKind.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/DimensionKind.cs @@ -2,10 +2,12 @@ namespace DataAnalysis.Core.Models; public enum DimensionKind { - Severity, - Source, - Hour, // Hour bucket of timestamp - MessageNormalized, - X, - Y + Severity, + Source, + Hour, // Hour bucket of timestamp + MessageNormalized, + ProgID, // Software version extracted from message + X, + Y, + MsgID, } diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterCompiler.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterCompiler.cs index cec3effcb..90588ce33 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterCompiler.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterCompiler.cs @@ -117,28 +117,28 @@ public static class FilterCompiler break; } - return e => + return e => { if (!TryGetFieldText(e, v.Field, out var text, out var typed)) - return false; + return false ; switch (v.DataType) { case FilterDataType.Number: if (!TryAsDouble(text, typed, culture, out var d)) return false; - return CompareNumbers(d, v.Operator, parsedFrom as double?, parsedTo as double?); + return v.Negate ^ CompareNumbers(d, v.Operator, parsedFrom as double?, parsedTo as double?); case FilterDataType.DateTime: if (!TryAsDateTimeOffset(text, typed, culture, formats, out var dt)) return false; - return CompareDateTimes(dt, v.Operator, parsedFrom as DateTimeOffset?, parsedTo as DateTimeOffset?); + return v.Negate ^ CompareDateTimes(dt, v.Operator, parsedFrom as DateTimeOffset?, parsedTo as DateTimeOffset?); case FilterDataType.Enum: if (!TryAsEnum(text, typed, parsedFrom, out var eobj)) return false; - return CompareEnums(eobj, v.Operator, parsedFrom, parsedTo); + return v.Negate ^ CompareEnums(eobj!, v.Operator, parsedFrom, parsedTo); case FilterDataType.String: default: - return CompareStrings(text ?? string.Empty, v.Operator, v.Value, v.ValueTo, cmp); + return v.Negate ^ CompareStrings(text ?? string.Empty, v.Operator, v.Value, v.ValueTo, cmp); } }; } diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterDefinitions.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterDefinitions.cs index 8379558dd..c4ebe5208 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterDefinitions.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/Filters/FilterDefinitions.cs @@ -10,6 +10,9 @@ namespace DataAnalysis.Core.Models; public abstract class FilterDefinition { [JsonPropertyName("type")] public string Type { get; init; } + + public bool Negate { get; init; } = false; + } public sealed class ValueFilterDefinition : FilterDefinition @@ -34,5 +37,4 @@ public sealed class GroupFilterDefinition : FilterDefinition // "and" oder "or" public required string Mode { get; init; } public required IReadOnlyList Filters { get; init; } - public bool Negate { get; init; } = false; } diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/QueryBuilder.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/QueryBuilder.cs index 488353515..5d304497f 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/QueryBuilder.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.Core/Models/QueryBuilder.cs @@ -56,7 +56,7 @@ public void Observe(SyslogEntry e) var col = GetKey(e, _q.Dimensions[1]); if (row is null || col is null) return; if (!_matrix.TryGetValue(row, out var d)) { d = new Dictionary(StringComparer.OrdinalIgnoreCase); _matrix[row] = d; } - d[col] = d.TryGetValue(col, out var c) ? c +1 :1; + d[col] = d.TryGetValue(col, out var c) && !_q.Grouped ? c +1 :1; } } @@ -152,6 +152,8 @@ public AggregationResult Build() DimensionKind.Source => string.IsNullOrWhiteSpace(e.Source) ? "(unbekannt)" : e.Source.Trim(), DimensionKind.Hour => e.Timestamp is DateTimeOffset ts ? new DateTimeOffset(ts.Year, ts.Month, ts.Day, ts.Hour,0,0, ts.Offset).LocalDateTime.ToString("yyyy-MM-dd HH:00") : null, DimensionKind.MessageNormalized => AnalysisModel.NormalizeMessage(e.Message), + DimensionKind.ProgID => TryGetAttributeAsString(e, "eventprogid", out var s) ? s : "", + DimensionKind.MsgID => TryGetAttributeAsString(e, "eventmsgid", out var s) ? s : null, DimensionKind.X => TryGetAttributeAsDouble(e, "X", out var x) ? x.ToString(CultureInfo.InvariantCulture) : null, DimensionKind.Y => TryGetAttributeAsDouble(e, "Y", out var y) ? y.ToString(CultureInfo.InvariantCulture) : null, _ => null @@ -176,5 +178,17 @@ private static bool TryGetAttributeAsDouble(SyslogEntry e, string key, out doubl } return false; } + + private static bool TryGetAttributeAsString(SyslogEntry e, string key, out string value) + { + value = ""; + if (e.Attributes.TryGetValue(key, out var s)) + { + value = s; + return true; + } + return false; + } + } From 7d36fc82195e3bfe3cdaa7fd1e06570d837518de Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 10:59:17 +0100 Subject: [PATCH 029/962] DataAnalysis --- .../DataAnalysis.WPF/ViewModels/MatrixAggregationViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CSharpBible/Data/DataAnalysis/DataAnalysis.WPF/ViewModels/MatrixAggregationViewModel.cs b/CSharpBible/Data/DataAnalysis/DataAnalysis.WPF/ViewModels/MatrixAggregationViewModel.cs index d5caeeaed..77a45ad0c 100644 --- a/CSharpBible/Data/DataAnalysis/DataAnalysis.WPF/ViewModels/MatrixAggregationViewModel.cs +++ b/CSharpBible/Data/DataAnalysis/DataAnalysis.WPF/ViewModels/MatrixAggregationViewModel.cs @@ -15,7 +15,7 @@ public MatrixAggregationViewModel(AggregationResult agg) var dt = new DataTable(); dt.Columns.Add("Key", typeof(string)); var columns = agg.Columns ?? Array.Empty(); - foreach (var col in columns) dt.Columns.Add(col.Replace(".", "˳"), typeof(int)); + foreach (var col in columns) dt.Columns.Add(col.Replace(".", "˳").Replace("/", "|"), typeof(int)); if (agg.Matrix is not null) { var rows = agg.Matrix.Keys.ToList(); From cd108b7bebc5695bfd6f197ae02370d94ea7d9d2 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 10:59:17 +0100 Subject: [PATCH 030/962] Common --- .../BaseLibTests/Helper/StringUtilsTests.cs | 34 +++ .../MVVM_06_Converters_4.csproj | 2 +- .../MVVM_06_Converters_4_net.csproj | 2 +- .../MVVM_06_Converters_4/MainWindow.xaml | 4 +- .../Views/Converter/WindowPortToGridLines.cs | 40 +-- ...M_20_Sysdialogs_net_xkx5qz05_wpftmp.csproj | 280 ------------------ ...M_31a_CTValidation2_cmyulbiy_wpftmp.csproj | 99 ------- ...M_31a_CTValidation2_oe2tnzz1_wpftmp.csproj | 99 ------- .../WpfApp1/Views/Frm_W2kMain.xaml | 55 ++-- 9 files changed, 90 insertions(+), 525 deletions(-) delete mode 100644 CSharpBible/MVVM_Tutorial/MVVM_20_Sysdialogs/MVVM_20_Sysdialogs_net_xkx5qz05_wpftmp.csproj delete mode 100644 CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_cmyulbiy_wpftmp.csproj delete mode 100644 CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_oe2tnzz1_wpftmp.csproj diff --git a/CSharpBible/Libraries/BaseLibTests/Helper/StringUtilsTests.cs b/CSharpBible/Libraries/BaseLibTests/Helper/StringUtilsTests.cs index ba2f59670..fd7a65ee3 100644 --- a/CSharpBible/Libraries/BaseLibTests/Helper/StringUtilsTests.cs +++ b/CSharpBible/Libraries/BaseLibTests/Helper/StringUtilsTests.cs @@ -326,4 +326,38 @@ public void IsValidIdentifyerTest(string sAct, bool xExp) { Assert.AreEqual(xExp,sAct.IsValidIdentifyer()); } + + [TestMethod()] + [DataRow("This is a test",14, "This is a test")] + [DataRow("This is a test",0, "")] + [DataRow("This is a test",-1, "This is a tes")] + [DataRow("This is a test",1, "T")] + [DataRow("This is a test",20, "This is a test")] + public void LeftTest(string sAct,int iAct, string sExp) + { + Assert.AreEqual(sExp, sAct.Left(iAct)); + } + + [TestMethod()] + [DataRow("This is a test",14, "This is a test")] + [DataRow("This is a test",0, "")] + [DataRow("This is a test",-1, "his is a test")] + [DataRow("This is a test",1, "t")] + [DataRow("This is a test",20, "This is a test")] + public void RightTest(string sAct,int iAct, string sExp) + { + Assert.AreEqual(sExp, sAct.Right(iAct)); + } + + [TestMethod()] + [DataRow(null, "")] + [DataRow("", "")] + [DataRow(true, "")] + [DataRow(false, "False")] + [DataRow(1234, "1234")] + + public void AsStringTest(object? oAct,string sExp) + { + Assert.AreEqual(sExp, oAct.AsString()); + } } diff --git a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4.csproj b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4.csproj index b1b65935c..9edd648c7 100644 --- a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4.csproj +++ b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4.csproj @@ -8,7 +8,7 @@ - + diff --git a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4_net.csproj b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4_net.csproj index 631f5e6bc..1c8179f80 100644 --- a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4_net.csproj +++ b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MVVM_06_Converters_4_net.csproj @@ -20,7 +20,7 @@ - + diff --git a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MainWindow.xaml b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MainWindow.xaml index 853ac37ea..0907f8d37 100644 --- a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MainWindow.xaml +++ b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/MainWindow.xaml @@ -13,11 +13,11 @@ - + - + diff --git a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/Views/Converter/WindowPortToGridLines.cs b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/Views/Converter/WindowPortToGridLines.cs index b0457bcf7..1693e469a 100644 --- a/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/Views/Converter/WindowPortToGridLines.cs +++ b/CSharpBible/MVVM_Tutorial/MVVM_06_Converters_4/Views/Converter/WindowPortToGridLines.cs @@ -179,31 +179,37 @@ public object Convert(object value, Type targetType, object parameter, CultureIn case ArrowList al: result = new ObservableCollection(); foreach (var sh in al) - { - var P1 = real2VisP(sh.Start, actPort); - var P2 = real2VisP(sh.End, actPort); - foreach (var el in CreateArrow(al.Pen?.Brush, al.Pen?.Thickness ?? 1, P1, P2)) - result.Add(el); - } + try + { + var P1 = real2VisP(sh.Start, actPort); + var P2 = real2VisP(sh.End, actPort); + foreach (var el in CreateArrow(al.Pen?.Brush, al.Pen?.Thickness ?? 1, P1, P2)) + result.Add(el); + } + catch { } return result; case CircleList cl: result = new ObservableCollection(); foreach (var sh in cl) - { - var P1 = real2VisP(sh.Center, actPort); - var r = Real2VisP(PointF.Add(sh.Center,new SizeF((float)sh.Radius,0)), actPort).X-P1.X; - result.Add(CreateCircle(cl.Pen?.Brush, cl.Pen?.Thickness ?? 1, P1, (float)r)); - } + try + { + var P1 = real2VisP(sh.Center, actPort); + var r = Real2VisP(PointF.Add(sh.Center, new SizeF((float)sh.Radius, 0)), actPort).X - P1.X; + result.Add(CreateCircle(cl.Pen?.Brush, cl.Pen?.Thickness ?? 1, P1, (float)r)); + } + catch { } return result; case PolynomeList pl: result = new ObservableCollection(); foreach (var sh in pl) - { - var p = new PointCollection(); - foreach (var pnt in sh.Points) - p.Add(real2VisP(pnt, actPort)); - result.Add(CreatePolynome(pl.Pen?.Brush, pl.Pen?.Thickness ?? 1, p)); - } + try + { + var p = new PointCollection(); + foreach (var pnt in sh.Points) + p.Add(real2VisP(pnt, actPort)); + result.Add(CreatePolynome(pl.Pen?.Brush, pl.Pen?.Thickness ?? 1, p)); + } + catch { } return result; default: return new ObservableCollection(); } diff --git a/CSharpBible/MVVM_Tutorial/MVVM_20_Sysdialogs/MVVM_20_Sysdialogs_net_xkx5qz05_wpftmp.csproj b/CSharpBible/MVVM_Tutorial/MVVM_20_Sysdialogs/MVVM_20_Sysdialogs_net_xkx5qz05_wpftmp.csproj deleted file mode 100644 index 2bfdba9dd..000000000 --- a/CSharpBible/MVVM_Tutorial/MVVM_20_Sysdialogs/MVVM_20_Sysdialogs_net_xkx5qz05_wpftmp.csproj +++ /dev/null @@ -1,280 +0,0 @@ - - - MVVM_20_Sysdialogs_net - ..\..\..\obj.net\MVVM_20_Sysdialogs_net\Debug\ - ..\..\..\obj.net\MVVM_20_Sysdialogs_net\ - C:\Projekte\CSharp\CSharpBible\MVVM_Tutorial\MVVM_20_Sysdialogs\..\..\..\obj.net\MVVM_20_Sysdialogs_net\ - <_TargetAssemblyProjectName>MVVM_20_Sysdialogs_net - MVVM_20_Sysdialogs - - - - WinExe - net8.0-windows - enable - true - - - - - $(TargetFrameworks);net9.0-windows - - - $(TargetFrameworks);net10.0-windows - - - - - - - - - - - - True - True - Resources.resx - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_cmyulbiy_wpftmp.csproj b/CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_cmyulbiy_wpftmp.csproj deleted file mode 100644 index 79456897c..000000000 --- a/CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_cmyulbiy_wpftmp.csproj +++ /dev/null @@ -1,99 +0,0 @@ - - - MVVM_31a_CTValidation2 - ..\..\..\obj\MVVM_31a_CTValidation2\Debug\ - ..\..\..\obj\MVVM_31a_CTValidation2\ - C:\Projekte\CSharp\CSharpBible\MVVM_Tutorial\MVVM_31a_CTValidation2\..\..\..\obj\MVVM_31a_CTValidation2\ - <_TargetAssemblyProjectName>MVVM_31a_CTValidation2 - MVVM_31a_CTValidation2 - - - - WinExe - net462;net472;net48;net481 - true - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - True - True - Settings.settings - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - - - - - PublicSettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_oe2tnzz1_wpftmp.csproj b/CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_oe2tnzz1_wpftmp.csproj deleted file mode 100644 index dc6df6a37..000000000 --- a/CSharpBible/MVVM_Tutorial/MVVM_31a_CTValidation2/MVVM_31a_CTValidation2_oe2tnzz1_wpftmp.csproj +++ /dev/null @@ -1,99 +0,0 @@ - - - MVVM_31a_CTValidation2 - ..\..\..\obj\MVVM_31a_CTValidation2\Debug\ - ..\..\..\obj\MVVM_31a_CTValidation2\ - C:\Projekte\CSharp\CSharpBible\MVVM_Tutorial\MVVM_31a_CTValidation2\..\..\..\obj\MVVM_31a_CTValidation2\ - <_TargetAssemblyProjectName>MVVM_31a_CTValidation2 - MVVM_31a_CTValidation2 - - - - WinExe - net462;net472;net48;net481 - true - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - True - True - Settings.settings - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - - - - - PublicSettingsSingleFileGenerator - Settings.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml b/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml index b1a09f27e..719aa0467 100644 --- a/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml +++ b/CSharpBible/MVVM_Tutorial/WpfApp1/Views/Frm_W2kMain.xaml @@ -2,57 +2,60 @@ - - - - - + + + + + - + - + - - - - + + + + - - + + - - + + + + - public partial class App : Application { - } + public new static App Current => (App)Application.Current; + + public IServiceProvider Services { get; } + + public App() + { + Services = ConfigureServices(); + } + + private static IServiceProvider ConfigureServices() + { + var services = new ServiceCollection(); + // Services + services.AddSingleton(); + services.AddSingleton(); + + // ViewModels + services.AddTransient(); + + return services.BuildServiceProvider(); + } + } } diff --git a/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs index 18c334772..13aed06ea 100644 --- a/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs +++ b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml.cs @@ -1,13 +1,7 @@ -using System.Text; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using System.Windows; +using Microsoft.Extensions.DependencyInjection; +using Trnsp.Show.Pas; +using Trnsp.Show.Pas.ViewModels; namespace Transp.Show.Pas { @@ -19,6 +13,7 @@ public partial class MainWindow : Window public MainWindow() { InitializeComponent(); + DataContext = App.Current.Services.GetService(); } } } \ No newline at end of file diff --git a/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs b/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs index 0804e5c18..13be2b699 100644 --- a/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs +++ b/Transpiler_pp/Trnsp.Show.Pas/ViewModels/MainViewModel.cs @@ -12,6 +12,9 @@ public partial class MainViewModel : ObservableObject public ObservableCollection RootNodes { get; } = new(); + [ObservableProperty] + private CodeBlockNode? _selectedNode; + public IRelayCommand LoadFileCommand { get; } public MainViewModel(IFileService fileService, IPascalParserService parserService) From a23fd4890905ce73a951997d318bfdb137abc0ba Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 18:45:19 +0100 Subject: [PATCH 034/962] Trnsp.Show.Pas.Tests --- .../ViewModels/MainViewModelTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs b/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs index cf8e6c6ab..6eb340ee9 100644 --- a/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs +++ b/Transpiler_pp/Trnsp.Show.Pas.Tests/ViewModels/MainViewModelTests.cs @@ -55,5 +55,27 @@ public void LoadFileCommand_ExecutesCorrectly() Assert.AreEqual(1, _testViewModel.RootNodes.Count); #pragma warning restore MSTEST0037 } + + [TestMethod] + public void SelectedNode_PropertyChange_RaisesNotification() + { + // Arrange + var node = new CodeBlockNode(Substitute.For()); + bool propertyChangedRaised = false; + _testViewModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(MainViewModel.SelectedNode)) + { + propertyChangedRaised = true; + } + }; + + // Act + _testViewModel.SelectedNode = node; + + // Assert + Assert.IsTrue(propertyChangedRaised); + Assert.AreEqual(node, _testViewModel.SelectedNode); + } } } From 1d0ff41557a0d877b46ebee9148b201345f6d120 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 21:15:39 +0100 Subject: [PATCH 035/962] TranspilerLib --- .../TranspilerLib/Docs/TranspilerLib.xml | 34 +++++++++++++++++++ .../TranspilerLib/Interfaces/IInterpreter.cs | 4 ++- .../TranspilerLib/Interfaces/IOutput.cs | 10 +++++- .../TranspilerLib/Models/CPascalScanner.cs | 4 +++ .../Models/Interpreter/InterpreterBase.cs | 4 ++- .../TranspilerLib/Models/TStrings.cs | 4 ++- 6 files changed, 56 insertions(+), 4 deletions(-) diff --git a/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml b/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml index 8eda1e5ff..cae9d2f94 100644 --- a/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml +++ b/Transpiler_pp/TranspilerLib/Docs/TranspilerLib.xml @@ -473,6 +473,24 @@ Das Parent-Objekt vom Typ oder null, wenn kein Parent vorhanden ist. + + + Interface for interpreters that can execute code blocks. + + + + + Interface for output handlers that process reader data and produce output. + + + + + Outputs the specified reader. + + The reader. + The write. + The debug. + Definiert einen vorwärtsgerichteten, zustandsbehafteten Reader über eine @@ -592,6 +610,12 @@ Represents a warning-number specific state used while scanning files. + + + Scanner for Pascal source files. + + + Defines the severity or kind of diagnostic messages that can be emitted by components in this library. @@ -1070,6 +1094,11 @@ Program Counter – zeigt auf den aktuell auszuführenden Block. + + + Base class for interpreters that can execute code blocks. + + Stellt eine Liste von Elementen bereit, die jeweils einen Verweis auf ihr übergeordnetes Objekt (Parent) besitzen. @@ -1658,6 +1687,11 @@ Initialisiert eine neue Instanz mit Standardwerten (alle numerischen Felder = 0, Flag = false). + + + Represents a collection of strings. + + Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. diff --git a/Transpiler_pp/TranspilerLib/Interfaces/IInterpreter.cs b/Transpiler_pp/TranspilerLib/Interfaces/IInterpreter.cs index d4a44ff96..5b338a3ab 100644 --- a/Transpiler_pp/TranspilerLib/Interfaces/IInterpreter.cs +++ b/Transpiler_pp/TranspilerLib/Interfaces/IInterpreter.cs @@ -1,5 +1,7 @@ namespace TranspilerLib.Interfaces; - +/// +/// Interface for interpreters that can execute code blocks. +/// public interface IInterpreter { diff --git a/Transpiler_pp/TranspilerLib/Interfaces/IOutput.cs b/Transpiler_pp/TranspilerLib/Interfaces/IOutput.cs index a3168e25d..11876a15a 100644 --- a/Transpiler_pp/TranspilerLib/Interfaces/IOutput.cs +++ b/Transpiler_pp/TranspilerLib/Interfaces/IOutput.cs @@ -2,8 +2,16 @@ using TranspilerLib.Interfaces; namespace TranspilerLib.Interfaces; - +/// +/// Interface for output handlers that process reader data and produce output. +/// public interface IOutput { + /// + /// Outputs the specified reader. + /// + /// The reader. + /// The write. + /// The debug. void Output(IReader reader, Action write, Action debug); } \ No newline at end of file diff --git a/Transpiler_pp/TranspilerLib/Models/CPascalScanner.cs b/Transpiler_pp/TranspilerLib/Models/CPascalScanner.cs index 6264143f7..db86e7204 100644 --- a/Transpiler_pp/TranspilerLib/Models/CPascalScanner.cs +++ b/Transpiler_pp/TranspilerLib/Models/CPascalScanner.cs @@ -6,6 +6,10 @@ namespace TranspilerLib.Models; +/// +/// Scanner for Pascal source files. +/// +/// public class CPascalScanner:CFileScanner { diff --git a/Transpiler_pp/TranspilerLib/Models/Interpreter/InterpreterBase.cs b/Transpiler_pp/TranspilerLib/Models/Interpreter/InterpreterBase.cs index c32830ed9..114b27e15 100644 --- a/Transpiler_pp/TranspilerLib/Models/Interpreter/InterpreterBase.cs +++ b/Transpiler_pp/TranspilerLib/Models/Interpreter/InterpreterBase.cs @@ -1,5 +1,7 @@ namespace TranspilerLib.Models.Interpreter; - +/// +/// Base class for interpreters that can execute code blocks. +/// public class InterpreterBase { } \ No newline at end of file diff --git a/Transpiler_pp/TranspilerLib/Models/TStrings.cs b/Transpiler_pp/TranspilerLib/Models/TStrings.cs index e7afb5b2f..833fe3396 100644 --- a/Transpiler_pp/TranspilerLib/Models/TStrings.cs +++ b/Transpiler_pp/TranspilerLib/Models/TStrings.cs @@ -1,5 +1,7 @@ namespace TranspilerLib.Models; - +/// +/// Represents a collection of strings. +/// public class TStrings { } \ No newline at end of file From c454ee1c54c9720ebc389136df2dfe5e2982a20d Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 21:15:40 +0100 Subject: [PATCH 036/962] TranspilerLib.IEC --- .../TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs | 6 +++--- .../TranspilerLib.IEC/Models/Scanner/IECTokenHandler.cs | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs index ddbd177c5..587899a57 100644 --- a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs +++ b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECCodeBuilder.cs @@ -211,7 +211,7 @@ private void BuildInstruction(TokenData tokenData, ICodeBuilderData data) td.Level = data.actualBlock.Level - 1; td.type = CodeBlockType.Block; base.OnToken(td, data); - while (data.actualBlock.Level > tokenData.Level) + while (data.actualBlock?.Level > tokenData.Level && data.actualBlock.Parent!= null) data.actualBlock = data.actualBlock.Parent; } break; @@ -245,10 +245,10 @@ private void BuildInstruction(TokenData tokenData, ICodeBuilderData data) else if (block.Type == CodeBlockType.Operation && new[] { "*", "/" }.Contains(block.Code) - && new[] { "+", "-" }.Contains(block.Parent.Code) + && new[] { "+", "-" }.Contains(block.Parent?.Code) && new[] { "+", "-" }.Contains(td.Code)) { - data.actualBlock.Parent = block.Parent.Parent; + data.actualBlock.Parent = block.Parent!.Parent; block.Parent.Parent = data.actualBlock; } else diff --git a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECTokenHandler.cs b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECTokenHandler.cs index c41c2d245..255e4b001 100644 --- a/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECTokenHandler.cs +++ b/Transpiler_pp/TranspilerLib.IEC/Models/Scanner/IECTokenHandler.cs @@ -141,8 +141,7 @@ private static void DefaultLabel(TokenDelegate? token, string OriginalCode, Toke private static void DefaultOperator(TokenDelegate? token, string originalCode, TokenizeData data) { char cNxt = GetNxtChar(data.Pos, originalCode); - if (CharSets.operatorSet.Contains(cNxt) && cNxt != '/') ; - else + if (!CharSets.operatorSet.Contains(cNxt) || cNxt == '/') { EmitToken(token, data, CodeBlockType.Operation, originalCode, 1); data.Pos2 = data.Pos + 1; From 9701c740ed7d345f15e84d7dddca615d4e54b8da Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 21:15:40 +0100 Subject: [PATCH 037/962] TranspilerLib.Pascal.Tests --- .../TranspilerLib.Pascal.Tests/Models/LfmTokenizerTests.cs | 2 +- .../TranspilerLib.Pascal.Tests/Models/Scanner/PasCodeTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmTokenizerTests.cs b/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmTokenizerTests.cs index 10340e823..bcf3f3108 100644 --- a/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmTokenizerTests.cs +++ b/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/LfmTokenizerTests.cs @@ -65,7 +65,7 @@ public void Tokenize_tests(string sTestName, string dir) { Assert.AreEqual(sExpectedTokens, sActualTokens, $"Testcase: {sTestName}"); } - catch (AssertFailedException ex) + catch (AssertFailedException) { if (File.Exists(Path.Combine(".", "Resources", sTestName, sTestName + "_actual.json"))) File.Delete(Path.Combine(".", "Resources", sTestName, sTestName + "_actual.json")); diff --git a/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/Scanner/PasCodeTests.cs b/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/Scanner/PasCodeTests.cs index 525b57b25..d08a7951a 100644 --- a/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/Scanner/PasCodeTests.cs +++ b/Transpiler_pp/TranspilerLib.Pascal.Tests/Models/Scanner/PasCodeTests.cs @@ -244,7 +244,7 @@ public void Tokenize_Scenarios_Dyn(string name, string source, string? expectedJ } } } - catch (AssertFailedException ex) + catch (AssertFailedException) { if (File.Exists(Path.Combine(".","Resources", name, name + "_actual.json"))) File.Delete(Path.Combine(".","Resources", name, name + "_actual.json")); @@ -289,7 +289,7 @@ public void Parse_CodeBlockList(string name, string tokens, string codeBlocks) $"CodeBlock-Code stimmt nicht an Index {i}. Erwartet: '{expectedBlock.Code}', Ist: '{actualBlock.Code}' fr Testcase: '{name}'."); } } - catch (AssertFailedException ex) + catch (AssertFailedException) { if (File.Exists(Path.Combine(".","Resources", name, name + "_actual_codeblocks.json"))) File.Delete(Path.Combine(".","Resources", name, name + "_actual_codeblocks.json")); From 3faafc2fe22a0696708bfe41e68cecf34663a14a Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 21:15:40 +0100 Subject: [PATCH 038/962] TranspilerLibTests --- .../Models/Interpreter/IECInterpreterTests.cs | 6 +++--- .../TranspilerLibTests/Models/ParentedItemsListTests.cs | 4 ++-- .../TranspilerLibTests/Models/Scanner/CCodeBlockTests.cs | 2 +- .../TranspilerLibTests/Models/Scanner/CSCodeTests.cs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Transpiler_pp/TranspilerLibTests/Models/Interpreter/IECInterpreterTests.cs b/Transpiler_pp/TranspilerLibTests/Models/Interpreter/IECInterpreterTests.cs index 7e71c2261..d392417b6 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/Interpreter/IECInterpreterTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/Interpreter/IECInterpreterTests.cs @@ -156,8 +156,8 @@ public void SystemfunctionsTest(Enum eAct, object value,bool xExp,object exp) Assert.AreEqual(xExp, IECInterpreter.systemfunctions.TryGetValue(eAct,out var methods)); if (value is object[] values) { - var m = methods.FirstOrDefault(m => (values.Count() == m?.GetParameters().Count()) && m.GetParameters().First().ParameterType.IsAssignableFrom(values[0].GetType())); - if (m.ReturnType.IsAssignableTo(typeof(double))) + var m = methods!.FirstOrDefault(m => (values.Count() == m?.GetParameters().Count()) && m.GetParameters().First().ParameterType.IsAssignableFrom(values[0].GetType())); + if (m!.ReturnType.IsAssignableTo(typeof(double))) Assert.AreEqual((double)exp, (double)(m?.Invoke(null, values) ?? throw new InvalidOperationException("Method returned null")), 1e-7d); else if (m.ReturnType.IsAssignableTo(typeof((int, int))) && exp is object[] aexp) Assert.AreEqual((aexp[0], aexp[1]), ((int,int))(m?.Invoke(null, values) ?? throw new InvalidOperationException("Method returned null"))); @@ -167,7 +167,7 @@ public void SystemfunctionsTest(Enum eAct, object value,bool xExp,object exp) else { // if (value is int i && exp is int ) value = (double)i; - var m = methods.First(m => m?.GetParameters().First().ParameterType.IsAssignableFrom(value.GetType())??false); + var m = methods!.First(m => m?.GetParameters().First().ParameterType.IsAssignableFrom(value.GetType())??false); if (m.ReturnType.IsAssignableTo(typeof(double))) Assert.AreEqual((double)exp, (double)(m?.Invoke(null, [value]) ?? throw new InvalidOperationException("Method returned null")), 1e-7); else diff --git a/Transpiler_pp/TranspilerLibTests/Models/ParentedItemsListTests.cs b/Transpiler_pp/TranspilerLibTests/Models/ParentedItemsListTests.cs index 9eede36e6..27b1863a4 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/ParentedItemsListTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/ParentedItemsListTests.cs @@ -24,7 +24,7 @@ public TestClass(int v) public ITestItf this[int idx] => _list[idx]; - public ITestItf Parent { get; set; } + public ITestItf? Parent { get; set; } public int iData { get; set; } private ParentedItemsList _list; @@ -45,7 +45,7 @@ public void Add(ITestItf item) [TestClass()] public class ParentedItemsListTests : ITestItf { - public ITestItf Parent { get; set; } + public ITestItf? Parent { get; set; } public int iData { get; set; } public ITestItf this[int idx] => testClass[idx]; diff --git a/Transpiler_pp/TranspilerLibTests/Models/Scanner/CCodeBlockTests.cs b/Transpiler_pp/TranspilerLibTests/Models/Scanner/CCodeBlockTests.cs index 8071d307e..92b01e5d1 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/Scanner/CCodeBlockTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/Scanner/CCodeBlockTests.cs @@ -127,7 +127,7 @@ public void GetItemByIndexIndexTest() var act = _testClass.Parse(TestCSDataClass.TestDataList1() as List); var act2 = act.SubBlocks[7]; ((CodeBlock)act2).DestinationIndex = new() { 1 }; - Assert.IsTrue(act2.Destination.TryGetTarget(out var act3)); + Assert.IsTrue(act2!.Destination!.TryGetTarget(out var act3)); Assert.AreEqual(act, act3); } diff --git a/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSCodeTests.cs b/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSCodeTests.cs index 36960b9cd..ded0af163 100644 --- a/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSCodeTests.cs +++ b/Transpiler_pp/TranspilerLibTests/Models/Scanner/CSCodeTests.cs @@ -266,7 +266,7 @@ public void Tokenize2Test(string _, string[] data, List expList) public void ParseEnumTest(string _, List actList, string[] data) { var act = _testClass.Parse(actList); - AssertAreEqual(data[0], act?.ToString().Replace("\"+", "\" +")); + AssertAreEqual(data[0], act?.ToString()?.Replace("\"+", "\" +")); } From a3a947509ff85dcad877c387b467f66d07d30523 Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 21:15:41 +0100 Subject: [PATCH 039/962] Trnsp.Show.Lfm.Tests --- .../Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs b/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs index e413f9556..18f977fbf 100644 --- a/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs +++ b/Transpiler_pp/Trnsp.Show.Lfm.Tests/Services/LfmObjectResolverTests.cs @@ -46,7 +46,7 @@ public void ResolveOrDefer_ResolvesAlreadyRegisteredObject() } }); - Assert.AreSame(action, comp.LinkedAction.TryGetTarget(out var t)?t:t); + Assert.AreSame(action, comp.LinkedAction!.TryGetTarget(out var t)?t:t); Assert.AreEqual("TestCaption", comp.EffectiveCaption); Assert.AreEqual("TestHint", comp.EffectiveHint); } @@ -73,7 +73,7 @@ public void ResolveOrDefer_ResolvesDeferredObject() var action = new TestAction(); resolver.RegisterObject("TestAction", action); - Assert.AreSame(action, comp.LinkedAction.TryGetTarget(out var t) ? t : t); + Assert.AreSame(action, comp.LinkedAction!.TryGetTarget(out var t) ? t : t); Assert.AreEqual("TestCaption", comp.EffectiveCaption); Assert.AreEqual("TestHint", comp.EffectiveHint); } From 32b4c4a5347482930ab302d751656ad84af09e2c Mon Sep 17 00:00:00 2001 From: Joe Care Date: Sun, 14 Dec 2025 21:15:42 +0100 Subject: [PATCH 040/962] Trnsp.Show.Pas --- Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml | 66 ++++++++++++++++++- .../Trnsp.Show.Pas/MainWindow.xaml.cs | 8 +++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml index 9c34f3e8a..98fc7bd33 100644 --- a/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml +++ b/Transpiler_pp/Trnsp.Show.Pas/MainWindow.xaml @@ -4,9 +4,73 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Transp.Show.Pas" + xmlns:vm="clr-namespace:Trnsp.Show.Pas.ViewModels" mc:Ignorable="d" - Title="MainWindow" Height="450" Width="800"> + Title="Pascal Code Visualizer" Height="450" Width="800" + d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}"> + + + + + + + + + + + +