Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved emphasis parser #301

Merged
merged 8 commits into from
Feb 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Markdig.Tests/Markdig.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<Compile Include="Specs\SmartyPantsSpecs.cs" />
<Compile Include="Specs\TaskListSpecs.cs" />
<Compile Include="Specs\YamlSpecs.cs" />
<Compile Include="TestEmphasisExtended.cs" />
<Compile Include="TestEmphasisPlus.cs" />
<Compile Include="TestEmphasisExtraOptions.cs" />
<Compile Include="TestDescendantsOrder.cs" />
Expand Down
168 changes: 168 additions & 0 deletions src/Markdig.Tests/TestEmphasisExtended.cs
Original file line number Diff line number Diff line change
@@ -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<EmphasisInlineParser>();
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<CustomEmphasisInline>
{
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<int, Tag> Tags = new Dictionary<int, Tag>();

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<EmphasisTestExtension>().Build();

[Test]
[TestCase("*foo**", "<em>foo</em>*")]
[TestCase("**foo*", "*<em>foo</em>")]
[TestCase("***foo***", "<em><strong>foo</strong></em>")]
[TestCase("**_foo_**", "<strong><em>foo</em></strong>")]
[TestCase("_**foo**_", "<em><strong>foo</strong></em>")]
[TestCase("\"foo\"", "<quotation>foo</quotation>")]
[TestCase("\"\"foo\"\"", "<quotation><quotation>foo</quotation></quotation>")]
[TestCase("\"foo\"\"", "<quotation>foo</quotation>&quot;")]
[TestCase("\"\"foo\"", "&quot;<quotation>foo</quotation>")]
[TestCase(", foo", ", foo")]
[TestCase(", foo,", ", foo,")]
[TestCase(",some, foo,", "<comma>some</comma> foo,")]
[TestCase(",,foo,,", "<extra-comma>foo</extra-comma>")]
[TestCase(",foo,,", "<comma>foo</comma>,")]
[TestCase(",,,foo,,,", "<comma><extra-comma>foo</extra-comma></comma>")]
[TestCase("!1!", "!1!")]
[TestCase("!!2!!", "<warning>2</warning>")]
[TestCase("!!!3!!!", "<error>3</error>")]
[TestCase("!!!34!!!!", "<error>34</error>!")]
[TestCase("!!!!43!!!", "!<error>43</error>")]
[TestCase("!!!!44!!!!", "!<error>44!</error>")] // This is a new case - should the second ! be before or after </error>?
[TestCase("!!!!!5!!!!!", "<warning><error>5</error></warning>")]
[TestCase("!!!!!!6!!!!!!", "<error><error>6</error></error>")]
[TestCase("!! !mixed!!!", "!! !mixed!!!")] // can't open the delimiter because of the whitespace
[TestCase("=", "=")]
[TestCase("==", "==")]
[TestCase("====", "====")]
[TestCase("=a", "=a")]
[TestCase("=a=", "<equal>a</equal>")]
[TestCase("==a=", "=<equal>a</equal>")]
[TestCase("==a==", "<really-equal>a</really-equal>")]
[TestCase("==a===", "<really-equal>a</really-equal>=")]
[TestCase("===a===", "<congruent>a</congruent>")]
[TestCase("====a====", "<equal><congruent>a</congruent></equal>")]
[TestCase("=====a=====", "<really-equal><congruent>a</congruent></really-equal>")]
[TestCase("1", "1")]
[TestCase("1 1", "1 1")]
[TestCase("1Foo1", "<one-only>Foo</one-only>")]
[TestCase("1121", "1<one-only>2</one-only>")]
[TestCase("22322", "<two-only>3</two-only>")]
[TestCase("2232", "2232")]
[TestCase("333", "333")]
[TestCase("3334333", "<three-only>4</three-only>")]
[TestCase("33334333", "3<three-only>4</three-only>")]
[TestCase("33343333", "<three-only>4</three-only>3")]
[TestCase("122122", "<one-only>22</one-only>22")]
[TestCase("221221", "<two-only>1</two-only>1")]
[TestCase("122foo221", "<one-only><two-only>foo</two-only></one-only>")]
[TestCase("122foo122", "<one-only>22foo</one-only>22")]
[TestCase("!!!!!Attention:!! \"==1+1== 2\",but ===333 and 222===, mod 111!!!",
"<error><warning>Attention:</warning> <quotation><really-equal><one-only>+</one-only></really-equal> 2</quotation><comma>but <congruent>333 and 222</congruent></comma> mod 111</error>")]
public void TestEmphasis(string markdown, string expectedHtml)
{
TestParser.TestSpec(markdown, "<p>" + expectedHtml + "</p>", Pipeline);
}
}
}
4 changes: 1 addition & 3 deletions src/Markdig.Tests/TestEmphasisPlus.cs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Markdig.Tests/TextAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
11 changes: 6 additions & 5 deletions src/Markdig/Extensions/Citations/CitationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;

