Beispiel #1
0
        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);
                }
            }

            switch (b.Tag)
            {
            case BlockTag.Paragraph:
                var sc = b.StringContent;
                if (!sc.StartsWith('['))
                {
                    break;
                }

                var subj = new Subject(b.Top.Document);
                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;
            }
        }
Beispiel #2
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;
            bool  all_matched = true;
            Block cur         = curptr;
            char  curChar;
            int   indent;

            // container starts at the document root.
            var container = cur.Top;

            bool blank;

            // 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.Meta:
                    // -1 means we've seen closer
                    if (container.MetaData.FenceLength == -1)
                    {
                        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 &&
                   container.Tag != BlockTag.Meta)
            {
                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.ScanAtxHeadingStart(ln, first_nonspace, ln.Length, out int level)))
                {
                    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(level);
                }
                else if (!indented && (curChar == '-') && 3 == (matched = Scanner.ScanMetaDataOpen(ln, first_nonspace, ln.Length)))
                {
                    container          = CreateChildBlock(container, line, BlockTag.Meta, first_nonspace);
                    container.MetaData = new MetaData()
                    {
                        FenceLength = matched
                    };
                    AdvanceOffset(ln, first_nonspace + matched - offset, false, ref offset, ref column, ref remainingSpaces);
                }
                else if (!indented && (curChar == '`' || curChar == '~') && 0 != (matched = Scanner.ScanOpenCodeFence(ln, first_nonspace, ln.Length)))
                {
                    container = CreateChildBlock(container, line, BlockTag.FencedCode, first_nonspace);
                    container.FencedCodeData = new FencedCodeData
                    {
                        FenceChar   = curChar,
                        FenceLength = matched,
                        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.ScanHtmlBlockStart(ln, first_nonspace, ln.Length)) ||
                          (container.Tag != BlockTag.Paragraph && 0 != (matched = (int)Scanner.ScanHtmlBlockStart7(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.ScanSetextHeadingStart(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.ScanThematicBreak(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 ListData 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.Meta &&
                                        !(container.Tag == BlockTag.ListItem &&
                                          container.FirstChild is 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 is null)
                    {
                        throw new PandocMarkException("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.Meta)
                {
                    if ((indent <= 3 &&
                         curChar == '.') &&
                        (0 != Scanner.ScanCloseMetaData(ln, first_nonspace, container.MetaData.FenceLength, ln.Length)))
                    {
                        // if closing fence, set fence length to -1. it will be closed when the next line is processed.
                        container.MetaData.FenceLength = -1;
                    }
                    else if (!ln.StartsWith("---"))
                    {
                        AddMetaDataEntry(container, line, ln, offset, remainingSpaces);
                    }
                }
                else if (container.Tag == BlockTag.FencedCode)
                {
                    if ((indent <= 3 &&
                         curChar == container.FencedCodeData.FenceChar) &&
                        (0 != Scanner.ScanCloseCodeFence(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.ScanHtmlBlockEnd(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, isAddOffsetRequired: container.Parent is 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;
        }