From 9d6d74dfdc71ecab500e5fe18af98e3f640f62a3 Mon Sep 17 00:00:00 2001 From: Toine db Date: Fri, 31 Jan 2025 11:42:51 +0100 Subject: [PATCH] Placeholder moved to inline textblocks --- src/MarkdownParser/IViewSupplier.cs | 7 --- .../Segments/Indicators/SegmentIndicator.cs | 3 +- .../Models/Segments/LinkSegment.cs | 4 +- .../Models/Segments/PlaceholderSegment.cs | 24 +++++++ src/MarkdownParser/Writer/ViewFormatter.cs | 20 +++--- src/MarkdownParser/Writer/ViewWriter.cs | 15 +++-- src/MarkdownParser/Writer/ViewWriterCache.cs | 12 +++- .../MarkdownParser.Test.csproj | 1 + .../MarkdownParserBlockSegmentsSpecs.cs | 62 +++++++++++++++++++ .../MarkdownParserBlocksSpecs.cs | 2 +- .../MarkdownParserSectionsSpecs.cs | 18 ++---- .../MarkdownParserSingleComponentSpecs.cs | 2 +- .../Mocks/BlockComponentSupplier.cs | 5 -- .../Mocks/PassThroughComponentSupplier.cs | 5 -- .../Mocks/StringComponentSupplier.cs | 5 -- .../Examples/Sections/referencedefinitions.md | 8 +-- .../TextEmphasis/basic-placeholders.md | 9 +++ 17 files changed, 140 insertions(+), 62 deletions(-) create mode 100644 src/MarkdownParser/Models/Segments/PlaceholderSegment.cs create mode 100644 test/MarkdownParser.Test/Resources/Examples/TextEmphasis/basic-placeholders.md diff --git a/src/MarkdownParser/IViewSupplier.cs b/src/MarkdownParser/IViewSupplier.cs index 97f2438..88ae446 100644 --- a/src/MarkdownParser/IViewSupplier.cs +++ b/src/MarkdownParser/IViewSupplier.cs @@ -73,13 +73,6 @@ public interface IViewSupplier /// T CreateThematicBreak(); - /// - /// a placeholder for views or other objects - /// - /// placeholder string - /// - T CreatePlaceholder(string placeholderName); - /// /// a view that shows fenced code (found in MD blocks starting with ```cs ) /// diff --git a/src/MarkdownParser/Models/Segments/Indicators/SegmentIndicator.cs b/src/MarkdownParser/Models/Segments/Indicators/SegmentIndicator.cs index 53583f2..3856462 100644 --- a/src/MarkdownParser/Models/Segments/Indicators/SegmentIndicator.cs +++ b/src/MarkdownParser/Models/Segments/Indicators/SegmentIndicator.cs @@ -8,6 +8,7 @@ public enum SegmentIndicator Strikethrough, Code, Link, - LineBreak + LineBreak, + Placeholder } } \ No newline at end of file diff --git a/src/MarkdownParser/Models/Segments/LinkSegment.cs b/src/MarkdownParser/Models/Segments/LinkSegment.cs index 5a2a5f3..90dd62f 100644 --- a/src/MarkdownParser/Models/Segments/LinkSegment.cs +++ b/src/MarkdownParser/Models/Segments/LinkSegment.cs @@ -7,8 +7,8 @@ public class LinkSegment : IndicatorSegment public string Url { get; } public string Title { get; } - public LinkSegment(SegmentIndicator indicator, SegmentIndicatorPosition indicatorPosition, string url, string title) - : base(indicator, indicatorPosition) + public LinkSegment(SegmentIndicatorPosition indicatorPosition, string url, string title) + : base(SegmentIndicator.Link, indicatorPosition) { Url = url; Title = title; diff --git a/src/MarkdownParser/Models/Segments/PlaceholderSegment.cs b/src/MarkdownParser/Models/Segments/PlaceholderSegment.cs new file mode 100644 index 0000000..a4313d7 --- /dev/null +++ b/src/MarkdownParser/Models/Segments/PlaceholderSegment.cs @@ -0,0 +1,24 @@ +using MarkdownParser.Models.Segments.Indicators; + +namespace MarkdownParser.Models.Segments +{ + public class PlaceholderSegment : IndicatorSegment + { + public string Url { get; } + public string Title { get; } + + public PlaceholderSegment(string url, string title) + : base(SegmentIndicator.Placeholder, SegmentIndicatorPosition.Start) + { + Url = url; + Title = title; + HasLiteralContent = !string.IsNullOrWhiteSpace(Title) + && !string.IsNullOrWhiteSpace(Url); + } + + public override string ToString() + { + return Title ?? Url ?? string.Empty; + } + } +} \ No newline at end of file diff --git a/src/MarkdownParser/Writer/ViewFormatter.cs b/src/MarkdownParser/Writer/ViewFormatter.cs index c847133..231528a 100644 --- a/src/MarkdownParser/Writer/ViewFormatter.cs +++ b/src/MarkdownParser/Writer/ViewFormatter.cs @@ -28,16 +28,20 @@ public List FormatSingleBlock(Block markdownBlock) return _writer.Flush(); } + private Dictionary ReferenceMap; + private void WriteBlockToView(Block block, ViewWriter writer, bool continueWithNextSibling = true) { - if (block == null) + var tag = block?.Tag; + if (tag == null) { return; } - switch (block.Tag) + switch (tag) { case BlockTag.Document: + ReferenceMap = block.Document.ReferenceMap; _writer.RegisterReferenceDefinitions(block.Document.ReferenceMap); WriteBlockToView(block.FirstChild, writer); break; @@ -81,7 +85,8 @@ private void WriteBlockToView(Block block, ViewWriter writer, bool continueWi writer.StartAndFinalizeHtmlBlock(block.StringContent); break; case BlockTag.ReferenceDefinition: - // ignore this tag, because it's handled before so the data can be used during the formatting + // ignore this tag, because it's handled at 'BlockTag.Document' with 'RegisterReferenceDefinitions' + // this way the data is available in time to be used during the formatting break; default: throw new CommonMarkException("Block type " + block.Tag + " is not supported.", block); @@ -95,12 +100,13 @@ private void WriteBlockToView(Block block, ViewWriter writer, bool continueWi private void WriteInlineToView(Inline inline, ViewWriter writer) { - if (inline == null) + var tag = inline?.Tag; + if (tag == null) { return; } - switch (inline.Tag) + switch (tag) { case InlineTag.Code: writer.AddEmphasis(inline, inline.SourcePosition, inline.SourceLength); @@ -111,7 +117,7 @@ private void WriteInlineToView(Inline inline, ViewWriter writer) writer.AddText(inline.LiteralContent, inline.SourcePosition); break; case InlineTag.Link: - writer.AddLink(inline, inline.SourcePosition, inline.SourceLength, inline.TargetUrl, inline.LiteralContent); // check if this works at links + writer.AddLink(inline.SourcePosition, inline.SourceLength, inline.TargetUrl, inline.LiteralContent); // check if this works at links WriteInlineToView(inline.FirstChild, writer); break; case InlineTag.Image: @@ -122,7 +128,7 @@ private void WriteInlineToView(Inline inline, ViewWriter writer) writer.AddEmphasis(inline, inline.SourcePosition, inline.SourceLength); break; case InlineTag.Placeholder: - writer.StartAndFinalizePlaceholderBlock(inline.TargetUrl); + writer.AddPlaceholder(inline.SourcePosition, inline.SourceLength, inline.TargetUrl, inline.FirstChild.LiteralContent); break; case InlineTag.Strikethrough: case InlineTag.Emphasis: diff --git a/src/MarkdownParser/Writer/ViewWriter.cs b/src/MarkdownParser/Writer/ViewWriter.cs index bab9dc9..fec2302 100644 --- a/src/MarkdownParser/Writer/ViewWriter.cs +++ b/src/MarkdownParser/Writer/ViewWriter.cs @@ -231,11 +231,16 @@ public void AddText(string content, int firstCharacterPosition) GetWorkbenchItem().Add(content, firstCharacterPosition); } - public void AddLink(Inline inline, int firstCharacterPosition, int length, string url, string urlTitle) + public void AddLink(int firstCharacterPosition, int length, string url, string urlTitle) { GetWorkbenchItem().AddLink(firstCharacterPosition, length, url, urlTitle); } + public void AddPlaceholder(int firstCharacterPosition, int length, string url, string title) + { + GetWorkbenchItem().AddPlaceholder(firstCharacterPosition, length, url, title); + } + public void AddEmphasis(Inline inline, int firstCharacterPosition, int length) { SegmentIndicator indicator; @@ -308,13 +313,7 @@ public void StartAndFinalizeThematicBreak() var separator = ViewSupplier.CreateThematicBreak(); StoreView(separator); } - - public void StartAndFinalizePlaceholderBlock(string placeholderName) - { - var placeholderView = ViewSupplier.CreatePlaceholder(placeholderName); - StoreView(placeholderView); - } - + private BlockType[] GetAncestorsTreeFromWorkbench(BlockType currentBlockType) { var blockTypeTree = new List(); diff --git a/src/MarkdownParser/Writer/ViewWriterCache.cs b/src/MarkdownParser/Writer/ViewWriterCache.cs index 74c129f..8dadb28 100644 --- a/src/MarkdownParser/Writer/ViewWriterCache.cs +++ b/src/MarkdownParser/Writer/ViewWriterCache.cs @@ -48,7 +48,15 @@ public void AddLink(int firstCharacterPosition, int length, string url, string u _pendingSegmentIndicators.Add((SegmentIndicator.Link, firstCharacterPosition + length - 1)); - var segmentIndicator = new LinkSegment(SegmentIndicator.Link, SegmentIndicatorPosition.Start, url, urlTitle); + var segmentIndicator = new LinkSegment(SegmentIndicatorPosition.Start, url, urlTitle); + _valuesStack.Push((segmentIndicator, _defaultT)); + } + + public void AddPlaceholder(int firstCharacterPosition, int length, string url, string title) + { + FinalizeSegmentIndicator(firstCharacterPosition); + + var segmentIndicator = new PlaceholderSegment(url, title); _valuesStack.Push((segmentIndicator, _defaultT)); } @@ -129,7 +137,7 @@ private void FinalizeSegmentIndicator(int toWritePosition) switch (pendingIndicator.SegmentIndicator) { case SegmentIndicator.Link: - segmentIndicator = new LinkSegment(pendingIndicator.SegmentIndicator, SegmentIndicatorPosition.End, string.Empty, string.Empty); + segmentIndicator = new LinkSegment(SegmentIndicatorPosition.End, string.Empty, string.Empty); break; default: segmentIndicator = new IndicatorSegment(pendingIndicator.SegmentIndicator, SegmentIndicatorPosition.End); diff --git a/test/MarkdownParser.Test/MarkdownParser.Test.csproj b/test/MarkdownParser.Test/MarkdownParser.Test.csproj index 7f4eb56..b70d495 100644 --- a/test/MarkdownParser.Test/MarkdownParser.Test.csproj +++ b/test/MarkdownParser.Test/MarkdownParser.Test.csproj @@ -40,6 +40,7 @@ + diff --git a/test/MarkdownParser.Test/MarkdownParserBlockSegmentsSpecs.cs b/test/MarkdownParser.Test/MarkdownParserBlockSegmentsSpecs.cs index 626eaf5..39c8607 100644 --- a/test/MarkdownParser.Test/MarkdownParserBlockSegmentsSpecs.cs +++ b/test/MarkdownParser.Test/MarkdownParserBlockSegmentsSpecs.cs @@ -136,4 +136,66 @@ public void When_parsing_text_with_links_it_should_output_ordered_segments() textBlock1!.TextSegments[6].As().Indicator.Should().Be(SegmentIndicator.Link); textBlock1!.TextSegments[6].As().IndicatorPosition.Should().Be(SegmentIndicatorPosition.End); } + + [TestMethod] + public void When_parsing_text_with_placeholders_it_should_output_ordered_segments() + { + //----------------------------------------------------------------------------------------------------------- + // Arrange + //----------------------------------------------------------------------------------------------------------- + var markdown = FileReader.ReadFile("TextEmphasis.basic-placeholders.md"); + + var componentSupplier = new PassThroughComponentSupplier(); + var parser = new MarkdownParser(componentSupplier); + + //----------------------------------------------------------------------------------------------------------- + // Act + //----------------------------------------------------------------------------------------------------------- + var parseResult = parser.Parse(markdown); + + //----------------------------------------------------------------------------------------------------------- + // Assert + //----------------------------------------------------------------------------------------------------------- + parseResult.Count.Should().Be(3); + + parseResult[0].Should().BeOfType(); + var textBlock0 = parseResult[0] as TextBlock; + textBlock0!.TextSegments.Length.Should().Be(3); + textBlock0!.TextSegments[0].As().Text.Should().Be("Use "); + textBlock0!.TextSegments[1].As().Indicator.Should().Be(SegmentIndicator.Placeholder); + textBlock0!.TextSegments[1].As().IndicatorPosition.Should().Be(SegmentIndicatorPosition.Start); + textBlock0!.TextSegments[1].As().Title.Should().Be("placeholder x"); + textBlock0!.TextSegments[1].As().Url.Should().Be("placeholder x"); + textBlock0!.TextSegments[1].As().ToString().Should().Be("placeholder x"); + textBlock0!.TextSegments[2].As().Text.Should().Be(" within sentence."); + + parseResult[1].Should().BeOfType(); + var textBlock1 = parseResult[1] as TextBlock; + textBlock1!.TextSegments.Length.Should().Be(10); + textBlock1!.TextSegments[0].As().Text.Should().Be("Paragraphs "); + textBlock1!.TextSegments[1].As().Indicator.Should().Be(SegmentIndicator.Strong); + textBlock1!.TextSegments[1].As().IndicatorPosition.Should().Be(SegmentIndicatorPosition.Start); + textBlock1!.TextSegments[2].As().Text.Should().Be("are "); + textBlock1!.TextSegments[3].As().Indicator.Should().Be(SegmentIndicator.Italic); + textBlock1!.TextSegments[3].As().IndicatorPosition.Should().Be(SegmentIndicatorPosition.Start); + textBlock1!.TextSegments[4].As().Text.Should().Be("separated "); + textBlock1!.TextSegments[5].As().Indicator.Should().Be(SegmentIndicator.Placeholder); + textBlock1!.TextSegments[5].As().Title.Should().Be("placeholder y"); + textBlock1!.TextSegments[5].As().Url.Should().Be("placeholder y"); + textBlock1!.TextSegments[5].As().ToString().Should().Be("placeholder y"); + textBlock1!.TextSegments[6].As().Indicator.Should().Be(SegmentIndicator.Italic); + textBlock1!.TextSegments[6].As().IndicatorPosition.Should().Be(SegmentIndicatorPosition.End); + textBlock1!.TextSegments[7].As().Text.Should().Be(" by"); + textBlock1!.TextSegments[8].As().Indicator.Should().Be(SegmentIndicator.Strong); + textBlock1!.TextSegments[8].As().IndicatorPosition.Should().Be(SegmentIndicatorPosition.End); + textBlock1!.TextSegments[9].As().Text.Should().Be(" a blank line."); + + parseResult[2].Should().BeOfType(); + var textBlock2 = parseResult[2] as TextBlock; + textBlock2!.TextSegments.Length.Should().Be(1); + textBlock2!.TextSegments[0].As().Indicator.Should().Be(SegmentIndicator.Placeholder); + textBlock2!.TextSegments[0].As().Title.Should().Be("placeholder z"); + textBlock2!.TextSegments[0].As().Url.Should().Be("placeholder z"); + textBlock2!.TextSegments[0].As().ToString().Should().Be("placeholder z"); + } } \ No newline at end of file diff --git a/test/MarkdownParser.Test/MarkdownParserBlocksSpecs.cs b/test/MarkdownParser.Test/MarkdownParserBlocksSpecs.cs index 5188952..7de0a13 100644 --- a/test/MarkdownParser.Test/MarkdownParserBlocksSpecs.cs +++ b/test/MarkdownParser.Test/MarkdownParserBlocksSpecs.cs @@ -260,7 +260,7 @@ public void When_parsing_reference_definitions_it_should_output_correct_ancestor // Assert //----------------------------------------------------------------------------------------------------------- parseResult.Count.Should().Be(1); - parseResult[0].Should().StartWith($"stackview>:+textview:[]{BlockType.Paragraph}"); + parseResult[0].Should().StartWith($"textview:[]{BlockType.Paragraph}:Aliquet in luctus in porttitor non quam donec."); } [TestMethod] diff --git a/test/MarkdownParser.Test/MarkdownParserSectionsSpecs.cs b/test/MarkdownParser.Test/MarkdownParserSectionsSpecs.cs index c9449ca..d4f2a2d 100644 --- a/test/MarkdownParser.Test/MarkdownParserSectionsSpecs.cs +++ b/test/MarkdownParser.Test/MarkdownParserSectionsSpecs.cs @@ -207,18 +207,12 @@ public void When_parsing_reference_definitions_it_should_output_specific_views() // Assert //----------------------------------------------------------------------------------------------------------- parseResult.Count.Should().Be(1); - parseResult[0].Should().StartWith("stackview>:+textview"); - - mockComponentSupplier.MarkdownReferenceDefinitions.Should().HaveCount(2); + parseResult[0].Should().StartWith("textview:Aliquet in luctus in porttitor non quam donec."); + mockComponentSupplier.MarkdownReferenceDefinitions.Should().HaveCount(1); mockComponentSupplier.MarkdownReferenceDefinitions[0].IsPlaceholder = false; - mockComponentSupplier.MarkdownReferenceDefinitions[0].Label = "LINK"; - mockComponentSupplier.MarkdownReferenceDefinitions[0].Title = "title"; - mockComponentSupplier.MarkdownReferenceDefinitions[0].Url = "/uri"; - - mockComponentSupplier.MarkdownReferenceDefinitions[1].IsPlaceholder = false; - mockComponentSupplier.MarkdownReferenceDefinitions[1].Label = "PORTTITOR NON QUAM"; - mockComponentSupplier.MarkdownReferenceDefinitions[1].Title = ""; - mockComponentSupplier.MarkdownReferenceDefinitions[1].Url = "https://lipsum.com/"; + mockComponentSupplier.MarkdownReferenceDefinitions[0].Label = "PORTTITOR NON QUAM"; + mockComponentSupplier.MarkdownReferenceDefinitions[0].Title = "lipsum"; + mockComponentSupplier.MarkdownReferenceDefinitions[0].Url = "https://lipsum.com/"; } -} +} \ No newline at end of file diff --git a/test/MarkdownParser.Test/MarkdownParserSingleComponentSpecs.cs b/test/MarkdownParser.Test/MarkdownParserSingleComponentSpecs.cs index 0c2eba2..0a239e4 100644 --- a/test/MarkdownParser.Test/MarkdownParserSingleComponentSpecs.cs +++ b/test/MarkdownParser.Test/MarkdownParserSingleComponentSpecs.cs @@ -244,6 +244,6 @@ public void When_parsing_a_placeholder_it_should_output_a_PlaceHolderView() // Assert //----------------------------------------------------------------------------------------------------------- parseResult.Count.Should().Be(1); - parseResult.First().Should().Be("placeholderview:my-placeholder"); + parseResult.First().Should().Be("textview:my-placeholder"); } } diff --git a/test/MarkdownParser.Test/Mocks/BlockComponentSupplier.cs b/test/MarkdownParser.Test/Mocks/BlockComponentSupplier.cs index 8a0c14e..9974ba2 100644 --- a/test/MarkdownParser.Test/Mocks/BlockComponentSupplier.cs +++ b/test/MarkdownParser.Test/Mocks/BlockComponentSupplier.cs @@ -64,11 +64,6 @@ public string CreateThematicBreak() return "thematicbreakview"; } - public string CreatePlaceholder(string placeholderName) - { - return $"placeholderview:{placeholderName}"; - } - public string CreateFencedCodeBlock(TextBlock textBlock, string codeInfo) { var content = textBlock.ExtractLiteralContent(Settings.TextualLineBreak); diff --git a/test/MarkdownParser.Test/Mocks/PassThroughComponentSupplier.cs b/test/MarkdownParser.Test/Mocks/PassThroughComponentSupplier.cs index 1bbc569..9f0dca4 100644 --- a/test/MarkdownParser.Test/Mocks/PassThroughComponentSupplier.cs +++ b/test/MarkdownParser.Test/Mocks/PassThroughComponentSupplier.cs @@ -51,11 +51,6 @@ public object CreateThematicBreak() return "ThematicBreak"; } - public object CreatePlaceholder(string placeholderName) - { - return placeholderName; - } - public object CreateFencedCodeBlock(TextBlock textBlock, string codeInfo) { return textBlock; diff --git a/test/MarkdownParser.Test/Mocks/StringComponentSupplier.cs b/test/MarkdownParser.Test/Mocks/StringComponentSupplier.cs index a1340fb..f49615b 100644 --- a/test/MarkdownParser.Test/Mocks/StringComponentSupplier.cs +++ b/test/MarkdownParser.Test/Mocks/StringComponentSupplier.cs @@ -58,11 +58,6 @@ public string CreateThematicBreak() return "thematicbreakview"; } - public string CreatePlaceholder(string placeholderName) - { - return $"placeholderview:{placeholderName}"; - } - public string CreateFencedCodeBlock(TextBlock textBlock, string codeInfo) { var content = textBlock.ExtractLiteralContent(Settings.TextualLineBreak); diff --git a/test/MarkdownParser.Test/Resources/Examples/Sections/referencedefinitions.md b/test/MarkdownParser.Test/Resources/Examples/Sections/referencedefinitions.md index 75933d9..b463d04 100644 --- a/test/MarkdownParser.Test/Resources/Examples/Sections/referencedefinitions.md +++ b/test/MarkdownParser.Test/Resources/Examples/Sections/referencedefinitions.md @@ -1,7 +1,3 @@ -[link]: /uri "title" +Aliquet in luctus in [porttitor non quam] donec. -Vestibulum dictum lacinia lacus, at ornare quam consequat ultrices. -Nam quam leo, aliquet in luctus in, [porttitor non quam]. Donec tincidunt augue nisi, -sed pellentesque nisl porttitor vel. - -[porttitor non quam]: https://lipsum.com/ \ No newline at end of file +[porttitor non quam]: https://lipsum.com/ "lipsum" \ No newline at end of file diff --git a/test/MarkdownParser.Test/Resources/Examples/TextEmphasis/basic-placeholders.md b/test/MarkdownParser.Test/Resources/Examples/TextEmphasis/basic-placeholders.md new file mode 100644 index 0000000..f2025ea --- /dev/null +++ b/test/MarkdownParser.Test/Resources/Examples/TextEmphasis/basic-placeholders.md @@ -0,0 +1,9 @@ +Use [placeholder x] within sentence. + +Paragraphs **are *separated [placeholder y]* by** a blank line. + +[placeholder z] + +[placeholder x]: https://lipsum.com/ "lipsum" +[placeholder y]: https://lipsum.com/ "lipsum" +[placeholder z]: https://lipsum.com/ "lipsum" \ No newline at end of file