private void TerminateLastRow(BlockProcessor state, GridTableState tableState, Table gridTable, bool isLastRow) { var columns = tableState.ColumnSlices; TableRow currentRow = null; foreach (var columnSlice in columns) { if (columnSlice.CurrentCell != null) { if (currentRow == null) { currentRow = new TableRow(); } currentRow.Add(columnSlice.CurrentCell); columnSlice.BlockProcessor.Close(columnSlice.CurrentCell); } // Renew the block parser processor (or reset it for the last row) if (columnSlice.BlockProcessor != null) { columnSlice.BlockProcessor.ReleaseChild(); columnSlice.BlockProcessor = isLastRow ? null : state.CreateChild(); } // Create or erase the cell if (isLastRow || columnSlice.CurrentColumnSpan == 0) { // We don't need the cell anymore if we have a last row // Or the cell has a columnspan == 0 columnSlice.CurrentCell = null; } else { // Else we can create a new cell columnSlice.CurrentCell = new TableCell(this) { ColumnSpan = columnSlice.CurrentColumnSpan }; if (columnSlice.BlockProcessor == null) { columnSlice.BlockProcessor = state.CreateChild(); } // Ensure that the BlockParser is aware that the TableCell is the top-level container columnSlice.BlockProcessor.Open(columnSlice.CurrentCell); } } if (currentRow != null) { gridTable.Add(currentRow); } }
internal virtual FlexiTableBlock CreateFlexiTableBlock(string blockName, FlexiTableType type, ReadOnlyDictionary <string, string> attributes, ProxyTableBlock proxyTableBlock, BlockProcessor blockProcessor) { // Create table block var flexiTableBlock = new FlexiTableBlock(blockName, type, attributes, proxyTableBlock.Parser) { Column = proxyTableBlock.Column, Line = proxyTableBlock.Line, Span = proxyTableBlock.Span }; // Create row blocks bool headerRowFound = false; BlockProcessor childBlockProcessor = blockProcessor.CreateChild(); List <Row> rows = proxyTableBlock.Rows; List <ColumnDefinition> columnDefinitions = proxyTableBlock.ColumnDefinitions; int numColumns = proxyTableBlock.NumColumns; int numRows = rows.Count; bool typeIsUnresponsive = type == FlexiTableType.Unresponsive; for (int rowIndex = 0; rowIndex < numRows; rowIndex++) { Row row = rows[rowIndex]; bool isHeaderRow = row.IsHeaderRow; if (!typeIsUnresponsive && isHeaderRow) { if (headerRowFound) { throw new OptionsException(nameof(IFlexiTableBlockOptions.Type), Strings.OptionsException_FlexiTableBlockFactory_TypeInvalidForTablesWithMultipleHeaderRows); } headerRowFound = true; } // Create and add row flexiTableBlock.Add(CreateFlexiTableRowBlock(type, childBlockProcessor, columnDefinitions, numColumns, rowIndex, row, isHeaderRow)); } // Release for reuse childBlockProcessor.ReleaseChild(); return(flexiTableBlock); }
private static void TerminateCurrentRow(BlockProcessor processor, GridTableState tableState, Table gridTable, bool isLastRow) { var columns = tableState.ColumnSlices; TableRow currentRow = null; for (int i = 0; i < columns.Count; i++) { var columnSlice = columns[i]; if (columnSlice.CurrentCell != null) { if (currentRow == null) { currentRow = new TableRow(); } // If this cell does not already belong to a row if (columnSlice.CurrentCell.Parent == null) { currentRow.Add(columnSlice.CurrentCell); } // If the cell is not going to span through to the next row if (columnSlice.CurrentCell.AllowClose) { columnSlice.BlockProcessor.Close(columnSlice.CurrentCell); } } // Renew the block parser processor (or reset it for the last row) if (columnSlice.BlockProcessor != null && (columnSlice.CurrentCell == null || columnSlice.CurrentCell.AllowClose)) { columnSlice.BlockProcessor.ReleaseChild(); columnSlice.BlockProcessor = isLastRow ? null : processor.CreateChild(); } // Create or erase the cell if (isLastRow || columnSlice.CurrentColumnSpan == 0 || (columnSlice.CurrentCell != null && columnSlice.CurrentCell.AllowClose)) { // We don't need the cell anymore if we have a last row // Or the cell has a columnspan == 0 // And the cell does not have to be kept open to span rows columnSlice.CurrentCell = null; } } if (currentRow != null && currentRow.Count > 0) { gridTable.Add(currentRow); } }
private BlockState HandleContents(BlockProcessor processor, GridTableState tableState, Table gridTable) { var isRowLine = processor.CurrentChar == '+'; var columns = tableState.ColumnSlices; var line = processor.Line; SetColumnSpanState(columns, line); if (!isRowLine && !CanContinueRow(columns)) { TerminateCurrentRow(processor, tableState, gridTable, false); } for (int i = 0; i < columns.Count;) { var columnSlice = columns[i]; var nextColumnIndex = i + columnSlice.CurrentColumnSpan; // If the span is 0, we exit if (nextColumnIndex == i) { break; } var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null; var sliceForCell = line; sliceForCell.Start = line.Start + columnSlice.Start + 1; if (nextColumn != null) { sliceForCell.End = line.Start + nextColumn.Start - 1; } else { var columnEnd = columns[columns.Count - 1].End; var columnEndChar = line.PeekCharExtra(columnEnd); // If there is a `|` (or a `+` in the case that we are dealing with a row line // with spanned contents) exactly at the expected end of the table row, we cut the line // otherwise we allow to have the last cell of a row to be open for longer cell content if (columnEndChar == '|' || (isRowLine && columnEndChar == '+')) { sliceForCell.End = line.Start + columnEnd - 1; } else if (line.PeekCharExtra(line.End) == '|') { sliceForCell.End = line.End - 1; } } sliceForCell.TrimEnd(); if (!isRowLine || !IsRowSeperator(sliceForCell)) { if (columnSlice.CurrentCell == null) { columnSlice.CurrentCell = new TableCell(this) { ColumnSpan = columnSlice.CurrentColumnSpan, ColumnIndex = i }; if (columnSlice.BlockProcessor == null) { columnSlice.BlockProcessor = processor.CreateChild(); } // Ensure that the BlockParser is aware that the TableCell is the top-level container columnSlice.BlockProcessor.Open(columnSlice.CurrentCell); } // Process the content of the cell columnSlice.BlockProcessor.LineIndex = processor.LineIndex; columnSlice.BlockProcessor.ProcessLine(sliceForCell); } // Go to next column i = nextColumnIndex; } return(BlockState.ContinueDiscard); }
internal virtual void ProcessContent(BlockProcessor blockProcessor, FlexiIncludeBlock flexiIncludeBlock, ContainerBlock parentOfNewBlocks, ReadOnlyCollection <string> content) { // The method used here is also used for GridTables. Using a child processor avoids conflicts with existing // open blocks in the root processor. BlockProcessor childProcessor = blockProcessor.CreateChild(); childProcessor.Open(parentOfNewBlocks); // If content is code, start with ``` bool isCode = flexiIncludeBlock.Type == FlexiIncludeType.Code; if (isCode) { childProcessor.ProcessLine(_codeBlockFence); } // TODO this has the potential to be really slow if content has lots of lines and we've got lots of clippings using start/end strings // Clippings - need not be sequential, they can also overlap int contentNumLines = content.Count; foreach (Clipping clipping in flexiIncludeBlock.Clippings ?? _defaultClippings) { string before = clipping.Before; if (isCode && before != null) { childProcessor.ProcessLine(new StringSlice(before)); // No issue even if Before is multiline since we're in a code block } int startLineNumber = -1; (string startString, string endString) = clipping.GetNormalizedStartAndEndStrings(); bool startStringSpecified = startString != null; bool endStringSpecified = endString != null; (int normalizedStartLine, int normalizedEndLine)normalizedStartAndEndLines = default; if (!startStringSpecified || !endStringSpecified) { normalizedStartAndEndLines = clipping.GetNormalizedStartAndEndLines(contentNumLines); } if (startStringSpecified) { int lastIndex = contentNumLines - 2; // Since demarcation lines are not included in the clipping, the last line cannot be a start demarcation line. for (int i = 0; i <= lastIndex; i++) { if (content[i].Contains(startString)) { startLineNumber = i + 2; break; } } if (startLineNumber == -1) { throw new OptionsException(nameof(Clipping.StartString), string.Format(Strings.OptionsException_FlexiIncludeBlockFactory_NoLineContainsStartString, startString)); } } else { startLineNumber = normalizedStartAndEndLines.normalizedStartLine; } // If we encounter an invalid block in the included content, this ensures the BlockException thrown has the right line number in the included content's source. childProcessor.LineIndex = startLineNumber - 1; for (int lineNumber = startLineNumber; lineNumber <= contentNumLines; lineNumber++) { var stringSlice = new StringSlice(content[lineNumber - 1]); if (!stringSlice.IsEmpty) { if (clipping.Indent > 0) { stringSlice = _leadingWhitespaceEditorService.Indent(stringSlice, clipping.Indent); } _leadingWhitespaceEditorService.Dedent(ref stringSlice, clipping.Dedent); _leadingWhitespaceEditorService.Collapse(ref stringSlice, clipping.Collapse); } childProcessor.ProcessLine(stringSlice); // Check whether we've reached the end of the clipping if (endStringSpecified) { if (lineNumber == contentNumLines) { throw new OptionsException(nameof(Clipping.EndString), string.Format(Strings.OptionsException_FlexiIncludeBlockFactory_NoLineContainsEndString, endString)); } // Check if next line contains the end line substring if (content[lineNumber].Contains(endString)) { break; } } else if (lineNumber == normalizedStartAndEndLines.normalizedEndLine) { break; } } string after = clipping.After; if (isCode && after != null) { childProcessor.ProcessLine(new StringSlice(after)); // No issue even if Before is multiline since we're in a code block } } if (isCode) // If content is code, end with ``` { childProcessor.ProcessLine(_codeBlockFence); } // Ensure that the last replacement block has been closed. While the block never makes it to the OpenedBlocks collection in the root processor, // calling Close for it ensures that it and its children's Close methods and events get called. childProcessor.Close(parentOfNewBlocks.LastChild); // BlockProcessors are pooled. Once we're done with innerProcessor, we must release it. This also removes all references to // tempContainerBlock, which should allow it to be collected quickly. childProcessor.ReleaseChild(); }