diff --git a/src/Textamina.Markdig.Tests/Specs/AttributesSpecs.md b/src/Textamina.Markdig.Tests/Specs/AttributesSpecs.md index 8463692e..f96a7729 100644 --- a/src/Textamina.Markdig.Tests/Specs/AttributesSpecs.md +++ b/src/Textamina.Markdig.Tests/Specs/AttributesSpecs.md @@ -4,12 +4,18 @@ This section describes the different extensions supported: ## Attributes -Attributes can be attached to a previous inline element or the current block if the previous inline element is a literal. Attributes can be: +Attributes can be attached to: +- The previous inline element if the previous element is not a literal +- The next block if the current block is a paragraph and the attributes is the only inline present in the paragraph +- Or the current block + +Attributes can be of 3 kinds: - An id element, starting by `#` that will be used to set the `id` property of the HTML element - A class element, starting by `.` that will be appended to the CSS class property of the HTML element - a `name=value` or `name="value"` that will be appended as an attribute of the HTML element +The following shows that attributes is attached to the current block or the previous inline: ```````````````````````````````` example # This is a heading with an an attribute{#heading-link} @@ -23,3 +29,15 @@ This is a heading{#heading-link2}

This is a link

This is a heading

```````````````````````````````` + +The following shows that the attributes is attached to the next fenced code block: + +```````````````````````````````` example +{#fenced-id .fenced-class} +~~~ +This is a fenced with attached attributes +~~~ +. +
This is a fenced with attached attributes
+
+```````````````````````````````` diff --git a/src/Textamina.Markdig.Tests/Specs/CustomContainerSpecs.md b/src/Textamina.Markdig.Tests/Specs/CustomContainerSpecs.md index 8d080d7f..d2730700 100644 --- a/src/Textamina.Markdig.Tests/Specs/CustomContainerSpecs.md +++ b/src/Textamina.Markdig.Tests/Specs/CustomContainerSpecs.md @@ -70,3 +70,14 @@ This is a spoiler
This is a spoiler
```````````````````````````````` + +The content of a custom container is not HTML escaped: + +```````````````````````````````` example +:::mycontainer +

This is a raw spoiler

+::: +. +

This is a raw spoiler

+
+```````````````````````````````` diff --git a/src/Textamina.Markdig.Tests/Specs/GridTableSpecs.md b/src/Textamina.Markdig.Tests/Specs/GridTableSpecs.md index 1bee7fa0..1d65025e 100644 --- a/src/Textamina.Markdig.Tests/Specs/GridTableSpecs.md +++ b/src/Textamina.Markdig.Tests/Specs/GridTableSpecs.md @@ -96,3 +96,91 @@ Col3a ```````````````````````````````` + +A row header is separated using `+========+` instead of `+---------+`: + +```````````````````````````````` example ++---------+---------+ +| This is | a table | ++=========+=========+ +. + +++ + + + + + +
This isa table
+```````````````````````````````` + +The last column separator `|` may be omitted: + +```````````````````````````````` example ++---------+---------+ +| This is | a table with a longer text in the second column +. + +++ + + + + + +
This isa table with a longer text in the second column
+```````````````````````````````` + +The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between: + +Total size is : 16 + +- `----` -> 4 +- `--------` -> 8 +- `----` -> 4 + +So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25% + +```````````````````````````````` example ++----+--------+----+ +| A | B C D | E | ++----+--------+----+ +. + ++++ + + + + + + +
AB C DE
+```````````````````````````````` + +Alignment might be specified on the first row using the character `:`: + + +```````````````````````````````` example ++-----+:---:+-----+ +| A | B | C | ++-----+-----+-----+ +. + ++++ + + + + + + +
ABC
+```````````````````````````````` diff --git a/src/Textamina.Markdig.Tests/Specs/Specs.cs b/src/Textamina.Markdig.Tests/Specs/Specs.cs index 21a0a36e..ba9aaca6 100644 --- a/src/Textamina.Markdig.Tests/Specs/Specs.cs +++ b/src/Textamina.Markdig.Tests/Specs/Specs.cs @@ -16895,11 +16895,18 @@ namespace Textamina.Markdig.Tests // // ## Attributes // - // Attributes can be attached to a previous inline element or the current block if the previous inline element is a literal. Attributes can be: + // Attributes can be attached to: + // - The previous inline element if the previous element is not a literal + // - The next block if the current block is a paragraph and the attributes is the only inline present in the paragraph + // - Or the current block + // + // Attributes can be of 3 kinds: // // - An id element, starting by `#` that will be used to set the `id` property of the HTML element // - A class element, starting by `.` that will be appended to the CSS class property of the HTML element // - a `name=value` or `name="value"` that will be appended as an attribute of the HTML element + // + // The following shows that attributes is attached to the current block or the previous inline: [TestFixture] public partial class TestExtensionsAttributes { @@ -16925,6 +16932,30 @@ namespace Textamina.Markdig.Tests Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Attributes"); TestParser.TestSpec("# This is a heading with an an attribute{#heading-link}\n\n[This is a link](http://google.com){#a-link .myclass data-lang=fr data-value=\"This is a value\"}\n\nThis is a heading{#heading-link2}\n-----------------", "

This is a heading with an an attribute

\n

This is a link

\n

This is a heading

", "attributes"); } + } + // The following shows that the attributes is attached to the next fenced code block: + [TestFixture] + public partial class TestExtensionsAttributes + { + [Test] + public void Example002() + { + // Example 2 + // Section: Extensions Attributes + // + // The following CommonMark: + // {#fenced-id .fenced-class} + // ~~~ + // This is a fenced with attached attributes + // ~~~ + // + // Should be rendered as: + //
This is a fenced with attached attributes
+            //     
+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Attributes"); + TestParser.TestSpec("{#fenced-id .fenced-class}\n~~~\nThis is a fenced with attached attributes\n~~~ ", "
This is a fenced with attached attributes\n
", "attributes"); + } } // # Extensions // @@ -17135,6 +17166,141 @@ namespace Textamina.Markdig.Tests Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Grid Table"); TestParser.TestSpec("+---------+---------+---------+\n| Col1 | Col2 | Col3 |\n| Col1a | Col2a | Col3a |\n| Col12 | Col3b |\n| Col123 |", "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Col1\nCol1aCol2\nCol2aCol3\nCol3a
Col12
Col123
", "gridtables"); } + } + // A row header is separated using `+========+` instead of `+---------+`: + [TestFixture] + public partial class TestExtensionsGridTable + { + [Test] + public void Example004() + { + // Example 4 + // Section: Extensions Grid Table + // + // The following CommonMark: + // +---------+---------+ + // | This is | a table | + // +=========+=========+ + // + // Should be rendered as: + // + // + // + // + // + // + // + // + // + //
This isa table
+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Grid Table"); + TestParser.TestSpec("+---------+---------+\n| This is | a table |\n+=========+=========+", "\n\n\n\n\n\n\n\n\n
This isa table
", "gridtables"); + } + } + // The last column separator `|` may be omitted: + [TestFixture] + public partial class TestExtensionsGridTable + { + [Test] + public void Example005() + { + // Example 5 + // Section: Extensions Grid Table + // + // The following CommonMark: + // +---------+---------+ + // | This is | a table with a longer text in the second column + // + // Should be rendered as: + // + // + // + // + // + // + // + // + // + //
This isa table with a longer text in the second column
+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Grid Table"); + TestParser.TestSpec("+---------+---------+\n| This is | a table with a longer text in the second column", "\n\n\n\n\n\n\n\n\n
This isa table with a longer text in the second column
", "gridtables"); + } + } + // The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between: + // + // Total size is : 16 + // + // - `----` -> 4 + // - `--------` -> 8 + // - `----` -> 4 + // + // So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25% + [TestFixture] + public partial class TestExtensionsGridTable + { + [Test] + public void Example006() + { + // Example 6 + // Section: Extensions Grid Table + // + // The following CommonMark: + // +----+--------+----+ + // | A | B C D | E | + // +----+--------+----+ + // + // Should be rendered as: + // + // + // + // + // + // + // + // + // + // + // + //
AB C DE
+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Grid Table"); + TestParser.TestSpec("+----+--------+----+\n| A | B C D | E |\n+----+--------+----+", "\n\n\n\n\n\n\n\n\n\n\n
AB C DE
", "gridtables"); + } + } + // Alignment might be specified on the first row using the character `:`: + [TestFixture] + public partial class TestExtensionsGridTable + { + [Test] + public void Example007() + { + // Example 7 + // Section: Extensions Grid Table + // + // The following CommonMark: + // +-----+:---:+-----+ + // | A | B | C | + // +-----+-----+-----+ + // + // Should be rendered as: + // + // + // + // + // + // + // + // + // + // + // + //
ABC
+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 7, "Extensions Grid Table"); + TestParser.TestSpec("+-----+:---:+-----+\n| A | B | C |\n+-----+-----+-----+", "\n\n\n\n\n\n\n\n\n\n\n
ABC
", "gridtables"); + } } // # Extensions // @@ -17267,6 +17433,29 @@ namespace Textamina.Markdig.Tests Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Custom Container"); TestParser.TestSpec(":::spoiler {#myspoiler myprop=yes}\nThis is a spoiler\n:::", "
This is a spoiler\n
", "customcontainers+attributes"); } + } + // The content of a custom container is not HTML escaped: + [TestFixture] + public partial class TestExtensionsCustomContainer + { + [Test] + public void Example006() + { + // Example 6 + // Section: Extensions Custom Container + // + // The following CommonMark: + // :::mycontainer + //

This is a raw spoiler

+ // ::: + // + // Should be rendered as: + //

This is a raw spoiler

+ //
+ + Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Custom Container"); + TestParser.TestSpec(":::mycontainer\n

This is a raw spoiler

\n:::", "

This is a raw spoiler

\n
", "customcontainers+attributes"); + } } // # Extensions // diff --git a/src/Textamina.Markdig.Tests/Specs/Specs.tt b/src/Textamina.Markdig.Tests/Specs/Specs.tt index 4c3ebf98..807e9685 100644 --- a/src/Textamina.Markdig.Tests/Specs/Specs.tt +++ b/src/Textamina.Markdig.Tests/Specs/Specs.tt @@ -1,5 +1,10 @@ <#/* -The MIT License (MIT) +Copyright (c) Alexandre Mutel. All rights reserved. +This file is licensed under the BSD-Clause 2 license. +See the license.txt file in the project root for more information.The MIT License (MIT) + +This is a modified version of code released previously with the following license: +-------------------------- Copyright (c) 2014 Morten Houston Ludvigsen diff --git a/src/Textamina.Markdig/Extensions/Attributes/AttributesParser.cs b/src/Textamina.Markdig/Extensions/Attributes/AttributesParser.cs index d9c78d21..533e4dab 100644 --- a/src/Textamina.Markdig/Extensions/Attributes/AttributesParser.cs +++ b/src/Textamina.Markdig/Extensions/Attributes/AttributesParser.cs @@ -46,6 +46,21 @@ namespace Textamina.Markdig.Extensions.Attributes } var objectToAttach = inline == null || inline == processor.Root ? (MarkdownObject) processor.Block : inline; + // If the current block is a Paragraph, but only the HtmlAttributes is used, + // Try to attach the attributes to the following block + var paragraph = objectToAttach as ParagraphBlock; + if (paragraph != null && paragraph.Inline.FirstChild == null && processor.Inline == null && slice.IsEmptyOrWhitespace()) + { + var parent = paragraph.Parent; + var indexOfParagraph = parent.IndexOf(paragraph); + if (indexOfParagraph + 1 < parent.Count) + { + objectToAttach = parent[indexOfParagraph + 1]; + // We can remove the paragraph as it is empty + parent.RemoveAt(indexOfParagraph); + } + } + var currentHtmlAttributes = objectToAttach.GetAttributes(); attributes.CopyTo(currentHtmlAttributes); diff --git a/src/Textamina.Markdig/Extensions/CustomContainers/CustomContainerParser.cs b/src/Textamina.Markdig/Extensions/CustomContainers/CustomContainerParser.cs index b3810c45..20301b6f 100644 --- a/src/Textamina.Markdig/Extensions/CustomContainers/CustomContainerParser.cs +++ b/src/Textamina.Markdig/Extensions/CustomContainers/CustomContainerParser.cs @@ -11,6 +11,8 @@ namespace Textamina.Markdig.Extensions.CustomContainers public CustomContainerParser() { OpeningCharacters = new [] {':'}; + // We don't prefix by LanguagePrefix + LanguagePrefix = null; } protected override FencedCodeBlock CreateFencedCodeBlock() diff --git a/src/Textamina.Markdig/Extensions/CustomContainers/HtmlCustomContainerRenderer.cs b/src/Textamina.Markdig/Extensions/CustomContainers/HtmlCustomContainerRenderer.cs index c43f3718..7b5aeb4a 100644 --- a/src/Textamina.Markdig/Extensions/CustomContainers/HtmlCustomContainerRenderer.cs +++ b/src/Textamina.Markdig/Extensions/CustomContainers/HtmlCustomContainerRenderer.cs @@ -15,29 +15,9 @@ namespace Textamina.Markdig.Extensions.CustomContainers protected override void Write(HtmlRenderer renderer, CustomContainer obj) { renderer.EnsureLine(); - renderer.Write(""); - renderer.WriteLeafRawLines(obj, true, true); + renderer.Write(""); + // We don't escape a CustomContainer + renderer.WriteLeafRawLines(obj, true, false); renderer.WriteLine(""); } } diff --git a/src/Textamina.Markdig/Extensions/Tables/GridTableParser.cs b/src/Textamina.Markdig/Extensions/Tables/GridTableParser.cs index 2bfd7dcf..1ee27a9f 100644 --- a/src/Textamina.Markdig/Extensions/Tables/GridTableParser.cs +++ b/src/Textamina.Markdig/Extensions/Tables/GridTableParser.cs @@ -82,7 +82,7 @@ namespace Textamina.Markdig.Extensions.Tables int totalWidth = 0; foreach (var columnSlice in tableState.ColumnSlices) { - totalWidth += columnSlice.End - columnSlice.Start + 1; + totalWidth += columnSlice.End - columnSlice.Start; } // Store the column width and alignment @@ -91,7 +91,7 @@ namespace Textamina.Markdig.Extensions.Tables var columnDefinition = new TableColumnDefinition { // Column width proportional to the total width - Width = (float)(columnSlice.End - columnSlice.Start + 1) * 100.0f / totalWidth, + Width = (float)(columnSlice.End - columnSlice.Start) * 100.0f / totalWidth, Alignment = columnSlice.Align, }; table.ColumnDefinitions.Add(columnDefinition); diff --git a/src/Textamina.Markdig/Extensions/Tables/GridTableState.cs b/src/Textamina.Markdig/Extensions/Tables/GridTableState.cs index 63c33b2b..464c1336 100644 --- a/src/Textamina.Markdig/Extensions/Tables/GridTableState.cs +++ b/src/Textamina.Markdig/Extensions/Tables/GridTableState.cs @@ -47,6 +47,9 @@ namespace Textamina.Markdig.Extensions.Tables CurrentColumnSpan = -1; } + /// + /// Gets or sets the index position of this column (after the |) + /// public int Start { get; set; } public int End { get; set; } diff --git a/src/Textamina.Markdig/Parsers/FencedCodeBlockParser.cs b/src/Textamina.Markdig/Parsers/FencedCodeBlockParser.cs index 0461a3e1..cc42c72e 100644 --- a/src/Textamina.Markdig/Parsers/FencedCodeBlockParser.cs +++ b/src/Textamina.Markdig/Parsers/FencedCodeBlockParser.cs @@ -2,6 +2,7 @@ // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using Textamina.Markdig.Helpers; +using Textamina.Markdig.Renderers.Html; using Textamina.Markdig.Syntax; namespace Textamina.Markdig.Parsers @@ -28,6 +29,7 @@ namespace Textamina.Markdig.Parsers { OpeningCharacters = new[] {'`', '~'}; InfoParser = DefaultInfoParser; + LanguagePrefix = "language-"; } /// @@ -35,6 +37,12 @@ namespace Textamina.Markdig.Parsers /// public InfoParserDelegate InfoParser { get; set; } + + /// + /// Gets or sets the language prefix (default is "language-") + /// + public string LanguagePrefix { get; set; } + /// /// The default parser for the information after the fenced code block special characters (usually ` or ~) /// @@ -67,7 +75,7 @@ namespace Textamina.Markdig.Parsers if (firstSpace > 0) { - infoString = line.Text.Substring(line.Start, firstSpace - line.Start); + infoString = line.Text.Substring(line.Start, firstSpace - line.Start).Trim(); // Skip any spaces after info string firstSpace++; @@ -84,11 +92,11 @@ namespace Textamina.Markdig.Parsers } } - argString = line.Text.Substring(firstSpace, line.End - firstSpace + 1); + argString = line.Text.Substring(firstSpace, line.End - firstSpace + 1).Trim(); } else { - infoString = line.ToString(); + infoString = line.ToString().Trim(); } fenced.Language = HtmlHelper.Unescape(infoString); @@ -143,6 +151,19 @@ namespace Textamina.Markdig.Parsers return BlockState.None; } + // Add the language as an attribute by default + if (!string.IsNullOrEmpty(fenced.Language)) + { + if (string.IsNullOrEmpty(LanguagePrefix)) + { + fenced.GetAttributes().AddClass(fenced.Language); + } + else + { + fenced.GetAttributes().AddClass(LanguagePrefix + fenced.Language); + } + } + // Store the number of matched string into the context processor.NewBlocks.Push(fenced); diff --git a/src/Textamina.Markdig/Renderers/Html/CodeBlockRenderer.cs b/src/Textamina.Markdig/Renderers/Html/CodeBlockRenderer.cs index bc042e47..7f658343 100644 --- a/src/Textamina.Markdig/Renderers/Html/CodeBlockRenderer.cs +++ b/src/Textamina.Markdig/Renderers/Html/CodeBlockRenderer.cs @@ -22,23 +22,9 @@ namespace Textamina.Markdig.Renderers.Html renderer.WriteAttributes(obj); } renderer.Write(">"); renderer.WriteLeafRawLines(obj, true, true);