Skip to content

A couple perf improvements #864

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

Merged
merged 13 commits into from
Apr 14, 2025
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
152 changes: 141 additions & 11 deletions src/Markdig.Tests/TestCharHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,73 @@ private static bool ExpectedIsWhitespace(char c)
{
// A Unicode whitespace character is any code point in the Unicode Zs general category,
// or a tab (U+0009), line feed (U+000A), form feed (U+000C), or carriage return (U+000D).
return c == '\t' || c == '\n' || c == '\u000C' || c == '\r' ||
return c == '\t' || c == '\n' || c == '\f' || c == '\r' ||
CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator;
}

[Test]
public void IsAcrossTab()
{
Assert.False(CharHelper.IsAcrossTab(0));
Assert.True(CharHelper.IsAcrossTab(1));
Assert.True(CharHelper.IsAcrossTab(2));
Assert.True(CharHelper.IsAcrossTab(3));
Assert.False(CharHelper.IsAcrossTab(4));
}

[Test]
public void AddTab()
{
Assert.AreEqual(4, CharHelper.AddTab(0));
Assert.AreEqual(4, CharHelper.AddTab(1));
Assert.AreEqual(4, CharHelper.AddTab(2));
Assert.AreEqual(4, CharHelper.AddTab(3));
Assert.AreEqual(8, CharHelper.AddTab(4));
Assert.AreEqual(8, CharHelper.AddTab(5));
}

[Test]
public void IsWhitespace()
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
char c = (char)i;
Test(
ExpectedIsWhitespace,
CharHelper.IsWhitespace);

Assert.AreEqual(ExpectedIsWhitespace(c), CharHelper.IsWhitespace(c));
}
Test(
ExpectedIsWhitespace,
CharHelper.WhitespaceChars.Contains);
}

[Test]
public void IsWhiteSpaceOrZero()
{
Test(
c => ExpectedIsWhitespace(c) || c == 0,
CharHelper.IsWhiteSpaceOrZero);
}

[Test]
public void IsAsciiPunctuation()
{
Test(
c => char.IsAscii(c) && ExpectedIsPunctuation(c),
CharHelper.IsAsciiPunctuation);
}

[Test]
public void IsAsciiPunctuationOrZero()
{
Test(
c => char.IsAscii(c) && (ExpectedIsPunctuation(c) || c == 0),
CharHelper.IsAsciiPunctuationOrZero);
}

[Test]
public void IsSpaceOrPunctuation()
{
Test(
c => c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuation(c),
CharHelper.IsSpaceOrPunctuation);
}

[Test]
Expand All @@ -76,15 +130,91 @@ public void CheckUnicodeCategory()
}

[Test]
public void IsSpaceOrPunctuation()
public void IsControl()
{
Test(
char.IsControl,
CharHelper.IsControl);
}

[Test]
public void IsAlpha()
{
Test(
c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'),
CharHelper.IsAlpha);
}

[Test]
public void IsAlphaUpper()
{
Test(
c => c >= 'A' && c <= 'Z',
CharHelper.IsAlphaUpper);
}

[Test]
public void IsAlphaNumeric()
{
Test(
c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'),
CharHelper.IsAlphaNumeric);
}

[Test]
public void IsDigit()
{
Test(
c => c >= '0' && c <= '9',
CharHelper.IsDigit);
}

[Test]
public void IsNewLineOrLineFeed()
{
Test(
c => c is '\r' or '\n',
CharHelper.IsNewLineOrLineFeed);
}

[Test]
public void IsSpaceOrTab()
{
Test(
c => c is ' ' or '\t',
CharHelper.IsSpaceOrTab);
}

[Test]
public void IsEscapableSymbol()
{
Test(
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~•".Contains,
CharHelper.IsEscapableSymbol);
}

[Test]
public void IsEmailUsernameSpecialChar()
{
Test(
".!#$%&'*+/=?^_`{|}~-+.~".Contains,
CharHelper.IsEmailUsernameSpecialChar);
}

[Test]
public void IsEmailUsernameSpecialCharOrDigit()
{
Test(
c => CharHelper.IsDigit(c) || ".!#$%&'*+/=?^_`{|}~-+.~".Contains(c),
CharHelper.IsEmailUsernameSpecialCharOrDigit);
}

private static void Test(Func<char, bool> expected, Func<char, bool> actual)
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
char c = (char)i;

bool expected = c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuation(c);

Assert.AreEqual(expected, CharHelper.IsSpaceOrPunctuation(c));
Assert.AreEqual(expected(c), actual(c));
}
}
}
2 changes: 1 addition & 1 deletion src/Markdig.WebApp/ApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class ApiController : Controller
{
[HttpGet()]
[Route("")]
public string Empty()
public new string Empty()
{
return string.Empty;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Markdig.WebApp/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public Startup(IWebHostEnvironment env)
if (env.IsEnvironment("Development"))
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
builder.AddApplicationInsightsSettings(connectionString: null, developerMode: true);
}

builder.AddEnvironmentVariables();
Expand Down
11 changes: 4 additions & 7 deletions src/Markdig/Extensions/Abbreviations/AbbreviationParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,20 +203,17 @@ private static bool IsValidAbbreviationEnding(string match, StringSlice content,
while (index <= contentNew.End)
{
var c = contentNew.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))

if (c.IsWhitespace())
{
return false;
break;
}

if (c.IsAlphaNumeric())
if (!c.IsAsciiPunctuationOrZero())
{
return false;
}

if (c.IsWhitespace())
{
break;
}
index++;
}
return true;
Expand Down
1 change: 0 additions & 1 deletion src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Markdig.Helpers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;

namespace Markdig.Extensions.Alerts;

Expand Down
1 change: 0 additions & 1 deletion src/Markdig/Extensions/Alerts/AlertInlineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace Markdig.Extensions.Alerts;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class AutoIdentifierExtension : IMarkdownExtension
private static readonly StripRendererCache _rendererCache = new();

private readonly AutoIdentifierOptions _options;
private readonly ProcessInlineDelegate _processInlinesBegin;
private readonly ProcessInlineDelegate _processInlinesEnd;

/// <summary>
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
Expand All @@ -30,6 +32,8 @@ public class AutoIdentifierExtension : IMarkdownExtension
public AutoIdentifierExtension(AutoIdentifierOptions options)
{
_options = options;
_processInlinesBegin = DocumentOnProcessInlinesBegin;
_processInlinesEnd = HeadingBlock_ProcessInlinesEnd;
}

public void Setup(MarkdownPipelineBuilder pipeline)
Expand Down Expand Up @@ -85,19 +89,19 @@ private void HeadingBlockParser_Closed(BlockProcessor processor, Block block)
{
dictionary = new Dictionary<string, HeadingLinkReferenceDefinition>();
doc.SetData(this, dictionary);
doc.ProcessInlinesBegin += DocumentOnProcessInlinesBegin;
doc.ProcessInlinesBegin += _processInlinesBegin;
}
dictionary[text] = linkRef;
}

// Then we register after inline have been processed to actually generate the proper #id
headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd;
headingBlock.ProcessInlinesEnd += _processInlinesEnd;
}

private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline)
{
var doc = processor.Document;
doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
doc.ProcessInlinesBegin -= _processInlinesBegin;
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this)!;
foreach (var keyPair in dictionary)
{
Expand All @@ -117,7 +121,7 @@ private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? in
/// Callback when there is a reference to found to a heading.
/// Note that reference are only working if they are declared after.
/// </summary>
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child)
private static Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child)
{
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
return new LinkInline()
Expand Down
Loading