Ejemplo n.º 1
0
        private Table ConvertTableBlock(MDT.Table tableBlock, int numberingId, int nestLevel)
        {
            var styleId = UserSettingStyleMap.GetStyleId(UserSettingStyleMap.StyleMapKeyType.Table, null);

            var oxmlTable = Manipulator.ElementCreator.CreateTableElement(styleId, numberingId, nestLevel);

            foreach (MDT.TableRow tableRow in tableBlock)
            {
                var oxmlTableRow = Manipulator.ElementCreator.CreateTableRowElement();
                foreach (MDT.TableCell tableCell in tableRow)
                {
                    var oxmlTableCell = Manipulator.ElementCreator.CreateTableCellElement();
                    foreach (ParagraphBlock paragraphBlock in tableCell)
                    {
                        var oxmlParagraph = ConvertParagraphBlock(paragraphBlock, WordDocumentNumberingManager.OutsideOfListNumberingId, 0);
                        oxmlTableCell.Append(oxmlParagraph);
                    }

                    oxmlTableRow.Append(oxmlTableCell);
                }

                oxmlTable.Append(oxmlTableRow);
            }

            return(oxmlTable);
        }
        private static string TranslateTableBlock(MDT.Table tableBlock, int nestLevel)
        {
            var tableBlockText = new StringBuilder();

            foreach (MDT.TableRow tableRow in tableBlock)
            {
                var cellTexts = new List <string>();

                foreach (MDT.TableCell tableCell in tableRow)
                {
                    var cellText = new StringBuilder();

                    foreach (ParagraphBlock paragraphBlock in tableCell)
                    {
                        cellText.Append(TranslateParagraphBlock(paragraphBlock, nestLevel));
                    }

                    cellTexts.Add(cellText.ToString());
                }

                tableBlockText.AppendLine(GetIndentWhitespaces(nestLevel) + "| " + string.Join(" | ", cellTexts.ToArray()) + " |");

                if (tableRow.IsHeader)
                {
                    var headerSeparator = new List <string>();
                    for (int i = 0; i < cellTexts.Count; i++)
                    {
                        headerSeparator.Add("--------");
                    }
                    tableBlockText.AppendLine(GetIndentWhitespaces(nestLevel) + "| " + string.Join(" | ", headerSeparator.ToArray()) + " |");
                }
            }

            return(tableBlockText.ToString());
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Calculate the width (in pixels) of a column in a table.
        /// This is the width of the widest cell in the column.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="columnIndex">Index of a column in the table.</param>
        private int GetColumnWidth(Table table, int columnIndex)
        {
            int width = 0;

            for (int i = 0; i < table.Count; i++)
            {
                TableRow row = (TableRow)table[i];
                if (columnIndex < row.Count)
                {
                    if (row[columnIndex] is TableCell cell)
                    {
                        string cellText  = GetCellRawText(cell);
                        int    cellWidth = MeasureText(cellText);
                        width = Math.Max(width, cellWidth + tableColumnPadding);
                    }
                    else
                    {
                        throw new NotImplementedException($"Unknown cell type {row[columnIndex].GetType().Name}.");
                    }
                }
            }
            return(width);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Display a table.
        /// </summary>
        /// <param name="insertPos"></param>
        /// <param name="table"></param>
        /// <param name="indent"></param>
        private void DisplayTable(ref TextIter insertPos, Table table, int indent)
        {
            int spaceWidth = MeasureText(" ");
            // Setup tab stops for the table.
            TabArray tabs     = new TabArray(table.ColumnDefinitions.Count(), true);
            int      cumWidth = 0;
            Dictionary <int, int> columnWidths = new Dictionary <int, int>();

            for (int i = 0; i < table.ColumnDefinitions.Count(); i++)
            {
                // The i-th tab stop will be set to the cumulative column width
                // of the first i columns (including padding).
                columnWidths[i] = GetColumnWidth(table, i);
                cumWidth       += columnWidths[i];
                tabs.SetTab(i, TabAlign.Left, cumWidth);
            }

            // Create a TextTag containing the custom tab stops.
            // This TextTag will be applied to all text in the table.
            TextTag tableTag = new TextTag(Guid.NewGuid().ToString());

            tableTag.Tabs     = tabs;
            tableTag.WrapMode = Gtk.WrapMode.None;
            textView.Buffer.TagTable.Add(tableTag);

            for (int i = 0; i < table.Count; i++)
            {
                TableRow row = (TableRow)table[i];
                for (int j = 0; j < Math.Min(table.ColumnDefinitions.Count(), row.Count); j++)
                {
                    if (row[j] is TableCell cell)
                    {
                        // Figure out which tags to use for this cell. We always
                        // need to use the tableTag (which includes the tab stops)
                        // but if it's the top row, we also include the bold tag.
                        TextTag[] tags;
                        if (row.IsHeader)
                        {
                            tags = new TextTag[2] {
                                tableTag, textView.Buffer.TagTable.Lookup("Bold")
                            }
                        }
                        ;
                        else
                        {
                            tags = new TextTag[1] {
                                tableTag
                            }
                        };

                        TableColumnAlign?alignment = table.ColumnDefinitions[j].Alignment;
                        // If the column is center- or right-aligned, we will insert
                        // some whitespace characters to pad out the text.
                        if (alignment == TableColumnAlign.Center || alignment == TableColumnAlign.Right)
                        {
                            // Calculate the width of the cell contents.
                            int cellWidth = MeasureText(GetCellRawText(cell));

                            // Number of whitespace characters we can fit will be the
                            // number of spare pixels in the cell (width of cell - width
                            // of cell text) divided by the width of a single space char.
                            int spareSpace = (columnWidths[j] - cellWidth) / spaceWidth;
                            if (spareSpace >= 2)
                            {
                                // The number of spaces to insert will be either the total
                                // amount we can fit if right-aligning, or half that amount
                                // if center-aligning.
                                int    numSpaces  = alignment == TableColumnAlign.Center ? spareSpace / 2 : spareSpace;
                                string whitespace = new string(' ', spareSpace / 2);
                                textView.Buffer.Insert(ref insertPos, whitespace);
                            }
                        }

                        // Recursively process all markdown blocks inside this cell. In
                        // theory, this supports both blocks and inline content. In practice
                        // I wouldn't recommend using blocks inside a table cell.
                        ProcessMarkdownBlocks(cell, ref insertPos, textView, indent, false, tags);
                        if (j != row.Count - 1)
                        {
                            textView.Buffer.InsertWithTags(ref insertPos, "\t", tableTag);
                        }
                    }
                    else
                    {
                        // This shouldn't happen.
                        throw new Exception($"Unknown cell type {row[(int)j].GetType()}.");
                    }
                }
                // Insert a newline after each row - except the last row.
                if (i + 1 < table.Count)
                {
                    textView.Buffer.Insert(ref insertPos, "\n");
                }
            }
        }
Ejemplo n.º 5
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 is null || tableState is null)
                {
                    return(true);
                }

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

                while (child != null)
                {
                    if (child is PipeTableDelimiterInline pipeDelimiter)
                    {
                        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 just 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 is null || container is 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 is 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 lastElementContainer)
                    {
                        var nextElement = lastElementContainer.LastChild;
                        if (nextElement != null)
                        {
                            lastElement = nextElement;
                            continue;
                        }
                    }
                    break;
                }

                var endOfTable = new LineBreakInline();
                // If the last element is a container, we have to add the EOL to its child
                // otherwise only next sibling
                if (lastElement is ContainerInline)
                {
                    ((ContainerInline)lastElement).AppendChild(endOfTable);
                }
                else
                {
                    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 is null)
                {
                    row = new TableRow();

                    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 is 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 is 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 is null))
                    {
                        beginOfCell = ((ContainerInline)cellContentIt).FirstChild;
                        if (endOfCell is null)
                        {
                            endOfCell = beginOfCell;
                        }
                        break;
                    }

                    beginOfCell = cellContentIt;
                    if (endOfCell is 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 beingOfCellLiteral && beingOfCellLiteral.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
            var tableRow = (TableRow)table[0];

            tableRow.IsHeader = Options.RequireHeaderSeparator;
            if (aligns != null)
            {
                tableRow.IsHeader = true;
                table.RemoveAt(1);
                table.ColumnDefinitions.AddRange(aligns);
            }

            // 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();

            // Normalize the table
            if (Options.UseHeaderForColumnCount)
            {
                table.NormalizeUsingHeaderRow();
            }
            else
            {
                table.NormalizeUsingMaxWidth();
            }

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