コード例 #1
0
        public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex, bool isFinalProcessing)
        {
            var container  = root as ContainerInline;
            var tableState = state.ParserStates[Index] as TableState;

            // If the delimiters are being processed by an image link, we need to transform them back to literals
            if (!isFinalProcessing)
            {
                if (container == null || tableState == null)
                {
                    return(true);
                }

                var child = container.LastChild;
                List <PipeTableDelimiterInline> delimitersToRemove = null;

                while (child != null)
                {
                    var pipeDelimiter = child as PipeTableDelimiterInline;
                    if (pipeDelimiter != null)
                    {
                        if (delimitersToRemove == null)
                        {
                            delimitersToRemove = new List <PipeTableDelimiterInline>();
                        }
                        delimitersToRemove.Add(pipeDelimiter);
                    }

                    if (child == lastChild)
                    {
                        break;
                    }

                    var subContainer = child as ContainerInline;
                    child = subContainer?.LastChild;
                }

                // If we have found any delimiters, transform them to literals
                if (delimitersToRemove != null)
                {
                    bool leftIsDelimiter  = false;
                    bool rightIsDelimiter = false;
                    for (int i = 0; i < delimitersToRemove.Count; i++)
                    {
                        var pipeDelimiter = delimitersToRemove[i];
                        pipeDelimiter.ReplaceByLiteral();

                        // Check that the pipe that is being removed is not going to make a line without pipe delimiters
                        var tableDelimiters = tableState.ColumnAndLineDelimiters;
                        var delimiterIndex  = tableDelimiters.IndexOf(pipeDelimiter);

                        if (i == 0)
                        {
                            leftIsDelimiter = delimiterIndex > 0 && tableDelimiters[delimiterIndex - 1] is PipeTableDelimiterInline;
                        }
                        else if (i + 1 == delimitersToRemove.Count)
                        {
                            rightIsDelimiter = delimiterIndex + 1 < tableDelimiters.Count &&
                                               tableDelimiters[delimiterIndex + 1] is PipeTableDelimiterInline;
                        }
                        // Remove this delimiter from the table processor
                        tableState.ColumnAndLineDelimiters.Remove(pipeDelimiter);
                    }

                    // If we didn't have any delimiter before and after the delimiters we jsut removed, we mark the processor of the current line as no pipe
                    if (!leftIsDelimiter && !rightIsDelimiter)
                    {
                        tableState.LineHasPipe = false;
                    }
                }

                return(true);
            }

            // Remove previous state
            state.ParserStates[Index] = null;

            // Continue
            if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe)  //|| tableState.LineIndex != state.LocalLineIndex)
            {
                return(true);
            }

            // Detect the header row
            var delimiters = tableState.ColumnAndLineDelimiters;
            // TODO: we could optimize this by merging FindHeaderRow and the cell loop
            var aligns = FindHeaderRow(delimiters);

            if (Options.RequireHeaderSeparator && aligns == null)
            {
                return(true);
            }

            var table = new Table();

            // If the current paragraph block has any attributes attached, we can copy them
            var attributes = state.Block.TryGetAttributes();

            if (attributes != null)
            {
                attributes.CopyTo(table.GetAttributes());
            }

            state.BlockNew = table;
            var cells = tableState.Cells;

            cells.Clear();

            //delimiters[0].DumpTo(state.DebugLog);

            // delimiters contain a list of `|` and `\n` delimiters
            // The `|` delimiters are created as child containers.
            // So the following:
            // | a | b \n
            // | d | e \n
            //
            // Will generate a tree of the following node:
            // |
            //   a
            //   |
            //     b
            //     \n
            //     |
            //       d
            //       |
            //         e
            //         \n
            // When parsing delimiters, we need to recover whether a row is of the following form:
            // 0)  | a | b | \n
            // 1)  | a | b \n
            // 2)    a | b \n
            // 3)    a | b | \n

            // If the last element is not a line break, add a line break to homogenize parsing in the next loop
            var lastElement = delimiters[delimiters.Count - 1];

            if (!(lastElement is LineBreakInline))
            {
                while (true)
                {
                    if (lastElement is ContainerInline)
                    {
                        var nextElement = ((ContainerInline)lastElement).LastChild;
                        if (nextElement != null)
                        {
                            lastElement = nextElement;
                            continue;
                        }
                    }
                    break;
                }

                var endOfTable = new LineBreakInline();
                lastElement.InsertAfter(endOfTable);
                delimiters.Add(endOfTable);
                tableState.EndOfLines.Add(endOfTable);
            }

            // Cell loop
            // Reconstruct the table from the delimiters
            TableRow row      = null;
            TableRow firstRow = null;

            for (int i = 0; i < delimiters.Count; i++)
            {
                var delimiter     = delimiters[i];
                var pipeSeparator = delimiter as PipeTableDelimiterInline;
                var isLine        = delimiter is LineBreakInline;

                if (row == null)
                {
                    row = new TableRow();
                    if (firstRow == null)
                    {
                        firstRow = row;
                    }

                    // If the first delimiter is a pipe and doesn't have any parent or previous sibling, for cases like:
                    // 0)  | a | b | \n
                    // 1)  | a | b \n
                    if (pipeSeparator != null && (delimiter.PreviousSibling == null || delimiter.PreviousSibling is LineBreakInline))
                    {
                        delimiter.Remove();
                        continue;
                    }
                }

                // We need to find the beginning/ending of a cell from a right delimiter. From the delimiter 'x', we need to find a (without the delimiter start `|`)
                // So we iterate back to the first pipe or line break
                //         x
                // 1)  | a | b \n
                // 2)    a | b \n
                Inline endOfCell     = null;
                Inline beginOfCell   = null;
                var    cellContentIt = delimiter;
                while (true)
                {
                    cellContentIt = cellContentIt.PreviousSibling ?? cellContentIt.Parent;

                    if (cellContentIt == null || cellContentIt is LineBreakInline)
                    {
                        break;
                    }

                    // The cell begins at the first effective child after a | or the top ContainerInline (which is not necessary to bring into the tree + it contains an invalid span calculation)
                    if (cellContentIt is PipeTableDelimiterInline || (cellContentIt.GetType() == typeof(ContainerInline) && cellContentIt.Parent == null))
                    {
                        beginOfCell = ((ContainerInline)cellContentIt).FirstChild;
                        if (endOfCell == null)
                        {
                            endOfCell = beginOfCell;
                        }
                        break;
                    }

                    beginOfCell = cellContentIt;
                    if (endOfCell == null)
                    {
                        endOfCell = beginOfCell;
                    }
                }


                // If the current deilimiter is a pipe `|` OR
                // the beginOfCell/endOfCell are not null and
                // either they are :
                // - different
                // - they contain a single element, but it is not a line break (\n) or an empty/whitespace Literal.
                // Then we can add a cell to the current row
                if (!isLine || (beginOfCell != null && endOfCell != null && (beginOfCell != endOfCell || !(beginOfCell is LineBreakInline || (beginOfCell is LiteralInline && ((LiteralInline)beginOfCell).Content.IsEmptyOrWhitespace())))))
                {
                    if (!isLine)
                    {
                        // If the delimiter is a pipe, we need to remove it from the tree
                        // so that previous loop looking for a parent will not go further on subsequent cells
                        delimiter.Remove();
                    }

                    // We trim whitespace at the beginning and ending of the cell
                    TrimStart(beginOfCell);
                    TrimEnd(endOfCell);

                    var cellContainer = new ContainerInline();

                    // Copy elements from beginOfCell on the first level
                    var cellIt = beginOfCell;
                    while (cellIt != null && !IsLine(cellIt) && !(cellIt is PipeTableDelimiterInline))
                    {
                        var nextSibling = cellIt.NextSibling;
                        cellIt.Remove();
                        if (cellContainer.Span.IsEmpty)
                        {
                            cellContainer.Line   = cellIt.Line;
                            cellContainer.Column = cellIt.Column;
                            cellContainer.Span   = cellIt.Span;
                        }
                        cellContainer.AppendChild(cellIt);
                        cellContainer.Span.End = cellIt.Span.End;
                        cellIt = nextSibling;
                    }

                    // Create the cell and add it to the pending row
                    var tableParagraph = new ParagraphBlock()
                    {
                        Span   = cellContainer.Span,
                        Line   = cellContainer.Line,
                        Column = cellContainer.Column,
                        Inline = cellContainer
                    };

                    var tableCell = new TableCell()
                    {
                        Span   = cellContainer.Span,
                        Line   = cellContainer.Line,
                        Column = cellContainer.Column,
                    };

                    tableCell.Add(tableParagraph);
                    if (row.Span.IsEmpty)
                    {
                        row.Span   = cellContainer.Span;
                        row.Line   = cellContainer.Line;
                        row.Column = cellContainer.Column;
                    }
                    row.Add(tableCell);
                    cells.Add(tableCell);
                }

                // If we have a new line, we can add the row
                if (isLine)
                {
                    Debug.Assert(row != null);
                    if (table.Span.IsEmpty)
                    {
                        table.Span   = row.Span;
                        table.Line   = row.Line;
                        table.Column = row.Column;
                    }
                    table.Add(row);
                    row = null;
                }
            }

            // Once we are done with the cells, we can remove all end of lines in the table tree
            foreach (var endOfLine in tableState.EndOfLines)
            {
                endOfLine.Remove();
            }

            // If we have a header row, we can remove it
            // TODO: we could optimize this by merging FindHeaderRow and the previous loop
            if (aligns != null)
            {
                table.RemoveAt(1);
                var tableRow = (TableRow)table[0];
                table.ColumnDefinitions.AddRange(aligns);
                tableRow.IsHeader = true;
            }

            // Perform delimiter processor that are coming after this processor
            foreach (var cell in cells)
            {
                var paragraph = (ParagraphBlock)cell[0];
                state.PostProcessInlines(postInlineProcessorIndex + 1, paragraph.Inline, null, true);
            }

            // Clear cells when we are done
            cells.Clear();

            // We don't want to continue procesing delimiters, as we are already processing them here
            return(false);
        }
