mirror of
https://github.com/xoofx/markdig.git
synced 2026-10-08 22:57:51 +00:00
Temp work
This commit is contained in:
@@ -40,7 +40,7 @@ namespace Testamina.Markdig.Benchmarks
|
||||
//CommonMark.CommonMarkConverter.Convert(new StringReader(text), new StringWriter());
|
||||
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
|
||||
var reader = new StringReader(text);
|
||||
CommonMark.CommonMarkConverter.Parse(reader);
|
||||
CommonMark.CommonMarkConverter.ProcessStage1(reader);
|
||||
reader.Dispose();
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ namespace Testamina.Markdig.Benchmarks
|
||||
var program = new Program();
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
program.TestMarkdig();
|
||||
//program.TestCommonMark();
|
||||
//program.TestMarkdig();
|
||||
program.TestCommonMark();
|
||||
}
|
||||
Console.WriteLine($"time: {clock.ElapsedMilliseconds}ms");
|
||||
DumpGC();
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Textamina.Markdig.Tests
|
||||
public void TestParseHtmlTagSimple()
|
||||
{
|
||||
var inputTag = "<a>";
|
||||
var text = new StringLineGroup(inputTag);
|
||||
var text = new StringSlice(inputTag);
|
||||
string outputTag;
|
||||
Assert.True(HtmlHelper.TryParseHtmlTag(text, out outputTag));
|
||||
Assert.AreEqual(inputTag, outputTag);
|
||||
@@ -24,7 +24,7 @@ namespace Textamina.Markdig.Tests
|
||||
public void TestParseHtmlTagSimpleWithAttribute()
|
||||
{
|
||||
var inputTag = "<a href='http://google.com'>";
|
||||
var text = new StringLineGroup(inputTag);
|
||||
var text = new StringSlice(inputTag);
|
||||
string outputTag;
|
||||
Assert.True(HtmlHelper.TryParseHtmlTag(text, out outputTag));
|
||||
Assert.AreEqual(inputTag, outputTag);
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlSimple()
|
||||
{
|
||||
var text = new StringLineGroup("toto tutu");
|
||||
var text = new StringSlice("toto tutu");
|
||||
string link;
|
||||
Assert.True(LinkHelper.TryParseUrl(text, out link));
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
|
||||
Assert.AreEqual("toto", link);
|
||||
Assert.AreEqual(' ', text.CurrentChar);
|
||||
}
|
||||
@@ -20,9 +20,9 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlUrl()
|
||||
{
|
||||
var text = new StringLineGroup("http://google.com)");
|
||||
var text = new StringSlice("http://google.com)");
|
||||
string link;
|
||||
Assert.True(LinkHelper.TryParseUrl(text, out link));
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual(')', text.CurrentChar);
|
||||
}
|
||||
@@ -30,9 +30,9 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlNestedParenthesis()
|
||||
{
|
||||
var text = new StringLineGroup("(toto)tutu(tata) nooo");
|
||||
var text = new StringSlice("(toto)tutu(tata) nooo");
|
||||
string link;
|
||||
Assert.True(LinkHelper.TryParseUrl(text, out link));
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
|
||||
Assert.AreEqual("(toto)tutu(tata)", link);
|
||||
Assert.AreEqual(' ', text.CurrentChar);
|
||||
}
|
||||
@@ -40,9 +40,9 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlAlternate()
|
||||
{
|
||||
var text = new StringLineGroup("<toto_tata_tutu> nooo");
|
||||
var text = new StringSlice("<toto_tata_tutu> nooo");
|
||||
string link;
|
||||
Assert.True(LinkHelper.TryParseUrl(text, out link));
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
|
||||
Assert.AreEqual("toto_tata_tutu", link);
|
||||
Assert.AreEqual(' ', text.CurrentChar);
|
||||
}
|
||||
@@ -50,26 +50,26 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlAlternateInvalid()
|
||||
{
|
||||
var text = new StringLineGroup("<toto_tata_tutu");
|
||||
var text = new StringSlice("<toto_tata_tutu");
|
||||
string link;
|
||||
Assert.False(LinkHelper.TryParseUrl(text, out link));
|
||||
Assert.False(LinkHelper.TryParseUrl(ref text, out link));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTitleSimple()
|
||||
{
|
||||
var text = new StringLineGroup(@"'tata\tutu\''");
|
||||
var text = new StringSlice(@"'tata\tutu\''");
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseTitle(text, out title));
|
||||
Assert.True(LinkHelper.TryParseTitle(ref text, out title));
|
||||
Assert.AreEqual(@"tata\tutu'", title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTitleSimpleAlternate()
|
||||
{
|
||||
var text = new StringLineGroup(@"""tata\tutu\"""" ");
|
||||
var text = new StringSlice(@"""tata\tutu\"""" ");
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseTitle(text, out title));
|
||||
Assert.True(LinkHelper.TryParseTitle(ref text, out title));
|
||||
Assert.AreEqual(@"tata\tutu""", title);
|
||||
Assert.AreEqual(' ', text.CurrentChar);
|
||||
}
|
||||
@@ -77,10 +77,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlAndTitle()
|
||||
{
|
||||
var text = new StringLineGroup(@"(http://google.com 'this is a title')ABC");
|
||||
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual("this is a title", title);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
@@ -89,10 +89,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlAndTitleEmpty()
|
||||
{
|
||||
var text = new StringLineGroup(@"(<>)A");
|
||||
var text = new StringSlice(@"(<>)A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
@@ -101,10 +101,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlAndTitleEmpty2()
|
||||
{
|
||||
var text = new StringLineGroup(@"( <> )A");
|
||||
var text = new StringSlice(@"( <> )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
@@ -114,10 +114,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlEmptyWithTitleWithMultipleSpaces()
|
||||
{
|
||||
var text = new StringLineGroup(@"( <> 'toto' )A");
|
||||
var text = new StringSlice(@"( <> 'toto' )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual("toto", title);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
@@ -126,10 +126,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlEmpty()
|
||||
{
|
||||
var text = new StringLineGroup(@"()A");
|
||||
var text = new StringSlice(@"()A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
@@ -138,45 +138,41 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestMultipleLines()
|
||||
{
|
||||
var text = new StringLineGroup()
|
||||
{
|
||||
new StringLine("("),
|
||||
new StringLine(" <http://google.com> "),
|
||||
new StringLine(" 'toto' )A")
|
||||
};
|
||||
var text = new StringSlice(@"(
|
||||
<http://google.com>
|
||||
'toto' )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(text, out link, out title));
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual("toto", title);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestLabelSimple()
|
||||
{
|
||||
var text = new StringLineGroup("[foo]");
|
||||
var text = new StringSlice("[foo]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(text, out label));
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
Assert.AreEqual("foo", label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLabelEscape()
|
||||
{
|
||||
var text = new StringLineGroup(@"[fo\[\]o]");
|
||||
var text = new StringSlice(@"[fo\[\]o]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(text, out label));
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
Assert.AreEqual(@"fo[]o", label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLabelEscape2()
|
||||
{
|
||||
var text = new StringLineGroup(@"[\]]");
|
||||
var text = new StringSlice(@"[\]]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(text, out label));
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
Assert.AreEqual(@"]", label);
|
||||
}
|
||||
|
||||
@@ -184,31 +180,31 @@ namespace Textamina.Markdig.Tests
|
||||
public void TestLabelInvalids()
|
||||
{
|
||||
string label;
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"a"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"["), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[\x]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[[]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[ ]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringLineGroup(@"[ \t \n ]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"a"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"["), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[\x]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[[]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ ]"), out label));
|
||||
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ \t \n ]"), out label));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLabelWhitespaceCollapsedAndTrim()
|
||||
{
|
||||
var text = new StringLineGroup(@"[ fo o z ]");
|
||||
var text = new StringSlice(@"[ fo o z ]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(text, out label));
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
Assert.AreEqual(@"fo o z", label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestlLinkReferenceDefinitionSimple()
|
||||
{
|
||||
var text = new StringLineGroup(@"[foo]: /toto 'title'");
|
||||
var text = new StringSlice(@"[foo]: /toto 'title'");
|
||||
string label;
|
||||
string url;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(text, out label, out url, out title));
|
||||
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title));
|
||||
Assert.AreEqual(@"foo", label);
|
||||
Assert.AreEqual(@"/toto", url);
|
||||
Assert.AreEqual(@"title", title);
|
||||
@@ -217,10 +213,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestAutoLinkUrlSimple()
|
||||
{
|
||||
var text = new StringLineGroup(@"<http://google.com>");
|
||||
var text = new StringSlice(@"<http://google.com>");
|
||||
string url;
|
||||
bool isEmail;
|
||||
Assert.True(LinkHelper.TryParseAutolink(text, out url, out isEmail));
|
||||
Assert.True(LinkHelper.TryParseAutolink(ref text, out url, out isEmail));
|
||||
Assert.False(isEmail);
|
||||
Assert.AreEqual("http://google.com", url);
|
||||
}
|
||||
@@ -228,10 +224,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestAutoLinkEmailSimple()
|
||||
{
|
||||
var text = new StringLineGroup(@"<user@host.com>");
|
||||
var text = new StringSlice(@"<user@host.com>");
|
||||
string email;
|
||||
bool isEmail;
|
||||
Assert.True(LinkHelper.TryParseAutolink(text, out email, out isEmail));
|
||||
Assert.True(LinkHelper.TryParseAutolink(ref text, out email, out isEmail));
|
||||
Assert.True(isEmail);
|
||||
Assert.AreEqual("user@host.com", email);
|
||||
}
|
||||
@@ -241,10 +237,10 @@ namespace Textamina.Markdig.Tests
|
||||
{
|
||||
string text;
|
||||
bool isEmail;
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@""), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@"<"), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@"<ab"), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringLineGroup(@"<user@>"), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@""), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<"), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestSimple()
|
||||
{
|
||||
var reader = new StringReader(@"####### foo");
|
||||
var reader = new StringReader(@"foo");
|
||||
// var reader = new StringReader(@"> > toto tata
|
||||
//> titi toto
|
||||
//");
|
||||
|
||||
@@ -8,24 +8,25 @@ namespace Textamina.Markdig.Tests
|
||||
[TestFixture]
|
||||
public class TestStringLine
|
||||
{
|
||||
// TODO: Add tests for StringLine
|
||||
/*
|
||||
// TODO: Add tests for StringSlice
|
||||
// TODO: Add more tests for StringLineGroup
|
||||
|
||||
[Test]
|
||||
public void TestStringLineGroupSimple()
|
||||
{
|
||||
var text = new StringLineGroup()
|
||||
var text = new StringSliceList()
|
||||
{
|
||||
new StringLine("ABC"),
|
||||
new StringLine("E"),
|
||||
new StringLine("F")
|
||||
new StringSlice("ABC"),
|
||||
new StringSlice("E"),
|
||||
new StringSlice("F")
|
||||
};
|
||||
|
||||
var chars = ToString(text);
|
||||
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
|
||||
}
|
||||
|
||||
private static string ToString(StringLineGroup text)
|
||||
private static string ToString(StringSliceList text)
|
||||
{
|
||||
var chars = new StringBuilder();
|
||||
while (text.CurrentChar != '\0')
|
||||
@@ -39,10 +40,10 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestStringLineGroupSaveAndRestore()
|
||||
{
|
||||
var text = new StringLineGroup()
|
||||
var text = new StringSliceList()
|
||||
{
|
||||
new StringLine("ABCD"),
|
||||
new StringLine("EF"),
|
||||
new StringSlice("ABCD"),
|
||||
new StringSlice("EF"),
|
||||
};
|
||||
|
||||
text.NextChar(); // B
|
||||
@@ -66,7 +67,7 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestSkipWhitespaces()
|
||||
{
|
||||
var text = new StringLineGroup(" ABC");
|
||||
var text = new StringSliceList(" ABC");
|
||||
Assert.True(text.SkipWhiteSpaces());
|
||||
Assert.False(text.SkipWhiteSpaces());
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
@@ -75,14 +76,14 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestStringLineGroupWithModifiedStart()
|
||||
{
|
||||
var line1 = new StringLine(" ABC");
|
||||
var line1 = new StringSlice(" ABC");
|
||||
line1.NextChar();
|
||||
line1.NextChar();
|
||||
|
||||
var line2 = new StringLine(" DEF ");
|
||||
var line2 = new StringSlice(" DEF ");
|
||||
line2.Trim();
|
||||
|
||||
var text = new StringLineGroup() {line1, line2};
|
||||
var text = new StringSliceList() {line1, line2};
|
||||
|
||||
var result = ToString(text);
|
||||
TextAssert.AreEqual("ABC\nDEF", result);
|
||||
@@ -92,19 +93,19 @@ namespace Textamina.Markdig.Tests
|
||||
[Test]
|
||||
public void TestStringLineGroupWithTrim()
|
||||
{
|
||||
var line1 = new StringLine(" ABC ");
|
||||
var line1 = new StringSlice(" ABC ");
|
||||
line1.NextChar();
|
||||
line1.NextChar();
|
||||
|
||||
var line2 = new StringLine(" DEF ");
|
||||
var line2 = new StringSlice(" DEF ");
|
||||
|
||||
var text = new StringLineGroup() { line1, line2 };
|
||||
var text = new StringSliceList() { line1, line2 };
|
||||
text.Trim();
|
||||
|
||||
var result = ToString(text);
|
||||
TextAssert.AreEqual("ABC \n DEF", result);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace Textamina.Markdig.Formatters
|
||||
[typeof(FencedCodeBlock)] = o => Write((FencedCodeBlock)o),
|
||||
[typeof(CodeBlock)] = o => Write((CodeBlock)o),
|
||||
[typeof(HeadingBlock)] = o => Write((HeadingBlock)o),
|
||||
[typeof(BreakBlock)] = o => Write((BreakBlock)o),
|
||||
[typeof(ThematicBreakBlock)] = o => Write((ThematicBreakBlock)o),
|
||||
[typeof(QuoteBlock)] = o => Write((QuoteBlock)o),
|
||||
[typeof(ParagraphBlock)] = o => Write((ParagraphBlock)o),
|
||||
[typeof(HtmlBlock)] = o => Write((HtmlBlock)o),
|
||||
@@ -135,7 +135,7 @@ namespace Textamina.Markdig.Formatters
|
||||
writer.WriteLineConstant(">");
|
||||
}
|
||||
|
||||
protected void Write(BreakBlock breakBlock)
|
||||
protected void Write(ThematicBreakBlock breakBlock)
|
||||
{
|
||||
writer.WriteLineConstant("<hr />");
|
||||
}
|
||||
@@ -282,7 +282,7 @@ namespace Textamina.Markdig.Formatters
|
||||
{
|
||||
writer.WriteLine();
|
||||
}
|
||||
var line = lines[i];
|
||||
var line = lines.Slices[i];
|
||||
if (escape)
|
||||
{
|
||||
HtmlHelper.EscapeHtml(line.ToString(), writer);
|
||||
|
||||
@@ -55,16 +55,22 @@ namespace Textamina.Markdig.Helpers
|
||||
"XMLRPC.BEEP", "XMLRPC.BEEPS", "XMPP", "XRI", "YMSGR", "Z39.50R", "Z39.50S"
|
||||
};
|
||||
|
||||
public static bool TryParseHtmlTag(StringLineGroup text, out string htmlTag)
|
||||
|
||||
public static bool TryParseHtmlTag(StringSlice text, out string htmlTag)
|
||||
{
|
||||
return TryParseHtmlTag(ref text, out htmlTag);
|
||||
}
|
||||
|
||||
public static bool TryParseHtmlTag(ref StringSlice text, out string htmlTag)
|
||||
{
|
||||
var builder = StringBuilderCache.Local();
|
||||
var result = TryParseHtmlTag(text, builder);
|
||||
var result = TryParseHtmlTag(ref text, builder);
|
||||
htmlTag = builder.ToString();
|
||||
builder.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static bool TryParseHtmlTag(StringLineGroup text, StringBuilder builder)
|
||||
public static bool TryParseHtmlTag(ref StringSlice text, StringBuilder builder)
|
||||
{
|
||||
if (builder == null) throw new ArgumentNullException(nameof(builder));
|
||||
var c = text.CurrentChar;
|
||||
@@ -101,7 +107,7 @@ namespace Textamina.Markdig.Helpers
|
||||
return TryParseHtmlTagOpenTag(text, builder);
|
||||
}
|
||||
|
||||
internal static bool TryParseHtmlTagOpenTag(StringLineGroup text, StringBuilder builder)
|
||||
internal static bool TryParseHtmlTagOpenTag(StringSlice text, StringBuilder builder)
|
||||
{
|
||||
var c = text.CurrentChar;
|
||||
|
||||
@@ -256,7 +262,7 @@ namespace Textamina.Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseHtmlTagDeclaration(StringLineGroup text, StringBuilder builder)
|
||||
private static bool TryParseHtmlTagDeclaration(StringSlice text, StringBuilder builder)
|
||||
{
|
||||
var c = text.CurrentChar;
|
||||
bool hasAlpha = false;
|
||||
@@ -291,7 +297,7 @@ namespace Textamina.Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseHtmlTagCData(StringLineGroup text, StringBuilder builder)
|
||||
private static bool TryParseHtmlTagCData(StringSlice text, StringBuilder builder)
|
||||
{
|
||||
builder.Append('[');
|
||||
var c = text.NextChar();
|
||||
@@ -334,7 +340,7 @@ namespace Textamina.Markdig.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryParseHtmlCloseTag(StringLineGroup text, StringBuilder builder)
|
||||
internal static bool TryParseHtmlCloseTag(StringSlice text, StringBuilder builder)
|
||||
{
|
||||
// </[A-Za-z][A-Za-z0-9]+\s*>
|
||||
builder.Append('/');
|
||||
@@ -379,7 +385,7 @@ namespace Textamina.Markdig.Helpers
|
||||
}
|
||||
|
||||
|
||||
private static bool TryParseHtmlTagHtmlComment(StringLineGroup text, StringBuilder builder)
|
||||
private static bool TryParseHtmlTagHtmlComment(StringSlice text, StringBuilder builder)
|
||||
{
|
||||
var c = text.NextChar();
|
||||
if (c != '-')
|
||||
@@ -388,7 +394,7 @@ namespace Textamina.Markdig.Helpers
|
||||
}
|
||||
builder.Append('-');
|
||||
builder.Append('-');
|
||||
if (text.PeekCharOnSameLine() == '>')
|
||||
if (text.PeekChar(1) == '>')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -417,7 +423,7 @@ namespace Textamina.Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseHtmlTagProcessingInstruction(StringLineGroup text, StringBuilder builder)
|
||||
private static bool TryParseHtmlTagProcessingInstruction(StringSlice text, StringBuilder builder)
|
||||
{
|
||||
builder.Append('?');
|
||||
var prevChar = '\0';
|
||||
|
||||
@@ -8,7 +8,12 @@ namespace Textamina.Markdig.Helpers
|
||||
{
|
||||
public static class LinkHelper
|
||||
{
|
||||
public static bool TryParseAutolink(StringLineGroup text, out string link, out bool isEmail)
|
||||
public static bool TryParseAutolink(StringSlice text, out string link, out bool isEmail)
|
||||
{
|
||||
return TryParseAutolink(ref text, out link, out isEmail);
|
||||
}
|
||||
|
||||
public static bool TryParseAutolink(ref StringSlice text, out string link, out bool isEmail)
|
||||
{
|
||||
link = null;
|
||||
isEmail = false;
|
||||
@@ -205,10 +210,13 @@ namespace Textamina.Markdig.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryParseInlineLink(StringLineGroup text, out string link, out string title)
|
||||
public static bool TryParseInlineLink(StringSlice text, out string link, out string title)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
return TryParseInlineLink(ref text, out link, out title);
|
||||
}
|
||||
|
||||
public static bool TryParseInlineLink(ref StringSlice text, out string link, out string title)
|
||||
{
|
||||
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
|
||||
// 2. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
|
||||
// 3. an optional link destination,
|
||||
@@ -224,11 +232,11 @@ namespace Textamina.Markdig.Helpers
|
||||
if (c == '(')
|
||||
{
|
||||
text.NextChar();
|
||||
text.SkipWhiteSpaces();
|
||||
text.TrimStart();
|
||||
|
||||
if (TryParseUrl(text, out link))
|
||||
if (TryParseUrl(ref text, out link))
|
||||
{
|
||||
var hasWhiteSpaces = text.SkipWhiteSpaces();
|
||||
var hasWhiteSpaces = text.TrimStart();
|
||||
|
||||
c = text.CurrentChar;
|
||||
if (c == ')')
|
||||
@@ -242,9 +250,9 @@ namespace Textamina.Markdig.Helpers
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
else if (TryParseTitle(text, out title))
|
||||
else if (TryParseTitle(ref text, out title))
|
||||
{
|
||||
text.SkipWhiteSpaces();
|
||||
text.TrimStart();
|
||||
c = text.CurrentChar;
|
||||
|
||||
if (c == ')')
|
||||
@@ -266,10 +274,13 @@ namespace Textamina.Markdig.Helpers
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public static bool TryParseTitle(StringLineGroup text, out string title)
|
||||
public static bool TryParseTitle(StringSlice text, out string title)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
return TryParseTitle(ref text, out title);
|
||||
}
|
||||
|
||||
public static bool TryParseTitle(ref StringSlice text, out string title)
|
||||
{
|
||||
bool isValid = false;
|
||||
var buffer = StringBuilderCache.Local();
|
||||
buffer.Clear();
|
||||
@@ -286,7 +297,7 @@ namespace Textamina.Markdig.Helpers
|
||||
{
|
||||
c = text.NextChar();
|
||||
|
||||
if (text.Current != null && text.Current.IsBlankLine())
|
||||
if (c == '\n') // TODO?
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -333,10 +344,13 @@ namespace Textamina.Markdig.Helpers
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public static bool TryParseUrl(StringLineGroup text, out string link)
|
||||
public static bool TryParseUrl(StringSlice text, out string link)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
return TryParseUrl(ref text, out link);
|
||||
}
|
||||
|
||||
public static bool TryParseUrl(ref StringSlice text, out string link)
|
||||
{
|
||||
bool isValid = false;
|
||||
var buffer = StringBuilderCache.Local();
|
||||
buffer.Clear();
|
||||
@@ -453,12 +467,18 @@ namespace Textamina.Markdig.Helpers
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public static bool TryParseLinkReferenceDefinition(StringLineGroup text, out string label, out string url,
|
||||
public static bool TryParseLinkReferenceDefinition(StringSlice text, out string label, out string url,
|
||||
out string title)
|
||||
{
|
||||
return TryParseLinkReferenceDefinition(ref text, out label, out url, out title);
|
||||
}
|
||||
|
||||
public static bool TryParseLinkReferenceDefinition(ref StringSlice text, out string label, out string url,
|
||||
out string title)
|
||||
{
|
||||
url = null;
|
||||
title = null;
|
||||
if (!TryParseLabel(text, out label))
|
||||
if (!TryParseLabel(ref text, out label))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -471,19 +491,19 @@ namespace Textamina.Markdig.Helpers
|
||||
text.NextChar(); // Skip ':'
|
||||
|
||||
// Skip any whitespaces before the url
|
||||
text.SkipWhiteSpaces();
|
||||
if (!TryParseUrl(text, out url) || string.IsNullOrEmpty(url))
|
||||
text.TrimStart();
|
||||
if (!TryParseUrl(ref text, out url) || string.IsNullOrEmpty(url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var saved = text.Save();
|
||||
var saved = text;
|
||||
int newLineCount;
|
||||
var hasWhiteSpaces = text.SkipWhiteSpaces(out newLineCount);
|
||||
var hasWhiteSpaces = text.TrimStart(out newLineCount);
|
||||
var c = text.CurrentChar;
|
||||
if (c == '\'' || c == '"' || c == '(')
|
||||
{
|
||||
if (TryParseTitle(text, out title))
|
||||
if (TryParseTitle(ref text, out title))
|
||||
{
|
||||
// If we have a title, it requires a whitespace after the url
|
||||
if (!hasWhiteSpaces)
|
||||
@@ -517,7 +537,7 @@ namespace Textamina.Markdig.Helpers
|
||||
// we are still returning a valid definition
|
||||
if (newLineCount > 0 && title != null)
|
||||
{
|
||||
text.Restore(ref saved);
|
||||
text = saved;
|
||||
title = null;
|
||||
return true;
|
||||
}
|
||||
@@ -531,13 +551,17 @@ namespace Textamina.Markdig.Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryParseLabel(StringLineGroup lines, out string label)
|
||||
public static bool TryParseLabel(StringSlice lines, out string label)
|
||||
{
|
||||
return TryParseLabel(lines, false, out label);
|
||||
return TryParseLabel(ref lines, false, out label);
|
||||
}
|
||||
|
||||
public static bool TryParseLabel(ref StringSlice lines, out string label)
|
||||
{
|
||||
return TryParseLabel(ref lines, false, out label);
|
||||
}
|
||||
|
||||
public static bool TryParseLabel(StringLineGroup lines, bool allowEmpty, out string label)
|
||||
public static bool TryParseLabel(ref StringSlice lines, bool allowEmpty, out string label)
|
||||
{
|
||||
label = null;
|
||||
char c = lines.CurrentChar;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Textamina.Markdig.Helpers;
|
||||
using Textamina.Markdig.Syntax;
|
||||
|
||||
@@ -20,7 +21,32 @@ namespace Textamina.Markdig.Parsing
|
||||
Add(root);
|
||||
}
|
||||
|
||||
public StringLine Line;
|
||||
|
||||
public StringSlice Line;
|
||||
|
||||
public int LineIndex;
|
||||
|
||||
public bool IsBlankLine => CurrentChar == '\0';
|
||||
|
||||
public bool IsEndOfLine => Line.IsEndOfSlice;
|
||||
|
||||
public char CurrentChar => Line.CurrentChar;
|
||||
|
||||
public char NextChar() => Line.NextChar();
|
||||
|
||||
public char CharAt(int index) => Line[index];
|
||||
|
||||
public int Start => Line.Start;
|
||||
|
||||
public int EndOffset => Line.End;
|
||||
|
||||
public int Indent => Column - ColumnBegin;
|
||||
|
||||
public bool IsCodeIndent => Indent >= 4;
|
||||
|
||||
public int ColumnBegin { get; private set; }
|
||||
|
||||
public int Column { get; set; }
|
||||
|
||||
public Block Pending { get; set; }
|
||||
|
||||
@@ -36,6 +62,40 @@ namespace Textamina.Markdig.Parsing
|
||||
|
||||
public StringBuilderCache StringBuilders { get; }
|
||||
|
||||
public char PeekChar(int offset)
|
||||
{
|
||||
return Line.PeekChar(offset);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public void SetCurrentLine(ref StringSlice line)
|
||||
{
|
||||
Line = line;
|
||||
EatSpaces();
|
||||
}
|
||||
|
||||
public void EatSpaces()
|
||||
{
|
||||
var c = CurrentChar;
|
||||
ColumnBegin = Column;
|
||||
while (c !='\0')
|
||||
{
|
||||
if (c == ' ')
|
||||
{
|
||||
Column++;
|
||||
}
|
||||
else if (c == '\t')
|
||||
{
|
||||
Column = ((Column + 3) >> 2) << 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = NextChar();
|
||||
}
|
||||
}
|
||||
|
||||
public Block NextPending
|
||||
{
|
||||
get { return PendingIndex + 1 < Count ? this[PendingIndex + 1] : null; }
|
||||
@@ -112,11 +172,5 @@ namespace Textamina.Markdig.Parsing
|
||||
Close(i);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Reset(StringLine line)
|
||||
{
|
||||
Line = line;
|
||||
Pending = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Textamina.Markdig.Parsing
|
||||
|
||||
public LeafBlock Block { get; internal set; }
|
||||
|
||||
public StringLineGroup Lines;
|
||||
public StringSlice Text;
|
||||
|
||||
public Inline Inline { get; set; }
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using Textamina.Markdig.Helpers;
|
||||
using Textamina.Markdig.Syntax;
|
||||
|
||||
@@ -13,9 +11,6 @@ namespace Textamina.Markdig.Parsing
|
||||
public class MarkdownParser
|
||||
{
|
||||
public static TextWriter Log;
|
||||
|
||||
private StringLine line;
|
||||
|
||||
private readonly List<BlockParser> blockParsers;
|
||||
private readonly List<InlineParser> inlineParsers;
|
||||
private readonly List<InlineParser> regularInlineParsers;
|
||||
@@ -36,7 +31,7 @@ namespace Textamina.Markdig.Parsing
|
||||
blockParserState = new BlockParserState(stringBuilderCache, document);
|
||||
blockParsers = new List<BlockParser>()
|
||||
{
|
||||
BreakBlock.Parser,
|
||||
ThematicBreakBlock.Parser,
|
||||
HeadingBlock.Parser,
|
||||
QuoteBlock.Parser,
|
||||
ListBlock.Parser,
|
||||
@@ -112,13 +107,12 @@ namespace Textamina.Markdig.Parsing
|
||||
public Document Parse()
|
||||
{
|
||||
ParseLines();
|
||||
ProcessInlines(document);
|
||||
//ProcessInlines(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
private void ParseLines()
|
||||
{
|
||||
int lineIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
var lineText = Reader.ReadLine();
|
||||
@@ -128,13 +122,15 @@ namespace Textamina.Markdig.Parsing
|
||||
{
|
||||
break;
|
||||
}
|
||||
line = new StringLine(lineText, lineIndex++);
|
||||
var line = new StringSlice(lineText);
|
||||
blockParserState.SetCurrentLine(ref line);
|
||||
blockParserState.LineIndex++;
|
||||
|
||||
bool continueProcessLiner = ProcessPendingBlocks();
|
||||
|
||||
// If we have already reached eol and the last block was a paragraph
|
||||
// we close it
|
||||
if (line.IsEol)
|
||||
if (blockParserState.Line.IsEndOfSlice)
|
||||
{
|
||||
int index = blockParserState.Count - 1;
|
||||
if (blockParserState[index] is ParagraphBlock)
|
||||
@@ -203,7 +199,7 @@ namespace Textamina.Markdig.Parsing
|
||||
}
|
||||
|
||||
// Create the line state that will be used by all parser
|
||||
blockParserState.Reset(line);
|
||||
blockParserState.Pending = null;
|
||||
|
||||
// Process any current block potentially opened
|
||||
for (int i = 1; i < blockParserState.Count; i++)
|
||||
@@ -220,7 +216,7 @@ namespace Textamina.Markdig.Parsing
|
||||
break;
|
||||
}
|
||||
|
||||
var saveLiner = line.Save();
|
||||
var saveLiner = blockParserState.Line;
|
||||
|
||||
// If we have a discard, we can remove it from the current state
|
||||
blockParserState.CurrentContainer = LastContainer;
|
||||
@@ -235,7 +231,7 @@ namespace Textamina.Markdig.Parsing
|
||||
if (result == MatchLineResult.None)
|
||||
{
|
||||
// Restore the Line where it was
|
||||
line.Restore(ref saveLiner);
|
||||
blockParserState.SetCurrentLine(ref saveLiner);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -258,7 +254,7 @@ namespace Textamina.Markdig.Parsing
|
||||
processLiner = false;
|
||||
if (result != MatchLineResult.LastDiscard && result != MatchLineResult.ContinueDiscard)
|
||||
{
|
||||
leaf.Append(line);
|
||||
leaf.Lines.Append(ref blockParserState.Line);
|
||||
}
|
||||
|
||||
if (blockParserState.NewBlocks.Count > 0)
|
||||
@@ -294,12 +290,12 @@ namespace Textamina.Markdig.Parsing
|
||||
|
||||
private void ParseNewBlocks(ref bool continueProcessLiner)
|
||||
{
|
||||
blockParserState.Reset(line);
|
||||
blockParserState.Pending = null;
|
||||
|
||||
for (int j = 0; j < blockParsers.Count; j++)
|
||||
{
|
||||
var blockParser = blockParsers[j];
|
||||
if (line.IsEol)
|
||||
if (blockParserState.Line.IsEndOfSlice)
|
||||
{
|
||||
continueProcessLiner = false;
|
||||
break;
|
||||
@@ -319,19 +315,19 @@ namespace Textamina.Markdig.Parsing
|
||||
blockParserState.CurrentContainer = LastContainer;
|
||||
blockParserState.LastBlock = lastBlock;
|
||||
|
||||
var saveLiner = line.Save();
|
||||
var saveLiner = blockParserState.Line;
|
||||
var result = blockParser.Match(blockParserState);
|
||||
if (result == MatchLineResult.None)
|
||||
{
|
||||
// If we have reached a blank line after trying to parse a paragraph
|
||||
// we can ignore it
|
||||
if (isParsingParagraph && line.IsBlankLine())
|
||||
if (isParsingParagraph && blockParserState.IsBlankLine)
|
||||
{
|
||||
continueProcessLiner = false;
|
||||
break;
|
||||
}
|
||||
|
||||
line.Restore(ref saveLiner);
|
||||
blockParserState.SetCurrentLine(ref saveLiner);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -342,7 +338,7 @@ namespace Textamina.Markdig.Parsing
|
||||
Debug.Assert(blockParserState.NewBlocks.Count == 0);
|
||||
|
||||
continueProcessLiner = false;
|
||||
paragraph.Append(line);
|
||||
paragraph.Lines.Append(ref blockParserState.Line);
|
||||
|
||||
// We have just found a lazy continuation for a paragraph, early exit
|
||||
// Mark all block opened after a lazy continuation
|
||||
@@ -383,7 +379,7 @@ namespace Textamina.Markdig.Parsing
|
||||
{
|
||||
var block = newBlocks.Pop();
|
||||
|
||||
block.Line = line.LineIndex;
|
||||
block.Line = blockParserState.LineIndex;
|
||||
|
||||
// If we have a leaf block
|
||||
var leaf = block as LeafBlock;
|
||||
@@ -391,7 +387,7 @@ namespace Textamina.Markdig.Parsing
|
||||
{
|
||||
if (result != MatchLineResult.LastDiscard && result != MatchLineResult.ContinueDiscard)
|
||||
{
|
||||
leaf.Append(line);
|
||||
leaf.Lines.Append(ref blockParserState.Line);
|
||||
}
|
||||
|
||||
if (newBlocks.Count > 0)
|
||||
@@ -433,28 +429,25 @@ namespace Textamina.Markdig.Parsing
|
||||
var lines = leafBlock.Lines;
|
||||
|
||||
leafBlock.Inline = new ContainerInline() {IsClosed = false};
|
||||
var inlineState = new InlineParserState(stringBuilderCache, document);
|
||||
|
||||
inlineState.Lines = lines;
|
||||
inlineState.Inline = leafBlock.Inline;
|
||||
inlineState.Block = leafBlock;
|
||||
|
||||
var saveLines = new StringLineGroup.State();
|
||||
|
||||
while (!lines.IsEndOfLines)
|
||||
var inlineState = new InlineParserState(stringBuilderCache, document)
|
||||
{
|
||||
lines.Save(ref saveLines);
|
||||
Text = new StringSlice(leafBlock.Lines.ToString()),
|
||||
Inline = leafBlock.Inline,
|
||||
Block = leafBlock
|
||||
};
|
||||
|
||||
var c = lines.CurrentChar;
|
||||
while (!inlineState.Text.IsEndOfSlice)
|
||||
{
|
||||
var saveLine = inlineState.Text;
|
||||
|
||||
var c = saveLine.CurrentChar;
|
||||
var inlineParser = c < 128 ? inlineWithFirstCharParsers[c] : null;
|
||||
if (inlineParser == null || !inlineParser.Match(inlineState))
|
||||
{
|
||||
for (int i = 0; i < regularInlineParsers.Count; i++)
|
||||
{
|
||||
lines.Restore(ref saveLines);
|
||||
|
||||
inlineState.Text = saveLine;
|
||||
inlineParser = regularInlineParsers[i];
|
||||
|
||||
if (inlineParser.Match(inlineState))
|
||||
{
|
||||
break;
|
||||
@@ -465,7 +458,7 @@ namespace Textamina.Markdig.Parsing
|
||||
|
||||
if (inlineParser == null)
|
||||
{
|
||||
lines.Restore(ref saveLines);
|
||||
inlineState.Text = saveLine;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using Textamina.Markdig.Helpers;
|
||||
using Textamina.Markdig.Parsing;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
@@ -28,56 +26,15 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public override MatchLineResult Match(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
int position = liner.Start;
|
||||
liner.SkipLeadingSpaces3();
|
||||
|
||||
// 4.4 Indented code blocks
|
||||
var c = liner.Current;
|
||||
var isTab = c.IsTab();
|
||||
var isSpace = c.IsSpace();
|
||||
var isBlankLine = liner.IsBlankLine();
|
||||
var codeBlock = state.Pending as CodeBlock;
|
||||
// && !isBlankLine) || (isBlankLine && codeBlock != null && !codeBlock.Lines[codeBlock.Lines.Count - 1].IsBlankLine())
|
||||
if ((codeBlock != null && isBlankLine) || (isTab || (isSpace && (liner.Start - position) == 3)))
|
||||
//if (((isTab || (isSpace && (liner.Start - position) == 3)) && !isBlankLine))
|
||||
if (!state.IsCodeIndent || state.IsBlankLine)
|
||||
{
|
||||
liner.NextChar();
|
||||
if (state.Pending == null)
|
||||
{
|
||||
if (isBlankLine)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
state.NewBlocks.Push(new CodeBlock(this) { Column = position });
|
||||
}
|
||||
return MatchLineResult.Continue;
|
||||
return state.IsBlankLine && state.Pending != null ? MatchLineResult.LastDiscard : MatchLineResult.None;
|
||||
}
|
||||
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
public override void Close(BlockParserState state)
|
||||
{
|
||||
var codeBlock = state.Pending as CodeBlock;
|
||||
if (codeBlock != null)
|
||||
if (state.Pending == null)
|
||||
{
|
||||
var lines = codeBlock.Lines;
|
||||
// Remove trailing blankline
|
||||
for (int i = lines.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (lines[i].IsBlankLine())
|
||||
{
|
||||
lines.RemoveAt(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.NewBlocks.Push(new CodeBlock(this) { Column = state.Column });
|
||||
}
|
||||
|
||||
return MatchLineResult.Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,149 +34,148 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public override MatchLineResult Match(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
int count;
|
||||
char matchChar;
|
||||
char c = state.CurrentChar;
|
||||
int offset = 0;
|
||||
|
||||
var fenced = state.Pending as FencedCodeBlock;
|
||||
if (fenced != null)
|
||||
var currentFenced = state.Pending as FencedCodeBlock;
|
||||
if (currentFenced != null)
|
||||
{
|
||||
var saveLiner = liner.Save();
|
||||
liner.SkipLeadingSpaces3();
|
||||
|
||||
var c = liner.Current;
|
||||
int count = fenced.fencedCharCount;
|
||||
var matchChar = fenced.fencedChar;
|
||||
while (!liner.IsEol)
|
||||
count = currentFenced.fencedCharCount;
|
||||
matchChar = currentFenced.fencedChar;
|
||||
while (c == matchChar)
|
||||
{
|
||||
if (c != matchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = liner.NextChar();
|
||||
count--;
|
||||
offset++;
|
||||
c = state.Line.PeekChar(offset);
|
||||
}
|
||||
|
||||
if (count <= 0)
|
||||
if (offset >= count)
|
||||
{
|
||||
int endPosition = liner.Start;
|
||||
liner.TrimEnd(true);
|
||||
if (liner.IsEol || liner.End == endPosition)
|
||||
state.Line.TrimEnd(true);
|
||||
if (state.CurrentChar == matchChar)
|
||||
{
|
||||
return MatchLineResult.LastDiscard;
|
||||
}
|
||||
}
|
||||
|
||||
liner.Restore(ref saveLiner);
|
||||
|
||||
// TODO: It is unclear how to handle this correctly
|
||||
// Break only if Eof
|
||||
return MatchLineResult.Continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: We need to count the number of leading space to remove them on each line
|
||||
var start = liner.Start;
|
||||
liner.SkipLeadingSpaces3();
|
||||
var column = liner.Start;
|
||||
var indentCount = liner.Start - start;
|
||||
var c = liner.Current;
|
||||
|
||||
int count = 0;
|
||||
var matchChar = (char)0;
|
||||
while (!liner.IsEol)
|
||||
// Else if the we have an indent, it is not valid
|
||||
if (state.IsCodeIndent)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
count = 0;
|
||||
matchChar = (char) 0;
|
||||
while (c != '\0')
|
||||
{
|
||||
if (count == 0 && (c == '`' || c == '~'))
|
||||
{
|
||||
if (count == 0 && (c == '`' || c == '~'))
|
||||
{
|
||||
matchChar = c;
|
||||
}
|
||||
else if (c != matchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
c = liner.NextChar();
|
||||
count++;
|
||||
matchChar = c;
|
||||
}
|
||||
else if (c != matchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
c = state.PeekChar(count);
|
||||
}
|
||||
|
||||
if (count >= 3)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
// TODO: We need to count the number of leading space to remove them on each line
|
||||
var column = state.Column;
|
||||
|
||||
// specs spaces: Is space and tabs? or only spaces? Use space and tab for this case
|
||||
while (c.IsSpaceOrTab())
|
||||
{
|
||||
offset++;
|
||||
c = state.PeekChar(offset);
|
||||
}
|
||||
var start = state.Start + count + offset;
|
||||
|
||||
string infoString;
|
||||
string argString = null;
|
||||
|
||||
// An info string cannot contain any backsticks
|
||||
int firstSpace = -1;
|
||||
for (int i = start; i <= state.EndOffset; i++)
|
||||
{
|
||||
c = state.Line[i];
|
||||
if (c == '`')
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
if (count >= 3)
|
||||
if (firstSpace < 0 && c.IsSpaceOrTab())
|
||||
{
|
||||
// specs spaces: Is space and tabs? or only spaces? Use space and tab for this case
|
||||
liner.TrimStart(true);
|
||||
liner.TrimEnd(true);
|
||||
firstSpace = i;
|
||||
}
|
||||
}
|
||||
|
||||
string infoString = null;
|
||||
string argString = null;
|
||||
if (firstSpace > 0)
|
||||
{
|
||||
infoString = state.Line.Text.Substring(start, firstSpace - start);
|
||||
|
||||
// An info string cannot contain any backsticks
|
||||
int firstSpace = -1;
|
||||
for (int i = liner.Start; i <= liner.End; i++)
|
||||
// Skip any spaces after info string
|
||||
firstSpace++;
|
||||
while (true)
|
||||
{
|
||||
c = state.Line[firstSpace];
|
||||
if (c.IsSpaceOrTab())
|
||||
{
|
||||
c = liner[i];
|
||||
if (c == '`')
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
else if (firstSpace < 0 && c.IsSpaceOrTab())
|
||||
{
|
||||
firstSpace = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstSpace > 0)
|
||||
{
|
||||
infoString = liner.Text.Substring(liner.Start, firstSpace - liner.Start);
|
||||
|
||||
// Skip any spaces after info string
|
||||
firstSpace++;
|
||||
while (true)
|
||||
{
|
||||
c = liner[firstSpace];
|
||||
if (c.IsSpaceOrTab())
|
||||
{
|
||||
firstSpace++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
argString = liner.Text.Substring(firstSpace, liner.End - firstSpace + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoString = liner.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
// Store the number of matched string into the context
|
||||
state.NewBlocks.Push(new FencedCodeBlock(this)
|
||||
{
|
||||
Column = column,
|
||||
fencedChar = matchChar,
|
||||
fencedCharCount = count,
|
||||
indentCount = indentCount,
|
||||
Language = HtmlHelper.Unescape(infoString),
|
||||
Arguments = HtmlHelper.Unescape(argString),
|
||||
});
|
||||
return MatchLineResult.ContinueDiscard;
|
||||
}
|
||||
|
||||
return MatchLineResult.None;
|
||||
argString = state.Line.Text.Substring(firstSpace, state.Line.End - firstSpace + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
infoString = state.Line.Text.Substring(start, state.EndOffset - start + 1);
|
||||
}
|
||||
|
||||
// Store the number of matched string into the context
|
||||
state.NewBlocks.Push(new FencedCodeBlock(this)
|
||||
{
|
||||
Column = column,
|
||||
fencedChar = matchChar,
|
||||
fencedCharCount = count,
|
||||
indentCount = state.Indent,
|
||||
Language = HtmlHelper.Unescape(infoString),
|
||||
Arguments = HtmlHelper.Unescape(argString),
|
||||
});
|
||||
|
||||
// Discard the current line
|
||||
return MatchLineResult.ContinueDiscard;
|
||||
}
|
||||
|
||||
public override void Close(BlockParserState state)
|
||||
{
|
||||
var fenced = (FencedCodeBlock) state.Pending;
|
||||
for (int i = 0; i < fenced.Lines.Count; i++)
|
||||
var fenced = ((FencedCodeBlock) state.Pending);
|
||||
var lines = fenced.Lines;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var line = fenced.Lines[i];
|
||||
|
||||
// Fences can be indented. If the opening fence is indented,
|
||||
// content lines will have equivalent opening indentation removed, if present:
|
||||
for (int j = 0; j < fenced.indentCount; j++)
|
||||
{
|
||||
if (line.Start < line.End && line[line.Start].IsSpace())
|
||||
var start = lines.Slices[i].Start;
|
||||
if (start < lines.Slices[i].End && lines.Slices[i][start].IsSpace())
|
||||
{
|
||||
line.Start++;
|
||||
lines.Slices[i].Start++;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public override MatchLineResult Match(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
liner.SkipLeadingSpaces3();
|
||||
|
||||
var column = liner.Start;
|
||||
if (state.IsCodeIndent)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
// 4.2 ATX headings
|
||||
// An ATX heading consists of a string of characters, parsed as inline content,
|
||||
@@ -35,37 +35,37 @@ namespace Textamina.Markdig.Syntax
|
||||
// the heading are stripped of leading and trailing spaces before being parsed as
|
||||
// inline content. The heading level is equal to the number of # characters in the
|
||||
// opening sequence.
|
||||
var c = liner.Current;
|
||||
var column = state.Column;
|
||||
var c = state.CurrentChar;
|
||||
|
||||
int leadingCount = 0;
|
||||
for (; !liner.IsEol && leadingCount <= 6; leadingCount++)
|
||||
for (; !state.Line.IsEndOfSlice && leadingCount <= 6; leadingCount++)
|
||||
{
|
||||
if (c != '#')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
c = liner.NextChar();
|
||||
c = state.PeekChar(leadingCount);
|
||||
}
|
||||
|
||||
// closing # will be handled later, because anyway we have matched
|
||||
|
||||
// A space is required after leading #
|
||||
if (leadingCount > 0 && leadingCount <=6 && (c.IsSpace() || liner.IsEol))
|
||||
if (leadingCount > 0 && leadingCount <=6 && (c.IsSpace() || state.Line.IsEndOfSlice))
|
||||
{
|
||||
liner.NextChar();
|
||||
state.Line.Start = leadingCount + 1;
|
||||
state.NewBlocks.Push(new HeadingBlock(this) {Level = leadingCount, Column = column });
|
||||
|
||||
|
||||
// The optional closing sequence of #s must be preceded by a space and may be followed by spaces only.
|
||||
int endState = 0;
|
||||
int countClosingTags = 0;
|
||||
for (int i = liner.End; i >= liner.Start - 1; i--) // Go up to Start - 1 in order to match the space after the first ###
|
||||
for (int i = state.Line.End; i >= state.Line.Start - 1; i--) // Go up to Start - 1 in order to match the space after the first ###
|
||||
{
|
||||
c = liner[i];
|
||||
c = state.Line[i];
|
||||
if (endState == 0)
|
||||
{
|
||||
if (CharHelper.IsSpace(c)) // TODO: Not clear if it is a space or space+tab in the specs
|
||||
if (c.IsSpace()) // TODO: Not clear if it is a space or space+tab in the specs
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -81,9 +81,9 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
if (countClosingTags > 0)
|
||||
{
|
||||
if (CharHelper.IsSpace(c))
|
||||
if (c.IsSpace())
|
||||
{
|
||||
liner.End = i - 1;
|
||||
state.Line.End = i - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -16,10 +16,6 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public HtmlBlockType Type { get; set; }
|
||||
|
||||
[ThreadStatic]
|
||||
private static readonly StringLineGroup HtmlLineGroup = new StringLineGroup();
|
||||
|
||||
|
||||
private class ParserInternal : BlockParser
|
||||
{
|
||||
private static readonly string[] HtmlTags =
|
||||
@@ -106,12 +102,11 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
private MatchLineResult MatchStart(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (!liner.PeekChar(index).IsSpace())
|
||||
if (!state.Line.PeekChar(index).IsSpace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -120,36 +115,34 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
// Early exit if it is not starting by an HTML tag
|
||||
var column = index;
|
||||
var c = liner.PeekChar(index++);
|
||||
var c = state.Line.PeekChar(index++);
|
||||
if (c != '<')
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
var result = TryParseTagType16(state, liner, index, column);
|
||||
var result = TryParseTagType16(state, ref state.Line, index, column);
|
||||
|
||||
// HTML blocks of type 7 cannot interrupt a paragraph:
|
||||
if (result == MatchLineResult.None && !(state.LastBlock is ParagraphBlock))
|
||||
{
|
||||
result = TryParseTagType7(state, liner, index, column);
|
||||
result = TryParseTagType7(state, ref state.Line, index, column);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MatchLineResult TryParseTagType7(BlockParserState state, StringLine liner, int index, int startColumn)
|
||||
private MatchLineResult TryParseTagType7(BlockParserState state, ref StringSlice liner, int index, int startColumn)
|
||||
{
|
||||
var builder = StringBuilderCache.Local();
|
||||
var text = HtmlLineGroup;
|
||||
text.Add(liner);
|
||||
text.SetColumn(index);
|
||||
var c = text.CurrentChar;
|
||||
liner.Start = index;
|
||||
var c = liner.CurrentChar;
|
||||
var result = MatchLineResult.None;
|
||||
if ((c == '/' && HtmlHelper.TryParseHtmlCloseTag(text, builder)) || HtmlHelper.TryParseHtmlTagOpenTag(text, builder))
|
||||
if ((c == '/' && HtmlHelper.TryParseHtmlCloseTag(liner, builder)) || HtmlHelper.TryParseHtmlTagOpenTag(liner, builder))
|
||||
{
|
||||
// Must be followed by whitespace only
|
||||
bool hasOnlySpaces = true;
|
||||
c = text.CurrentChar;
|
||||
c = liner.CurrentChar;
|
||||
while (true)
|
||||
{
|
||||
if (c == '\0')
|
||||
@@ -161,7 +154,7 @@ namespace Textamina.Markdig.Syntax
|
||||
hasOnlySpaces = false;
|
||||
break;
|
||||
}
|
||||
c = text.NextChar();
|
||||
c = liner.NextChar();
|
||||
}
|
||||
|
||||
if (hasOnlySpaces)
|
||||
@@ -170,12 +163,11 @@ namespace Textamina.Markdig.Syntax
|
||||
}
|
||||
}
|
||||
|
||||
text.Clear();
|
||||
builder.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
private MatchLineResult TryParseTagType16(BlockParserState state, StringLine liner, int index, int startColumn)
|
||||
private MatchLineResult TryParseTagType16(BlockParserState state, ref StringSlice liner, int index, int startColumn)
|
||||
{
|
||||
char c;
|
||||
c = liner.PeekChar(index);
|
||||
@@ -255,51 +247,49 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
private MatchLineResult MatchEnd(BlockParserState state, HtmlBlock htmlBlock)
|
||||
{
|
||||
var liner = state.Line;
|
||||
|
||||
// Early exit if it is not starting by an HTML tag
|
||||
var c = liner.Current;
|
||||
var c = state.Line.CurrentChar;
|
||||
switch (htmlBlock.Type)
|
||||
{
|
||||
case HtmlBlockType.Comment:
|
||||
if (liner.Search("-->"))
|
||||
if (state.Line.Search("-->"))
|
||||
{
|
||||
return MatchLineResult.Last;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.CData:
|
||||
if (liner.Search("]]>"))
|
||||
if (state.Line.Search("]]>"))
|
||||
{
|
||||
return MatchLineResult.Last;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.ProcessingInstruction:
|
||||
if (liner.Search("?>"))
|
||||
if (state.Line.Search("?>"))
|
||||
{
|
||||
return MatchLineResult.Last;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.DocumentType:
|
||||
if (liner.Search(">"))
|
||||
if (state.Line.Search(">"))
|
||||
{
|
||||
return MatchLineResult.Last;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.ScriptPreOrStyle:
|
||||
// TODO: could be optimized with a dedicated parser
|
||||
if (liner.SearchLowercase("</script>") || liner.SearchLowercase("</pre>") || liner.SearchLowercase("</style>"))
|
||||
if (state.Line.SearchLowercase("</script>") || state.Line.SearchLowercase("</pre>") || state.Line.SearchLowercase("</style>"))
|
||||
{
|
||||
return MatchLineResult.Last;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.InterruptingBlock:
|
||||
if (liner.IsBlankLine())
|
||||
if (state.IsBlankLine)
|
||||
{
|
||||
return MatchLineResult.LastDiscard;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.NonInterruptingBlock:
|
||||
if (liner.IsBlankLine())
|
||||
if (state.IsBlankLine)
|
||||
{
|
||||
return MatchLineResult.LastDiscard;
|
||||
}
|
||||
|
||||
@@ -20,20 +20,18 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public override bool Match(InlineParserState state)
|
||||
{
|
||||
var text = state.Lines;
|
||||
|
||||
string link;
|
||||
bool isEmail;
|
||||
var saved = text.Save();
|
||||
if (LinkHelper.TryParseAutolink(text, out link, out isEmail))
|
||||
var saved = state.Text;
|
||||
if (LinkHelper.TryParseAutolink(ref state.Text, out link, out isEmail))
|
||||
{
|
||||
state.Inline = new AutolinkInline() {IsEmail = isEmail, Url = link};
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Restore(ref saved);
|
||||
state.Text = saved;
|
||||
string htmlTag;
|
||||
if (!HtmlHelper.TryParseHtmlTag(text, out htmlTag))
|
||||
if (!HtmlHelper.TryParseHtmlTag(ref state.Text, out htmlTag))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public override bool Match(InlineParserState state)
|
||||
{
|
||||
var text = state.Lines;
|
||||
var text = state.Text;
|
||||
|
||||
int openSticks = 0;
|
||||
if (text.PreviousChar1 == '`')
|
||||
if (text.PeekChar(-1) == '`')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -215,15 +215,13 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public override bool Match(InlineParserState state)
|
||||
{
|
||||
var lines = state.Lines;
|
||||
|
||||
// First, some definitions. A delimiter run is either a sequence of one or more * characters that
|
||||
// is not preceded or followed by a * character, or a sequence of one or more _ characters that
|
||||
// is not preceded or followed by a _ character.
|
||||
|
||||
var delimiterChar = lines.CurrentChar;
|
||||
var pc = lines.PreviousChar1;
|
||||
if (delimiterChar == pc && lines.PreviousChar2 != '\\')
|
||||
var delimiterChar = state.Text.CurrentChar;
|
||||
var pc = state.Text.PeekChar(-1);
|
||||
if (delimiterChar == pc && state.Text.PeekChar(-2) != '\\')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -233,7 +231,7 @@ namespace Textamina.Markdig.Syntax
|
||||
do
|
||||
{
|
||||
delimiterCount++;
|
||||
c = lines.NextChar();
|
||||
c = state.Text.NextChar();
|
||||
} while (c == delimiterChar);
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public override bool Match(InlineParserState state)
|
||||
{
|
||||
var lines = state.Lines;
|
||||
var lines = state.Text;
|
||||
|
||||
// Go to escape character
|
||||
lines.NextChar();
|
||||
|
||||
@@ -17,8 +17,7 @@ namespace Textamina.Markdig.Syntax
|
||||
public override bool Match(InlineParserState state)
|
||||
{
|
||||
// Hard line breaks are for separating inline content within a block. Neither syntax for hard line breaks works at the end of a paragraph or other block element:
|
||||
var text = state.Lines;
|
||||
if (!(state.Block is ParagraphBlock) || text.ColumnPosition == 0 || !text.PreviousChar1.IsSpace() || !text.PreviousChar2.IsSpace())
|
||||
if (!(state.Block is ParagraphBlock) || state.Text.Column == 0 || !state.Text.PeekChar(-1).IsSpace() || !state.Text.PeekChar(-2).IsSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -30,15 +30,13 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public override bool Match(InlineParserState state)
|
||||
{
|
||||
var text = state.Lines;
|
||||
|
||||
var c = text.CurrentChar;
|
||||
var c = state.Text.CurrentChar;
|
||||
|
||||
bool isImage = false;
|
||||
if (c == '!')
|
||||
{
|
||||
isImage = true;
|
||||
c = text.NextChar();
|
||||
c = state.Text.NextChar();
|
||||
if (c != '[')
|
||||
{
|
||||
return false;
|
||||
@@ -50,21 +48,21 @@ namespace Textamina.Markdig.Syntax
|
||||
case '[':
|
||||
// If this is not an image, we may have a reference link shortcut
|
||||
// so we try to resolve it here
|
||||
var saved = text.Save();
|
||||
var saved = state.Text;
|
||||
string label;
|
||||
|
||||
// If the label is followed by either a ( or a [, this is not a shortcut
|
||||
if (LinkHelper.TryParseLabel(text, out label))
|
||||
if (LinkHelper.TryParseLabel(ref state.Text, out label))
|
||||
{
|
||||
if (!state.Document.LinkReferenceDefinitions.ContainsKey(label))
|
||||
{
|
||||
label = null;
|
||||
}
|
||||
}
|
||||
text.Restore(ref saved);
|
||||
state.Text = saved;
|
||||
|
||||
// Else we insert a LinkDelimiter
|
||||
text.NextChar();
|
||||
state.Text.NextChar();
|
||||
state.Inline = new LinkDelimiterInline(this)
|
||||
{
|
||||
Type = DelimiterType.Open,
|
||||
@@ -74,10 +72,10 @@ namespace Textamina.Markdig.Syntax
|
||||
return true;
|
||||
|
||||
case ']':
|
||||
text.NextChar();
|
||||
state.Text.NextChar();
|
||||
if (state.Inline != null)
|
||||
{
|
||||
if (TryProcessLinkOrImage(state, text))
|
||||
if (TryProcessLinkOrImage(state, ref state.Text))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -146,7 +144,7 @@ namespace Textamina.Markdig.Syntax
|
||||
return isValidLink;
|
||||
}
|
||||
|
||||
private bool TryProcessLinkOrImage(InlineParserState inlineState, StringLineGroup text)
|
||||
private bool TryProcessLinkOrImage(InlineParserState inlineState, ref StringSlice text)
|
||||
{
|
||||
LinkDelimiterInline openParent = null;
|
||||
foreach (var parent in inlineState.Inline.FindParentOfType<LinkDelimiterInline>())
|
||||
@@ -164,7 +162,7 @@ namespace Textamina.Markdig.Syntax
|
||||
case '(':
|
||||
string url;
|
||||
string title;
|
||||
if (LinkHelper.TryParseInlineLink(text, out url, out title))
|
||||
if (LinkHelper.TryParseInlineLink(ref text, out url, out title))
|
||||
{
|
||||
// Inline Link
|
||||
var link = new LinkInline()
|
||||
@@ -192,7 +190,7 @@ namespace Textamina.Markdig.Syntax
|
||||
// Handle Collapsed links
|
||||
if (text.CurrentChar == '[')
|
||||
{
|
||||
if (text.PeekCharOnSameLine() == ']')
|
||||
if (text.PeekChar(1) == ']')
|
||||
{
|
||||
label = openParent.Label;
|
||||
text.NextChar(); // Skip [
|
||||
@@ -204,7 +202,7 @@ namespace Textamina.Markdig.Syntax
|
||||
label = openParent.Label;
|
||||
}
|
||||
|
||||
if (label != null || LinkHelper.TryParseLabel(text, true, out label))
|
||||
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label))
|
||||
{
|
||||
if (ProcessLinkReference(inlineState, label, openParent.IsImage,
|
||||
openParent.FirstChild))
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Textamina.Markdig.Syntax
|
||||
builder = literal.ContentBuilder;
|
||||
}
|
||||
|
||||
var text = state.Lines;
|
||||
var text = state.Text;
|
||||
builder.Append(text.CurrentChar);
|
||||
text.NextChar();
|
||||
return true;
|
||||
|
||||
@@ -6,18 +6,13 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
protected LeafBlock(BlockParser parser) : base(parser)
|
||||
{
|
||||
Lines = new StringLineGroup();
|
||||
Lines = new StringSliceList();
|
||||
}
|
||||
|
||||
public StringLineGroup Lines { get; set; }
|
||||
public StringSliceList Lines { get; set; }
|
||||
|
||||
public Inline Inline { get; set; }
|
||||
|
||||
public bool NoInline { get; set; }
|
||||
|
||||
internal void Append(StringLine line)
|
||||
{
|
||||
Lines.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,14 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public static bool TryParse(StringLineGroup lines, out LinkReferenceDefinitionBlock block)
|
||||
public static bool TryParse(ref StringSlice text, out LinkReferenceDefinitionBlock block)
|
||||
{
|
||||
block = null;
|
||||
string label;
|
||||
string url;
|
||||
string title;
|
||||
|
||||
if (!LinkHelper.TryParseLinkReferenceDefinition(lines, out label, out url, out title))
|
||||
if (!LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -38,26 +38,24 @@ namespace Textamina.Markdig.Syntax
|
||||
return MatchLineResult.Skip;
|
||||
}
|
||||
|
||||
var liner = state.Line;
|
||||
|
||||
// When both a thematic break and a list item are possible
|
||||
// interpretations of a line, the thematic break takes precedence
|
||||
var save = liner.Save();
|
||||
if (BreakBlock.Parser.Match(state) == MatchLineResult.Last)
|
||||
var save = state.Line;
|
||||
if (ThematicBreakBlock.Parser.Match(state) == MatchLineResult.Last)
|
||||
{
|
||||
// Remove the BreakBlock as we will let the BreakBlock to catch it later
|
||||
// Remove the ThematicBreakBlock as we will let the ThematicBreakBlock to catch it later
|
||||
state.NewBlocks.Pop();
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
liner.Restore(ref save);
|
||||
state.SetCurrentLine(ref save);
|
||||
|
||||
// 5.2 List items
|
||||
// TODO: Check with specs, it is not clear that list marker or bullet marker must be followed by at least 1 space
|
||||
|
||||
int preIndent = 0;
|
||||
for (int i = liner.Start - 1; i >= 0; i--)
|
||||
for (int i = state.Line.Start - 1; i >= 0; i--)
|
||||
{
|
||||
if (liner[i].IsSpaceOrTab())
|
||||
if (state.Line[i].IsSpaceOrTab())
|
||||
{
|
||||
preIndent++;
|
||||
}
|
||||
@@ -67,7 +65,7 @@ namespace Textamina.Markdig.Syntax
|
||||
}
|
||||
}
|
||||
|
||||
var saveLiner = liner.Save();
|
||||
var saveLiner = state.Line;
|
||||
|
||||
// If we have already a ListItemBlock, we are going to try to append to it
|
||||
var listItem = state.Pending as ListItemBlock;
|
||||
@@ -78,7 +76,7 @@ namespace Textamina.Markdig.Syntax
|
||||
// Allow all blanks lines if the last block is a fenced code block
|
||||
// Allow 1 blank line inside a list
|
||||
// If > 1 blank line, terminate this list
|
||||
var isBlankLine = liner.IsBlankLine();
|
||||
var isBlankLine = state.IsBlankLine;
|
||||
//if (isBlankLine && !(state.LastBlock is FencedCodeBlock)) // TODO: Handle this case
|
||||
var isInFencedBlock = state.LastBlock is FencedCodeBlock;
|
||||
if (isBlankLine)
|
||||
@@ -114,26 +112,26 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
list.CountBlankLinesReset = 0;
|
||||
|
||||
var c = liner.Current;
|
||||
var startPosition = liner.Column;
|
||||
var c = state.Line.CurrentChar;
|
||||
var startPosition = state.Line.Start;
|
||||
|
||||
// List Item starting with a blank line (-1)
|
||||
if (listItem.NumberOfSpaces < 0)
|
||||
{
|
||||
int expectedCount = -listItem.NumberOfSpaces;
|
||||
int countSpaces = 0;
|
||||
var saved = new StringLine.State();
|
||||
var saved = new StringSlice();
|
||||
while (c.IsSpaceOrTab())
|
||||
{
|
||||
c = liner.NextChar();
|
||||
countSpaces = preIndent + liner.Column - startPosition;
|
||||
c = state.Line.NextChar();
|
||||
countSpaces = preIndent + state.Line.Column - startPosition;
|
||||
if (countSpaces == expectedCount)
|
||||
{
|
||||
saved = liner.Save();
|
||||
saved = state.Line;
|
||||
}
|
||||
else if (countSpaces >= 4)
|
||||
{
|
||||
liner.Restore(ref saved);
|
||||
state.SetCurrentLine(ref saved);
|
||||
countSpaces = expectedCount;
|
||||
break;
|
||||
}
|
||||
@@ -149,15 +147,15 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
while (c.IsSpaceOrTab())
|
||||
{
|
||||
c = liner.NextChar();
|
||||
var countSpaces = preIndent + liner.Column - startPosition;
|
||||
c = state.Line.NextChar();
|
||||
var countSpaces = preIndent + state.Line.Column - startPosition;
|
||||
if (countSpaces >= listItem.NumberOfSpaces)
|
||||
{
|
||||
return MatchLineResult.Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
liner.Restore(ref saveLiner);
|
||||
state.SetCurrentLine(ref saveLiner);
|
||||
}
|
||||
|
||||
return TryParseListItem(ref state, preIndent);
|
||||
@@ -165,33 +163,32 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
private MatchLineResult TryParseListItem(ref BlockParserState state, int preIndent)
|
||||
{
|
||||
var liner = state.Line;
|
||||
|
||||
var isInList = state.Pending is ListItemBlock;
|
||||
|
||||
var preStartPosition = liner.Start;
|
||||
var preStartPosition = state.Line.Start;
|
||||
|
||||
var c = liner.Current;
|
||||
var c = state.Line.CurrentChar;
|
||||
if (isInList)
|
||||
{
|
||||
while (c.IsSpaceOrTab())
|
||||
{
|
||||
c = liner.NextChar();
|
||||
c = state.Line.NextChar();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
liner.SkipLeadingSpaces3();
|
||||
c = liner.Current;
|
||||
// TODO
|
||||
//state.Line.SkipLeadingSpaces3();
|
||||
c = state.Line.CurrentChar;
|
||||
}
|
||||
preIndent = preIndent + liner.Start - preStartPosition;
|
||||
preIndent = preIndent + state.Line.Start - preStartPosition;
|
||||
|
||||
var isOrdered = false;
|
||||
var bulletChar = (char) 0;
|
||||
int orderedStart = 0;
|
||||
var orderedDelimiter = (char) 0;
|
||||
|
||||
var column = liner.Start;
|
||||
var column = state.Line.Start;
|
||||
|
||||
if (c.IsBulletListMarker())
|
||||
{
|
||||
@@ -204,7 +201,7 @@ namespace Textamina.Markdig.Syntax
|
||||
while (c.IsDigit())
|
||||
{
|
||||
orderedStart = orderedStart*10 + c - '0';
|
||||
c = liner.NextChar();
|
||||
c = state.Line.NextChar();
|
||||
preIndent++;
|
||||
countDigit++;
|
||||
}
|
||||
@@ -230,11 +227,11 @@ namespace Textamina.Markdig.Syntax
|
||||
}
|
||||
|
||||
// Skip Bullet or '.'
|
||||
liner.NextChar();
|
||||
state.Line.NextChar();
|
||||
|
||||
// Item starting with a blank line
|
||||
int numberOfSpaces;
|
||||
if (liner.IsBlankLine())
|
||||
if (state.IsBlankLine)
|
||||
{
|
||||
// Use a negative number to store the number of expected chars
|
||||
numberOfSpaces = -(preIndent + 1);
|
||||
@@ -243,34 +240,34 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
var startPosition = -1;
|
||||
int countSpaceAfterBullet = 0;
|
||||
var saved = new StringLine.State();
|
||||
var saved = new StringSlice();
|
||||
for (int i = 0; i <= 4; i++)
|
||||
{
|
||||
c = liner.Current;
|
||||
c = state.Line.CurrentChar;
|
||||
if (!c.IsSpaceOrTab())
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
startPosition = liner.Column;
|
||||
startPosition = state.Line.Column;
|
||||
}
|
||||
|
||||
var endPosition = liner.Column;
|
||||
var endPosition = state.Line.Column;
|
||||
countSpaceAfterBullet = endPosition - startPosition;
|
||||
|
||||
if (countSpaceAfterBullet == 1)
|
||||
{
|
||||
saved = liner.Save();
|
||||
saved = state.Line;
|
||||
}
|
||||
else if (countSpaceAfterBullet >= 4)
|
||||
{
|
||||
liner.SpaceHeaderCount = countSpaceAfterBullet - 4;
|
||||
//state.Line.SpaceHeaderCount = countSpaceAfterBullet - 4;
|
||||
countSpaceAfterBullet = 0;
|
||||
liner.Restore(ref saved);
|
||||
state.SetCurrentLine(ref saved);
|
||||
break;
|
||||
}
|
||||
liner.NextChar();
|
||||
state.Line.NextChar();
|
||||
}
|
||||
|
||||
// If we haven't matched any spaces, early exit
|
||||
|
||||
@@ -25,13 +25,15 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public override MatchLineResult Match(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
liner.SkipLeadingSpaces3();
|
||||
if (state.IsCodeIndent)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
var column = liner.Start;
|
||||
var column = state.Column;
|
||||
|
||||
// Else it is a continue, we don't break on blank lines
|
||||
var isBlankLine = liner.IsBlankLine();
|
||||
var isBlankLine = state.IsBlankLine;
|
||||
|
||||
var paragraph = state.Pending as ParagraphBlock;
|
||||
|
||||
@@ -59,9 +61,9 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
var headingChar = (char) 0;
|
||||
bool checkForSpaces = false;
|
||||
for (int i = liner.Start; i <= liner.End; i++)
|
||||
for (int i = state.Start; i <= state.EndOffset; i++)
|
||||
{
|
||||
var c = liner[i];
|
||||
var c = state.Line[i];
|
||||
if (headingChar == 0)
|
||||
{
|
||||
if (c == '=' || c == '-')
|
||||
@@ -112,10 +114,7 @@ namespace Textamina.Markdig.Syntax
|
||||
Level = level,
|
||||
Lines = paragraph.Lines,
|
||||
};
|
||||
foreach (var line in heading.Lines)
|
||||
{
|
||||
line.Trim();
|
||||
}
|
||||
heading.Lines.Trim();
|
||||
|
||||
// Remove the paragraph as a pending block
|
||||
state.NewBlocks.Push(heading);
|
||||
@@ -129,46 +128,46 @@ namespace Textamina.Markdig.Syntax
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool TryMatchLinkReferenceDefinition(StringLineGroup localLineGroup, BlockParserState state)
|
||||
private bool TryMatchLinkReferenceDefinition(StringSliceList localLineGroup, BlockParserState state)
|
||||
{
|
||||
bool atLeastOneFound = false;
|
||||
|
||||
var saved = new StringLineGroup.State();
|
||||
while (true)
|
||||
{
|
||||
// If we have found a LinkReferenceDefinition, we can discard the previous paragraph
|
||||
localLineGroup.Save(ref saved);
|
||||
LinkReferenceDefinitionBlock linkReferenceDefinition;
|
||||
if (LinkReferenceDefinitionBlock.TryParse(localLineGroup, out linkReferenceDefinition))
|
||||
{
|
||||
if (!state.Root.LinkReferenceDefinitions.ContainsKey(linkReferenceDefinition.Label))
|
||||
{
|
||||
state.Root.LinkReferenceDefinitions[linkReferenceDefinition.Label] = linkReferenceDefinition;
|
||||
}
|
||||
atLeastOneFound = true;
|
||||
//var saved = new StringSliceList.State();
|
||||
//while (true)
|
||||
//{
|
||||
// // If we have found a LinkReferenceDefinition, we can discard the previous paragraph
|
||||
// localLineGroup.Save(ref saved);
|
||||
// LinkReferenceDefinitionBlock linkReferenceDefinition;
|
||||
// if (LinkReferenceDefinitionBlock.TryParse(localLineGroup, out linkReferenceDefinition))
|
||||
// {
|
||||
// if (!state.Root.LinkReferenceDefinitions.ContainsKey(linkReferenceDefinition.Label))
|
||||
// {
|
||||
// state.Root.LinkReferenceDefinitions[linkReferenceDefinition.Label] = linkReferenceDefinition;
|
||||
// }
|
||||
// atLeastOneFound = true;
|
||||
|
||||
// Remove lines that have been matched
|
||||
if (localLineGroup.LinePosition == localLineGroup.Count)
|
||||
{
|
||||
localLineGroup.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = localLineGroup.LinePosition - 1; i >= 0; i--)
|
||||
{
|
||||
localLineGroup.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!atLeastOneFound)
|
||||
{
|
||||
localLineGroup.Restore(ref saved);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// // Remove lines that have been matched
|
||||
// if (localLineGroup.LinePosition == localLineGroup.Count)
|
||||
// {
|
||||
// localLineGroup.Clear();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// for (int i = localLineGroup.LinePosition - 1; i >= 0; i--)
|
||||
// {
|
||||
// localLineGroup.RemoveAt(i);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (!atLeastOneFound)
|
||||
// {
|
||||
// localLineGroup.Restore(ref saved);
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
return atLeastOneFound;
|
||||
}
|
||||
@@ -193,7 +192,7 @@ namespace Textamina.Markdig.Syntax
|
||||
var lineCount = lines.Count;
|
||||
for (int i = 0; i < lineCount; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
var line = lines.Slices[i];
|
||||
line.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,17 +15,18 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public override MatchLineResult Match(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
|
||||
liner.SkipLeadingSpaces3();
|
||||
if (state.IsCodeIndent)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
// 5.1 Block quotes
|
||||
// A block quote marker consists of 0-3 spaces of initial indent, plus (a) the character > together with a following space, or (b) a single character > not followed by a space.
|
||||
var c = liner.Current;
|
||||
var column = liner.Start;
|
||||
var c = state.CurrentChar;
|
||||
var column = state.Column;
|
||||
if (c != '>')
|
||||
{
|
||||
if (state.Pending != null && liner.IsBlankLine())
|
||||
if (state.Pending != null && state.IsBlankLine)
|
||||
{
|
||||
state.Close(state.Pending);
|
||||
return MatchLineResult.None;
|
||||
@@ -33,10 +34,10 @@ namespace Textamina.Markdig.Syntax
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
c = liner.NextChar();
|
||||
c = state.NextChar();
|
||||
if (c.IsSpace())
|
||||
{
|
||||
liner.NextChar();
|
||||
state.NextChar();
|
||||
}
|
||||
|
||||
if (state.Pending == null)
|
||||
|
||||
@@ -1,251 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Textamina.Markdig.Helpers;
|
||||
using Textamina.Markdig.Parsing;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public sealed class StringLine
|
||||
{
|
||||
public StringLine(string text, int lineIndex = 0)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
LineIndex = lineIndex;
|
||||
Text = text;
|
||||
Start = 0;
|
||||
End = Text.Length - 1;
|
||||
Column = 0;
|
||||
SpaceHeaderCount = 0;
|
||||
Current = End >= 0 ? Text[0] : (char)0;
|
||||
}
|
||||
|
||||
public int LineIndex { get; }
|
||||
|
||||
public string Text { get; private set; }
|
||||
|
||||
public int Start { get; set; }
|
||||
|
||||
public int End { get; set; }
|
||||
|
||||
public int SpaceHeaderCount { get; set; }
|
||||
|
||||
public int Column { get; private set; }
|
||||
|
||||
public char Current { get; private set; }
|
||||
|
||||
public bool IsEol => Start > End;
|
||||
|
||||
private bool isBlankLine;
|
||||
|
||||
public char this[int index] => Text[index];
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public State Save()
|
||||
{
|
||||
return new State(Start, End, Column, SpaceHeaderCount, Current);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public void Restore(State state)
|
||||
{
|
||||
Restore(ref state);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public void Restore(ref State state)
|
||||
{
|
||||
Start = state.Start;
|
||||
End = state.End;
|
||||
Column = state.Column;
|
||||
SpaceHeaderCount = state.SpaceHeaderCount;
|
||||
Current = state.Current;
|
||||
}
|
||||
|
||||
public char NextChar()
|
||||
{
|
||||
Start++;
|
||||
if (Start <= End)
|
||||
{
|
||||
// If previous character was a tab make the Column += 4
|
||||
Column++;
|
||||
if (Current.IsTab())
|
||||
{
|
||||
// Align the tab on a column
|
||||
Column = ((Column + 3) / 4) * 4;
|
||||
}
|
||||
Current = Text[Start].EscapeInsecure();
|
||||
}
|
||||
else
|
||||
{
|
||||
Start = End + 1;
|
||||
Current = (char) 0;
|
||||
}
|
||||
return Current;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekChar(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index <= End ? Text[index].EscapeInsecure() : (char) 0;
|
||||
}
|
||||
|
||||
public bool Match(string text, int offset = 0)
|
||||
{
|
||||
return Match(text, End, offset);
|
||||
}
|
||||
|
||||
public bool Match(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != Text[index])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
}
|
||||
|
||||
public bool MatchLowercase(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != char.ToLowerInvariant(Text[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
}
|
||||
|
||||
public bool Search(string text, int offset = 0)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
for (int i = Start; i <= end; i++)
|
||||
{
|
||||
if (Match(text, End, i))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SearchLowercase(string text, int offset = 0)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
for (int i = Start; i <= end; i++)
|
||||
{
|
||||
if (MatchLowercase(text, End, i))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsBlankLine()
|
||||
{
|
||||
if (isBlankLine)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = Start; i <= End; i++)
|
||||
{
|
||||
if (!Text[i].IsSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
isBlankLine = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public void SkipLeadingSpaces3()
|
||||
{
|
||||
var c = Current;
|
||||
if (c.IsSpace())
|
||||
{
|
||||
c = NextChar();
|
||||
if (c.IsSpace())
|
||||
{
|
||||
c = NextChar();
|
||||
if (c.IsSpace())
|
||||
{
|
||||
NextChar();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public StringSlice ToSlice()
|
||||
{
|
||||
return new StringSlice(Text, Start, End);
|
||||
}
|
||||
|
||||
public void TrimStart(bool includeTabs = false)
|
||||
{
|
||||
// Strip leading spaces
|
||||
var c = Current;
|
||||
while (includeTabs ? c.IsSpaceOrTab() :c.IsSpace())
|
||||
{
|
||||
c = NextChar();
|
||||
}
|
||||
}
|
||||
|
||||
public void TrimEnd(bool includeTabs = false)
|
||||
{
|
||||
for (int i = End; i >= Start; i--)
|
||||
{
|
||||
End = i;
|
||||
var c = this[i];
|
||||
if (!(includeTabs ? c.IsSpaceOrTab() : c.IsSpace()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
{
|
||||
TrimStart();
|
||||
TrimEnd();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Start <= End ? Text.Substring(Start, End - Start + 1) : string.Empty;
|
||||
}
|
||||
|
||||
public struct State
|
||||
{
|
||||
public State(int start, int end, int column, int spaceHeaderCount, char current)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Column = column;
|
||||
SpaceHeaderCount = spaceHeaderCount;
|
||||
Current = current;
|
||||
}
|
||||
|
||||
public readonly int Start;
|
||||
|
||||
public readonly int End;
|
||||
|
||||
public readonly int Column;
|
||||
|
||||
public readonly int SpaceHeaderCount;
|
||||
|
||||
public readonly char Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using Textamina.Markdig.Helpers;
|
||||
using Textamina.Markdig.Parsing;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public class StringLineGroup : Collection<StringLine>
|
||||
{
|
||||
private StringLine currentLine;
|
||||
|
||||
public StringLineGroup()
|
||||
{
|
||||
}
|
||||
|
||||
public StringLineGroup(string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
Add(new StringLine(text));
|
||||
}
|
||||
|
||||
public int LinePosition { get; private set; }
|
||||
|
||||
public int ColumnPosition { get; private set; }
|
||||
|
||||
public StringLine Current => currentLine;
|
||||
|
||||
public char CurrentChar { get; private set; }
|
||||
|
||||
public char PreviousChar1 { get; private set; }
|
||||
|
||||
public char PreviousChar2 { get; private set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ColumnPosition = -1;
|
||||
LinePosition = 0;
|
||||
currentLine = Count > 0 ? this[0] : null;
|
||||
if (currentLine != null)
|
||||
{
|
||||
ColumnPosition = currentLine.Start - 1;
|
||||
}
|
||||
NextChar();
|
||||
}
|
||||
|
||||
public void SetColumn(int index)
|
||||
{
|
||||
if (Current != null && index <= Current.End)
|
||||
{
|
||||
CurrentChar = Current[index];
|
||||
ColumnPosition = index;
|
||||
// Don't set previous char
|
||||
PreviousChar1 = '\0';
|
||||
PreviousChar2 = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
public char PeekCharOnSameLine()
|
||||
{
|
||||
if (Current != null && (ColumnPosition+1) <= Current.End)
|
||||
{
|
||||
return Current[ColumnPosition + 1];
|
||||
}
|
||||
return (char)0;
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
base.ClearItems();
|
||||
Reset();
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, StringLine item)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
if (index == 0)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetItem(int index, StringLine item)
|
||||
{
|
||||
base.SetItem(index, item);
|
||||
if (index == 0)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
base.RemoveItem(index);
|
||||
if (index == 0)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEndOfLines => CurrentChar == '\0';
|
||||
|
||||
public State Save()
|
||||
{
|
||||
return new State(currentLine, LinePosition, ColumnPosition, CurrentChar, PreviousChar1, PreviousChar2);
|
||||
}
|
||||
|
||||
public void Save(ref State state)
|
||||
{
|
||||
state.CurrentLine = currentLine;
|
||||
state.LinePosition = LinePosition;
|
||||
state.ColumnPosition = ColumnPosition;
|
||||
state.Current = CurrentChar;
|
||||
state.PreviousChar1 = PreviousChar1;
|
||||
state.PreviousChar2 = PreviousChar2;
|
||||
}
|
||||
|
||||
public void Restore(ref State state)
|
||||
{
|
||||
currentLine = state.CurrentLine;
|
||||
LinePosition = state.LinePosition;
|
||||
ColumnPosition = state.ColumnPosition;
|
||||
CurrentChar = state.Current;
|
||||
PreviousChar1 = state.PreviousChar1;
|
||||
PreviousChar2 = state.PreviousChar2;
|
||||
}
|
||||
|
||||
public char NextChar()
|
||||
{
|
||||
PreviousChar2 = PreviousChar1;
|
||||
PreviousChar1 = CurrentChar;
|
||||
if (currentLine != null)
|
||||
{
|
||||
ColumnPosition++;
|
||||
if (ColumnPosition <= currentLine.End)
|
||||
{
|
||||
CurrentChar = currentLine[ColumnPosition].EscapeInsecure();
|
||||
}
|
||||
else
|
||||
{
|
||||
LinePosition++;
|
||||
if (LinePosition < Count)
|
||||
{
|
||||
currentLine = this[LinePosition];
|
||||
}
|
||||
else
|
||||
{
|
||||
currentLine = null;
|
||||
LinePosition = Count;
|
||||
}
|
||||
|
||||
ColumnPosition = -1;
|
||||
if (currentLine != null)
|
||||
{
|
||||
ColumnPosition = currentLine.Start - 1;
|
||||
}
|
||||
CurrentChar = currentLine != null ? '\n' : '\0';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentChar = '\0';
|
||||
}
|
||||
|
||||
return CurrentChar;
|
||||
}
|
||||
|
||||
public bool SkipWhiteSpaces()
|
||||
{
|
||||
bool hasWhitespaces = false;
|
||||
while (CurrentChar.IsWhitespace())
|
||||
{
|
||||
NextChar();
|
||||
hasWhitespaces = true;
|
||||
}
|
||||
return hasWhitespaces;
|
||||
}
|
||||
|
||||
public bool SkipWhiteSpaces(out int newLineCount)
|
||||
{
|
||||
bool hasWhitespaces = false;
|
||||
newLineCount = 0;
|
||||
while (CurrentChar.IsWhitespace())
|
||||
{
|
||||
if (CurrentChar == '\n')
|
||||
{
|
||||
newLineCount++;
|
||||
}
|
||||
NextChar();
|
||||
hasWhitespaces = true;
|
||||
}
|
||||
return hasWhitespaces;
|
||||
}
|
||||
|
||||
public void TrimStart()
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip leading and
|
||||
this[0].TrimStart();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void TrimEnd()
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this[Count - 1].TrimEnd();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
{
|
||||
TrimStart();
|
||||
TrimEnd();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
bool firstLine = true;
|
||||
foreach (var line in this)
|
||||
{
|
||||
if (!firstLine)
|
||||
{
|
||||
stringBuilder.Append('\n');
|
||||
}
|
||||
stringBuilder.Append(line);
|
||||
firstLine = false;
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
public struct State
|
||||
{
|
||||
public State(StringLine currentLine, int linePosition, int columnPosition, char current, char previousChar1, char previousChar2)
|
||||
{
|
||||
CurrentLine = currentLine;
|
||||
LinePosition = linePosition;
|
||||
ColumnPosition = columnPosition;
|
||||
Current = current;
|
||||
PreviousChar1 = previousChar1;
|
||||
PreviousChar2 = previousChar2;
|
||||
}
|
||||
|
||||
public StringLine CurrentLine;
|
||||
|
||||
public int LinePosition;
|
||||
|
||||
public int ColumnPosition;
|
||||
|
||||
public char Current;
|
||||
|
||||
public char PreviousChar1;
|
||||
|
||||
public char PreviousChar2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,171 @@
|
||||
namespace Textamina.Markdig.Syntax
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Textamina.Markdig.Helpers;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public struct StringSlice
|
||||
{
|
||||
public StringSlice(string text, int start, int end)
|
||||
public StringSlice(string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
Text = text;
|
||||
Start = start;
|
||||
End = end;
|
||||
Start = 0;
|
||||
End = Text.Length - 1;
|
||||
}
|
||||
|
||||
public string Text;
|
||||
public readonly string Text;
|
||||
|
||||
public int Start;
|
||||
|
||||
public int End;
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return End - Start; }
|
||||
public int Column => Start;
|
||||
|
||||
set { End = Start + value; }
|
||||
public char CurrentChar => Start <= End ? this[Start] : '\0';
|
||||
|
||||
public bool IsEndOfSlice => Start > End;
|
||||
|
||||
public char this[int index] => Text[index].EscapeInsecure();
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char NextChar()
|
||||
{
|
||||
Start++;
|
||||
if (Start > End)
|
||||
{
|
||||
Start = End + 1;
|
||||
}
|
||||
return CurrentChar;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekChar(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= Start && index <= End ? Text[index].EscapeInsecure() : (char) 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekCharExtra(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= 0 && index < Text.Length ? Text[index].EscapeInsecure() : (char)0;
|
||||
}
|
||||
|
||||
public bool Match(string text, int offset = 0)
|
||||
{
|
||||
return Match(text, End, offset);
|
||||
}
|
||||
|
||||
public bool Match(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != Text[index])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
}
|
||||
|
||||
public bool MatchLowercase(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != char.ToLowerInvariant(Text[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
}
|
||||
|
||||
public bool Search(string text, int offset = 0)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
for (int i = Start; i <= end; i++)
|
||||
{
|
||||
if (Match(text, End, i))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SearchLowercase(string text, int offset = 0)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
for (int i = Start; i <= end; i++)
|
||||
{
|
||||
if (MatchLowercase(text, End, i))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TrimStart()
|
||||
{
|
||||
// Strip leading spaces
|
||||
var c = CurrentChar;
|
||||
var hasWhitespaces = false;
|
||||
while (c.IsWhitespace())
|
||||
{
|
||||
c = NextChar();
|
||||
hasWhitespaces = true;
|
||||
}
|
||||
return hasWhitespaces;
|
||||
}
|
||||
|
||||
public bool TrimStart(out int newLineCount)
|
||||
{
|
||||
bool hasWhitespaces = false;
|
||||
newLineCount = 0;
|
||||
var c = CurrentChar;
|
||||
while (c.IsWhitespace())
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
newLineCount++;
|
||||
}
|
||||
c = NextChar();
|
||||
hasWhitespaces = true;
|
||||
}
|
||||
return hasWhitespaces;
|
||||
}
|
||||
|
||||
public void TrimEnd(bool includeTabs = false)
|
||||
{
|
||||
for (int i = End; i >= Start; i--)
|
||||
{
|
||||
End = i;
|
||||
var c = this[i];
|
||||
if (!(includeTabs ? c.IsSpaceOrTab() : c.IsSpace()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
{
|
||||
TrimStart();
|
||||
TrimEnd();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Start <= End ? Text.Substring(Start, End - Start + 1) : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/Textamina.Markdig/Syntax/StringSliceList.cs
Normal file
67
src/Textamina.Markdig/Syntax/StringSliceList.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Textamina.Markdig.Helpers;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public class StringSliceList
|
||||
{
|
||||
private static readonly StringSlice[] Empty = new StringSlice[0];
|
||||
|
||||
private StringSlice currentLine;
|
||||
|
||||
public StringSliceList()
|
||||
{
|
||||
Slices = Empty;
|
||||
}
|
||||
|
||||
public StringSliceList(string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
Append(new StringSlice(text));
|
||||
}
|
||||
|
||||
public StringSlice[] Slices;
|
||||
|
||||
public int Count;
|
||||
|
||||
public void Append(ref StringSlice slice)
|
||||
{
|
||||
if (Count == Slices.Length) IncreaseCapacity();
|
||||
Slices[Count++] = slice;
|
||||
}
|
||||
|
||||
public void Append(StringSlice slice)
|
||||
{
|
||||
if (Count == Slices.Length) IncreaseCapacity();
|
||||
Slices[Count++] = slice;
|
||||
}
|
||||
|
||||
private void IncreaseCapacity()
|
||||
{
|
||||
int newCapacity = Slices.Length == 0 ? 4 : Slices.Length * 2;
|
||||
var newItems = new StringSlice[newCapacity];
|
||||
if (Count > 0)
|
||||
{
|
||||
Array.Copy(Slices, 0, newItems, 0, Count);
|
||||
}
|
||||
Slices = newItems;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var stringBuilder = StringBuilderCache.Local();
|
||||
for(int i = 0; i < Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
stringBuilder.Append('\n');
|
||||
}
|
||||
stringBuilder.Append(Slices[i]);
|
||||
}
|
||||
var str = stringBuilder.ToString();
|
||||
stringBuilder.Clear();
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Textamina.Markdig/Syntax/StringSliceListExtensions.cs
Normal file
14
src/Textamina.Markdig/Syntax/StringSliceListExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public static class StringSliceListExtensions
|
||||
{
|
||||
public static void Trim(this StringSliceList slices)
|
||||
{
|
||||
for (int i = 0; i < slices.Count; i++)
|
||||
{
|
||||
slices.Slices[i].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ namespace Textamina.Markdig.Syntax
|
||||
/// <summary>
|
||||
/// Repressents a thematic break.
|
||||
/// </summary>
|
||||
public class BreakBlock : LeafBlock
|
||||
public class ThematicBreakBlock : LeafBlock
|
||||
{
|
||||
public new static readonly BlockParser Parser = new ParserInternal();
|
||||
|
||||
public BreakBlock(BlockParser parser) : base(parser)
|
||||
public ThematicBreakBlock(BlockParser parser) : base(parser)
|
||||
{
|
||||
NoInline = true;
|
||||
}
|
||||
@@ -19,20 +19,23 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public override MatchLineResult Match(BlockParserState state)
|
||||
{
|
||||
var liner = state.Line;
|
||||
liner.SkipLeadingSpaces3();
|
||||
if (state.IsCodeIndent)
|
||||
{
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
var column = liner.Start;
|
||||
var column = state.Column;
|
||||
|
||||
// 4.1 Thematic breaks
|
||||
// A line consisting of 0-3 spaces of indentation, followed by a sequence of three or more matching -, _, or * characters, each followed optionally by any number of spaces
|
||||
var c = liner.Current;
|
||||
var c = state.CurrentChar;
|
||||
|
||||
int count = 0;
|
||||
var matchChar = (char)0;
|
||||
bool hasSpacesSinceLastMatch = false;
|
||||
bool hasInnerSpaces = false;
|
||||
while (!liner.IsEol)
|
||||
int offset = 0;
|
||||
while (c != '\0')
|
||||
{
|
||||
if (count == 0 && (c == '-' || c == '_' || c == '*'))
|
||||
{
|
||||
@@ -56,7 +59,9 @@ namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
hasSpacesSinceLastMatch = true;
|
||||
}
|
||||
c = liner.NextChar();
|
||||
|
||||
offset++;
|
||||
c = state.PeekChar(offset);
|
||||
}
|
||||
|
||||
// If it as less than 3 chars or it is a setex heading and we are already in a paragraph, let the paragraph handle it
|
||||
@@ -77,8 +82,8 @@ namespace Textamina.Markdig.Syntax
|
||||
return MatchLineResult.None;
|
||||
}
|
||||
|
||||
state.NewBlocks.Push(new BreakBlock(this) { Column = column });
|
||||
return MatchLineResult.Last;
|
||||
state.NewBlocks.Push(new ThematicBreakBlock(this) { Column = column });
|
||||
return MatchLineResult.LastDiscard;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,6 @@
|
||||
<Compile Include="Syntax\Inlines\LinkDelimiterInline.cs" />
|
||||
<Compile Include="Syntax\Inlines\LiteralInline.cs" />
|
||||
<Compile Include="Syntax\Inlines\RawHtmlInline.cs" />
|
||||
<Compile Include="Syntax\StringSlice.cs" />
|
||||
<Compile Include="Syntax\Block.cs" />
|
||||
<Compile Include="Syntax\ContainerBlock.cs" />
|
||||
<Compile Include="Syntax\HtmlBlock.cs" />
|
||||
@@ -75,7 +74,8 @@
|
||||
<Compile Include="Syntax\ListBlock.cs" />
|
||||
<Compile Include="Syntax\ListItemBlock.cs" />
|
||||
<Compile Include="Syntax\QuoteBlock.cs" />
|
||||
<Compile Include="Syntax\BreakBlock.cs" />
|
||||
<Compile Include="Syntax\StringSliceListExtensions.cs" />
|
||||
<Compile Include="Syntax\ThematicBreakBlock.cs" />
|
||||
<Compile Include="Helpers\CharHelper.cs" />
|
||||
<Compile Include="Syntax\CodeBlock.cs" />
|
||||
<Compile Include="Syntax\Document.cs" />
|
||||
@@ -86,8 +86,8 @@
|
||||
<Compile Include="Syntax\Inlines\Inline.cs" />
|
||||
<Compile Include="Syntax\ParagraphBlock.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Syntax\StringLine.cs" />
|
||||
<Compile Include="Syntax\StringLineGroup.cs" />
|
||||
<Compile Include="Syntax\StringSlice.cs" />
|
||||
<Compile Include="Syntax\StringSliceList.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="project.json" />
|
||||
|
||||
Reference in New Issue
Block a user