Skip to content

Commit

Permalink
Merge e192831 into cb26f30
Browse files Browse the repository at this point in the history
  • Loading branch information
MihaZupan committed Apr 15, 2022
2 parents cb26f30 + e192831 commit 8f2fbc7
Show file tree
Hide file tree
Showing 31 changed files with 674 additions and 238 deletions.
67 changes: 67 additions & 0 deletions src/Markdig.Tests/TestLazySubstring.cs
@@ -0,0 +1,67 @@
using Markdig.Helpers;
using NUnit.Framework;

namespace Markdig.Tests
{
public class TestLazySubstring
{
[Theory]
[TestCase("")]
[TestCase("a")]
[TestCase("foo")]
public void LazySubstring_ReturnsCorrectSubstring(string text)
{
var substring = new LazySubstring(text);
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);

Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);

Assert.AreSame(substring.ToString(), substring.ToString());
Assert.AreEqual(text, substring.ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);

Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
}

[Theory]
[TestCase("", 0, 0)]
[TestCase("a", 0, 0)]
[TestCase("a", 1, 0)]
[TestCase("a", 0, 1)]
[TestCase("foo", 1, 0)]
[TestCase("foo", 1, 1)]
[TestCase("foo", 1, 2)]
[TestCase("foo", 0, 3)]
public void LazySubstring_ReturnsCorrectSubstring(string text, int start, int length)
{
var substring = new LazySubstring(text, start, length);
Assert.AreEqual(start, substring.Offset);
Assert.AreEqual(length, substring.Length);

string expectedSubstring = text.Substring(start, length);

Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(start, substring.Offset);
Assert.AreEqual(length, substring.Length);

Assert.AreSame(substring.ToString(), substring.ToString());
Assert.AreEqual(expectedSubstring, substring.ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(length, substring.Length);

Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(length, substring.Length);
}
}
}
29 changes: 29 additions & 0 deletions src/Markdig.Tests/TestMarkdigCoreApi.cs
Expand Up @@ -74,6 +74,35 @@ public void TestToHtmlWithWriter()
}
}

[Test]
public void TestDocumentToHtmlWithWriter()
{
var writer = new StringWriter();

for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse("This is a text with some *emphasis*");
document.ToHtml(writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}

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

for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse("This is a text with a https://link.tld/", pipeline);
document.ToHtml(writer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}

[Test]
public void TestConvert()
{
Expand Down
94 changes: 47 additions & 47 deletions src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs
Expand Up @@ -49,6 +49,53 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
return false;
}

var startPosition = slice.Start;
int domainOffset = 0;

var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 't':
if (!slice.MatchLowercase("el:", 1))
{
return false;
}
domainOffset = 4;
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = 4; // www.
break;
}

List<char> pendingEmphasis = _listOfCharCache.Get();
try
{
Expand All @@ -58,53 +105,6 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
return false;
}

var startPosition = slice.Start;
int domainOffset = 0;

var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 't':
if (!slice.MatchLowercase("el:", 1))
{
return false;
}
domainOffset = 4;
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = 4; // www.
break;
}

// Parse URL
if (!LinkHelper.TryParseUrl(ref slice, out string? link, out _, true))
{
Expand Down
21 changes: 12 additions & 9 deletions src/Markdig/Extensions/Bootstrap/BootstrapExtension.cs
Expand Up @@ -28,9 +28,16 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)