コード例 #2
0
ファイル: PipeTableParser.cs プロジェクト: Jither/markdig
        public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex, bool isFinalProcessing)
        {
            var container  = root as ContainerInline;
            var tableState = state.ParserStates[Index] as TableState;

            // If the delimiters are being processed by an image link, we need to transform them back to literals
            if (!isFinalProcessing)
            {
                if (container == null || tableState == null)
                {
                    return(true);
                }

                var child = container.LastChild;
                List <PiprTableDelimiterInline> delimitersToRemove = null;

                while (child != null)
                {
                    var pipeDelimiter = child as PiprTableDelimiterInline;
                    if (pipeDelimiter != null)
                    {
                        if (delimitersToRemove == null)
                        {
                            delimitersToRemove = new List <PiprTableDelimiterInline>();
                        }
                        delimitersToRemove.Add(pipeDelimiter);
                    }

                    if (child == lastChild)
                    {
                        break;
                    }

                    var subContainer = child as ContainerInline;
                    child = subContainer?.LastChild;
                }

                // If we have found any delimiters, transform them to literals
                if (delimitersToRemove != null)
                {
                    bool leftIsDelimiter  = false;
                    bool rightIsDelimiter = false;
                    for (int i = 0; i < delimitersToRemove.Count; i++)
                    {
                        var pipeDelimiter = delimitersToRemove[i];
                        var literalInline = new LiteralInline()
                        {
                            Content = new StringSlice("|"), IsClosed = true
                        };
                        pipeDelimiter.ReplaceBy(literalInline);

                        // Check that the pipe that is being removed is not going to make a line without pipe delimiters
                        var tableDelimiters = tableState.ColumnAndLineDelimiters;
                        var delimiterIndex  = tableDelimiters.IndexOf(pipeDelimiter);

                        if (i == 0)
                        {
                            leftIsDelimiter = delimiterIndex > 0 && tableDelimiters[delimiterIndex - 1] is PiprTableDelimiterInline;
                        }
                        else if (i + 1 == delimitersToRemove.Count)
                        {
                            rightIsDelimiter = delimiterIndex + 1 < tableDelimiters.Count &&
                                               tableDelimiters[delimiterIndex + 1] is PiprTableDelimiterInline;
                        }
                        // Remove this delimiter from the table processor
                        tableState.ColumnAndLineDelimiters.Remove(pipeDelimiter);
                    }

                    // If we didn't have any delimiter before and after the delimiters we jsut removed, we mark the processor of the current line as no pipe
                    if (!leftIsDelimiter && !rightIsDelimiter)
                    {
                        tableState.LineHasPipe = false;
                    }
                }

                return(true);
            }

            // Continue
            if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe)  //|| tableState.LineIndex != state.LocalLineIndex)
            {
                return(true);
            }

            var delimiters = tableState.ColumnAndLineDelimiters;

            delimiters.Add(null);
            var aligns = FindHeaderRow(delimiters);

            if (Options.RequireHeaderSeparator && aligns == null)
            {
                return(true);
            }

            var table = new Table();

            // If the current paragraph block has any attributes attached, we can copy them
            var attributes = state.Block.TryGetAttributes();

            if (attributes != null)
            {
                attributes.CopyTo(table.GetAttributes());
            }

            state.BlockNew = table;
            TableRow firstRow  = null;
            int      maxColumn = 0;
            var      cells     = tableState.Cells;

            cells.Clear();

            Inline column = container.FirstChild;

            if (column is PiprTableDelimiterInline)
            {
                column = ((PiprTableDelimiterInline)column).FirstChild;
            }

            // TODO: This is not accurate for the table
            table.Span.Start = column.Span.Start;
            table.Span.End   = column.Span.End;
            table.Line       = column.Line;
            table.Column     = column.Column;

            int lastIndex = 0;

            for (int i = 0; i < delimiters.Count; i++)
            {
                var delimiter = delimiters[i];
                if (delimiter == null || IsLine(delimiter))
                {
                    var beforeDelimiter = delimiter?.PreviousSibling;
                    var nextLineColumn  = delimiter?.NextSibling;

                    TableRow row = null;

                    for (int j = lastIndex; j <= i; j++)
                    {
                        var columnSeparator = delimiters[j];
                        var pipeSeparator   = columnSeparator as PiprTableDelimiterInline;

                        var endOfColumn = columnSeparator?.PreviousSibling;

                        // This is the first column empty
                        if (j == lastIndex && pipeSeparator != null && endOfColumn == null)
                        {
                            columnSeparator.Remove();
                            column = pipeSeparator.FirstChild;
                            continue;
                        }

                        if (pipeSeparator != null && IsTrailingColumnDelimiter(pipeSeparator))
                        {
                            TrimEnd(endOfColumn);
                            columnSeparator.Remove();
                            continue;
                        }

                        var cellContainer = new ContainerInline();
                        var item          = column;
                        var isFirstItem   = true;
                        TrimStart(item);
                        while (item != null && !IsLine(item) && !(item is PiprTableDelimiterInline))
                        {
                            var nextSibling = item.NextSibling;
                            item.Remove();
                            cellContainer.AppendChild(item);
                            if (isFirstItem)
                            {
                                cellContainer.Line       = item.Line;
                                cellContainer.Column     = item.Column;
                                cellContainer.Span.Start = item.Span.Start;
                                isFirstItem = false;
                            }
                            cellContainer.Span.End = item.Span.End;
                            item = nextSibling;
                        }

                        var tableParagraph = new ParagraphBlock()
                        {
                            Span   = cellContainer.Span,
                            Line   = cellContainer.Line,
                            Column = cellContainer.Column,
                            Inline = cellContainer
                        };

                        var tableCell = new TableCell()
                        {
                            Span   = cellContainer.Span,
                            Line   = cellContainer.Line,
                            Column = cellContainer.Column,
                        };

                        tableCell.Add(tableParagraph);

                        if (row == null)
                        {
                            row = new TableRow()
                            {
                                Span   = cellContainer.Span,
                                Line   = cellContainer.Line,
                                Column = cellContainer.Column,
                            };
                        }
                        row.Add(tableCell);
                        cells.Add(tableCell);

                        // If we have reached the end, we can add remaining delimiters as pure child of the current cell
                        if (row.Count == maxColumn && columnSeparator is PiprTableDelimiterInline)
                        {
                            columnSeparator.Remove();
                            tableParagraph.Inline.AppendChild(columnSeparator);
                            break;
                        }
                        TrimEnd(endOfColumn);

                        //TrimEnd(previousSibling);
                        if (columnSeparator != null)
                        {
                            if (pipeSeparator != null)
                            {
                                column = pipeSeparator.FirstChild;
                            }
                            columnSeparator.Remove();
                        }
                    }

                    if (row != null)
                    {
                        table.Add(row);
                    }

                    TrimEnd(beforeDelimiter);

                    if (delimiter != null)
                    {
                        delimiter.Remove();
                    }

                    if (nextLineColumn != null)
                    {
                        column = nextLineColumn;
                    }

                    if (firstRow == null)
                    {
                        firstRow  = row;
                        maxColumn = firstRow.Count;
                    }

                    lastIndex = i + 1;
                }
            }

            // If we have a header row, we can remove it
            if (aligns != null)
            {
                table.RemoveAt(1);
                var tableRow = (TableRow)table[0];
                table.ColumnDefinitions.AddRange(aligns);
                tableRow.IsHeader = true;
            }

            // Perform delimiter processor that are coming after this processor
            foreach (var cell in cells)
            {
                var paragraph = (ParagraphBlock)cell[0];
                state.PostProcessInlines(postInlineProcessorIndex + 1, paragraph.Inline, null, true);
            }

            // Clear cells when we are done
            cells.Clear();

            // We don't want to continue procesing delimiters, as we are already processing them here
            return(false);
        }