using System.Diagnostics;

namespace Markdig.Extensions.Citations
{
/// <summary>
/// Extension for cite ""...""
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class CitationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
Expand All @@ -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<EmphasisInlineRenderer>();
Expand All @@ -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;
}
}
}
28 changes: 13 additions & 15 deletions src/Markdig/Extensions/CustomContainers/CustomContainerExtension.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Extension to allow custom containers.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class CustomContainerExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
Expand All @@ -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<HtmlCustomContainerRenderer>())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Markdig.Extensions.CustomContainers
/// <summary>
/// A HTML renderer for a <see cref="CustomContainerInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{CustomContainerInline}" />
/// <seealso cref="Renderers.Html.HtmlObjectRenderer{CustomContainerInline}" />
public class HtmlCustomContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline>
{
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
Expand Down
13 changes: 7 additions & 6 deletions src/Markdig/Extensions/EmphasisExtras/EmphasisExtraExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;

using System.Diagnostics;

namespace Markdig.Extensions.EmphasisExtras
{
/// <summary>
/// Extension for strikethrough, subscript, superscript, inserted and marked.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class EmphasisExtraExtension : IMarkdownExtension
{
/// <summary>
Expand Down Expand Up @@ -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<EmphasisInlineRenderer>();
Expand All @@ -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 '+':
Expand Down
14 changes: 7 additions & 7 deletions src/Markdig/Extensions/SmartyPants/SmartyPantsInlineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// We are matching the following characters:
//
// ' � � &lsquo; &rsquo; 'left-single-quote', 'right-single-quote'
// '' � � &ldquo; &rdquo; 'left-double-quote', 'right-double-quote'
// " � � &ldquo; &rdquo; 'left-double-quote', 'right-double-quote'
// << >> � � &laquo; &raquo; 'left-angle-quote', 'right-angle-quote'
// ... &hellip; 'ellipsis'
// ' ‘ ’ &lsquo; &rsquo; 'left-single-quote', 'right-single-quote'
// '' “ ” &ldquo; &rdquo; 'left-double-quote', 'right-double-quote'
// " “ ” &ldquo; &rdquo; 'left-double-quote', 'right-double-quote'
// << >> « » &laquo; &raquo; 'left-angle-quote', 'right-angle-quote'
// ... &hellip; 'ellipsis'

// Special case: &ndash; and &mdash; are handle as a PostProcess step to avoid conflicts with pipetables header separator row
// -- &ndash; 'ndash'
// --- &mdash; 'mdash'
// -- &ndash; 'ndash'
// --- &mdash; 'mdash'

var pc = slice.PeekCharExtra(-1);
var c = slice.CurrentChar;
Expand Down
8 changes: 2 additions & 6 deletions src/Markdig/Helpers/CharHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/Markdig/Markdig.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>7.3</LangVersion>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
Expand Down
Loading