/// <summary> /// Break out of all containing lists /// </summary> private static void BreakOutOfLists(ref Block blockRef, LineInfo line) { Block container = blockRef; Block b = container.Top; // find first containing list: while (b != null && b.Tag != BlockTag.List) b = b.LastChild; if (b != null) { while (container != null && container != b) { Finalize(container, line); container = container.Parent; } Finalize(b, line); blockRef = b.Parent; } }
private static void AddLine(Block block, LineInfo lineInfo, string ln, int offset, int length = -1) { if (!block.IsOpen) throw new CommonMarkException(string.Format(CultureInfo.InvariantCulture, "Attempted to add line '{0}' to closed container ({1}).", ln, block.Tag)); var len = length == -1 ? ln.Length - offset : length; if (len <= 0) return; var curSC = block.StringContent; if (curSC == null) { block.StringContent = curSC = new StringContent(); if (lineInfo.IsTrackingPositions) curSC.PositionTracker = new PositionTracker(lineInfo.LineOffset); } if (lineInfo.IsTrackingPositions) curSC.PositionTracker.AddOffset(lineInfo, offset, len); curSC.Append(ln, offset, len); }
// Process one line at a time, modifying a block. // Returns 0 if successful. curptr is changed to point to // the currently open block. public static void IncorporateLine(LineInfo line, ref Block curptr) { var ln = line.Line; Block last_matched_container; var offset = 0; int matched; int i; ListData data; bool all_matched = true; Block cur = curptr; bool blank = false; int first_nonspace; char curChar; int indent; // container starts at the document root. var container = cur.Top; // for each containing block, try to parse the associated line start. // bail out on failure: container will point to the last matching block. while (container.LastChild != null && container.LastChild.IsOpen) { container = container.LastChild; first_nonspace = offset; while ((curChar = ln[first_nonspace]) == ' ') first_nonspace++; indent = first_nonspace - offset; blank = curChar == '\n'; switch (container.Tag) { case BlockTag.BlockQuote: { if (indent <= 3 && curChar == '>') { offset = first_nonspace + 1; if (ln[offset] == ' ') offset++; } else { all_matched = false; } break; } case BlockTag.ListItem: { if (indent >= container.ListData.MarkerOffset + container.ListData.Padding) offset += container.ListData.MarkerOffset + container.ListData.Padding; else if (blank) offset = first_nonspace; else all_matched = false; break; } case BlockTag.IndentedCode: { if (indent >= CODE_INDENT) offset += CODE_INDENT; else if (blank) offset = first_nonspace; else all_matched = false; break; } case BlockTag.AtxHeader: case BlockTag.SETextHeader: { // a header can never contain more than one line all_matched = false; if (blank) container.IsLastLineBlank = true; break; } case BlockTag.FencedCode: { // -1 means we've seen closer if (container.FencedCodeData.FenceLength == -1) { all_matched = false; if (blank) container.IsLastLineBlank = true; } else { // skip optional spaces of fence offset i = container.FencedCodeData.FenceOffset; while (i > 0 && ln[offset] == ' ') { offset++; i--; } } break; } case BlockTag.HtmlBlock: { if (blank) { container.IsLastLineBlank = true; all_matched = false; } break; } case BlockTag.Paragraph: { if (blank) { container.IsLastLineBlank = true; all_matched = false; } break; } } if (!all_matched) { container = container.Parent; // back up to last matching block break; } } last_matched_container = container; // check to see if we've hit 2nd blank line, break out of list: if (blank && container.IsLastLineBlank) BreakOutOfLists(ref container, line); var maybeLazy = cur.Tag == BlockTag.Paragraph; // unless last matched container is code block, try new container starts: while (container.Tag != BlockTag.FencedCode && container.Tag != BlockTag.IndentedCode && container.Tag != BlockTag.HtmlBlock) { first_nonspace = offset; while ((curChar = ln[first_nonspace]) == ' ') first_nonspace++; indent = first_nonspace - offset; blank = curChar == '\n'; var indented = indent >= CODE_INDENT; if (!indented && curChar == '>') { offset = first_nonspace + 1; // optional following character if (ln[offset] == ' ') offset++; container = CreateChildBlock(container, line, BlockTag.BlockQuote, first_nonspace); } else if (!indented && curChar == '#' && 0 != (matched = Scanner.scan_atx_header_start(ln, first_nonspace, ln.Length, out i))) { offset = first_nonspace + matched; container = CreateChildBlock(container, line, BlockTag.AtxHeader, first_nonspace); container.HeaderLevel = i; } else if (!indented && (curChar == '`' || curChar == '~') && 0 != (matched = Scanner.scan_open_code_fence(ln, first_nonspace, ln.Length))) { container = CreateChildBlock(container, line, BlockTag.FencedCode, first_nonspace); container.FencedCodeData = new FencedCodeData(); container.FencedCodeData.FenceChar = curChar; container.FencedCodeData.FenceLength = matched; container.FencedCodeData.FenceOffset = first_nonspace - offset; offset = first_nonspace + matched; } else if (!indented && curChar == '<' && Scanner.scan_html_block_tag(ln, first_nonspace, ln.Length)) { container = CreateChildBlock(container, line, BlockTag.HtmlBlock, first_nonspace); // note, we don't adjust offset because the tag is part of the text } else if (!indented && container.Tag == BlockTag.Paragraph && (curChar == '=' || curChar == '-') && 0 != (matched = Scanner.scan_setext_header_line(ln, first_nonspace, ln.Length)) && ContainsSingleLine(container.StringContent)) { container.Tag = BlockTag.SETextHeader; container.HeaderLevel = matched; offset = ln.Length - 1; } else if (!indented && !(container.Tag == BlockTag.Paragraph && !all_matched) && 0 != (Scanner.scan_hrule(ln, first_nonspace, ln.Length))) { // it's only now that we know the line is not part of a setext header: container = CreateChildBlock(container, line, BlockTag.HorizontalRuler, first_nonspace); Finalize(container, line); container = container.Parent; offset = ln.Length - 1; } else if ((!indented || container.Tag == BlockTag.List) && 0 != (matched = ParseListMarker(ln, first_nonspace, out data))) { // compute padding: offset = first_nonspace + matched; i = 0; while (i <= 5 && ln[offset + i] == ' ') i++; // i = number of spaces after marker, up to 5 if (i >= 5 || i < 1 || ln[offset] == '\n') { data.Padding = matched + 1; if (i > 0) offset++; } else { data.Padding = matched + i; offset += i; } // check container; if it's a list, see if this list item // can continue the list; otherwise, create a list container. data.MarkerOffset = indent; if (container.Tag != BlockTag.List || !ListsMatch(container.ListData, data)) { container = CreateChildBlock(container, line, BlockTag.List, first_nonspace); container.ListData = data; } // add the list item container = CreateChildBlock(container, line, BlockTag.ListItem, first_nonspace); container.ListData = data; } else if (indented && !maybeLazy && !blank) { offset += CODE_INDENT; container = CreateChildBlock(container, line, BlockTag.IndentedCode, offset); } else { break; } if (AcceptsLines(container.Tag)) { // if it's a line container, it can't contain other containers break; } maybeLazy = false; } // what remains at offset is a text line. add the text to the // appropriate container. first_nonspace = offset; if (offset >= ln.Length) curChar = '\0'; else while ((curChar = ln[first_nonspace]) == ' ') first_nonspace++; indent = first_nonspace - offset; blank = curChar == '\n'; // 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. container.IsLastLineBlank = (blank && container.Tag != BlockTag.BlockQuote && container.Tag != BlockTag.SETextHeader && container.Tag != BlockTag.FencedCode && !(container.Tag == BlockTag.ListItem && container.FirstChild == null && container.SourcePosition >= line.LineOffset)); Block cont = container; while (cont.Parent != null) { cont.Parent.IsLastLineBlank = false; cont = cont.Parent; } if (cur != last_matched_container && container == last_matched_container && !blank && cur.Tag == BlockTag.Paragraph && cur.StringContent.Length > 0) { AddLine(cur, line, ln, offset); } else { // not a lazy continuation // finalize any blocks that were not matched and set cur to container: while (cur != last_matched_container) { Finalize(cur, line); cur = cur.Parent; if (cur == null) throw new CommonMarkException("Cannot finalize container block. Last matched container tag = " + last_matched_container.Tag); } if (container.Tag == BlockTag.IndentedCode) { AddLine(container, line, ln, offset); } else if (container.Tag == BlockTag.FencedCode) { if ((indent <= 3 && curChar == container.FencedCodeData.FenceChar) && (0 != Scanner.scan_close_code_fence(ln, first_nonspace, container.FencedCodeData.FenceLength, ln.Length))) { // if closing fence, set fence length to -1. it will be closed when the next line is processed. container.FencedCodeData.FenceLength = -1; } else { AddLine(container, line, ln, offset); } } else if (container.Tag == BlockTag.HtmlBlock) { AddLine(container, line, ln, offset); } else if (blank) { // ??? do nothing } else if (container.Tag == BlockTag.AtxHeader) { int p = ln.Length - 1; // trim trailing spaces while (p >= 0 && (ln[p] == ' ' || ln[p] == '\n')) p--; // if string ends in #s, remove these: while (p >= 0 && ln[p] == '#') p--; // there must be a space before the last hashtag if (p < 0 || ln[p] != ' ') p = ln.Length - 1; AddLine(container, line, ln, first_nonspace, p - first_nonspace + 1); Finalize(container, line); container = container.Parent; } else if (AcceptsLines(container.Tag)) { AddLine(container, line, ln, first_nonspace); } else if (container.Tag != BlockTag.HorizontalRuler && container.Tag != BlockTag.SETextHeader) { // create paragraph container for line container = CreateChildBlock(container, line, BlockTag.Paragraph, first_nonspace); AddLine(container, line, ln, first_nonspace); } else { Utilities.Warning("Line {0} with container type {1} did not match any condition:\n\"{2}\"", line.LineNumber, container.Tag, ln); } curptr = container; } }
/// <summary> /// Adds a new block as child of another. Return the child. /// </summary> /// <remarks>Original: add_child</remarks> public static Block CreateChildBlock(Block parent, LineInfo line, BlockTag blockType, int startColumn) { // if 'parent' isn't the kind of block that can accept this child, // then back up til we hit a block that can. while (!CanContain(parent.Tag, blockType)) { Finalize(parent, line); parent = parent.Parent; } var startPosition = line.IsTrackingPositions ? line.CalculateOrigin(startColumn, true) : line.LineOffset; #pragma warning disable 0618 Block child = new Block(blockType, line.LineNumber, startColumn + 1, startPosition); #pragma warning restore 0618 child.Parent = parent; child.Top = parent.Top; var lastChild = parent.LastChild; if (lastChild != null) { lastChild.NextSibling = child; #pragma warning disable 0618 child.Previous = lastChild; #pragma warning restore 0618 } else { parent.FirstChild = child; } parent.LastChild = child; return child; }
public void ReadLine(LineInfo line) { line.LineOffset = this._previousBufferLength + this._bufferPosition; line.LineNumber++; line.OffsetCount = 0; line.Line = null; var tabIncreaseCount = 0; if (this._bufferPosition == this._bufferLength && !this.ReadBuffer()) return; bool useBuilder = false; int num; char c; while (true) { num = this._bufferPosition; do { c = this._buffer[num]; if (c == '\r' || c == '\n') { goto IL_4A; } if (c == '\t') { if (!useBuilder) { useBuilder = true; this._builder.Length = 0; } this._builder.Append(this._buffer, this._bufferPosition, num - this._bufferPosition); if (line.IsTrackingPositions) { var delta = -3 + (this._builder.Length % 4); line.AddOffset(this._previousBufferLength + num + tabIncreaseCount, delta); tabIncreaseCount -= delta; } this._builder.Append(' ', 4 - (this._builder.Length % 4)); this._bufferPosition = num + 1; } else if (c == '\0') { this._buffer[num] = '\uFFFD'; } num++; } while (num < this._bufferLength); num = this._bufferLength - this._bufferPosition; if (!useBuilder) { useBuilder = true; this._builder.Length = 0; } this._builder.Append(this._buffer, this._bufferPosition, num); if (!this.ReadBuffer()) { this._builder.Append('\n'); line.Line = this._builder.ToString(); return; } } IL_4A: string result; this._buffer[num] = '\n'; if (useBuilder) { this._builder.Append(this._buffer, this._bufferPosition, num - this._bufferPosition + 1); result = this._builder.ToString(); } else { result = new string(this._buffer, this._bufferPosition, num - this._bufferPosition + 1); } this._bufferPosition = num + 1; if (c == '\r' && (this._bufferPosition < this._bufferLength || this.ReadBuffer()) && this._buffer[this._bufferPosition] == '\n') { if (line.IsTrackingPositions) line.AddOffset(this._previousBufferLength + this._bufferPosition - 1 + tabIncreaseCount, 1); this._bufferPosition++; } line.Line = result; }
public static void Finalize(Block b, LineInfo line) { // don't do anything if the block is already closed if (!b.IsOpen) return; b.IsOpen = false; if (line.IsTrackingPositions) { // (b.SourcePosition >= line.LineOffset) determines if the block started on this line. if (b.SourcePosition >= line.LineOffset && line.Line != null) b.SourceLastPosition = line.CalculateOrigin(line.Line.Length, false); else b.SourceLastPosition = line.CalculateOrigin(0, false); } #pragma warning disable 0618 b.EndLine = (line.LineNumber > b.StartLine) ? line.LineNumber - 1 : line.LineNumber; #pragma warning restore 0618 switch (b.Tag) { case BlockTag.Paragraph: var sc = b.StringContent; if (!sc.StartsWith('[')) break; var subj = new Subject(b.Top.ReferenceMap); sc.FillSubject(subj); var origPos = subj.Position; while (subj.Position < subj.Buffer.Length && subj.Buffer[subj.Position] == '[' && 0 != InlineMethods.ParseReference(subj)) { } if (subj.Position != origPos) { sc.Replace(subj.Buffer, subj.Position, subj.Buffer.Length - subj.Position); if (sc.PositionTracker != null) sc.PositionTracker.AddBlockOffset(subj.Position - origPos); if (Utilities.IsFirstLineBlank(subj.Buffer, subj.Position)) b.Tag = BlockTag.ReferenceDefinition; } break; case BlockTag.IndentedCode: b.StringContent.RemoveTrailingBlankLines(); break; case BlockTag.FencedCode: // first line of contents becomes info var firstlinelen = b.StringContent.IndexOf('\n') + 1; b.FencedCodeData.Info = InlineMethods.Unescape(b.StringContent.TakeFromStart(firstlinelen, true).Trim()); break; case BlockTag.List: // determine tight/loose status b.ListData.IsTight = true; // tight by default var item = b.FirstChild; Block subitem; while (item != null) { // check for non-final non-empty list item ending with blank line: if (item.IsLastLineBlank && item.NextSibling != null) { b.ListData.IsTight = false; break; } // recurse into children of list item, to see if there are spaces between them: subitem = item.FirstChild; while (subitem != null) { if (EndsWithBlankLine(subitem) && (item.NextSibling != null || subitem.NextSibling != null)) { b.ListData.IsTight = false; break; } subitem = subitem.NextSibling; } if (!b.ListData.IsTight) break; item = item.NextSibling; } break; } }
public static void Finalize(Block b, LineInfo line) { // don't do anything if the block is already closed if (!b.IsOpen) { return; } b.IsOpen = false; if (line.IsTrackingPositions) { // HTML Blocks other than type 7 call Finalize when the last line is encountered. // Block types 6 and 7 calls Finalize once it finds the next empty row but that empty row is no longer considered to be part of the block. var includesThisLine = b.HtmlBlockType != HtmlBlockType.None && b.HtmlBlockType != HtmlBlockType.InterruptingBlock && b.HtmlBlockType != HtmlBlockType.NonInterruptingBlock; // (b.SourcePosition >= line.LineOffset) determines if the block started on this line. includesThisLine = includesThisLine || b.SourcePosition >= line.LineOffset; if (includesThisLine && line.Line != null) { b.SourceLastPosition = line.CalculateOrigin(line.Line.Length, false); } else { b.SourceLastPosition = line.CalculateOrigin(0, false); } } #pragma warning disable 0618 b.EndLine = (line.LineNumber > b.StartLine) ? line.LineNumber - 1 : line.LineNumber; #pragma warning restore 0618 switch (b.Tag) { case BlockTag.Paragraph: var sc = b.StringContent; if (!sc.StartsWith('[')) { break; } var subj = new Subject(b.Top.Document); sc.FillSubject(subj, line.Settings.RenderEmptyLines); var origPos = subj.Position; while (subj.Position < subj.Buffer.Length && subj.Buffer[subj.Position] == '[' && 0 != InlineMethods.ParseReference(subj)) { } if (subj.Position != origPos) { sc.Replace(subj.Buffer, subj.Position, subj.Buffer.Length - subj.Position); if (sc.PositionTracker != null) { sc.PositionTracker.AddBlockOffset(subj.Position - origPos); } if (Utilities.IsFirstLineBlank(subj.Buffer, subj.Position)) { b.Tag = BlockTag.ReferenceDefinition; } } break; case BlockTag.IndentedCode: b.StringContent.RemoveTrailingBlankLines(); break; case BlockTag.FencedCode: // first line of contents becomes info var firstlinelen = b.StringContent.IndexOf('\n') + 1; b.FencedCodeData.Info = InlineMethods.Unescape(b.StringContent.TakeFromStart(firstlinelen, true).Trim()); break; case BlockTag.List: // determine tight/loose status b.ListData.IsTight = true; // tight by default var item = b.FirstChild; Block subitem; while (item != null) { // check for non-final non-empty list item ending with blank line: if (item.IsLastLineBlank && item.NextSibling != null) { b.ListData.IsTight = false; break; } // recurse into children of list item, to see if there are spaces between them: subitem = item.FirstChild; while (subitem != null) { if (EndsWithBlankLine(subitem) && (item.NextSibling != null || subitem.NextSibling != null)) { b.ListData.IsTight = false; break; } subitem = subitem.NextSibling; } if (!b.ListData.IsTight) { break; } item = item.NextSibling; } break; } }
public static Syntax.Block ProcessStage1(TextReader source, CommonMarkSettings settings = null, Tuple<string, int> prologue = null) { if (source == null) throw new ArgumentNullException(nameof(source)); if (settings == null) settings = CommonMarkSettings.Default; var cur = Syntax.Block.CreateDocument(); var doc = cur; var line = new LineInfo(settings.TrackSourcePosition); if (prologue != null && settings.IncludePrologueInLineCount) line.LineNumber = prologue.Item2 + 1; try { var reader = new TabTextReader(source, prologue?.Item1); reader.ReadLine(line); while (line.Line != null) { BlockMethods.IncorporateLine(line, ref cur, settings); reader.ReadLine(line); } } catch(IOException) { throw; } catch(CommonMarkException) { throw; } catch(Exception ex) { throw new CommonMarkException("An error occurred while parsing line " + line.ToString(), cur, ex); } try { do { BlockMethods.Finalize(cur, line, settings); cur = cur.Parent; } while (cur != null); } catch (CommonMarkException) { throw; } catch (Exception ex) { throw new CommonMarkException("An error occurred while finalizing open containers.", cur, ex); } return doc; }
// Process one line at a time, modifying a block. // Returns 0 if successful. curptr is changed to point to // the currently open block. public static void IncorporateLine(LineInfo line, ref Block curptr) { var ln = line.Line; Block last_matched_container; // offset is the char position in the line var offset = 0; // column is the virtual position in the line that takes TAB expansion into account var column = 0; // the adjustment to the virtual position `column` that points to the number of spaces from the TAB that have not been included in any indent. var remainingSpaces = 0; // the char position of the first non-space char int first_nonspace; // the virtual position of the first non-space chart, that includes TAB expansion int first_nonspace_column; int matched; int i; ListData data; bool all_matched = true; Block cur = curptr; var blank = false; char curChar; int indent; // container starts at the document root. var container = cur.Top; // for each containing block, try to parse the associated line start. // bail out on failure: container will point to the last matching block. while (container.LastChild != null && container.LastChild.IsOpen) { container = container.LastChild; FindFirstNonspace(ln, offset, column, out first_nonspace, out first_nonspace_column, out curChar); indent = first_nonspace_column - column + remainingSpaces; blank = curChar == '\n'; switch (container.Tag) { case BlockTag.BlockQuote: { if (indent <= 3 && curChar == '>') { AdvanceOffset(ln, indent + 1, true, ref offset, ref column, ref remainingSpaces); AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces); } else { all_matched = false; } break; } case BlockTag.ListItem: { if (indent >= container.ListData.MarkerOffset + container.ListData.Padding) { AdvanceOffset(ln, container.ListData.MarkerOffset + container.ListData.Padding, true, ref offset, ref column, ref remainingSpaces); } else if (blank && !line.Settings.RenderEmptyLines && container.FirstChild != null) { // if container->first_child is NULL, then the opening line // of the list item was blank after the list marker; in this // case, we are done with the list item. AdvanceOffset(ln, first_nonspace - offset, false, ref offset, ref column, ref remainingSpaces); } else { all_matched = false; } break; } case BlockTag.IndentedCode: { if (indent >= CODE_INDENT) { AdvanceOffset(ln, CODE_INDENT, true, ref offset, ref column, ref remainingSpaces); } else if (blank) { AdvanceOffset(ln, first_nonspace - offset, false, ref offset, ref column, ref remainingSpaces); } else { all_matched = false; } break; } case BlockTag.AtxHeading: case BlockTag.SetextHeading: { // a heading can never contain more than one line all_matched = false; if (blank) { container.IsLastLineBlank = true; } break; } case BlockTag.FencedCode: { // -1 means we've seen closer if (container.FencedCodeData.FenceLength == -1) { all_matched = false; if (blank) { container.IsLastLineBlank = true; } } else { // skip optional spaces of fence offset i = container.FencedCodeData.FenceOffset; while (i > 0 && ln[offset] == ' ') { offset++; column++; i--; } } break; } case BlockTag.HtmlBlock: { // all other block types can accept blanks if (blank && container.HtmlBlockType >= HtmlBlockType.InterruptingBlock) { container.IsLastLineBlank = true; all_matched = false; } break; } case BlockTag.Paragraph: { if (blank && !(line.Settings.RenderSoftLineBreaksAsLineBreaks && line.Settings.RenderEmptyLines)) { container.IsLastLineBlank = true; all_matched = false; } break; } } if (!all_matched) { container = container.Parent; // back up to last matching block break; } } last_matched_container = container; var maybeLazy = cur.Tag == BlockTag.Paragraph; // unless last matched container is code block, try new container starts: while (container.Tag != BlockTag.FencedCode && container.Tag != BlockTag.IndentedCode && container.Tag != BlockTag.HtmlBlock) { FindFirstNonspace(ln, offset, column, out first_nonspace, out first_nonspace_column, out curChar); indent = first_nonspace_column - column + remainingSpaces; blank = curChar == '\n'; var indented = indent >= CODE_INDENT; if (!indented && curChar == '>') { AdvanceOffset(ln, first_nonspace + 1 - offset, false, ref offset, ref column, ref remainingSpaces); AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces); container = CreateChildBlock(container, line, BlockTag.BlockQuote, first_nonspace); } else if (!indented && curChar == '#' && 0 != (matched = Scanner.scan_atx_heading_start(ln, first_nonspace, ln.Length, out i))) { AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces); container = CreateChildBlock(container, line, BlockTag.AtxHeading, first_nonspace); container.Heading = new HeadingData(i); } else if (!indented && (curChar == '`' || curChar == '~') && 0 != (matched = Scanner.scan_open_code_fence(ln, first_nonspace, ln.Length))) { container = CreateChildBlock(container, line, BlockTag.FencedCode, first_nonspace); container.FencedCodeData = new FencedCodeData(); container.FencedCodeData.FenceChar = curChar; container.FencedCodeData.FenceLength = matched; container.FencedCodeData.FenceOffset = first_nonspace - offset; AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces); } else if (!line.Settings.HtmlEntityEncode && !indented && curChar == '<' && (0 != (matched = (int)Scanner.scan_html_block_start(ln, first_nonspace, ln.Length)) || (container.Tag != BlockTag.Paragraph && 0 != (matched = (int)Scanner.scan_html_block_start_7(ln, first_nonspace, ln.Length))) )) { container = CreateChildBlock(container, line, BlockTag.HtmlBlock, first_nonspace); container.HtmlBlockType = (HtmlBlockType)matched; // note, we don't adjust offset because the tag is part of the text } else if (line.Settings.AllowSetextHeadings && !indented && container.Tag == BlockTag.Paragraph && (curChar == '=' || curChar == '-') && 0 != (matched = Scanner.scan_setext_heading_line(ln, first_nonspace, ln.Length))) { container.Tag = BlockTag.SetextHeading; container.Heading = new HeadingData(matched); AdvanceOffset(ln, ln.Length - 1 - offset, false, ref offset, ref column, ref remainingSpaces); } else if (line.Settings.AllowThematicBreak && !indented && !(container.Tag == BlockTag.Paragraph && !all_matched) && 0 != (Scanner.scan_thematic_break(ln, first_nonspace, ln.Length))) { // it's only now that we know the line is not part of a setext heading: container = CreateChildBlock(container, line, BlockTag.ThematicBreak, first_nonspace); Finalize(container, line); container = container.Parent; AdvanceOffset(ln, ln.Length - 1 - offset, false, ref offset, ref column, ref remainingSpaces); } else if ((!indented || container.Tag == BlockTag.List) && 0 != (matched = ParseListMarker(ln, first_nonspace, container.Tag == BlockTag.Paragraph, out data))) { // compute padding: AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces); var prevOffset = offset; var prevColumn = column; var prevRemainingSpaces = remainingSpaces; while (column - prevColumn <= CODE_INDENT) { if (!AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces)) { break; } } // i = number of spaces after marker, up to 5 if (column == prevColumn) { // no spaces at all data.Padding = matched + 1; } else if (column - prevColumn > CODE_INDENT || ln[offset] == '\n') { data.Padding = matched + 1; // too many (or none) spaces, ignoring everything but the first one offset = prevOffset; column = prevColumn; remainingSpaces = prevRemainingSpaces; AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces); } else { data.Padding = matched + column - prevColumn; } // check container; if it's a list, see if this list item // can continue the list; otherwise, create a list container. data.MarkerOffset = indent; if (container.Tag != BlockTag.List || !ListsMatch(container.ListData, data)) { container = CreateChildBlock(container, line, BlockTag.List, first_nonspace); container.ListData = data; } // add the list item container = CreateChildBlock(container, line, BlockTag.ListItem, first_nonspace); container.ListData = data; } else if (indented && !maybeLazy && !blank && line.Settings.AllowCodeIndent) { AdvanceOffset(ln, CODE_INDENT, true, ref offset, ref column, ref remainingSpaces); container = CreateChildBlock(container, line, BlockTag.IndentedCode, offset); } else { break; } if (AcceptsLines(container.Tag)) { // if it's a line container, it can't contain other containers break; } maybeLazy = false; } // what remains at offset is a text line. add the text to the // appropriate container. if (line.Settings.AllowWhiteSpace) { first_nonspace = offset; first_nonspace_column = column; curChar = ln[first_nonspace]; } else { FindFirstNonspace(ln, offset, column, out first_nonspace, out first_nonspace_column, out curChar); } indent = first_nonspace_column - column; blank = curChar == '\n'; if (blank && container.LastChild != null) { container.LastChild.IsLastLineBlank = true; } // 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. container.IsLastLineBlank = (blank && container.Tag != BlockTag.BlockQuote && container.Tag != BlockTag.SetextHeading && container.Tag != BlockTag.FencedCode && !(container.Tag == BlockTag.ListItem && container.FirstChild == null && container.SourcePosition >= line.LineOffset)); Block cont = container; while (cont.Parent != null) { cont.Parent.IsLastLineBlank = false; cont = cont.Parent; } if (cur != last_matched_container && container == last_matched_container && !blank && cur.Tag == BlockTag.Paragraph && cur.StringContent.Length > 0) { AddLine(cur, line, ln, offset, remainingSpaces); } else { // not a lazy continuation // finalize any blocks that were not matched and set cur to container: while (cur != last_matched_container) { Finalize(cur, line); cur = cur.Parent; if (cur == null) { throw new CommonMarkException("Cannot finalize container block. Last matched container tag = " + last_matched_container.Tag); } } if (container.Tag == BlockTag.IndentedCode) { AddLine(container, line, ln, offset, remainingSpaces); } else if (container.Tag == BlockTag.FencedCode) { if ((indent <= 3 && curChar == container.FencedCodeData.FenceChar) && (0 != Scanner.scan_close_code_fence(ln, first_nonspace, container.FencedCodeData.FenceLength, ln.Length))) { // if closing fence, set fence length to -1. it will be closed when the next line is processed. container.FencedCodeData.FenceLength = -1; } else { AddLine(container, line, ln, offset, remainingSpaces); } } else if (container.Tag == BlockTag.HtmlBlock) { AddLine(container, line, ln, offset, remainingSpaces); if (Scanner.scan_html_block_end(container.HtmlBlockType, ln, first_nonspace, ln.Length)) { Finalize(container, line); container = container.Parent; } } else if (blank && !line.Settings.RenderEmptyLines) { // ??? do nothing } else if (container.Tag == BlockTag.AtxHeading) { int p = ln.Length - 1; // trim trailing spaces while (p >= 0 && (ln[p] == ' ' || ln[p] == '\t' || ln[p] == '\n')) { p--; } int px = p; // if string ends in #s, remove these: while (p >= 0 && ln[p] == '#') { p--; } // there must be a space before the last hashtag if (p < 0 || (ln[p] != ' ' && ln[p] != '\t')) { p = px; } // trim trailing spaces that are before the closing #s while (p >= first_nonspace && (ln[p] == ' ' || ln[p] == '\t')) { p--; } AddLine(container, line, ln, first_nonspace, remainingSpaces, p - first_nonspace + 1); Finalize(container, line); container = container.Parent; } else if (AcceptsLines(container.Tag)) { AddLine(container, line, ln, first_nonspace, remainingSpaces, isAddOffsetRequired: container.Parent == null || container.Parent.Tag == BlockTag.Document); } else if (container.Tag != BlockTag.ThematicBreak && container.Tag != BlockTag.SetextHeading) { // create paragraph container for line container = CreateChildBlock(container, line, BlockTag.Paragraph, first_nonspace); AddLine(container, line, ln, first_nonspace, remainingSpaces); } else { Utilities.Warning("Line {0} with container type {1} did not match any condition:\n\"{2}\"", line.LineNumber, container.Tag, ln); } curptr = container; } }
public void AddOffset(LineInfo line, int startIndex, int length) { if (this.OffsetCount + line.OffsetCount + 2 >= this.Offsets.Length) { Array.Resize(ref this.Offsets, this.Offsets.Length + line.OffsetCount + 20); } PositionOffset po1, po2; if (startIndex > 0) { po1 = new PositionOffset( CalculateOrigin(line.Offsets, line.OffsetCount, line.LineOffset, false, true), startIndex); } else { po1 = EmptyPositionOffset; } if (line.Line.Length - startIndex - length > 0) { po2 = new PositionOffset( CalculateOrigin(line.Offsets, line.OffsetCount, line.LineOffset + startIndex + length, false, true), line.Line.Length - startIndex - length); } else { po2 = EmptyPositionOffset; } var indexAfterLastCopied = 0; if (po1.Offset == 0) { if (po2.Offset == 0) { goto FINTOTAL; } po1 = po2; po2 = EmptyPositionOffset; } for (var i = 0; i < line.OffsetCount; i++) { var pc = line.Offsets[i]; if (pc.Position > po1.Position) { if (i > indexAfterLastCopied) { Array.Copy(line.Offsets, indexAfterLastCopied, this.Offsets, this.OffsetCount, i - indexAfterLastCopied); this.OffsetCount += i - indexAfterLastCopied; indexAfterLastCopied = i; } this.Offsets[this.OffsetCount++] = po1; po1 = po2; if (po1.Offset == 0) { goto FIN; } po2 = EmptyPositionOffset; } } FIN: if (po1.Offset != 0) { this.Offsets[this.OffsetCount++] = po1; } if (po2.Offset != 0) { this.Offsets[this.OffsetCount++] = po2; } FINTOTAL: Array.Copy(line.Offsets, indexAfterLastCopied, this.Offsets, this.OffsetCount, line.OffsetCount - indexAfterLastCopied); this.OffsetCount += line.OffsetCount - indexAfterLastCopied; }
// Process one line at a time, modifying a block. // Returns 0 if successful. curptr is changed to point to // the currently open block. public static void IncorporateLine(LineInfo line, ref Block curptr) { var ln = line.Line; Block last_matched_container; // offset is the char position in the line var offset = 0; // column is the virtual position in the line that takes TAB expansion into account var column = 0; // the adjustment to the virtual position `column` that points to the number of spaces from the TAB that have not been included in any indent. var remainingSpaces = 0; // the char position of the first non-space char int first_nonspace; // the virtual position of the first non-space chart, that includes TAB expansion int first_nonspace_column; int matched; int i; ListData data; bool all_matched = true; Block cur = curptr; var blank = false; char curChar; int indent; // container starts at the document root. var container = cur.Top; // for each containing block, try to parse the associated line start. // bail out on failure: container will point to the last matching block. while (container.LastChild != null && container.LastChild.IsOpen) { container = container.LastChild; FindFirstNonspace(ln, offset, column, out first_nonspace, out first_nonspace_column, out curChar); indent = first_nonspace_column - column + remainingSpaces; blank = curChar == '\n'; switch (container.Tag) { case BlockTag.BlockQuote: { if (indent <= 3 && curChar == '>') { AdvanceOffset(ln, indent + 1, true, ref offset, ref column, ref remainingSpaces); AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces); } else { all_matched = false; } break; } case BlockTag.ListItem: { if (indent >= container.ListData.MarkerOffset + container.ListData.Padding) { AdvanceOffset(ln, container.ListData.MarkerOffset + container.ListData.Padding, true, ref offset, ref column, ref remainingSpaces); } else if (blank && container.FirstChild != null) { // if container->first_child is NULL, then the opening line // of the list item was blank after the list marker; in this // case, we are done with the list item. AdvanceOffset(ln, first_nonspace - offset, false, ref offset, ref column, ref remainingSpaces); } else { all_matched = false; } break; } case BlockTag.IndentedCode: { if (indent >= CODE_INDENT) AdvanceOffset(ln, CODE_INDENT, true, ref offset, ref column, ref remainingSpaces); else if (blank) AdvanceOffset(ln, first_nonspace - offset, false, ref offset, ref column, ref remainingSpaces); else all_matched = false; break; } case BlockTag.AtxHeading: case BlockTag.SetextHeading: { // a heading can never contain more than one line all_matched = false; if (blank) container.IsLastLineBlank = true; break; } case BlockTag.FencedCode: { // -1 means we've seen closer if (container.FencedCodeData.FenceLength == -1) { all_matched = false; if (blank) container.IsLastLineBlank = true; } else { // skip optional spaces of fence offset i = container.FencedCodeData.FenceOffset; while (i > 0 && ln[offset] == ' ') { offset++; column++; i--; } } break; } case BlockTag.HtmlBlock: { // all other block types can accept blanks if (blank && container.HtmlBlockType >= HtmlBlockType.InterruptingBlock) { container.IsLastLineBlank = true; all_matched = false; } break; } case BlockTag.Paragraph: { if (blank) { container.IsLastLineBlank = true; all_matched = false; } break; } } if (!all_matched) { container = container.Parent; // back up to last matching block break; } } last_matched_container = container; var maybeLazy = cur.Tag == BlockTag.Paragraph; // unless last matched container is code block, try new container starts: while (container.Tag != BlockTag.FencedCode && container.Tag != BlockTag.IndentedCode && container.Tag != BlockTag.HtmlBlock) { FindFirstNonspace(ln, offset, column, out first_nonspace, out first_nonspace_column, out curChar); indent = first_nonspace_column - column; blank = curChar == '\n'; var indented = indent >= CODE_INDENT; if (!indented && curChar == '>') { AdvanceOffset(ln, first_nonspace + 1 - offset, false, ref offset, ref column, ref remainingSpaces); AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces); container = CreateChildBlock(container, line, BlockTag.BlockQuote, first_nonspace); } else if (!indented && curChar == '#' && 0 != (matched = Scanner.scan_atx_heading_start(ln, first_nonspace, ln.Length, out i))) { AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces); container = CreateChildBlock(container, line, BlockTag.AtxHeading, first_nonspace); container.Heading = new HeadingData(i); } else if (!indented && (curChar == '`' || curChar == '~') && 0 != (matched = Scanner.scan_open_code_fence(ln, first_nonspace, ln.Length))) { container = CreateChildBlock(container, line, BlockTag.FencedCode, first_nonspace); container.FencedCodeData = new FencedCodeData(); container.FencedCodeData.FenceChar = curChar; container.FencedCodeData.FenceLength = matched; container.FencedCodeData.FenceOffset = first_nonspace - offset; AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces); } else if (!indented && curChar == '<' && (0 != (matched = (int)Scanner.scan_html_block_start(ln, first_nonspace, ln.Length)) || (container.Tag != BlockTag.Paragraph && 0 != (matched = (int)Scanner.scan_html_block_start_7(ln, first_nonspace, ln.Length))) )) { container = CreateChildBlock(container, line, BlockTag.HtmlBlock, first_nonspace); container.HtmlBlockType = (HtmlBlockType)matched; // note, we don't adjust offset because the tag is part of the text } else if (!indented && container.Tag == BlockTag.Paragraph && (curChar == '=' || curChar == '-') && 0 != (matched = Scanner.scan_setext_heading_line(ln, first_nonspace, ln.Length))) { container.Tag = BlockTag.SetextHeading; container.Heading = new HeadingData(matched); AdvanceOffset(ln, ln.Length - 1 - offset, false, ref offset, ref column, ref remainingSpaces); } else if (!indented && !(container.Tag == BlockTag.Paragraph && !all_matched) && 0 != (Scanner.scan_thematic_break(ln, first_nonspace, ln.Length))) { // it's only now that we know the line is not part of a setext heading: container = CreateChildBlock(container, line, BlockTag.ThematicBreak, first_nonspace); Finalize(container, line); container = container.Parent; AdvanceOffset(ln, ln.Length - 1 - offset, false, ref offset, ref column, ref remainingSpaces); } else if ((!indented || container.Tag == BlockTag.List) && 0 != (matched = ParseListMarker(ln, first_nonspace, container.Tag == BlockTag.Paragraph, out data))) { // compute padding: AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces); var prevOffset = offset; var prevColumn = column; var prevRemainingSpaces = remainingSpaces; while (column - prevColumn <= CODE_INDENT) { if (!AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces)) break; } // i = number of spaces after marker, up to 5 if (column == prevColumn) { // no spaces at all data.Padding = matched + 1; } else if (column - prevColumn > CODE_INDENT || ln[offset] == '\n') { data.Padding = matched + 1; // too many (or none) spaces, ignoring everything but the first one offset = prevOffset; column = prevColumn; remainingSpaces = prevRemainingSpaces; AdvanceOptionalSpace(ln, ref offset, ref column, ref remainingSpaces); } else { data.Padding = matched + column - prevColumn; } // check container; if it's a list, see if this list item // can continue the list; otherwise, create a list container. data.MarkerOffset = indent; if (container.Tag != BlockTag.List || !ListsMatch(container.ListData, data)) { container = CreateChildBlock(container, line, BlockTag.List, first_nonspace); container.ListData = data; } // add the list item container = CreateChildBlock(container, line, BlockTag.ListItem, first_nonspace); container.ListData = data; } else if (indented && !maybeLazy && !blank) { AdvanceOffset(ln, CODE_INDENT, true, ref offset, ref column, ref remainingSpaces); container = CreateChildBlock(container, line, BlockTag.IndentedCode, offset); } else { break; } if (AcceptsLines(container.Tag)) { // if it's a line container, it can't contain other containers break; } maybeLazy = false; } // what remains at offset is a text line. add the text to the // appropriate container. FindFirstNonspace(ln, offset, column, out first_nonspace, out first_nonspace_column, out curChar); indent = first_nonspace_column - column; blank = curChar == '\n'; if (blank && container.LastChild != null) { container.LastChild.IsLastLineBlank = true; } // 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. container.IsLastLineBlank = (blank && container.Tag != BlockTag.BlockQuote && container.Tag != BlockTag.SetextHeading && container.Tag != BlockTag.FencedCode && !(container.Tag == BlockTag.ListItem && container.FirstChild == null && container.SourcePosition >= line.LineOffset)); Block cont = container; while (cont.Parent != null) { cont.Parent.IsLastLineBlank = false; cont = cont.Parent; } if (cur != last_matched_container && container == last_matched_container && !blank && cur.Tag == BlockTag.Paragraph && cur.StringContent.Length > 0) { AddLine(cur, line, ln, offset, remainingSpaces); } else { // not a lazy continuation // finalize any blocks that were not matched and set cur to container: while (cur != last_matched_container) { Finalize(cur, line); cur = cur.Parent; if (cur == null) throw new CommonMarkException("Cannot finalize container block. Last matched container tag = " + last_matched_container.Tag); } if (container.Tag == BlockTag.IndentedCode) { AddLine(container, line, ln, offset, remainingSpaces); } else if (container.Tag == BlockTag.FencedCode) { if ((indent <= 3 && curChar == container.FencedCodeData.FenceChar) && (0 != Scanner.scan_close_code_fence(ln, first_nonspace, container.FencedCodeData.FenceLength, ln.Length))) { // if closing fence, set fence length to -1. it will be closed when the next line is processed. container.FencedCodeData.FenceLength = -1; } else { AddLine(container, line, ln, offset, remainingSpaces); } } else if (container.Tag == BlockTag.HtmlBlock) { AddLine(container, line, ln, offset, remainingSpaces); if (Scanner.scan_html_block_end(container.HtmlBlockType, ln, first_nonspace, ln.Length)) { Finalize(container, line); container = container.Parent; } } else if (blank) { // ??? do nothing } else if (container.Tag == BlockTag.AtxHeading) { int p = ln.Length - 1; // trim trailing spaces while (p >= 0 && (ln[p] == ' ' || ln[p] == '\t' || ln[p] == '\n')) p--; int px = p; // if string ends in #s, remove these: while (p >= 0 && ln[p] == '#') p--; // there must be a space before the last hashtag if (p < 0 || (ln[p] != ' ' && ln[p] != '\t')) p = px; // trim trailing spaces that are before the closing #s while (p >= first_nonspace && (ln[p] == ' ' || ln[p] == '\t')) p--; AddLine(container, line, ln, first_nonspace, remainingSpaces, p - first_nonspace + 1); Finalize(container, line); container = container.Parent; } else if (AcceptsLines(container.Tag)) { AddLine(container, line, ln, first_nonspace, remainingSpaces); } else if (container.Tag != BlockTag.ThematicBreak && container.Tag != BlockTag.SetextHeading) { // create paragraph container for line container = CreateChildBlock(container, line, BlockTag.Paragraph, first_nonspace); AddLine(container, line, ln, first_nonspace, remainingSpaces); } else { Utilities.Warning("Line {0} with container type {1} did not match any condition:\n\"{2}\"", line.LineNumber, container.Tag, ln); } curptr = container; } }
public void ReadLine(LineInfo line) { line.LineOffset = this._previousBufferLength + this._bufferPosition; line.LineNumber++; line.OffsetCount = 0; line.Line = null; var tabIncreaseCount = 0; if (this._bufferPosition == this._bufferLength && !this.ReadBuffer()) { return; } bool useBuilder = false; int num; char c; while (true) { num = this._bufferPosition; do { c = this._buffer[num]; if (c == '\r' || c == '\n') { goto IL_4A; } if (c == '\0') { this._buffer[num] = '\uFFFD'; } num++; }while (num < this._bufferLength); num = this._bufferLength - this._bufferPosition; if (!useBuilder) { useBuilder = true; this._builder.Length = 0; } this._builder.Append(this._buffer, this._bufferPosition, num); if (!this.ReadBuffer()) { this._builder.Append('\n'); line.Line = this._builder.ToString(); return; } } IL_4A: string result; this._buffer[num] = '\n'; if (useBuilder) { this._builder.Append(this._buffer, this._bufferPosition, num - this._bufferPosition + 1); result = this._builder.ToString(); } else { result = new string(this._buffer, this._bufferPosition, num - this._bufferPosition + 1); } this._bufferPosition = num + 1; if (c == '\r' && (this._bufferPosition < this._bufferLength || this.ReadBuffer()) && this._buffer[this._bufferPosition] == '\n') { if (line.IsTrackingPositions) { line.AddOffset(this._previousBufferLength + this._bufferPosition - 1 + tabIncreaseCount, 1); } this._bufferPosition++; } line.Line = result; }
public void AddOffset(LineInfo line, int startIndex, int length) { if (this.OffsetCount + line.OffsetCount + 2 >= this.Offsets.Length) Array.Resize(ref this.Offsets, this.Offsets.Length + line.OffsetCount + 20); PositionOffset po1, po2; if (startIndex > 0) po1 = new PositionOffset( CalculateOrigin(line.Offsets, line.OffsetCount, line.LineOffset, false, true), startIndex); else po1 = EmptyPositionOffset; if (line.Line.Length - startIndex - length > 0) po2 = new PositionOffset( CalculateOrigin(line.Offsets, line.OffsetCount, line.LineOffset + startIndex + length, false, true), line.Line.Length - startIndex - length); else po2 = EmptyPositionOffset; var indexAfterLastCopied = 0; if (po1.Offset == 0) { if (po2.Offset == 0) goto FINTOTAL; po1 = po2; po2 = EmptyPositionOffset; } for (var i = 0; i < line.OffsetCount; i++) { var pc = line.Offsets[i]; if (pc.Position > po1.Position) { if (i > indexAfterLastCopied) { Array.Copy(line.Offsets, indexAfterLastCopied, this.Offsets, this.OffsetCount, i - indexAfterLastCopied); this.OffsetCount += i - indexAfterLastCopied; indexAfterLastCopied = i; } this.Offsets[this.OffsetCount++] = po1; po1 = po2; if (po1.Offset == 0) goto FIN; po2 = EmptyPositionOffset; } } FIN: if (po1.Offset != 0) this.Offsets[this.OffsetCount++] = po1; if (po2.Offset != 0) this.Offsets[this.OffsetCount++] = po2; FINTOTAL: Array.Copy(line.Offsets, indexAfterLastCopied, this.Offsets, this.OffsetCount, line.OffsetCount - indexAfterLastCopied); this.OffsetCount += line.OffsetCount - indexAfterLastCopied; }