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