using Textamina.Markdig.Helpers;
using Textamina.Markdig.Parsing;
namespace Textamina.Markdig.Syntax
{
///
/// Repressents a fenced code block.
///
///
/// Related to CommonMark spec: 4.5 Fenced code blocks
///
public class FencedCodeBlock : CodeBlock
{
public new static readonly BlockParser Parser = new ParserInternal();
public string Language { get; set; }
public string Arguments { get; set; }
private int fencedCharCount;
private char fencedChar;
private bool hasFencedEnd;
private int indentCount;
private class ParserInternal : BlockParser
{
public override MatchLineResult Match(MatchLineState state)
{
var liner = state.Line;
var fenced = state.Block as FencedCodeBlock;
if (fenced != null)
{
var saveLiner = liner.Save();
liner.SkipLeadingSpaces3();
var c = liner.Current;
int count = fenced.fencedCharCount;
var matchChar = fenced.fencedChar;
while (!liner.IsEol)
{
if (c != matchChar)
{
break;
}
c = liner.NextChar();
count--;
}
if (count <= 0)
{
int endPosition = liner.Start;
liner.TrimEnd(true);
if (liner.IsEol || liner.End == endPosition)
{
fenced.hasFencedEnd = true;
return MatchLineResult.Last;
}
}
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 indentCount = liner.Start - start;
var c = liner.Current;
int count = 0;
var matchChar = (char)0;
while (!liner.IsEol)
{
if (count == 0 && (c == '`' || c == '~'))
{
matchChar = c;
}
else if (c != matchChar)
{
break;
}
c = liner.NextChar();
count++;
}
if (count >= 3)
{
// specs spaces: Is space and tabs? or only spaces? Use space and tab for this case
liner.TrimStart(true);
liner.TrimEnd(true);
string infoString = null;
string argString = null;
// An info string cannot contain any backsticks
int firstSpace = -1;
for (int i = liner.Start; i <= liner.End; i++)
{
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();
}
// Store the number of matched string into the context
state.Block = new FencedCodeBlock()
{
fencedChar = matchChar,
fencedCharCount = count,
indentCount = indentCount,
Language = HtmlHelper.Unescape(infoString),
Arguments = HtmlHelper.Unescape(argString),
};
return MatchLineResult.Continue;
}
return MatchLineResult.None;
}
}
public override void Close(MatchLineState state)
{
var fenced = (FencedCodeBlock) state.Block;
fenced.Lines.RemoveAt(0);
if (fenced.hasFencedEnd)
{
fenced.Lines.RemoveAt(fenced.Lines.Count - 1);
}
for (int i = 0; i < fenced.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[line.Start].IsSpace())
{
line.Start++;
}
else
{
break;
}
}
}
}
}
}
}