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); }
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); }