Skip to content

Commit

Permalink
Merge pull request #387 from MihaZupan/descendants-api
Browse files Browse the repository at this point in the history
Add missing Descendants<T> api
  • Loading branch information
xoofx authored Jan 21, 2020
2 parents 2e1b1a1 + fa2b157 commit 7875e4b
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 56 deletions.
88 changes: 78 additions & 10 deletions src/Markdig.Tests/TestDescendantsOrder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Markdig.Syntax.Inlines;
using System.Linq;
using System.Collections.Generic;
using Markdig.Helpers;

namespace Markdig.Tests
{
Expand All @@ -12,24 +13,91 @@ public class TestDescendantsOrder
[Test]
public void TestSchemas()
{
foreach (var markdown in TestParser.SpecsMarkdown)
foreach (var syntaxTree in TestParser.SpecsSyntaxTrees)
{
AssertSameDescendantsOrder(markdown);
AssertIEnumerablesAreEqual(
Descendants_Legacy(syntaxTree),
syntaxTree.Descendants());

AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
syntaxTree.Descendants<ParagraphBlock>());

AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
(syntaxTree as ContainerBlock).Descendants<ParagraphBlock>());

AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<LiteralInline>(),
syntaxTree.Descendants<LiteralInline>());

foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
{
Assert.AreSame(ArrayHelper<ListBlock>.Empty, literalInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, literalInline.Descendants<ParagraphBlock>());
Assert.AreSame(ArrayHelper<ContainerInline>.Empty, literalInline.Descendants<ContainerInline>());
}

foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
{
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());

AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
(containerInline as MarkdownObject).Descendants<LiteralInline>());

if (containerInline.FirstChild is null)
{
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerInline as MarkdownObject).Descendants<LiteralInline>());
}

Assert.AreSame(ArrayHelper<ListBlock>.Empty, containerInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, containerInline.Descendants<ParagraphBlock>());
}

foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
{
AssertIEnumerablesAreEqual(
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
paragraphBlock.Descendants<LiteralInline>());

Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, paragraphBlock.Descendants<ParagraphBlock>());
}

foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
{
AssertIEnumerablesAreEqual(
containerBlock.Descendants<LiteralInline>(),
(containerBlock as MarkdownObject).Descendants<LiteralInline>());

AssertIEnumerablesAreEqual(
containerBlock.Descendants<ParagraphBlock>(),
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());

if (containerBlock.Count == 0)
{
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as Block).Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as MarkdownObject).Descendants<LiteralInline>());
}
}
}
}

private void AssertSameDescendantsOrder(string markdown)
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
{
var syntaxTree = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());

var descendants_legacy = Descendants_Legacy(syntaxTree).ToList();
var descendants_new = syntaxTree.Descendants().ToList();
var firstList = new List<T>(first);
var secondList = new List<T>(second);

Assert.AreEqual(descendants_legacy.Count, descendants_new.Count);
Assert.AreEqual(firstList.Count, secondList.Count);

for (int i = 0; i < descendants_legacy.Count; i++)
for (int i = 0; i < firstList.Count; i++)
{
Assert.AreSame(descendants_legacy[i], descendants_new[i]);
Assert.AreSame(firstList[i], secondList[i]);
}
}

Expand Down
14 changes: 13 additions & 1 deletion src/Markdig.Tests/TestParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using Markdig.Syntax;
using NUnit.Framework;

namespace Markdig.Tests
Expand Down Expand Up @@ -151,6 +152,11 @@ private static string Compact(string html)
/// Contains the markdown source for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
/// </summary>
public static readonly string[] SpecsMarkdown;
/// <summary>
/// Contains the markdown syntax tree for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
/// </summary>
public static readonly MarkdownDocument[] SpecsSyntaxTrees;

