diff --git a/src/Markdig.Tests/Markdig.Tests.csproj b/src/Markdig.Tests/Markdig.Tests.csproj
index 23801f21d..c461cd769 100644
--- a/src/Markdig.Tests/Markdig.Tests.csproj
+++ b/src/Markdig.Tests/Markdig.Tests.csproj
@@ -58,6 +58,7 @@
+
diff --git a/src/Markdig.Tests/TestDescendantsOrder.cs b/src/Markdig.Tests/TestDescendantsOrder.cs
new file mode 100644
index 000000000..643bb903e
--- /dev/null
+++ b/src/Markdig.Tests/TestDescendantsOrder.cs
@@ -0,0 +1,85 @@
+using NUnit.Framework;
+using Markdig.Syntax;
+using Markdig.Syntax.Inlines;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace Markdig.Tests
+{
+ [TestFixture]
+ public class TestDescendantsOrder
+ {
+ [Test]
+ public void TestSchemas()
+ {
+ foreach (var markdown in TestParser.SpecsMarkdown)
+ {
+ AssertSameDescendantsOrder(markdown);
+ }
+ }
+
+ private void AssertSameDescendantsOrder(string markdown)
+ {
+ var syntaxTree = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
+
+ var descendants_legacy = Descendants_Legacy(syntaxTree).ToList();
+ var descendants_new = syntaxTree.Descendants().ToList();
+
+ Assert.AreEqual(descendants_legacy.Count, descendants_new.Count);
+
+ for (int i = 0; i < descendants_legacy.Count; i++)
+ {
+ Assert.AreSame(descendants_legacy[i], descendants_new[i]);
+ }
+ }
+
+ private static IEnumerable Descendants_Legacy(MarkdownObject markdownObject)
+ {
+ // TODO: implement a recursiveless method
+
+ var block = markdownObject as ContainerBlock;
+ if (block != null)
+ {
+ foreach (var subBlock in block)
+ {
+ yield return subBlock;
+
+ foreach (var sub in Descendants_Legacy(subBlock))
+ {
+ yield return sub;
+ }
+
+ // Visit leaf block that have inlines
+ var leafBlock = subBlock as LeafBlock;
+ if (leafBlock?.Inline != null)
+ {
+ foreach (var subInline in Descendants_Legacy(leafBlock.Inline))
+ {
+ yield return subInline;
+ }
+ }
+ }
+ }
+ else
+ {
+ var inline = markdownObject as ContainerInline;
+ if (inline != null)
+ {
+ var child = inline.FirstChild;
+ while (child != null)
+ {
+ var next = child.NextSibling;
+ yield return child;
+
+ foreach (var sub in Descendants_Legacy(child))
+ {
+ yield return sub;
+ }
+
+ child = next;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Markdig.Tests/TestParser.cs b/src/Markdig.Tests/TestParser.cs
index dbe24d405..237bd005f 100644
--- a/src/Markdig.Tests/TestParser.cs
+++ b/src/Markdig.Tests/TestParser.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
@@ -155,5 +156,20 @@ private static string Compact(string html)
html = html.Normalize(NormalizationForm.FormKD);
return html;
}
+
+ public static readonly string[] SpecsMarkdown;
+ static TestParser()
+ {
+ string assemblyDir = Path.GetDirectoryName(typeof(TestParser).Assembly.Location);
+ string specsDir = Path.GetFullPath(Path.Combine(assemblyDir, "../../Specs"));
+
+ var files = Directory.GetFiles(specsDir).Where(file => file.EndsWith(".md", StringComparison.Ordinal)).ToList();
+ SpecsMarkdown = new string[files.Count];
+
+ for (int i = 0; i < files.Count; i++)
+ {
+ SpecsMarkdown[i] = File.ReadAllText(files[i]);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Markdig/Syntax/Inlines/ContainerInline.cs b/src/Markdig/Syntax/Inlines/ContainerInline.cs
index f7535a016..cb954a16e 100644
--- a/src/Markdig/Syntax/Inlines/ContainerInline.cs
+++ b/src/Markdig/Syntax/Inlines/ContainerInline.cs
@@ -94,25 +94,36 @@ public bool ContainsChild(Inline childToFind)
/// An enumeration of T
public IEnumerable FindDescendants() where T : Inline
{
- var child = FirstChild;
- while (child != null)
- {
- var next = child.NextSibling;
+ // Fast-path an empty container to avoid allocating a Stack
+ if (LastChild == null) yield break;
- if (child is T)
- {
- yield return (T)child;
- }
+ Stack stack = new Stack();
- if (child is ContainerInline)
- {
- foreach (var subChild in ((ContainerInline) child).FindDescendants())
- {
- yield return subChild;
- }
- }
-
- child = next;
+ var child = LastChild;
+ while (child != null)
+ {
+ stack.Push(child);
+ child = child.PreviousSibling;
+ }
+
+ while (stack.Count > 0)
+ {
+ child = stack.Pop();
+
+ if (child is T childT)
+ {
+ yield return childT;
+ }
+
+ if (child is ContainerInline containerInline)
+ {
+ child = containerInline.LastChild;
+ while (child != null)
+ {
+ stack.Push(child);
+ child = child.PreviousSibling;
+ }
+ }
}
}
diff --git a/src/Markdig/Syntax/MarkdownObjectExtensions.cs b/src/Markdig/Syntax/MarkdownObjectExtensions.cs
index c7fac83fb..c47b36882 100644
--- a/src/Markdig/Syntax/MarkdownObjectExtensions.cs
+++ b/src/Markdig/Syntax/MarkdownObjectExtensions.cs
@@ -1,131 +1,119 @@
-// 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.Collections.Generic;
-using Markdig.Syntax.Inlines;
-
-namespace Markdig.Syntax
-{
- ///
- /// Extensions for visiting or
- ///
- public static class MarkdownObjectExtensions
- {
- ///
- /// Iterates over the descendant elements for the specified markdown element, including and .
- ///
- /// The markdown object.
- /// An iteration over the descendant elements
- public static IEnumerable Descendants(this MarkdownObject markdownObject)
- {
- // TODO: implement a recursiveless method
-
- var block = markdownObject as ContainerBlock;
- if (block != null)
- {
- foreach (var subBlock in block)
- {
- yield return subBlock;
-
- foreach (var sub in subBlock.Descendants())
- {
- yield return sub;
- }
-
- // Visit leaf block that have inlines
- var leafBlock = subBlock as LeafBlock;
- if (leafBlock?.Inline != null)
- {
- foreach (var subInline in Descendants(leafBlock.Inline))
- {
- yield return subInline;
- }
- }
- }
- }
- else
- {
- var inline = markdownObject as ContainerInline;
- if (inline != null)
- {
- var child = inline.FirstChild;
- while (child != null)
- {
- var next = child.NextSibling;
- yield return child;
-
- foreach (var sub in child.Descendants())
- {
- yield return sub;
- }
-
- child = next;
- }
- }
- }
- }
-
- ///
- /// Iterates over the descendant elements for the specified markdown element and filters by the type {T}.
- ///
- /// Type to use for filtering the descendants
- /// The inline markdown object.
- ///
- /// An iteration over the descendant elements
- ///
- public static IEnumerable Descendants(this ContainerInline inline) where T : Inline
- {
- var child = inline.FirstChild;
- while (child != null)
- {
- var next = child.NextSibling;
- var childT = child as T;
- if (childT != null)
- {
- yield return childT;
- }
-
- var subContainer = child as ContainerInline;
- if (subContainer != null)
- {
- foreach (var sub in subContainer.Descendants())
- {
- yield return sub;
- }
- }
-
- child = next;
- }
- }
-
- ///
- /// Iterates over the descendant elements for the specified markdown element and filters by the type {T}.
- ///
- /// Type to use for filtering the descendants
- /// The markdown object.
- ///
- /// An iteration over the descendant elements
- ///
- public static IEnumerable Descendants(this ContainerBlock block) where T : Block
- {
- foreach (var subBlock in block)
- {
- var subBlockT = subBlock as T;
- if (subBlockT != null)
- {
- yield return subBlockT;
- }
-
- var subBlockContainer = subBlock as ContainerBlock;
- if (subBlockContainer != null)
- {
- foreach (var sub in subBlockContainer.Descendants())
- {
- yield return sub;
- }
- }
- }
- }
- }
+// 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.Collections.Generic;
+using Markdig.Syntax.Inlines;
+
+namespace Markdig.Syntax
+{
+ ///
+ /// Extensions for visiting or
+ ///
+ public static class MarkdownObjectExtensions
+ {
+ ///
+ /// Iterates over the descendant elements for the specified markdown element, including and .
+ /// The descendant elements are returned in DFS-like order.
+ ///
+ /// The markdown object.
+ /// An iteration over the descendant elements
+ public static IEnumerable Descendants(this MarkdownObject markdownObject)
+ {
+ // Fast-path an object with no children to avoid allocating Stack objects
+ if (!(markdownObject is ContainerBlock) && !(markdownObject is ContainerInline)) yield break;
+
+ // TODO: A single Stack<(MarkdownObject block, bool push)> when ValueTuples are available
+ Stack stack = new Stack();
+ Stack pushStack = new Stack();
+
+ stack.Push(markdownObject);
+ pushStack.Push(false);
+
+ while (stack.Count > 0)
+ {
+ var block = stack.Pop();
+ if (pushStack.Pop()) yield return block;
+ if (block is ContainerBlock containerBlock)
+ {
+ int subBlockIndex = containerBlock.Count;
+ while (subBlockIndex-- > 0)
+ {
+ var subBlock = containerBlock[subBlockIndex];
+ if (subBlock is LeafBlock leafBlock)
+ {
+ if (leafBlock.Inline != null)
+ {
+ stack.Push(leafBlock.Inline);
+ pushStack.Push(false);
+ }
+ }
+ stack.Push(subBlock);
+ pushStack.Push(true);
+ }
+ }
+ else if (block is ContainerInline containerInline)
+ {
+ var child = containerInline.LastChild;
+ while (child != null)
+ {
+ stack.Push(child);
+ pushStack.Push(true);
+ child = child.PreviousSibling;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Iterates over the descendant elements for the specified markdown element and filters by the type {T}.
+ ///
+ /// Type to use for filtering the descendants
+ /// The inline markdown object.
+ ///
+ /// An iteration over the descendant elements
+ ///
+ public static IEnumerable Descendants(this ContainerInline inline) where T : Inline
+ => inline.FindDescendants();
+
+ ///
+ /// Iterates over the descendant elements for the specified markdown element and filters by the type {T}.
+ ///
+ /// Type to use for filtering the descendants
+ /// The markdown object.
+ ///
+ /// An iteration over the descendant elements
+ ///
+ public static IEnumerable Descendants(this ContainerBlock block) where T : Block
+ {
+ // Fast-path an empty container to avoid allocating a Stack
+ if (block.Count == 0) yield break;
+
+ Stack stack = new Stack();
+
+ int childrenCount = block.Count;
+ while (childrenCount-- > 0)
+ {
+ stack.Push(block[childrenCount]);
+ }
+
+ while (stack.Count > 0)
+ {
+ var subBlock = stack.Pop();
+ if (subBlock is T subBlockT)
+ {
+ yield return subBlockT;
+ }
+
+ if (subBlock is ContainerBlock subBlockContainer)
+ {
+ childrenCount = subBlockContainer.Count;
+ while (childrenCount-- > 0)
+ {
+ stack.Push(subBlockContainer[childrenCount]);
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file