/// <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;
            }
        }
Exemple #10
0
        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;
        }
Exemple #11
0
        // 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;
            }
        }
Exemple #12
0
        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;
        }