static TestParser()
{
SpecsFilePaths = Directory.GetDirectories(TestsDirectory)
Expand All @@ -161,10 +167,16 @@ static TestParser()
.ToArray();

SpecsMarkdown = new string[SpecsFilePaths.Length];
SpecsSyntaxTrees = new MarkdownDocument[SpecsFilePaths.Length];

var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();

for (int i = 0; i < SpecsFilePaths.Length; i++)
{
SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
string markdown = SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
SpecsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Markdig.Tests/TestPlayParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void TestEmptyLiteral()
> some other text";
var doc = Markdown.Parse(text);

Assert.True(doc.Descendants().OfType<LiteralInline>().All(x => !x.Content.IsEmpty),
Assert.True(doc.Descendants<LiteralInline>().All(x => !x.Content.IsEmpty),
"There should not have any empty literals");
}

Expand Down
17 changes: 8 additions & 9 deletions src/Markdig.Tests/TestSourcePosition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Markdig.Extensions.Footnotes;
using Markdig.Helpers;
using Markdig.Extensions.Footnotes;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
Expand Down Expand Up @@ -147,7 +146,7 @@ public void TestHeadingWithEmphasis()
public void TestFootnoteLinkReferenceDefinition()
{
// 01 2 345678
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants().OfType<FootnoteLinkReferenceDefinition>().FirstOrDefault();
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants<FootnoteLinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(footnote);

Assert.AreEqual(2, footnote.Line);
Expand All @@ -160,7 +159,7 @@ public void TestLinkReferenceDefinition1()
{
// 0 1
// 0123456789012345
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);

Assert.AreEqual(0, link.Line);
Expand All @@ -175,7 +174,7 @@ public void TestLinkReferenceDefinition2()
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);

Assert.AreEqual(2, link.Line);
Expand Down Expand Up @@ -213,7 +212,7 @@ public void TestLinkParts1()
{
// 0 1
// 01 2 3456789012345
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
Assert.NotNull(link);

Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
Expand All @@ -226,7 +225,7 @@ public void TestLinkParts2()
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
Assert.NotNull(link);

Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
Expand All @@ -240,7 +239,7 @@ public void TestLinkParts3()
{
// 0 1
// 01 2 3456789012345
var link = Markdown.Parse("0\n\n01![234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
var link = Markdown.Parse("0\n\n01![234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
Assert.NotNull(link);

Assert.AreEqual(new SourceSpan(5, 15), link.Span);
Expand Down Expand Up @@ -408,7 +407,7 @@ 5. Foo
6. Bar
987123. FooBar";
test = test.Replace("\r\n", "\n");
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<ListBlock>().FirstOrDefault();
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<ListBlock>().FirstOrDefault();
Assert.NotNull(list);

Assert.AreEqual(1, list.Line);
Expand Down
5 changes: 2 additions & 3 deletions src/Markdig/Extensions/Bootstrap/BootstrapExtension.cs
Original file line number Diff line number Diff line change
@@ -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.

Expand Down Expand Up @@ -51,8 +51,7 @@ private static void PipelineOnDocumentProcessed(MarkdownDocument document)
}
else if (node is Inline)
{
var link = node as LinkInline;
if (link != null && link.IsImage)
if (node is LinkInline link && link.IsImage)
{
link.GetAttributes().AddClass("img-fluid");
}
Expand Down
19 changes: 8 additions & 11 deletions src/Markdig/Extensions/Globalization/GlobalizationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private void Pipeline_DocumentProcessed(MarkdownDocument document)
var attributes = node.GetAttributes();
attributes.AddPropertyIfNotExist("dir", "rtl");

if (node is Table table)
if (node is Table)
{
attributes.AddPropertyIfNotExist("align", "right");
}
Expand Down Expand Up @@ -71,29 +71,26 @@ private bool ShouldBeRightToLeft(MarkdownObject item)
}
else if (item is LiteralInline literal)
{
return StartsWithRtlCharacter(literal.ToString());
return StartsWithRtlCharacter(literal.Content);
}

foreach (var descendant in item.Descendants())
foreach (var paragraph in item.Descendants<ParagraphBlock>())
{
if (descendant is ParagraphBlock p)
foreach (var inline in paragraph.Inline)
{
foreach (var i in p.Inline)
if (inline is LiteralInline literal)
{
if (i is LiteralInline l)
{
return StartsWithRtlCharacter(l.ToString());
}
return StartsWithRtlCharacter(literal.Content);
}
}
}

return false;
}

private bool StartsWithRtlCharacter(string text)
private bool StartsWithRtlCharacter(StringSlice slice)
{
foreach (var c in CharHelper.ToUtf32(text))
foreach (var c in CharHelper.ToUtf32(slice))
{
if (CharHelper.IsRightToLeft(c))
return true;
Expand Down
27 changes: 16 additions & 11 deletions src/Markdig/Helpers/CharHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public static bool IsWhitespace(this char c)
{
// 2.1 Characters and lines
// A whitespace character is a space(U + 0020), tab(U + 0009), newline(U + 000A), line tabulation (U + 000B), form feed (U + 000C), or carriage return (U + 000D).
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
return c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r');
}

[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
Expand Down Expand Up @@ -303,8 +303,8 @@ public static bool IsAlphaUpper(this char c)

[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsAlpha(this char c)
{
return ((uint)(c - 'a') <= ('z' - 'a')) || ((uint)(c - 'A') <= ('Z' - 'A'));
{
return (uint)((c - 'A') & ~0x20) <= ('Z' - 'A');
}

[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
Expand Down Expand Up @@ -370,16 +370,20 @@ public static bool IsEmailUsernameSpecialChar(char c)

[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsHighSurrogate(char c)
{
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
{
return IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd);
}

[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsLowSurrogate(char c)
{
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd);
}

[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsInInclusiveRange(char c, char min, char max)
=> (uint) (c - min) <= (uint) (max - min);

public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
{
if (!IsHighSurrogate(highSurrogate))
Expand All @@ -393,13 +397,14 @@ public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
}

public static IEnumerable<int> ToUtf32(string text)
public static IEnumerable<int> ToUtf32(StringSlice text)
{
for (int i = 0; i < text.Length; i++)
for (int i = text.Start; i <= text.End; i++)
{
if (IsHighSurrogate(text[i]) && i < text.Length - 1 && IsLowSurrogate(text[i + 1]))
{
yield return ConvertToUtf32(text[i], text[i + 1]);
if (IsHighSurrogate(text[i]) && i < text.End && IsLowSurrogate(text[i + 1]))
{
Debug.Assert(char.IsSurrogatePair(text[i], text[i + 1]));
yield return char.ConvertToUtf32(text[i], text[i + 1]);
}
else
{
Expand Down
18 changes: 16 additions & 2 deletions src/Markdig/Syntax/Inlines/ContainerInline.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// 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.Helpers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

namespace Markdig.Syntax.Inlines
Expand Down Expand Up @@ -94,8 +96,20 @@ public bool ContainsChild(Inline childToFind)
/// <returns>An enumeration of T</returns>
public IEnumerable<T> FindDescendants<T>() where T : Inline
{
// Fast-path an empty container to avoid allocating a Stack
if (LastChild == null) yield break;
if (FirstChild is null)
{
return ArrayHelper<T>.Empty;
}
else
{
return FindDescendantsInternal<T>();
}
}
internal IEnumerable<T> FindDescendantsInternal<T>() where T : MarkdownObject
{
#if !UAP
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
#endif

Stack<Inline> stack = new Stack<Inline>();

Expand Down
Loading

0 comments on commit 7875e4b

Please sign in to comment.