diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj
index 0111c3e2b..10369fa28 100644
--- a/src/Markdig.Tests/Markdig.Tests.csproj
+++ b/src/Markdig.Tests/Markdig.Tests.csproj
@@ -76,6 +76,7 @@
+
diff --git a/src/Markdig.Tests/TestEmphasisExtended.cs b/src/Markdig.Tests/TestEmphasisExtended.cs
new file mode 100644
index 000000000..dff35c26c
--- /dev/null
+++ b/src/Markdig.Tests/TestEmphasisExtended.cs
@@ -0,0 +1,168 @@
+using Markdig.Parsers.Inlines;
+using Markdig.Renderers;
+using Markdig.Renderers.Html;
+using Markdig.Syntax.Inlines;
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Markdig.Tests
+{
+ [TestFixture]
+ public class TestEmphasisExtended
+ {
+ class EmphasisTestExtension : IMarkdownExtension
+ {
+ public void Setup(MarkdownPipelineBuilder pipeline)
+ {
+ var emphasisParser = pipeline.InlineParsers.Find();
+ Debug.Assert(emphasisParser != null);
+
+ foreach (var emphasis in EmphasisTestDescriptors)
+ {
+ emphasisParser.EmphasisDescriptors.Add(
+ new EmphasisDescriptor(emphasis.Character, emphasis.Minimum, emphasis.Maximum, true));
+ }
+ emphasisParser.TryCreateEmphasisInlineList.Add((delimiterChar, delimiterCount) =>
+ {
+ return delimiterChar == '*' || delimiterChar == '_'
+ ? null
+ : new CustomEmphasisInline() { DelimiterChar = delimiterChar, DelimiterCount = delimiterCount };
+ });
+ }
+
+ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
+ {
+ renderer.ObjectRenderers.Insert(0, new EmphasisRenderer());
+ }
+
+ class EmphasisRenderer : HtmlObjectRenderer
+ {
+ protected override void Write(HtmlRenderer renderer, CustomEmphasisInline obj)
+ {
+ var tag = EmphasisTestDescriptors.First(test => test.Character == obj.DelimiterChar).Tags[obj.DelimiterCount];
+
+ renderer.Write(tag.OpeningTag);
+ renderer.WriteChildren(obj);
+ renderer.Write(tag.ClosingTag);
+ }
+ }
+ }
+ class Tag
+ {
+#pragma warning disable CS0649
+ public int Level;
+#pragma warning restore CS0649
+ public string RawTag;
+ public string OpeningTag;
+ public string ClosingTag;
+
+ public Tag(string tag)
+ {
+ RawTag = tag;
+ OpeningTag = "<" + tag + ">";
+ ClosingTag = "" + tag + ">";
+ }
+
+ public static implicit operator Tag(string tag)
+ => new Tag(tag);
+ }
+ class EmphasisTestDescriptor
+ {
+ public char Character;
+ public int Minimum;
+ public int Maximum;
+ public Dictionary Tags = new Dictionary();
+
+ private EmphasisTestDescriptor(char character, int min, int max)
+ {
+ Character = character;
+ Minimum = min;
+ Maximum = max;
+ }
+ public EmphasisTestDescriptor(char character, int min, int max, params Tag[] tags)
+ : this(character, min, max)
+ {
+ Debug.Assert(tags.Length == max - min + 1);
+ foreach (var tag in tags)
+ {
+ Tags.Add(min++, tag);
+ }
+ }
+ public EmphasisTestDescriptor(char character, int min, int max, string tag)
+ : this(character, min, max, new Tag(tag)) { }
+ }
+ class CustomEmphasisInline : EmphasisInline { }
+ static readonly EmphasisTestDescriptor[] EmphasisTestDescriptors = new[]
+ {
+ // Min Max
+ new EmphasisTestDescriptor('"', 1, 1, "quotation"),
+ new EmphasisTestDescriptor(',', 1, 2, "comma", "extra-comma"),
+ new EmphasisTestDescriptor('!', 2, 3, "warning", "error"),
+ new EmphasisTestDescriptor('=', 1, 3, "equal", "really-equal", "congruent"),
+ new EmphasisTestDescriptor('1', 1, 1, "one-only"),
+ new EmphasisTestDescriptor('2', 2, 2, "two-only"),
+ new EmphasisTestDescriptor('3', 3, 3, "three-only"),
+ };
+
+ static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder().Use().Build();
+
+ [Test]
+ [TestCase("*foo**", "foo*")]
+ [TestCase("**foo*", "*foo")]
+ [TestCase("***foo***", "foo")]
+ [TestCase("**_foo_**", "foo")]
+ [TestCase("_**foo**_", "foo")]
+ [TestCase("\"foo\"", "foo")]
+ [TestCase("\"\"foo\"\"", "foo")]
+ [TestCase("\"foo\"\"", "foo"")]
+ [TestCase("\"\"foo\"", ""foo")]
+ [TestCase(", foo", ", foo")]
+ [TestCase(", foo,", ", foo,")]
+ [TestCase(",some, foo,", "some foo,")]
+ [TestCase(",,foo,,", "foo")]
+ [TestCase(",foo,,", "foo,")]
+ [TestCase(",,,foo,,,", "foo")]
+ [TestCase("!1!", "!1!")]
+ [TestCase("!!2!!", "2")]
+ [TestCase("!!!3!!!", "3")]
+ [TestCase("!!!34!!!!", "34!")]
+ [TestCase("!!!!43!!!", "!43")]
+ [TestCase("!!!!44!!!!", "!44!")] // This is a new case - should the second ! be before or after ?
+ [TestCase("!!!!!5!!!!!", "5")]
+ [TestCase("!!!!!!6!!!!!!", "6")]
+ [TestCase("!! !mixed!!!", "!! !mixed!!!")] // can't open the delimiter because of the whitespace
+ [TestCase("=", "=")]
+ [TestCase("==", "==")]
+ [TestCase("====", "====")]
+ [TestCase("=a", "=a")]
+ [TestCase("=a=", "a")]
+ [TestCase("==a=", "=a")]
+ [TestCase("==a==", "a")]
+ [TestCase("==a===", "a=")]
+ [TestCase("===a===", "a")]
+ [TestCase("====a====", "a")]
+ [TestCase("=====a=====", "a")]
+ [TestCase("1", "1")]
+ [TestCase("1 1", "1 1")]
+ [TestCase("1Foo1", "Foo")]
+ [TestCase("1121", "12")]
+ [TestCase("22322", "3")]
+ [TestCase("2232", "2232")]
+ [TestCase("333", "333")]
+ [TestCase("3334333", "4")]
+ [TestCase("33334333", "34")]
+ [TestCase("33343333", "43")]
+ [TestCase("122122", "2222")]
+ [TestCase("221221", "11")]
+ [TestCase("122foo221", "foo")]
+ [TestCase("122foo122", "22foo22")]
+ [TestCase("!!!!!Attention:!! \"==1+1== 2\",but ===333 and 222===, mod 111!!!",
+ "Attention: + 2but 333 and 222 mod 111")]
+ public void TestEmphasis(string markdown, string expectedHtml)
+ {
+ TestParser.TestSpec(markdown, "" + expectedHtml + "
", Pipeline);
+ }
+ }
+}
diff --git a/src/Markdig.Tests/TestEmphasisPlus.cs b/src/Markdig.Tests/TestEmphasisPlus.cs
index 440230310..4822f543e 100644
--- a/src/Markdig.Tests/TestEmphasisPlus.cs
+++ b/src/Markdig.Tests/TestEmphasisPlus.cs
@@ -1,8 +1,6 @@
-ο»Ώ// Copyright (c) Alexandre Mutel. All rights reserved.
+// 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.
-
-using System;
using NUnit.Framework;
namespace Markdig.Tests
diff --git a/src/Markdig.Tests/TextAssert.cs b/src/Markdig.Tests/TextAssert.cs
index 5f9c78955..1521736d8 100644
--- a/src/Markdig.Tests/TextAssert.cs
+++ b/src/Markdig.Tests/TextAssert.cs
@@ -22,12 +22,12 @@ public enum DiffStyle
public static void AreEqual(string expectedValue, string actualValue)
{
- AreEqual(actualValue, expectedValue, DiffStyle.Full, Console.Out);
+ AreEqual(expectedValue, actualValue, DiffStyle.Full, Console.Out);
}
public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle)
{
- AreEqual(actualValue, expectedValue, diffStyle, Console.Out);
+ AreEqual(expectedValue, actualValue, diffStyle, Console.Out);
}
public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle, TextWriter output)
diff --git a/src/Markdig/Extensions/Citations/CitationExtension.cs b/src/Markdig/Extensions/Citations/CitationExtension.cs
index 296e3186b..7d04b1532 100644
--- a/src/Markdig/Extensions/Citations/CitationExtension.cs
+++ b/src/Markdig/Extensions/Citations/CitationExtension.cs
@@ -6,13 +6,14 @@
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
-
+using System.Diagnostics;
+
namespace Markdig.Extensions.Citations
{
///
/// Extension for cite ""...""
///
- ///
+ ///
public class CitationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -26,8 +27,7 @@ public void Setup(MarkdownPipelineBuilder pipeline)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
- var htmlRenderer = renderer as HtmlRenderer;
- if (htmlRenderer != null)
+ if (renderer is HtmlRenderer htmlRenderer)
{
// Extend the rendering here.
var emphasisRenderer = renderer.ObjectRenderers.FindExact();
@@ -42,7 +42,8 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
private static string GetTag(EmphasisInline emphasisInline)
{
- return emphasisInline.IsDouble && emphasisInline.DelimiterChar == '"' ? "cite" : null;
+ Debug.Assert(emphasisInline.DelimiterCount <= 2);
+ return emphasisInline.DelimiterCount == 2 && emphasisInline.DelimiterChar == '"' ? "cite" : null;
}
}
}
\ No newline at end of file
diff --git a/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs b/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs
index 7876b9511..bd85868aa 100644
--- a/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs
+++ b/src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs
@@ -1,16 +1,16 @@
-ο»Ώ// Copyright (c) Alexandre Mutel. All rights reserved.
+// 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.
using Markdig.Parsers.Inlines;
-using Markdig.Renderers;
-
+using Markdig.Renderers;
+
namespace Markdig.Extensions.CustomContainers
{
///
/// Extension to allow custom containers.
///
- ///
+ ///
public class CustomContainerExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -26,22 +26,20 @@ public void Setup(MarkdownPipelineBuilder pipeline)
if (inlineParser != null && !inlineParser.HasEmphasisChar(':'))
{
inlineParser.EmphasisDescriptors.Add(new EmphasisDescriptor(':', 2, 2, true));
- var previousCreateEmphasisInline = inlineParser.CreateEmphasisInline;
- inlineParser.CreateEmphasisInline = (emphasisChar, strong) =>
- {
- if (strong && emphasisChar == ':')
- {
- return new CustomContainerInline();
- }
- return previousCreateEmphasisInline?.Invoke(emphasisChar, strong);
- };
+ inlineParser.TryCreateEmphasisInlineList.Add((emphasisChar, delimiterCount) =>
+ {
+ if (delimiterCount == 2 && emphasisChar == ':')
+ {
+ return new CustomContainerInline();
+ }
+ return null;
+ });
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
- var htmlRenderer = renderer as HtmlRenderer;
- if (htmlRenderer != null)
+ if (renderer is HtmlRenderer htmlRenderer)
{
if (!htmlRenderer.ObjectRenderers.Contains())
{
diff --git a/src/Markdig/Extensions/CustomContainers/HtmlCustomContainerInlineRenderer.cs b/src/Markdig/Extensions/CustomContainers/HtmlCustomContainerInlineRenderer.cs
index 63bbb1ca9..e659f20db 100644
--- a/src/Markdig/Extensions/CustomContainers/HtmlCustomContainerInlineRenderer.cs
+++ b/src/Markdig/Extensions/CustomContainers/HtmlCustomContainerInlineRenderer.cs
@@ -9,7 +9,7 @@ namespace Markdig.Extensions.CustomContainers
///
/// A HTML renderer for a .
///
- ///
+ ///
public class HtmlCustomContainerInlineRenderer : HtmlObjectRenderer
{
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
diff --git a/src/Markdig/Extensions/EmphasisExtras/EmphasisExtraExtension.cs b/src/Markdig/Extensions/EmphasisExtras/EmphasisExtraExtension.cs
index 0b915e937..d3d06a6c3 100644
--- a/src/Markdig/Extensions/EmphasisExtras/EmphasisExtraExtension.cs
+++ b/src/Markdig/Extensions/EmphasisExtras/EmphasisExtraExtension.cs
@@ -6,13 +6,14 @@
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
-
+using System.Diagnostics;
+
namespace Markdig.Extensions.EmphasisExtras
{
///
/// Extension for strikethrough, subscript, superscript, inserted and marked.
///
- ///
+ ///
public class EmphasisExtraExtension : IMarkdownExtension
{
///
@@ -89,8 +90,7 @@ public void Setup(MarkdownPipelineBuilder pipeline)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
- var htmlRenderer = renderer as HtmlRenderer;
- if (htmlRenderer != null)
+ if (renderer is HtmlRenderer htmlRenderer)
{
// Extend the rendering here.
var emphasisRenderer = htmlRenderer.ObjectRenderers.FindExact();
@@ -107,8 +107,9 @@ private string GetTag(EmphasisInline emphasisInline)
var c = emphasisInline.DelimiterChar;
switch (c)
{
- case '~':
- return emphasisInline.IsDouble ? "del" : "sub";
+ case '~':
+ Debug.Assert(emphasisInline.DelimiterCount <= 2);
+ return emphasisInline.DelimiterCount == 2 ? "del" : "sub";
case '^':
return "sup";
case '+':
diff --git a/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs b/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs
index 448d09b79..28b1f47a6 100644
--- a/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs
+++ b/src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs
@@ -26,15 +26,15 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// We are matching the following characters:
//
- // ' ‘ ’ 'left-single-quote', 'right-single-quote'
- // '' “ ” 'left-double-quote', 'right-double-quote'
- // " “ ” 'left-double-quote', 'right-double-quote'
- // << >> « » « » 'left-angle-quote', 'right-angle-quote'
- // ...
… 'ellipsis'
+ // ' β β ‘ ’ 'left-single-quote', 'right-single-quote'
+ // '' β β “ ” 'left-double-quote', 'right-double-quote'
+ // " β β “ ” 'left-double-quote', 'right-double-quote'
+ // << >> Β« Β» « » 'left-angle-quote', 'right-angle-quote'
+ // ... β¦ … 'ellipsis'
// Special case: – and — are handle as a PostProcess step to avoid conflicts with pipetables header separator row
- // -- – 'ndash'
- // --- — 'mdash'
+ // -- β – 'ndash'
+ // --- β — 'mdash'
var pc = slice.PeekCharExtra(-1);
var c = slice.CurrentChar;
diff --git a/src/Markdig/Helpers/CharHelper.cs b/src/Markdig/Helpers/CharHelper.cs
index 5125abd96..5e6da0bdd 100644
--- a/src/Markdig/Helpers/CharHelper.cs
+++ b/src/Markdig/Helpers/CharHelper.cs
@@ -71,12 +71,8 @@ public static void CheckOpenCloseDelimiter(char pc, char c, bool enableWithinWor
// (b) either not followed by a punctuation character, or preceded by Unicode whitespace
// or a punctuation character.
// For purposes of this definition, the beginning and the end of the line count as Unicode whitespace.
- bool nextIsPunctuation;
- bool nextIsWhiteSpace;
- bool prevIsPunctuation;
- bool prevIsWhiteSpace;
- pc.CheckUnicodeCategory(out prevIsWhiteSpace, out prevIsPunctuation);
- c.CheckUnicodeCategory(out nextIsWhiteSpace, out nextIsPunctuation);
+ pc.CheckUnicodeCategory(out bool prevIsWhiteSpace, out bool prevIsPunctuation);
+ c.CheckUnicodeCategory(out bool nextIsWhiteSpace, out bool nextIsPunctuation);
var prevIsExcepted = prevIsPunctuation && punctuationExceptions.Contains(pc);
var nextIsExcepted = nextIsPunctuation && punctuationExceptions.Contains(c);
diff --git a/src/Markdig/Markdig.csproj b/src/Markdig/Markdig.csproj
index 2ff3212a6..c9d980d18 100644
--- a/src/Markdig/Markdig.csproj
+++ b/src/Markdig/Markdig.csproj
@@ -18,6 +18,7 @@
https://github.com/lunet-io/markdig
1.6.0
true
+ 7.3
diff --git a/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs b/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs
index 50228094f..5c76d64ba 100644
--- a/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs
+++ b/src/Markdig/Parsers/Inlines/EmphasisDescriptor.cs
@@ -23,7 +23,6 @@ public EmphasisDescriptor(char character, int minimumCount, int maximumCount, bo
if (minimumCount < 1) throw new ArgumentOutOfRangeException(nameof(minimumCount), "minimumCount must be >= 1");
if (maximumCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumCount), "maximumCount must be >= 1");
if (minimumCount > maximumCount) throw new ArgumentOutOfRangeException(nameof(minimumCount), "minimumCount must be <= maximumCount");
- if (maximumCount > 2) throw new ArgumentOutOfRangeException(nameof(maximumCount), "maximum must be <= 2");
Character = character;
MinimumCount = minimumCount;
@@ -42,7 +41,7 @@ public EmphasisDescriptor(char character, int minimumCount, int maximumCount, bo
public readonly int MinimumCount;
///
- /// The maximum number of character this emphasis is expected to have (must be >=1 and >= minumunCount and <= 2)
+ /// The maximum number of character this emphasis is expected to have (must be >=1 and >= minumunCount)
///
public readonly int MaximumCount;
diff --git a/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs b/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs
index 327ffeeab..888845aa4 100644
--- a/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs
+++ b/src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using Markdig.Helpers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
@@ -14,14 +15,16 @@ namespace Markdig.Parsers.Inlines
///
/// An inline parser for .
///
- ///
+ ///
///
public class EmphasisInlineParser : InlineParser, IPostInlineProcessor
{
private CharacterMap emphasisMap;
- private readonly DelimitersObjectCache inlinesCache;
+ private readonly DelimitersObjectCache inlinesCache = new DelimitersObjectCache();
+ [Obsolete("Use TryCreateEmphasisInlineDelegate instead", error: false)]
public delegate EmphasisInline CreateEmphasisInlineDelegate(char emphasisChar, bool isStrong);
+ public delegate EmphasisInline TryCreateEmphasisInlineDelegate(char emphasisChar, int delimiterCount);
///
/// Initializes a new instance of the class.
@@ -33,7 +36,6 @@ public EmphasisInlineParser()
new EmphasisDescriptor('*', 1, 2, true),
new EmphasisDescriptor('_', 1, 2, false)
};
- inlinesCache = new DelimitersObjectCache();
}
///
@@ -61,7 +63,9 @@ public bool HasEmphasisChar(char c)
///
/// Gets or sets the create emphasis inline delegate (allowing to create a different emphasis inline class)
///
+ [Obsolete("Use TryCreateEmphasisInlineList instead", error: false)]
public CreateEmphasisInlineDelegate CreateEmphasisInline { get; set; }
+ public readonly List TryCreateEmphasisInlineList = new List();
public override void Initialize()
{
@@ -87,17 +91,16 @@ public override void Initialize()
public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex, bool isFinalProcessing)
{
- var container = root as ContainerInline;
- if (container == null)
+ if (!(root is ContainerInline container))
{
return true;
}
List delimiters = null;
- if (container is EmphasisDelimiterInline)
+ if (container is EmphasisDelimiterInline emphasisDelimiter)
{
delimiters = inlinesCache.Get();
- delimiters.Add((EmphasisDelimiterInline)container);
+ delimiters.Add(emphasisDelimiter);
}
// Move current_position forward in the delimiter stack (if needed) until
@@ -109,8 +112,7 @@ public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, in
{
break;
}
- var delimiter = child as EmphasisDelimiterInline;
- if (delimiter != null)
+ if (child is EmphasisDelimiterInline delimiter)
{
if (delimiters == null)
{
@@ -131,18 +133,17 @@ public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, in
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
- {
- // First, some definitions. A delimiter run is either a sequence of one or more * characters that
- // is not preceded or followed by a * character, or a sequence of one or more _ characters that
- // is not preceded or followed by a _ character.
+ {
+ // First, some definitions.
+ // A delimiter run is a sequence of one or more delimiter characters that is not preceded or followed by the same delimiter character
+ // The amount of delimiter characters in the delimiter run may exceed emphasisDesc.MaximumCount, as that is handeled in `ProcessEmphasis`
var delimiterChar = slice.CurrentChar;
var emphasisDesc = emphasisMap[delimiterChar];
- var pc = (char)0;
- if (processor.Inline is HtmlEntityInline)
+ char pc = (char)0;
+ if (processor.Inline is HtmlEntityInline htmlEntityInline)
{
- var htmlEntityInline = (HtmlEntityInline) processor.Inline;
if (htmlEntityInline.Transcoded.Length > 0)
{
pc = htmlEntityInline.Transcoded[htmlEntityInline.Transcoded.End];
@@ -152,7 +153,10 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
- {
+ {
+ // If we get here, we determined that either:
+ // a) there weren't enough delimiters in the delimiter run to satisfy the MinimumCount condition
+ // b) the previous character couldn't open/close
return false;
}
}
@@ -174,38 +178,26 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
}
// The following character is actually an entity, we need to decode it
- int htmlLength;
- string htmlString;
- if (HtmlEntityParser.TryParse(ref slice, out htmlString, out htmlLength))
+ if (HtmlEntityParser.TryParse(ref slice, out string htmlString, out int htmlLength))
{
c = htmlString[0];
}
// Calculate Open-Close for current character
- bool canOpen;
- bool canClose;
- CharHelper.CheckOpenCloseDelimiter(pc, c, emphasisDesc.EnableWithinWord, out canOpen, out canClose);
+ CharHelper.CheckOpenCloseDelimiter(pc, c, emphasisDesc.EnableWithinWord, out bool canOpen, out bool canClose);
// We have potentially an open or close emphasis
if (canOpen || canClose)
{
var delimiterType = DelimiterType.Undefined;
- if (canOpen)
- {
- delimiterType |= DelimiterType.Open;
- }
- if (canClose)
- {
- delimiterType |= DelimiterType.Close;
- }
+ if (canOpen) delimiterType |= DelimiterType.Open;
+ if (canClose) delimiterType |= DelimiterType.Close;
- int line;
- int column;
var delimiter = new EmphasisDelimiterInline(this, emphasisDesc)
{
DelimiterCount = delimiterCount,
Type = delimiterType,
- Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.Start - 1)),
+ Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
Column = column,
Line = line,
};
@@ -223,25 +215,27 @@ private void ProcessEmphasis(InlineProcessor processor, List 0)
+ if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount)
{
while (true)
{
// Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type)
// for the first matching potential opener (βmatchingβ means same delimiter).
EmphasisDelimiterInline openDelimiter = null;
- EmphasisDescriptor emphasisDesc = null;
int openDelimiterIndex = -1;
for (int j = i - 1; j >= 0; j--)
{
@@ -254,32 +248,54 @@ private void ProcessEmphasis(InlineProcessor processor, List 0 && !isOddMatch)
+ previousOpenDelimiter.DelimiterCount >= emphasisDesc.MinimumCount && !isOddMatch)
{
openDelimiter = previousOpenDelimiter;
- emphasisDesc = emphasisMap[previousOpenDelimiter.DelimiterChar];
openDelimiterIndex = j;
break;
}
}
if (openDelimiter != null)
- {
- process_delims:
- bool isStrong = openDelimiter.DelimiterCount >= 2 && closeDelimiter.DelimiterCount >= 2 && emphasisDesc.MaximumCount >= 2;
-
- // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener.
- var emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong)
- ?? new EmphasisInline()
- {
- DelimiterChar = closeDelimiter.DelimiterChar,
- IsDouble = isStrong
- };
+ {
+ process_delims:
+ Debug.Assert(openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount, "Extra emphasis should have been discarded by now");
+ Debug.Assert(closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount, "Extra emphasis should have been discarded by now");
+ int delimiterDelta = Math.Min(Math.Min(openDelimiter.DelimiterCount, closeDelimiter.DelimiterCount), emphasisDesc.MaximumCount);
+
+ // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener.
+ EmphasisInline emphasis = null;
+ {
+ if (delimiterDelta <= 2) // We can try using the legacy delegate
+ {
+ #pragma warning disable CS0618 // Support fields marked as obsolete
+ emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong: delimiterDelta == 2);
+ #pragma warning restore CS0618 // Support fields marked as obsolete
+ }
+ if (emphasis == null)
+ {
+ // Go in backwards order to give priority to newer delegates
+ for (int delegateIndex = TryCreateEmphasisInlineList.Count - 1; delegateIndex >= 0; delegateIndex--)
+ {
+ emphasis = TryCreateEmphasisInlineList[delegateIndex].Invoke(closeDelimiter.DelimiterChar, delimiterDelta);
+ if (emphasis != null) break;
+ }
+
+ if (emphasis == null)
+ {
+ emphasis = new EmphasisInline()
+ {
+ DelimiterChar = closeDelimiter.DelimiterChar,
+ DelimiterCount = delimiterDelta
+ };
+ }
+ }
+ }
+ Debug.Assert(emphasis != null);
// Update position for emphasis
var openDelimitercount = openDelimiter.DelimiterCount;
var closeDelimitercount = closeDelimiter.DelimiterCount;
- var delimiterDelta = isStrong ? 2 : 1;
emphasis.Span.Start = openDelimiter.Span.Start;
emphasis.Line = openDelimiter.Line;
@@ -310,17 +326,7 @@ private void ProcessEmphasis(InlineProcessor processor, List= openDelimiterIndex + 1; k--)
{
var literalDelimiter = delimiters[k];
- var literal = new LiteralInline()
- {
- Content = new StringSlice(literalDelimiter.ToLiteral()),
- IsClosed = true,
- Span = literalDelimiter.Span,
- Line = literalDelimiter.Line,
- Column = literalDelimiter.Column
- };
-
- literalDelimiter.ReplaceBy(literal);
-
+ literalDelimiter.ReplaceBy(literalDelimiter.AsLiteralInline());
delimiters.RemoveAt(k);
i--;
}
@@ -345,10 +351,17 @@ private void ProcessEmphasis(InlineProcessor processor, List 0)
+ if (openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount)
{
goto process_delims;
}
+ else if (openDelimiter.DelimiterCount > 0)
+ {
+ // There are still delimiter characters left, there's just not enough of them
+ openDelimiter.ReplaceBy(openDelimiter.AsLiteralInline());
+ delimiters.RemoveAt(openDelimiterIndex);
+ i--;
+ }
else
{
// Remove the open delimiter if it is also empty
@@ -364,17 +377,7 @@ private void ProcessEmphasis(InlineProcessor processor, List
/// A HTML renderer for an .
///
- ///
+ ///
public class EmphasisInlineRenderer : HtmlObjectRenderer
{
///
@@ -55,7 +56,8 @@ public string GetDefaultTag(EmphasisInline obj)
{
if (obj.DelimiterChar == '*' || obj.DelimiterChar == '_')
{
- return obj.IsDouble ? "strong" : "em";
+ Debug.Assert(obj.DelimiterCount <= 2);
+ return obj.DelimiterCount == 2 ? "strong" : "em";
}
return null;
}
diff --git a/src/Markdig/Renderers/Normalize/Inlines/EmphasisInlineRenderer.cs b/src/Markdig/Renderers/Normalize/Inlines/EmphasisInlineRenderer.cs
index 36633f99d..a258ffd93 100644
--- a/src/Markdig/Renderers/Normalize/Inlines/EmphasisInlineRenderer.cs
+++ b/src/Markdig/Renderers/Normalize/Inlines/EmphasisInlineRenderer.cs
@@ -8,12 +8,12 @@ namespace Markdig.Renderers.Normalize.Inlines
///
/// A Normalize renderer for an .
///
- ///
+ ///
public class EmphasisInlineRenderer : NormalizeObjectRenderer
{
protected override void Write(NormalizeRenderer renderer, EmphasisInline obj)
{
- var emphasisText = new string(obj.DelimiterChar, obj.IsDouble ? 2 : 1);
+ var emphasisText = new string(obj.DelimiterChar, obj.DelimiterCount);
renderer.Write(emphasisText);
renderer.WriteChildren(obj);
renderer.Write(emphasisText);
diff --git a/src/Markdig/Syntax/Inlines/EmphasisDelimiterInline.cs b/src/Markdig/Syntax/Inlines/EmphasisDelimiterInline.cs
index dd2291c53..b1e908c67 100644
--- a/src/Markdig/Syntax/Inlines/EmphasisDelimiterInline.cs
+++ b/src/Markdig/Syntax/Inlines/EmphasisDelimiterInline.cs
@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using System;
+using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
@@ -11,7 +12,7 @@ namespace Markdig.Syntax.Inlines
///
/// A delimiter used for parsing emphasis.
///
- ///
+ ///
public class EmphasisDelimiterInline : DelimiterInline
{
///
@@ -19,11 +20,10 @@ public class EmphasisDelimiterInline : DelimiterInline
///
/// The parser.
/// The descriptor.
- ///
+ ///
public EmphasisDelimiterInline(InlineParser parser, EmphasisDescriptor descriptor) : base(parser)
{
- if (descriptor == null) throw new ArgumentNullException(nameof(descriptor));
- Descriptor = descriptor;
+ Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
DelimiterChar = descriptor.Character;
}
@@ -46,5 +46,17 @@ public override string ToLiteral()
{
return DelimiterCount > 0 ? new string(DelimiterChar, DelimiterCount) : string.Empty;
}
+
+ public LiteralInline AsLiteralInline()
+ {
+ return new LiteralInline()
+ {
+ Content = new StringSlice(ToLiteral()),
+ IsClosed = true,
+ Span = Span,
+ Line = Line,
+ Column = Column
+ };
+ }
}
}
\ No newline at end of file
diff --git a/src/Markdig/Syntax/Inlines/EmphasisInline.cs b/src/Markdig/Syntax/Inlines/EmphasisInline.cs
index 80d7f03d9..65dd5a1c4 100644
--- a/src/Markdig/Syntax/Inlines/EmphasisInline.cs
+++ b/src/Markdig/Syntax/Inlines/EmphasisInline.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 System;
using System.Diagnostics;
namespace Markdig.Syntax.Inlines
@@ -9,8 +10,8 @@ namespace Markdig.Syntax.Inlines
///
/// An emphasis and strong emphasis (Section 6.4 CommonMark specs).
///
- ///
- [DebuggerDisplay("{DelimiterChar} Strong: {IsDouble}")]
+ ///
+ [DebuggerDisplay("{DelimiterChar} Count: {DelimiterCount}")]
public class EmphasisInline : ContainerInline
{
///
@@ -20,7 +21,18 @@ public class EmphasisInline : ContainerInline
///
/// Gets or sets a value indicating whether this is strong.
+ /// Marked obsolete as EmphasisInline can now be represented by more than two delimiter characters
///
- public bool IsDouble { get; set; }
+ [Obsolete("Use `DelimiterCount == 2` instead", error: false)]
+ public bool IsDouble
+ {
+ get => DelimiterCount == 2;
+ set => DelimiterCount = value ? 2 : 1;
+ }
+
+ ///
+ /// Gets or sets the number of delimiter characters for this emphasis.
+ ///
+ public int DelimiterCount { get; set; }
}
}
\ No newline at end of file