public bool Parse(ParserContext context, Subject subject) { var saved = subject.Save(); subject.AdvanceWhile(' ', 3); if (subject.Char == '-' || subject.Char == '_' || subject.Char == '*') { var hRuleChar = subject.Char; var length = subject.AdvanceWhile(hRuleChar); subject.SkipWhiteSpace(); while (subject.Char == hRuleChar) { length += subject.AdvanceWhile(hRuleChar); subject.SkipWhiteSpace(); } if (length >= 3) { subject.SkipWhiteSpace(); if (subject.EndOfString) { context.AddBlock(new HorizontalRule()); context.BlocksParsed = true; return(true); } } } saved.Restore(); return(false); }
public bool Parse(ParserContext context, Subject subject) { // Do not match if in a paragraph if (context.Tip is Paragraph) { return(false); } // Do not match blank line if (subject.IsBlank) { return(false); } if (subject.Indent >= CommonMarkParser.CODE_INDENT) { // indented code subject.Advance(CommonMarkParser.CODE_INDENT); context.CloseUnmatchedBlocks(); context.Container = context.AddBlock(new IndentedCode()); return(true); } return(false); }
public bool Parse(ParserContext context, Subject subject) { var saved = subject.Save(); var fenceOffset = subject.AdvanceWhile(' ', 3); if (subject.Char == '`' || subject.Char == '~') { var fenceChar = subject.Char; var fenceLength = subject.AdvanceWhile(fenceChar); if (fenceLength >= 3 && !subject.Contains(fenceChar)) { context.AddBlock(new FencedCode { Length = fenceLength, Char = fenceChar, Offset = fenceOffset }); context.BlocksParsed = true; return(true); } } saved.Restore(); return(false); }
public bool Parse(ParserContext context, Subject subject) { var saved = subject.Save(); subject.AdvanceWhile(' ', 3); var level = subject.AdvanceWhile('#'); var contents = new StringBuilder(); if (level >= 1 && level <= 6 && (subject.Char == ' ' || subject.EndOfString)) { subject.AdvanceToFirstNonSpace(); while (!subject.EndOfString) { if (subject.Char == '\\') { contents.Append(subject.Take()); if (!subject.EndOfString) { contents.Append(subject.Take()); } } else if (subject.Char == '#') { var closingSequence = subject.TakeWhile('#'); var closingSpace = subject.TakeWhile(' '); if (!subject.EndOfString) { contents.Append(closingSequence); contents.Append(closingSpace); } } else { contents.Append(subject.TakeWhile(c => c != '#' && c != '\\' && c != ' ')); var closingSpace = subject.TakeWhile(' '); if (!subject.EndOfString) { contents.Append(closingSpace); } } } context.AddBlock(new ATXHeader(level, contents.ToString())); context.BlocksParsed = true; return(true); } saved.Restore(); return(false); }
public bool Parse(ParserContext context, Subject subject) { var saved = subject.Save(); subject.AdvanceWhile(' ', 3); if (subject.Char == '<') { subject.Advance(); if (subject.Char == '!' || subject.Char == '?') { subject.Advance(); } else { if (subject.Char == '/') { subject.Advance(); var tagName = subject.TakeWhile(c => _tagNameChars.Contains(c), _maxTagNameLength); if ((subject.Char != ' ' && subject.Char != '>') || !_tagNames.Contains(tagName)) { saved.Restore(); return(false); } } else { var tagName = subject.TakeWhile(c => _tagNameChars.Contains(c), _maxTagNameLength); if ((subject.Char != ' ' && subject.Char != '>' && !subject.StartsWith("/>")) || !_tagNames.Contains(tagName)) { saved.Restore(); return(false); } } } context.AddBlock(new HtmlBlock()); context.BlocksParsed = true; // note, we restore subject because the tag is part of the text saved.Restore(); return(true); } saved.Restore(); return(false); }
public bool Parse(ParserContext context, Subject subject) { if (subject.FirstNonSpaceChar == '>') { // blockquote subject.AdvanceToFirstNonSpace(1); // optional following space if (subject.Char == ' ') { subject.Advance(); } context.AddBlock(new BlockQuote()); return(true); } return(false); }
public bool Parse(ParserContext context, Subject subject) { var saved = subject.Save(); var indent = subject.AdvanceWhile(' ', 3); var ok = false; var spacesAfterMarker = 0; var data = new ListData(); var length = 0; if (subject.Char == '*' || subject.Char == '+' || subject.Char == '-') { data.Type = "Bullet"; data.BulletChar = subject.Char; subject.Advance(); length += 1; ok = true; } else if (Patterns.Digits.Contains(subject.Char)) { var start = subject.TakeWhile(c => Patterns.Digits.Contains(c)); length += start.Length; if (subject.Char == '.' || subject.Char == ')') { data.Type = "Ordered"; data.Start = int.Parse(start); data.Delimiter = subject.Char; subject.Advance(); length += 1; ok = true; } } spacesAfterMarker = subject.AdvanceWhile(' '); if (!ok || spacesAfterMarker == 0 && !subject.EndOfString) { saved.Restore(); return(false); } data.Padding = length + spacesAfterMarker; if (spacesAfterMarker >= 5 || spacesAfterMarker < 1 || subject.EndOfString) { data.Padding = data.Padding - spacesAfterMarker + 1; } saved.Restore(); subject.AdvanceToFirstNonSpace(data.Padding); // list item data.MarkerOffset = indent; var list = context.Container as List; // add the list if needed if (list == null || !list.Data.Matches(data)) { context.AddBlock(new List { Data = data }); } // add the list item context.AddBlock(new ListItem { Data = data }); return(true); }
private void ProcessLine(ParserContext context) { var groups = new string[0]; var subject = new Subject(context.Line); context.Container = context.Document; while (context.Container.LastChild != null && context.Container.LastChild.IsOpen) { context.Container = context.Container.LastChild; if (!context.Container.MatchNextLine(subject)) { context.Container = context.Container.Parent; // back up to last matching block break; } } var lastMatchedContainer = context.Container; // This function is used to finalize and close any unmatched // blocks. We aren't ready to do this now, because we might // have a lazy paragraph continuation, in which case we don't // want to close unmatched blocks. So we store this closure for // use later, when we have more information. var oldtip = context.Tip; context.CloseUnmatchedBlocks = () => { // finalize any blocks not matched while (oldtip != lastMatchedContainer) { oldtip.Close(context); oldtip = oldtip.Parent; } context.CloseUnmatchedBlocks = () => { }; }; // Check to see if we've hit 2nd blank line; if so break out of list: if (subject.IsBlank && context.Container.LastLineIsBlank) { BreakOutOfLists(context, context.Container, context.LineNumber); } // Unless last matched context.Container is a code block, try new context.Container starts, // adding children to the last matched context.Container: while (!context.Container.IsCode && !context.BlocksParsed) { var parsed = context.Parsers.IndentedCodeParser.Parse(context, subject); parsed = parsed || context.Parsers.LazyParagraphContinuationParser.Parse(context, subject); parsed = parsed || context.Parsers.BlockQuoteParser.Parse(context, subject); parsed = parsed || context.Parsers.ATXHeaderParser.Parse(context, subject); parsed = parsed || context.Parsers.FencedCodeParser.Parse(context, subject); parsed = parsed || context.Parsers.HtmlBlockParser.Parse(context, subject); parsed = parsed || context.Parsers.SetExtHeaderParser.Parse(context, subject); parsed = parsed || context.Parsers.HorizontalRuleParser.Parse(context, subject); parsed = parsed || context.Parsers.ListParser.Parse(context, subject); if (!parsed || context.Container.AcceptsLines) { // if none were found or it's a line context.Container, it can't contain other containers context.BlocksParsed = true; } } // What remains at the offset is a text line. Add the text to the // appropriate context.Container. // First check for a lazy paragraph continuation: if (context.Tip != lastMatchedContainer && !subject.IsBlank && context.Tip is Paragraph && context.Tip.Strings.Any()) { // lazy paragraph continuation //context.Tip.LastLineIsBlank = false; context.Tip.AddLine(subject.Rest); } else { // not a lazy continuation // finalize any blocks not matched context.CloseUnmatchedBlocks(); // Block quote lines are never blank as they start with > // and we don't count blanks in fenced code for purposes of tight/loose // lists or breaking out of lists. We also don't set last_line_blank // on an empty list item. if (!subject.IsBlank || context.Container is BlockQuote || context.Container is FencedCode) { context.Container.LastLineIsBlank = false; } else if (context.Container is ListItem) { context.Container.LastLineIsBlank = context.Container.StartLine < context.LineNumber; } else { context.Container.LastLineIsBlank = true; } var cont = context.Container; while (cont.Parent is Block) { cont.Parent.LastLineIsBlank = false; cont = cont.Parent; } if (context.Container is IndentedCode || context.Container is HtmlBlock) { context.Tip.AddLine(subject.Rest); } else if (context.Container is FencedCode) { var fencedCode = context.Container as FencedCode; var saved = subject.Save(); // check for closing code fence: var match = subject.Indent <= 3; subject.AdvanceWhile(c => c == ' '); match = match && subject.Char == fencedCode.Char && subject.AdvanceWhile(c => c == fencedCode.Char) >= fencedCode.Length; subject.AdvanceWhile(c => c == ' '); if (match && subject.EndOfString) { // don't add closing fence to context.Container; instead, close it: context.Container.Close(context); } else { saved.Restore(); context.Tip.AddLine(subject.Rest); } } else if (context.Container is ATXHeader || context.Container is SetExtHeader || context.Container is HorizontalRule) { // nothing to do; we already added the contents. } else { if (context.Container.AcceptsLines) { subject.AdvanceToFirstNonSpace(); context.Tip.AddLine(subject.Rest); } else if (subject.IsBlank) { // do nothing } else if (!(context.Container is HorizontalRule || context.Container is SetExtHeader)) { // create paragraph context.Container for line context.AddBlock(new Paragraph()); subject.AdvanceToFirstNonSpace(); context.Tip.AddLine(subject.Rest); } else { throw new Exception( "Line " + context.LineNumber.ToString() + " with context.Container type " + context.Container.GetType().Name + " did not match any condition." ); } } } }