From 70c567422d2ecc0be17ffe353a66d87da905c1dd Mon Sep 17 00:00:00 2001 From: Dominic Burger Date: Thu, 21 Nov 2024 15:48:49 +0100 Subject: [PATCH] Generate nested tables for struct attributes --- .../MarkdownDocumentationVisitorTest.cs | 62 +++++++++++- .../Visitors/MarkdownDocumentationVisitor.cs | 95 +++++++++++++++---- 2 files changed, 135 insertions(+), 22 deletions(-) diff --git a/language-server/src/Geowerkstatt.Interlis.LanguageServer.Test/Visitors/MarkdownDocumentationVisitorTest.cs b/language-server/src/Geowerkstatt.Interlis.LanguageServer.Test/Visitors/MarkdownDocumentationVisitorTest.cs index 66deaeb..145b87c 100644 --- a/language-server/src/Geowerkstatt.Interlis.LanguageServer.Test/Visitors/MarkdownDocumentationVisitorTest.cs +++ b/language-server/src/Geowerkstatt.Interlis.LanguageServer.Test/Visitors/MarkdownDocumentationVisitorTest.cs @@ -55,6 +55,24 @@ MODEL TestModel (de) AT "http://models.geow.cloud" VERSION "1" = END TestModel; """; + private const string TestModelNestedStruct = """ + INTERLIS 2.4; + + MODEL TestModel (de) AT "http://models.geow.cloud" VERSION "1" = + TOPIC TestTopic = + CLASS TestClass = + attr1: MANDATORY TestStruct; + attr2: 10..20; + END TestClass; + + STRUCTURE TestStruct = + attr1: TEXT*10; + attr2: MANDATORY (value1, value2); + END TestStruct; + END TestTopic; + END TestModel; + """; + [TestMethod] public void TestInterlisFile() { @@ -127,7 +145,49 @@ public void TestInterlisFileEnumeration() ### TestClass | Attributname | Kardinalität | Typ | | --- | --- | --- | - | attr1 | 0..1 | (**topValue1**, **topValue2** (subValue1, subValue2, subValue3 (*subSubValue1*, *subSubValue2*)), **topValue3**) | + | attr1 | 0..1 | (topValue1, topValue2 (subValue1, subValue2, subValue3 (subSubValue1, subSubValue2)), topValue3) | + + + """; + + Assert.AreEqual(expected.ReplaceLineEndings(), documentation.ReplaceLineEndings()); + } + + [TestMethod] + public void TestInterlisFileNestedStruct() + { + var reader = new InterlisReader(); + var interlisFile = reader.ReadFile(new StringReader(TestModelNestedStruct)); + + var visitor = new MarkdownDocumentationVisitor(); + visitor.VisitInterlisFile(interlisFile); + var documentation = visitor.GetDocumentation(); + + const string structInlineTable = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
AttributnameKardinalitätTyp
attr10..1Text [10]
attr21(value1, value2)
"; + + var expected = $""" + # TestModel + ## TestTopic + ### TestClass + | Attributname | Kardinalität | Typ | + | --- | --- | --- | + | attr1 | 1 | TestStruct
{structInlineTable} | + | attr2 | 0..1 | 10..20 | + + ### TestStruct + | Attributname | Kardinalität | Typ | + | --- | --- | --- | + | attr1 | 0..1 | Text [10] | + | attr2 | 1 | (value1, value2) | """; diff --git a/language-server/src/Geowerkstatt.Interlis.LanguageServer/Visitors/MarkdownDocumentationVisitor.cs b/language-server/src/Geowerkstatt.Interlis.LanguageServer/Visitors/MarkdownDocumentationVisitor.cs index d9b4e0d..f786b88 100644 --- a/language-server/src/Geowerkstatt.Interlis.LanguageServer/Visitors/MarkdownDocumentationVisitor.cs +++ b/language-server/src/Geowerkstatt.Interlis.LanguageServer/Visitors/MarkdownDocumentationVisitor.cs @@ -11,6 +11,7 @@ namespace Geowerkstatt.Interlis.LanguageServer.Visitors; public class MarkdownDocumentationVisitor : Interlis24AstBaseVisitor { private readonly StringBuilder documentation = new StringBuilder(); + private bool useHtml; /// /// Generates markdown documentation for the given model. @@ -39,14 +40,30 @@ public class MarkdownDocumentationVisitor : Interlis24AstBaseVisitor /// The INTERLIS class. public override object? VisitClassDef([NotNull] ClassDef classDef) { - documentation.AppendLine($"### {classDef.Name}"); - documentation.AppendLine("| Attributname | Kardinalität | Typ |"); - documentation.AppendLine("| --- | --- | --- |"); - var result = base.VisitClassDef(classDef); - VisitRelatedAssociations(classDef); - documentation.AppendLine(); - - return result; + void VisitTableBody() + { + base.VisitClassDef(classDef); + VisitRelatedAssociations(classDef); + } + + if (useHtml) + { + documentation.Append(""); + documentation.Append(""); + documentation.Append(""); + VisitTableBody(); + documentation.Append("
AttributnameKardinalitätTyp
"); + } + else + { + documentation.AppendLine($"### {classDef.Name}"); + documentation.AppendLine("| Attributname | Kardinalität | Typ |"); + documentation.AppendLine("| --- | --- | --- |"); + VisitTableBody(); + documentation.AppendLine(); + } + + return null; } private void VisitRelatedAssociations(ClassDef classDef) @@ -83,9 +100,20 @@ private void VisitRelatedAssociation(ClassDef classDef, AttributeDef left, Attri public override object? VisitAttributeDef([NotNull] AttributeDef attributeDef) { var cardinality = CalculateCardinality(attributeDef.TypeDef.Cardinality); - var type = GetTypeName(attributeDef.TypeDef); - documentation.AppendLine($"| {attributeDef.Name} | {cardinality} | {type} |"); + if (useHtml) + { + documentation.Append($"{attributeDef.Name}{cardinality}"); + VisitTypeName(attributeDef.TypeDef); + documentation.Append(""); + } + else + { + documentation.Append($"| {attributeDef.Name} | {cardinality} | "); + VisitTypeName(attributeDef.TypeDef); + documentation.AppendLine(" |"); + } + return base.VisitAttributeDef(attributeDef); } @@ -107,9 +135,15 @@ private static string CalculateCardinality(Cardinality? cardinality) return ""; } - private static string? GetTypeName(TypeDef? type) + private void VisitTypeName(TypeDef? type) { - return type switch + if (type is ReferenceType referenceType) + { + VisitReferenceType(referenceType); + return; + } + + var typeName = type switch { TextType textType => textType.Length == null ? "Text" : $"Text [{textType.Length}]", NumericType numericType => numericType.Min != null && numericType.Max != null ? $"{numericType.Min}..{numericType.Max}" : "Numerisch", @@ -121,28 +155,47 @@ private static string CalculateCardinality(Cardinality? cardinality) _ => "Blackbox", }, EnumerationType enumerationType => FormatEnumerationValues(enumerationType.Values), - ReferenceType referenceType => referenceType.Target.Value?.Path.Last(), TypeRef typeRef => typeRef.Extends?.Path.Last(), RoleType roleType => string.Join(", ", roleType.Targets.Select(target => target.Value?.Path.Last()).Where(target => target is not null)), _ => type?.ToString(), }; + documentation.Append(typeName); } private static string FormatEnumerationValues(EnumerationValuesList enumerationValues, int depth = 0) { - const string bold = "**"; - const string italic = "*"; - - var formatting = depth switch + var (formatStart, formatEnd) = depth switch { - 0 => bold, - 1 => "", - _ => italic, + 0 => ("", ""), + 1 => ("", ""), + _ => ("", ""), }; - var formattedValues = enumerationValues.Select(v => $"{formatting}{v.Name}{formatting}{(v.SubValues.Count == 0 ? "" : " " + FormatEnumerationValues(v.SubValues, depth + 1))}"); + var formattedValues = enumerationValues.Select(v => $"{formatStart}{v.Name}{formatEnd}{(v.SubValues.Count == 0 ? "" : " " + FormatEnumerationValues(v.SubValues, depth + 1))}"); return $"({string.Join(", ", formattedValues)})"; } + /// + /// Appends the name of the referenced type to the documentation. + /// If the referenced type is a structure, its attributes and associations are also documented using an HTML table. + /// + /// The referenced type. + private void VisitReferenceType(ReferenceType referenceType) + { + var reference = referenceType.Target.Value; + var typeName = reference?.Path.Last(); + documentation.Append(typeName); + + if (reference?.Target is ClassDef classDef && classDef.IsStructure) + { + documentation.Append("
"); + + var didUseHtml = useHtml; + useHtml = true; + VisitClassDef(classDef); + useHtml = didUseHtml; + } + } + /// /// Returns the generated markdown documentation of the visited elements. ///