mirror of
https://github.com/xoofx/markdig.git
synced 2026-10-08 14:48:32 +00:00
Add tests for StringLine and StringLineGroup. Fix issues with StringLine and StringLineGroup. Restore tests passed (253)
This commit is contained in:
144
src/Textamina.Markdig.Tests/TestLinkInline.cs
Normal file
144
src/Textamina.Markdig.Tests/TestLinkInline.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using NUnit.Framework;
|
||||
using Textamina.Markdig.Syntax;
|
||||
|
||||
namespace Textamina.Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestLinkInline
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void TestLinkSimple()
|
||||
{
|
||||
var text = new StringLineGroup("toto tutu");
|
||||
string link;
|
||||
Assert.True(LinkInline.TryParseLink(text, out link));
|
||||
Assert.AreEqual("toto", link);
|
||||
Assert.AreEqual(' ', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkUrl()
|
||||
{
|
||||
var text = new StringLineGroup("http://google.com)");
|
||||
string link;
|
||||
Assert.True(LinkInline.TryParseLink(text, out link));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual(')', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkNestedParenthesis()
|
||||
{
|
||||
var text = new StringLineGroup("(toto)tutu(tata) nooo");
|
||||
string link;
|
||||
Assert.True(LinkInline.TryParseLink(text, out link));
|
||||
Assert.AreEqual("(toto)tutu(tata)", link);
|
||||
Assert.AreEqual(' ', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkAlternate()
|
||||
{
|
||||
var text = new StringLineGroup("<toto_tata_tutu> nooo");
|
||||
string link;
|
||||
Assert.True(LinkInline.TryParseLink(text, out link));
|
||||
Assert.AreEqual("toto_tata_tutu", link);
|
||||
Assert.AreEqual(' ', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkAlternateInvalid()
|
||||
{
|
||||
var text = new StringLineGroup("<toto_tata_tutu");
|
||||
string link;
|
||||
Assert.False(LinkInline.TryParseLink(text, out link));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTitleSimple()
|
||||
{
|
||||
var text = new StringLineGroup(@"'tata\tutu\''");
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseTitle(text, out title));
|
||||
Assert.AreEqual(@"tata\tutu'", title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTitleSimpleAlternate()
|
||||
{
|
||||
var text = new StringLineGroup(@"""tata\tutu\"""" ");
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseTitle(text, out title));
|
||||
Assert.AreEqual(@"tata\tutu""", title);
|
||||
Assert.AreEqual(' ', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkAndTitle()
|
||||
{
|
||||
var text = new StringLineGroup(@"(http://google.com 'this is a title')ABC");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseLinkAndTitle(text, out link, out title));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual("this is a title", title);
|
||||
Assert.AreEqual('A', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkAndTitleEmpty()
|
||||
{
|
||||
var text = new StringLineGroup(@"(<>)A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseLinkAndTitle(text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual('A', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkAndTitleEmpty2()
|
||||
{
|
||||
var text = new StringLineGroup(@"( <> )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseLinkAndTitle(text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual('A', text.Current);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestLinkEmptyWithTitleWithMultipleSpaces()
|
||||
{
|
||||
var text = new StringLineGroup(@"( <> 'toto' )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseLinkAndTitle(text, out link, out title));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual("toto", title);
|
||||
Assert.AreEqual('A', text.Current);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestMultipleLines()
|
||||
{
|
||||
var text = new StringLineGroup()
|
||||
{
|
||||
new StringLine("("),
|
||||
new StringLine(" <http://google.com> "),
|
||||
new StringLine(" 'toto' )A")
|
||||
};
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkInline.TryParseLinkAndTitle(text, out link, out title));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual("toto", title);
|
||||
Assert.AreEqual('A', text.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
src/Textamina.Markdig.Tests/TestStringLine.cs
Normal file
110
src/Textamina.Markdig.Tests/TestStringLine.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Textamina.Markdig.Syntax;
|
||||
|
||||
namespace Textamina.Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestStringLine
|
||||
{
|
||||
// TODO: Add tests for StringLine
|
||||
// TODO: Add more tests for StringLineGroup
|
||||
|
||||
[Test]
|
||||
public void TestStringLineGroupSimple()
|
||||
{
|
||||
var text = new StringLineGroup()
|
||||
{
|
||||
new StringLine("ABC"),
|
||||
new StringLine("E"),
|
||||
new StringLine("F")
|
||||
};
|
||||
|
||||
var chars = ToString(text);
|
||||
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
|
||||
}
|
||||
|
||||
private static string ToString(StringLineGroup text)
|
||||
{
|
||||
var chars = new StringBuilder();
|
||||
while (text.Current != '\0')
|
||||
{
|
||||
chars.Append(text.Current);
|
||||
text.NextChar();
|
||||
}
|
||||
return chars.ToString();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStringLineGroupSaveAndRestore()
|
||||
{
|
||||
var text = new StringLineGroup()
|
||||
{
|
||||
new StringLine("ABCD"),
|
||||
new StringLine("EF"),
|
||||
};
|
||||
|
||||
text.NextChar(); // B
|
||||
|
||||
var save = text.Save();
|
||||
|
||||
text.NextChar(); // C
|
||||
text.NextChar(); // D
|
||||
text.NextChar(); // \n
|
||||
text.NextChar();
|
||||
Assert.AreEqual('E', text.Current);
|
||||
Assert.AreEqual(1, text.LinePosition);
|
||||
Assert.AreEqual(0, text.ColumnPosition);
|
||||
|
||||
text.Restore(ref save);
|
||||
Assert.AreEqual('B', text.Current);
|
||||
Assert.AreEqual(0, text.LinePosition);
|
||||
Assert.AreEqual(1, text.ColumnPosition);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkipWhitespaces()
|
||||
{
|
||||
var text = new StringLineGroup(" ABC");
|
||||
Assert.True(text.SkipWhiteSpaces());
|
||||
Assert.False(text.SkipWhiteSpaces());
|
||||
Assert.AreEqual('A', text.Current);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStringLineGroupWithModifiedStart()
|
||||
{
|
||||
var line1 = new StringLine(" ABC");
|
||||
line1.NextChar();
|
||||
line1.NextChar();
|
||||
|
||||
var line2 = new StringLine(" DEF ");
|
||||
line2.Trim();
|
||||
|
||||
var text = new StringLineGroup() {line1, line2};
|
||||
|
||||
var result = ToString(text);
|
||||
TextAssert.AreEqual("ABC\nDEF", result);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestStringLineGroupWithTrim()
|
||||
{
|
||||
var line1 = new StringLine(" ABC ");
|
||||
line1.NextChar();
|
||||
line1.NextChar();
|
||||
|
||||
var line2 = new StringLine(" DEF ");
|
||||
|
||||
var text = new StringLineGroup() { line1, line2 };
|
||||
text.Trim();
|
||||
|
||||
var result = ToString(text);
|
||||
TextAssert.AreEqual("ABC \n DEF", result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,8 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Specs.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TestLinkInline.cs" />
|
||||
<Compile Include="TestStringLine.cs" />
|
||||
<Compile Include="TextAssert.cs" />
|
||||
<Compile Include="TestParser.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Textamina.Markdig.Formatters
|
||||
[typeof(EscapeInline)] = o => Write((EscapeInline)o),
|
||||
[typeof(LiteralInline)] = o => Write((LiteralInline)o),
|
||||
[typeof(CodeInline)] = o => Write((CodeInline)o),
|
||||
[typeof(ContainerInline)] = o => Write((ContainerInline)o),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,6 +124,21 @@ namespace Textamina.Markdig.Formatters
|
||||
writer.WriteConstant("</code>");
|
||||
}
|
||||
|
||||
protected void Write(ContainerInline containerInline)
|
||||
{
|
||||
var inline = containerInline.FirstChild;
|
||||
while (inline != null)
|
||||
{
|
||||
Action<object> writerAction;
|
||||
if (registeredWriters.TryGetValue(inline.GetType(), out writerAction))
|
||||
{
|
||||
writerAction(inline);
|
||||
}
|
||||
|
||||
inline = inline.NextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
protected void WriteLeaf(LeafBlock leafBlock, bool writeEndOfLines)
|
||||
{
|
||||
var inline = leafBlock.Inline;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using Textamina.Markdig.Syntax;
|
||||
|
||||
namespace Textamina.Markdig.Parsing
|
||||
@@ -417,13 +418,15 @@ namespace Textamina.Markdig.Parsing
|
||||
private void ProcessInlines(LeafBlock leafBlock)
|
||||
{
|
||||
var lines = leafBlock.Lines;
|
||||
lines.Initialize();
|
||||
|
||||
var state = new MatchInlineState(lines) {Builder = new StringBuilder() };
|
||||
leafBlock.Inline = new ContainerInline();
|
||||
var state = new MatchInlineState(lines)
|
||||
{
|
||||
Builder = new StringBuilder(),
|
||||
Inline = leafBlock.Inline
|
||||
};
|
||||
|
||||
var opened = new List<Inline>();
|
||||
|
||||
Inline previousInline = null;
|
||||
var previousInline = leafBlock.Inline;
|
||||
while (!lines.IsEndOfLines)
|
||||
{
|
||||
var saveLines = lines.Save();
|
||||
@@ -460,14 +463,17 @@ namespace Textamina.Markdig.Parsing
|
||||
}
|
||||
}
|
||||
|
||||
if (previousInline != state.Inline)
|
||||
var nextInline = state.Inline;
|
||||
|
||||
if (previousInline != nextInline)
|
||||
{
|
||||
if (previousInline is LeafInline)
|
||||
{
|
||||
opened.Remove(previousInline);
|
||||
previousInline.Close(state);
|
||||
previousInline.InsertAfter(state.Inline);
|
||||
opened.Add(state.Inline);
|
||||
if (nextInline.Parent == null)
|
||||
{
|
||||
previousInline.InsertAfter(nextInline);
|
||||
}
|
||||
}
|
||||
else if (previousInline != null)
|
||||
{
|
||||
@@ -475,25 +481,23 @@ namespace Textamina.Markdig.Parsing
|
||||
|
||||
if (container.IsClosed)
|
||||
{
|
||||
opened.Remove(previousInline);
|
||||
container.InsertAfter(state.Inline);
|
||||
container.InsertAfter(nextInline);
|
||||
}
|
||||
else
|
||||
{
|
||||
container.AppendChild(state.Inline);
|
||||
container.AppendChild(nextInline);
|
||||
}
|
||||
}
|
||||
|
||||
// Store first inline
|
||||
if (leafBlock.Inline == null)
|
||||
{
|
||||
leafBlock.Inline = state.Inline;
|
||||
}
|
||||
}
|
||||
|
||||
previousInline = state.Inline;
|
||||
previousInline = nextInline;
|
||||
}
|
||||
|
||||
while (previousInline != null)
|
||||
{
|
||||
previousInline.Close(state);
|
||||
previousInline = previousInline.Parent;
|
||||
}
|
||||
|
||||
// TODO: Close opened inlines
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using Textamina.Markdig.Parsing;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public abstract class ContainerInline : Inline
|
||||
public class ContainerInline : Inline
|
||||
{
|
||||
public Inline FirstChild { get; private set; }
|
||||
|
||||
|
||||
@@ -11,134 +11,144 @@ namespace Textamina.Markdig.Syntax
|
||||
[ThreadStatic]
|
||||
private static readonly StringBuilder TempBuilder = new StringBuilder();
|
||||
|
||||
private class ParserInternal : InlineParser
|
||||
{
|
||||
public ParserInternal()
|
||||
{
|
||||
FirstChars = new[] {'[', ']', '!'};
|
||||
}
|
||||
|
||||
public override bool Match(MatchInlineState state)
|
||||
{
|
||||
var lines = state.Lines;
|
||||
|
||||
var c = lines.Current;
|
||||
|
||||
bool isImage = false;
|
||||
if (c == '!')
|
||||
{
|
||||
isImage = true;
|
||||
c = lines.NextChar();
|
||||
if (c != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '[')
|
||||
{
|
||||
state.Inline = new LinkDelimiterInline(this)
|
||||
{
|
||||
Type = DelimiterType.Open,
|
||||
IsImage = isImage
|
||||
};
|
||||
return true;
|
||||
}
|
||||
else if (c == ']')
|
||||
{
|
||||
if (state.Inline != null)
|
||||
{
|
||||
LinkDelimiterInline firstParent = null;
|
||||
foreach (var parent in state.Inline.FindParentOfType<LinkDelimiterInline>())
|
||||
{
|
||||
firstParent = parent;
|
||||
break;
|
||||
}
|
||||
|
||||
// This will be matched as a literal
|
||||
if (firstParent != null)
|
||||
{
|
||||
// TODO: continue parsing of ]
|
||||
|
||||
// We have a nested [ ]
|
||||
// firstParent.Remove();
|
||||
// The opening [ will be transformed to a literal followed by all the childrens of the [
|
||||
|
||||
var literal = new LiteralInline() {Content = firstParent.IsImage ? "![" : "["};
|
||||
state.Inline = firstParent.ReplaceBy(literal);
|
||||
|
||||
return true;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// var link = firstParent.IsImage
|
||||
// ? (LinkInline) new ImageLinkInline()
|
||||
// : new TextLinkInline();
|
||||
|
||||
// // 1. Process all delimiters inside firstParent to convert them to inlines
|
||||
// // 2. Replace firstParent with link, and move all child to this one
|
||||
|
||||
// // TODO: continue parsing of ]
|
||||
|
||||
// if (state.Inline == firstParent)
|
||||
// {
|
||||
// state.Inline = link;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
return false;
|
||||
|
||||
// Match a close tag
|
||||
}
|
||||
|
||||
|
||||
// We don't have an emphasis
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryParseLinkUrl(MatchInlineState state)
|
||||
{
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private bool TryParseLinkTitle(MatchInlineState state)
|
||||
{
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static bool TryParseLinkTitle(StringLineGroup text, out string title)
|
||||
public static bool TryParseLinkAndTitle(StringLineGroup text, out string link, out string title)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
|
||||
// a sequence of zero or more characters between straight single-quote characters ('), including a ' character only if it is backslash-escaped, or
|
||||
// a sequence of zero or more characters between matching parentheses ((...)), including a ) character only if it is backslash-escaped.
|
||||
|
||||
// 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,
|
||||
// 4. an optional link title separated from the link destination by whitespace,
|
||||
// 5. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
|
||||
// 6. and a right parenthesis )
|
||||
bool isValid = false;
|
||||
var c = text.Current;
|
||||
link = null;
|
||||
title = null;
|
||||
|
||||
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
|
||||
if (c == '(')
|
||||
{
|
||||
text.NextChar();
|
||||
text.SkipWhiteSpaces();
|
||||
|
||||
if (TryParseLink(text, out link))
|
||||
{
|
||||
var hasWhiteSpaces = text.SkipWhiteSpaces();
|
||||
|
||||
c = text.Current;
|
||||
if (c == ')')
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
else if (hasWhiteSpaces)
|
||||
{
|
||||
c = text.Current;
|
||||
if (c == ')')
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
else if (TryParseTitle(text, out title))
|
||||
{
|
||||
text.SkipWhiteSpaces();
|
||||
c = text.Current;
|
||||
|
||||
if (c == ')')
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
// Skip ')'
|
||||
text.NextChar();
|
||||
title = title ?? string.Empty;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
public static bool TryParseTitle(StringLineGroup text, out string title)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
|
||||
bool isValid = false;
|
||||
var buffer = TempBuilder;
|
||||
buffer.Clear();
|
||||
|
||||
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
|
||||
// a sequence of zero or more characters between straight single-quote characters ('), including a ' character only if it is backslash-escaped, or
|
||||
var c = text.Current;
|
||||
if (c == '\'' || c == '"')
|
||||
{
|
||||
|
||||
var quote = c;
|
||||
bool hasEscape = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
c = text.NextChar();
|
||||
|
||||
if (c == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (c == quote)
|
||||
{
|
||||
if (hasEscape)
|
||||
{
|
||||
buffer.Append(quote);
|
||||
hasEscape = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip last quote
|
||||
text.NextChar();
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasEscape)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
|
||||
buffer.Append(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// a sequence of zero or more characters between matching parentheses ((...)), including a ) character only if it is backslash-escaped.
|
||||
// TODO
|
||||
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
title = isValid ? buffer.ToString() : null;
|
||||
buffer.Clear();
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
||||
public static bool TryParseLinkDestination(StringLineGroup text, out string link)
|
||||
public static bool TryParseLink(StringLineGroup text, out string link)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
|
||||
var destination = TempBuilder;
|
||||
destination.Clear();
|
||||
link = null;
|
||||
bool isValid = false;
|
||||
var buffer = TempBuilder;
|
||||
buffer.Clear();
|
||||
|
||||
var c = text.Current;
|
||||
|
||||
@@ -152,9 +162,9 @@ namespace Textamina.Markdig.Syntax
|
||||
c = text.NextChar();
|
||||
if (!nextEscape && c == '>')
|
||||
{
|
||||
link = destination.ToString();
|
||||
destination.Clear();
|
||||
return true;
|
||||
text.NextChar();
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!nextEscape && c == '<')
|
||||
@@ -175,9 +185,11 @@ namespace Textamina.Markdig.Syntax
|
||||
break;
|
||||
}
|
||||
|
||||
destination.Append(c);
|
||||
buffer.Append(c);
|
||||
|
||||
} while (c != '\0');
|
||||
|
||||
link = isValid ? buffer.ToString() : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,10 +199,8 @@ namespace Textamina.Markdig.Syntax
|
||||
// parentheses.
|
||||
bool isEscaped = false;
|
||||
int openedParent = 0;
|
||||
do
|
||||
while (c != '\0')
|
||||
{
|
||||
c = text.NextChar();
|
||||
|
||||
// Match opening and closing parenthesis
|
||||
if (c == '(')
|
||||
{
|
||||
@@ -211,11 +221,17 @@ namespace Textamina.Markdig.Syntax
|
||||
openedParent--;
|
||||
if (openedParent < 0)
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isEscaped)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
{
|
||||
@@ -225,26 +241,134 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
isEscaped = false;
|
||||
|
||||
var isSpace = Utility.IsSpaceOrTab(c);
|
||||
if (isSpace || Utility.IsControl(c)) // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
|
||||
if (Utility.IsSpaceOrTab(c) || Utility.IsControl(c)) // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
|
||||
{
|
||||
if (isSpace && destination.Length > 0)
|
||||
{
|
||||
link = destination.ToString();
|
||||
destination.Clear();
|
||||
return true;
|
||||
}
|
||||
isValid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
destination.Append(c);
|
||||
buffer.Append(c);
|
||||
|
||||
} while (c != '\0');
|
||||
c = text.NextChar();
|
||||
}
|
||||
|
||||
isValid = isValid && buffer.Length > 0;
|
||||
link = isValid ? buffer.ToString() : null;
|
||||
}
|
||||
|
||||
buffer.Clear();
|
||||
return isValid;
|
||||
}
|
||||
|
||||
private class ParserInternal : InlineParser
|
||||
{
|
||||
public ParserInternal()
|
||||
{
|
||||
FirstChars = new[] {'[', ']', '!'};
|
||||
}
|
||||
|
||||
public override bool Match(MatchInlineState state)
|
||||
{
|
||||
var text = state.Lines;
|
||||
|
||||
var c = text.Current;
|
||||
|
||||
bool isImage = false;
|
||||
if (c == '!')
|
||||
{
|
||||
isImage = true;
|
||||
c = text.NextChar();
|
||||
if (c != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '[')
|
||||
{
|
||||
state.Inline = new LinkDelimiterInline(this)
|
||||
{
|
||||
Type = DelimiterType.Open,
|
||||
IsImage = isImage
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c == ']')
|
||||
{
|
||||
if (state.Inline != null)
|
||||
{
|
||||
return TryParseEndOfLinkOrImage(ref state.Inline, text);
|
||||
}
|
||||
return false;
|
||||
// Match a close tag
|
||||
}
|
||||
|
||||
// We don't have an emphasis
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryParseEndOfLinkOrImage(ref Inline current, StringLineGroup text)
|
||||
{
|
||||
LinkDelimiterInline firstParent = null;
|
||||
foreach (var parent in current.FindParentOfType<LinkDelimiterInline>())
|
||||
{
|
||||
firstParent = parent;
|
||||
break;
|
||||
}
|
||||
|
||||
// This will be matched as a literal
|
||||
if (firstParent != null)
|
||||
{
|
||||
// TODO: continue parsing of ]
|
||||
|
||||
var savePoint = text.Save();
|
||||
|
||||
var link = string.Empty;
|
||||
var title = string.Empty;
|
||||
if (text.Current == '(')
|
||||
{
|
||||
if (TryParseLinkAndTitle(text, out link, out title))
|
||||
{
|
||||
// Inline Link
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Restore(ref savePoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
// We have a nested [ ]
|
||||
// firstParent.Remove();
|
||||
// The opening [ will be transformed to a literal followed by all the childrens of the [
|
||||
|
||||
var literal = new LiteralInline() { Content = firstParent.IsImage ? "![" : "[" };
|
||||
current = firstParent.ReplaceBy(literal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private bool TryParseLinkTitle(MatchInlineState state)
|
||||
{
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the StringBuilder even after in order to avoid storing things around after using it
|
||||
destination.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,25 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
return MatchLineResult.Continue;
|
||||
}
|
||||
|
||||
public override void Close(Block block)
|
||||
{
|
||||
var paragraph = block as ParagraphBlock;
|
||||
if (paragraph != null)
|
||||
{
|
||||
var lines = paragraph.Lines;
|
||||
var lineCount = lines.Count;
|
||||
for (int i = 0; i < lineCount; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
line.TrimStart();
|
||||
}
|
||||
if (lineCount > 0)
|
||||
{
|
||||
lines[lineCount - 1].TrimEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,34 @@ namespace Textamina.Markdig.Syntax
|
||||
return new StringSlice(Text, Start, End);
|
||||
}
|
||||
|
||||
public void TrimStart()
|
||||
{
|
||||
// Strip leading spaces
|
||||
var c = Current;
|
||||
while (Utility.IsSpace(c))
|
||||
{
|
||||
c = NextChar();
|
||||
}
|
||||
}
|
||||
|
||||
public void TrimEnd()
|
||||
{
|
||||
for (int i = End; i >= Start; i--)
|
||||
{
|
||||
End = i;
|
||||
if (!Utility.IsSpace(this[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
{
|
||||
TrimStart();
|
||||
TrimEnd();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Start <= End ? Text.Substring(Start, End - Start + 1) : string.Empty;
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using Textamina.Markdig.Parsing;
|
||||
|
||||
namespace Textamina.Markdig.Syntax
|
||||
{
|
||||
public class StringLineGroup : List<StringLine>
|
||||
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; }
|
||||
@@ -18,13 +29,50 @@ namespace Textamina.Markdig.Syntax
|
||||
|
||||
public char PreviousChar2 { get; private set; }
|
||||
|
||||
internal void Initialize()
|
||||
public void Reset()
|
||||
{
|
||||
ColumnPosition = -1;
|
||||
currentLine = Count > 0 ? this[0] : null;
|
||||
if (currentLine != null)
|
||||
{
|
||||
ColumnPosition = currentLine.Start - 1;
|
||||
}
|
||||
NextChar();
|
||||
}
|
||||
|
||||
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 => Current == '\0';
|
||||
|
||||
public State Save()
|
||||
@@ -55,9 +103,13 @@ namespace Textamina.Markdig.Syntax
|
||||
}
|
||||
else
|
||||
{
|
||||
ColumnPosition = -1;
|
||||
LinePosition++;
|
||||
currentLine = LinePosition < Count ? this[LinePosition] : null;
|
||||
ColumnPosition = -1;
|
||||
if (currentLine != null)
|
||||
{
|
||||
ColumnPosition = currentLine.Start - 1;
|
||||
}
|
||||
Current = currentLine != null ? '\n' : '\0';
|
||||
}
|
||||
}
|
||||
@@ -69,6 +121,17 @@ namespace Textamina.Markdig.Syntax
|
||||
return Current;
|
||||
}
|
||||
|
||||
public bool SkipWhiteSpaces()
|
||||
{
|
||||
bool hasWhitespaces = false;
|
||||
while (Utility.IsWhiteSpace(Current))
|
||||
{
|
||||
NextChar();
|
||||
hasWhitespaces = true;
|
||||
}
|
||||
return hasWhitespaces;
|
||||
}
|
||||
|
||||
public void TrimStart()
|
||||
{
|
||||
if (Count == 0)
|
||||
@@ -77,11 +140,8 @@ namespace Textamina.Markdig.Syntax
|
||||
}
|
||||
|
||||
// Strip leading and
|
||||
var liner = this[0];
|
||||
while (Utility.IsSpace(liner.Current))
|
||||
{
|
||||
liner.NextChar();
|
||||
}
|
||||
this[0].TrimStart();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void TrimEnd()
|
||||
@@ -91,15 +151,8 @@ namespace Textamina.Markdig.Syntax
|
||||
return;
|
||||
}
|
||||
|
||||
var liner = this[Count - 1];
|
||||
for (int i = liner.End; i >= liner.Start; i--)
|
||||
{
|
||||
liner.End = i;
|
||||
if (!Utility.IsSpace(liner[i]))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
this[Count - 1].TrimEnd();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Trim()
|
||||
@@ -108,6 +161,21 @@ namespace Textamina.Markdig.Syntax
|
||||
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 class State
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user