Example #1
0
        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);
        }
Example #3
0
        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);
            }
        }
Example #4
0
        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);
        }
Example #5
0
        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();
        }