private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
foreach(var node in document.Descendants())
foreach (var node in document.Descendants())
{
if (node is Block)
if (node.IsInline)
{
if (node.IsContainerInline && node is LinkInline link && link.IsImage)
{
link.GetAttributes().AddClass("img-fluid");
}
}
else if (node.IsContainerBlock)
{
if (node is Tables.Table)
{
Expand All @@ -44,16 +51,12 @@ private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
node.GetAttributes().AddClass("figure");
}
else if (node is Figures.FigureCaption)
{
node.GetAttributes().AddClass("figure-caption");
}
}
else if (node is Inline)
else
{
if (node is LinkInline link && link.IsImage)
if (node is Figures.FigureCaption)
{
link.GetAttributes().AddClass("img-fluid");
node.GetAttributes().AddClass("figure-caption");
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Markdig/Helpers/ArrayHelper.cs
@@ -1,6 +1,7 @@
// 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.

namespace Markdig.Helpers
{
/// <summary>
Expand Down
30 changes: 30 additions & 0 deletions src/Markdig/Helpers/BlockWrapper.cs
@@ -0,0 +1,30 @@
// 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;
using System;

namespace Markdig.Helpers
{
// Used to avoid the overhead of type covariance checks
internal readonly struct BlockWrapper : IEquatable<BlockWrapper>
{
public readonly Block Block;

public BlockWrapper(Block block)
{
Block = block;
}

public static implicit operator Block(BlockWrapper wrapper) => wrapper.Block;

public static implicit operator BlockWrapper(Block block) => new BlockWrapper(block);

public bool Equals(BlockWrapper other) => ReferenceEquals(Block, other.Block);

public override bool Equals(object obj) => Block.Equals(obj);

public override int GetHashCode() => Block.GetHashCode();
}
}
2 changes: 1 addition & 1 deletion src/Markdig/Helpers/CharHelper.cs
Expand Up @@ -744,7 +744,7 @@ internal static string SmallNumberToString(int number)
return cache[number];
}

return number.ToString();
return number.ToString(CultureInfo.InvariantCulture);
}
}
}
44 changes: 44 additions & 0 deletions src/Markdig/Helpers/LazySubstring.cs
@@ -0,0 +1,44 @@
// 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 System.Diagnostics;

namespace Markdig.Helpers
{
internal struct LazySubstring
{
private string _text;
public int Offset;
public int Length;

public LazySubstring(string text)
{
_text = text;
Offset = 0;
Length = text.Length;
}

public LazySubstring(string text, int offset, int length)
{
Debug.Assert((ulong)offset + (ulong)length <= (ulong)text.Length, $"{offset}-{length} in {text}");
_text = text;
Offset = offset;
Length = length;
}

public ReadOnlySpan<char> AsSpan() => _text.AsSpan(Offset, Length);

public override string ToString()
{
if (Offset != 0 || Length != _text.Length)
{
_text = _text.Substring(Offset, Length);
Offset = 0;
}

return _text;
}
}
}
22 changes: 22 additions & 0 deletions src/Markdig/Markdown.cs
Expand Up @@ -132,6 +132,28 @@ public static string ToHtml(this MarkdownDocument document, MarkdownPipeline? pi
return renderer.Writer.ToString() ?? string.Empty;
}

/// <summary>
/// Converts a Markdown document to HTML.
/// </summary>
/// <param name="document">A Markdown document.</param>
/// <param name="writer">The destination <see cref="TextWriter"/> that will receive the result of the conversion.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns>
/// <exception cref="ArgumentNullException">if markdown document variable is null</exception>
public static void ToHtml(this MarkdownDocument document, TextWriter writer, MarkdownPipeline? pipeline = null)
{
if (document is null) ThrowHelper.ArgumentNullException(nameof(document));
if (writer is null) ThrowHelper.ArgumentNullException_writer();

pipeline ??= DefaultPipeline;

using var rentedRenderer = pipeline.RentHtmlRenderer(writer);
HtmlRenderer renderer = rentedRenderer.Instance;

renderer.Render(document);
renderer.Writer.Flush();
}

/// <summary>
/// Converts a Markdown string to HTML and output to the specified writer.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Markdig/MarkdownPipeline.cs
Expand Up @@ -94,7 +94,7 @@ internal RentedHtmlRenderer RentHtmlRenderer(TextWriter? writer = null)

internal sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
{
private static readonly TextWriter s_dummyWriter = new StringWriter();
private static readonly TextWriter s_dummyWriter = new FastStringWriter();

private readonly MarkdownPipeline _pipeline;
private readonly bool _customWriter;
Expand Down

0 comments on commit 8f2fbc7

Please sign in to comment.