From 16b8a9ba9769ecb34cd467bbd01433fdceba5a9e Mon Sep 17 00:00:00 2001 From: Andreas Gruenwald Date: Sun, 26 Apr 2020 16:07:49 +0200 Subject: [PATCH] feat: Add MdHeadingAnchorStyle.Auto option Add option MdHeadingAnchorStyle.Auto that allows to inlcude anchor tags in the output only if an anchor different from the auto-generated anchor was set. --- .../MdHeadingAnchorStyle/index.md | 12 +++--- ...t_have_unapproved_API_changes.approved.txt | 1 + .../DocumentSerializerTest.cs | 37 +++++++++++++++++++ .../_DocumentSerializer/DocumentSerializer.cs | 22 ++++++++--- .../_Model/_Blocks/MdHeading.cs | 8 +++- .../_Model/_Options/MdHeadingAnchorStyle.cs | 7 +++- 6 files changed, 75 insertions(+), 12 deletions(-) diff --git a/docs/apireference/Grynwald/MarkdownGenerator/MdHeadingAnchorStyle/index.md b/docs/apireference/Grynwald/MarkdownGenerator/MdHeadingAnchorStyle/index.md index 0f6eecc3..626ad9a0 100644 --- a/docs/apireference/Grynwald/MarkdownGenerator/MdHeadingAnchorStyle/index.md +++ b/docs/apireference/Grynwald/MarkdownGenerator/MdHeadingAnchorStyle/index.md @@ -10,7 +10,8 @@ Defines the available options for including text anchors for headings in the out public enum MdHeadingAnchorStyle { None = 0, - Tag = 1 + Tag = 1, + Auto = 2 } ``` @@ -18,10 +19,11 @@ public enum MdHeadingAnchorStyle ## Fields -| Name | Description | -| ---- | ----------------------------------------------------------- | -| None | Do not emit an anchor (default) | -| Tag | Include an anchor\-tag (``) for headings in the output | +| Name | Description | +| ---- | ------------------------------------------------------------------------------------------------------- | +| Auto | Include an anchor tag for heading if the heading's id is different from the automatically\-generated id | +| None | Do not emit an anchor (default) | +| Tag | Include an anchor\-tag (``) for headings in the output | ___ diff --git a/src/MarkdownGenerator.Test.ApiApproval/PublicApiApproval.MarkdownGenerator_must_not_have_unapproved_API_changes.approved.txt b/src/MarkdownGenerator.Test.ApiApproval/PublicApiApproval.MarkdownGenerator_must_not_have_unapproved_API_changes.approved.txt index 770a58fb..4eb37285 100644 --- a/src/MarkdownGenerator.Test.ApiApproval/PublicApiApproval.MarkdownGenerator_must_not_have_unapproved_API_changes.approved.txt +++ b/src/MarkdownGenerator.Test.ApiApproval/PublicApiApproval.MarkdownGenerator_must_not_have_unapproved_API_changes.approved.txt @@ -306,6 +306,7 @@ namespace Grynwald.MarkdownGenerator { None = 0, Tag = 1, + Auto = 2, } public enum MdHeadingStyle { diff --git a/src/MarkdownGenerator.Test/Internal/_DocumentSerializer/DocumentSerializerTest.cs b/src/MarkdownGenerator.Test/Internal/_DocumentSerializer/DocumentSerializerTest.cs index 547f26ab..c523704c 100644 --- a/src/MarkdownGenerator.Test/Internal/_DocumentSerializer/DocumentSerializerTest.cs +++ b/src/MarkdownGenerator.Test/Internal/_DocumentSerializer/DocumentSerializerTest.cs @@ -687,6 +687,43 @@ public void Serializer_respects_HeadingAnchor_serialization_option_03() ); } + [Fact] + public void Serializer_respects_HeadingAnchor_serialization_option_04() + { + // When HeadingAnchorStyle is 'Auto' do not include a tag if the + // id is the same as the auto generated one + + var options = new MdSerializationOptions() + { + HeadingStyle = MdHeadingStyle.Atx, + HeadingAnchorStyle = MdHeadingAnchorStyle.Auto + }; + + AssertToStringEquals( + "## Heading\r\n", + new MdDocument(new MdHeading(2, "Heading")), + options + ); + } + + [Fact] + public void Serializer_respects_HeadingAnchor_serialization_option_05() + { + // When HeadingAnchorStyle is 'Auto' include a tag if the + // id is the different from the auto generated one + + var options = new MdSerializationOptions() + { + HeadingStyle = MdHeadingStyle.Atx, + HeadingAnchorStyle = MdHeadingAnchorStyle.Auto + }; + + AssertToStringEquals( + "## Heading\r\n", + new MdDocument(new MdHeading(2, "Heading") { Anchor = "foo" }), + options + ); + } [Theory] [InlineData(null)] diff --git a/src/MarkdownGenerator/Internal/_DocumentSerializer/DocumentSerializer.cs b/src/MarkdownGenerator/Internal/_DocumentSerializer/DocumentSerializer.cs index 9ea3d2db..13e607c5 100644 --- a/src/MarkdownGenerator/Internal/_DocumentSerializer/DocumentSerializer.cs +++ b/src/MarkdownGenerator/Internal/_DocumentSerializer/DocumentSerializer.cs @@ -63,10 +63,14 @@ public void Visit(MdHeading block) { m_Writer.RequestBlankLine(); - string anchor = ""; - if (m_Options.HeadingAnchorStyle == MdHeadingAnchorStyle.Tag && !String.IsNullOrWhiteSpace(block.Anchor)) + var anchor = ""; + if (!String.IsNullOrWhiteSpace(block.Anchor)) { - anchor = $""; + if (m_Options.HeadingAnchorStyle == MdHeadingAnchorStyle.Tag || + (m_Options.HeadingAnchorStyle == MdHeadingAnchorStyle.Auto && !StringComparer.Ordinal.Equals(block.Anchor, block.AutoGeneratedId))) + { + anchor = $""; + } } if (m_Options.HeadingStyle == MdHeadingStyle.Setext && block.Level <= 2) @@ -84,7 +88,7 @@ public void Visit(MdHeading block) if (m_Options.MaxLineLength <= 0) { m_Writer.WriteLine(text); - m_Writer.WriteLine(new String(underlineChar, text.Length)); + m_Writer.WriteLine(new string(underlineChar, text.Length)); } // if max line length was specified, split the value into multiple lines if necessary else @@ -95,7 +99,7 @@ public void Visit(MdHeading block) { m_Writer.WriteLine(line); } - m_Writer.WriteLine(new String(underlineChar, headingTextLines.Max(x => x.Length))); + m_Writer.WriteLine(new string(underlineChar, headingTextLines.Max(x => x.Length))); } } else @@ -128,7 +132,9 @@ public void Visit(MdParagraph paragraph) // skip paragraph if it is empty if (lines.Length == 0) + { return; + } for (var i = 0; i < lines.Length; i++) { @@ -207,11 +213,15 @@ void OnBlankLineRequested(object s, EventArgs e) // blank lines before top level lists ( = before the first item) // should not be suppressed if (listItemNumber == 1 && m_ListLevel == 1) + { return; + } // while no other line was written, suppress blank lines if (!lineWritten) + { m_Writer.CancelRequestBlankLine(); + } } // event handler to update the prefix after the first line of a list item @@ -242,7 +252,9 @@ void OnLineWritten(object s, EventArgs e) // top-level lists should be surrounded by blank lines if (m_ListLevel == 1) + { m_Writer.RequestBlankLine(); + } m_ListLevel -= 1; } diff --git a/src/MarkdownGenerator/_Model/_Blocks/MdHeading.cs b/src/MarkdownGenerator/_Model/_Blocks/MdHeading.cs index 0c806ef5..3c3b76d0 100644 --- a/src/MarkdownGenerator/_Model/_Blocks/MdHeading.cs +++ b/src/MarkdownGenerator/_Model/_Blocks/MdHeading.cs @@ -51,6 +51,11 @@ public sealed class MdHeading : MdLeafBlock /// public string? Anchor { get; set; } + /// + /// Gets the automatically generated id for this heading. + /// + internal string AutoGeneratedId { get; } + /// /// Initializes a new instance of @@ -80,7 +85,8 @@ public MdHeading(MdSpan text, int level) Level = level; - Anchor = HtmlUtilities.ToUrlSlug(Text.ToString()); + AutoGeneratedId = HtmlUtilities.ToUrlSlug(Text.ToString()); + Anchor = AutoGeneratedId; } /// diff --git a/src/MarkdownGenerator/_Model/_Options/MdHeadingAnchorStyle.cs b/src/MarkdownGenerator/_Model/_Options/MdHeadingAnchorStyle.cs index 3efd5186..708d0921 100644 --- a/src/MarkdownGenerator/_Model/_Options/MdHeadingAnchorStyle.cs +++ b/src/MarkdownGenerator/_Model/_Options/MdHeadingAnchorStyle.cs @@ -13,6 +13,11 @@ public enum MdHeadingAnchorStyle /// /// Include an anchor-tag (<a />) for headings in the output /// - Tag = 1 + Tag = 1, + + /// + /// Include an anchor tag for heading if the heading's id is different from the automatically-generated id + /// + Auto = 2 } }