Compare commits

...

4 Commits

Author SHA1 Message Date
Alexandre Mutel
16a9bbc84e Bump to 0.14.2 2017-11-01 07:35:07 +01:00
Alexandre Mutel
0e5338a709 Add option to disable smiley parsing in EmojiAndSmiley extension 2017-11-01 07:34:57 +01:00
Alexandre Mutel
9139e0142b Fix issue with emphasis preceded/followed by an HTML entity (#157) 2017-11-01 07:26:28 +01:00
Alexandre Mutel
9a38312df0 Add support for link reference definitions for Normalize renderer (#155) 2017-10-28 09:26:14 +02:00
14 changed files with 199 additions and 37 deletions

View File

@@ -304,6 +304,24 @@ asdf
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy \"" Company"")");
}
[Test]
public void LinkReferenceDefinition()
{
// Full link
AssertNormalizeNoTrim("This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("[MyLink]: http://company.com\nThis is a [link][MyLink]",
"This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("This is a [link][MyLink] a normal link [link](http://google.com) and another def link [link2][MyLink2]\n\n[MyLink]: http://company.com\n[MyLink2]: http://company2.com");
// Collapsed link
AssertNormalizeNoTrim("This is a [link][]\n\n[link]: http://company.com");
// Shortcut link
AssertNormalizeNoTrim("This is a [link]\n\n[link]: http://company.com");
}
[Test]
public void EscapeInline()
{

View File

@@ -1,4 +1,4 @@
// 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;
@@ -6,11 +6,19 @@ using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using NUnit.Framework;
namespace Markdig.Tests
{
public class TestParser
{
[Test]
public void TestEmphasisAndHtmlEntity()
{
var markdownText = "*Unlimited-Fun®*®";
TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null)
{
foreach (var pipeline in GetPipeline(extensions))

View File

@@ -1,4 +1,4 @@
// 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.
@@ -12,12 +12,19 @@ namespace Markdig.Extensions.Emoji
/// <seealso cref="Markdig.IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
private readonly bool _enableSmiley;
public EmojiExtension(bool enableSmiley = true)
{
_enableSmiley = enableSmiley;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new EmojiParser());
pipeline.InlineParsers.Insert(0, new EmojiParser(_enableSmiley));
}
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -24,13 +24,19 @@ namespace Markdig.Extensions.Emoji
/// <summary>
/// Initializes a new instance of the <see cref="EmojiParser"/> class.
/// </summary>
public EmojiParser()
public EmojiParser(bool enableSmiley = true)
{
EnableSmiley = enableSmiley;
OpeningCharacters = null;
EmojiToUnicode = new Dictionary<string, string>(EmojiToUnicodeDefault);
SmileyToEmoji = new Dictionary<string, string>(SmileyToEmojiDefault);
}
/// <summary>
/// Gets or sets a boolean indicating whether to process smiley.
/// </summary>
public bool EnableSmiley { get; set; }
/// <summary>
/// Gets the emoji to unicode mapping. This can be modified before this parser is initialized.
/// </summary>
@@ -81,11 +87,14 @@ namespace Markdig.Extensions.Emoji
return false;
}
// If we have a smiley, we decode it to emoji
string emoji;
if (!SmileyToEmoji.TryGetValue(match, out emoji))
string emoji = match;
if (EnableSmiley)
{
emoji = match;
// If we have a smiley, we decode it to emoji
if (!SmileyToEmoji.TryGetValue(match, out emoji))
{
emoji = match;
}
}
// Decode the eomji to unicode

View File

@@ -5,7 +5,7 @@
<Copyright>Alexandre Mutel</Copyright>
<AssemblyTitle>Markdig</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.14.1</VersionPrefix>
<VersionPrefix>0.14.2</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;uap10.0</TargetFrameworks>
<AssemblyName>Markdig</AssemblyName>
@@ -13,6 +13,10 @@
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>
&gt; 0.14.2
- Fix issue with emphasis preceded/followed by an HTML entity (#157)
- Add support for link reference definitions for Normalize renderer (#155)
- Add option to disable smiley parsing in EmojiAndSmiley extension
&gt; 0.14.1
- Fix crash in Markdown.Normalize to handle HtmlBlock correctly
- Add better handling of bullet character for lists in Markdown.Normalize

View File

@@ -1,4 +1,4 @@
// 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.
@@ -409,10 +409,14 @@ namespace Markdig
/// Uses the emoji and smiley extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
{
pipeline.Extensions.AddIfNotAlready<EmojiExtension>();
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
}
return pipeline;
}

View File

@@ -101,7 +101,7 @@ namespace Markdig.Parsers.Inlines
}
// Move current_position forward in the delimiter stack (if needed) until
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
var child = container.LastChild;
while (child != null)
{
@@ -138,12 +138,24 @@ namespace Markdig.Parsers.Inlines
var delimiterChar = slice.CurrentChar;
var emphasisDesc = emphasisMap[delimiterChar];
var pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
{
return false;
}
var pc = (char)0;
if (processor.Inline is HtmlEntityInline)
{
var htmlEntityInline = (HtmlEntityInline) processor.Inline;
if (htmlEntityInline.Transcoded.Length > 0)
{
pc = htmlEntityInline.Transcoded[htmlEntityInline.Transcoded.End];
}
}
if (pc == 0)
{
pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
{
return false;
}
}
var startPosition = slice.Start;
int delimiterCount = 0;
@@ -161,6 +173,14 @@ namespace Markdig.Parsers.Inlines
return false;
}
// 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))
{
c = htmlString[0];
}
// Calculate Open-Close for current character
bool canOpen;
bool canClose;
@@ -204,7 +224,7 @@ namespace Markdig.Parsers.Inlines
// at the end of the CommonMark specs.
// Move current_position forward in the delimiter stack (if needed) until
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
for (int i = 0; i < delimiters.Count; i++)
{
var closeDelimiter = delimiters[i];
@@ -219,7 +239,7 @@ namespace Markdig.Parsers.Inlines
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).
// for the first matching potential opener (“matching” means same delimiter).
EmphasisDelimiterInline openDelimiter = null;
int openDelimiterIndex = -1;
for (int j = i - 1; j >= 0; j--)

View File

@@ -21,18 +21,18 @@ namespace Markdig.Parsers.Inlines
OpeningCharacters = new[] {'&'};
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
public static bool TryParse(ref StringSlice slice, out string literal, out int match)
{
literal = null;
string entityName;
int entityValue;
var startPosition = slice.Start;
int match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
if (match == 0)
{
return false;
}
string literal = null;
if (entityName != null)
{
literal = EntityHelper.DecodeEntity(entityName);
@@ -41,6 +41,19 @@ namespace Markdig.Parsers.Inlines
{
literal = (entityValue == 0 ? null : EntityHelper.DecodeEntity(entityValue)) ?? CharHelper.ZeroSafeString;
}
return true;
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
int match;
string literal;
if (!TryParse(ref slice, out literal, out match))
{
return false;
}
var startPosition = slice.Start;
if (literal != null)
{

View File

@@ -86,7 +86,7 @@ namespace Markdig.Parsers.Inlines
}
}
// If we dont find one, we return a literal slice node ].
// If we dont find one, we return a literal slice node ].
// (Done after by the LiteralInline parser)
return false;
}
@@ -95,7 +95,7 @@ namespace Markdig.Parsers.Inlines
return false;
}
private bool ProcessLinkReference(InlineProcessor state, string label, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
private bool ProcessLinkReference(InlineProcessor state, string label, bool isShortcut, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
{
bool isValidLink = false;
LinkReferenceDefinition linkRef;
@@ -120,6 +120,7 @@ namespace Markdig.Parsers.Inlines
LabelSpan = labelSpan,
UrlSpan = linkRef.UrlSpan,
IsImage = parent.IsImage,
IsShortcut = isShortcut,
Reference = linkRef,
Span = new SourceSpan(parent.Span.Start, endPosition),
Line = parent.Line,
@@ -189,7 +190,7 @@ namespace Markdig.Parsers.Inlines
if (openParent != null)
{
// If we do find one, but its not active,
// If we do find one, but its not active,
// we remove the inactive delimiter from the stack,
// and return a literal text node ].
if (!openParent.IsActive)
@@ -205,7 +206,7 @@ namespace Markdig.Parsers.Inlines
return false;
}
// If we find one and its active,
// If we find one and its active,
// then we parse ahead to see if we have
// an inline link/image, reference link/image,
// compact reference link/image,
@@ -261,6 +262,8 @@ namespace Markdig.Parsers.Inlines
var labelSpan = SourceSpan.Empty;
string label = null;
bool isLabelSpanLocal = true;
bool isShortcut = false;
// Handle Collapsed links
if (text.CurrentChar == '[')
{
@@ -276,6 +279,7 @@ namespace Markdig.Parsers.Inlines
else
{
label = openParent.Label;
isShortcut = true;
}
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
@@ -285,7 +289,7 @@ namespace Markdig.Parsers.Inlines
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
}
if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
if (ProcessLinkReference(inlineState, label, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
{
// Remove the open parent
openParent.Remove();

View File

@@ -20,18 +20,40 @@ namespace Markdig.Renderers.Normalize.Inlines
renderer.Write('[');
renderer.WriteChildren(link);
renderer.Write(']');
if (!string.IsNullOrEmpty(link.Url))
if (link.Label != null)
{
renderer.Write('(').Write(link.Url);
if (!string.IsNullOrEmpty(link.Title))
var literal = link.FirstChild as LiteralInline;
if (literal != null && literal.Content.Match(link.Label) && literal.Content.Length == link.Label.Length)
{
renderer.Write(" \"");
renderer.Write(link.Title.Replace(@"""", @"\"""));
renderer.Write("\"");
// collapsed reference and shortcut links
if (!link.IsShortcut)
{
renderer.Write("[]");
}
}
else
{
// full link
renderer.Write('[').Write(link.Label).Write(']');
}
}
else
{
if (!string.IsNullOrEmpty(link.Url))
{
renderer.Write('(').Write(link.Url);
renderer.Write(')');
if (!string.IsNullOrEmpty(link.Title))
{
renderer.Write(" \"");
renderer.Write(link.Title.Replace(@"""", @"\"""));
renderer.Write("\"");
}
renderer.Write(')');
}
}
}
}

View File

@@ -0,0 +1,18 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
public class LinkReferenceDefinitionGroupRenderer : NormalizeObjectRenderer<LinkReferenceDefinitionGroup>
{
protected override void Write(NormalizeRenderer renderer, LinkReferenceDefinitionGroup obj)
{
renderer.EnsureLine();
renderer.WriteChildren(obj);
renderer.FinishBlock(false);
}
}
}

View File

@@ -0,0 +1,28 @@
// 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.Syntax;
namespace Markdig.Renderers.Normalize
{
public class LinkReferenceDefinitionRenderer : NormalizeObjectRenderer<LinkReferenceDefinition>
{
protected override void Write(NormalizeRenderer renderer, LinkReferenceDefinition linkDef)
{
renderer.EnsureLine();
renderer.Write('[');
renderer.Write(linkDef.Label);
renderer.Write("]: ");
renderer.Write(linkDef.Url);
if (linkDef.Title != null)
{
renderer.Write(" \"");
renderer.Write(linkDef.Title.Replace("\"", "\\\""));
renderer.Write('"');
}
renderer.FinishBlock(false);
}
}
}

View File

@@ -31,6 +31,8 @@ namespace Markdig.Renderers.Normalize
ObjectRenderers.Add(new ParagraphRenderer());
ObjectRenderers.Add(new QuoteBlockRenderer());
ObjectRenderers.Add(new ThematicBreakRenderer());
ObjectRenderers.Add(new LinkReferenceDefinitionGroupRenderer());
ObjectRenderers.Add(new LinkReferenceDefinitionRenderer());
// Default inline renderers
ObjectRenderers.Add(new AutolinkInlineRenderer());

View File

@@ -64,6 +64,11 @@ namespace Markdig.Syntax.Inlines
/// </summary>
public bool IsImage { get; set; }
/// <summary>
/// Gets or sets a boolean indicating if this link is a shortcut link to a <see cref="LinkReferenceDefinition"/>
/// </summary>
public bool IsShortcut { get; set; }
/// <summary>
/// Gets or sets the reference this link is attached to. May be null.
/// </summary>