/// <summary>
        /// Opens a <see cref="ProxyTableBlock"/> if a line is a column definitions line.
        /// </summary>
        /// <param name="blockProcessor">The <see cref="BlockProcessor"/> for the document that contains a line starting with '+'.</param>
        /// <returns>
        /// <see cref="BlockState.None"/> if the current line has code indent.
        /// <see cref="BlockState.None"/> if the current line is not a column definitions line.
        /// <see cref="BlockState.ContinueDiscard"/> if a <see cref="ProxyTableBlock"/> is opened.
        /// </returns>
        protected override BlockState TryOpenBlock(BlockProcessor blockProcessor)
        {
            // A grid table cannot start more than an indent
            if (blockProcessor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            // Check if line is a column definitions line, parses column definitions if it is
            List <ColumnDefinition> columnDefinitions;

            if ((columnDefinitions = TryParseColumnDefinitionsLine(blockProcessor.Line, '+', -1)) == null)
            {
                return(BlockState.None);
            }

            // Create ProxyTableBlock
            ProxyTableBlock proxyTableBlock = _flexiTableBlockFactory.CreateProxy(blockProcessor, this);

            proxyTableBlock.ColumnDefinitions = columnDefinitions;
            proxyTableBlock.NumColumns        = columnDefinitions.Count;
            blockProcessor.NewBlocks.Push(proxyTableBlock);

            // Store current line in case the grid FlexiTableBlock is invalid
            proxyTableBlock.Lines.Add(blockProcessor.Line);

            return(BlockState.ContinueDiscard);
        }
        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);
        }
        /// <inheritdoc />
        public FlexiTableBlock Create(ProxyTableBlock proxyTableBlock, BlockProcessor blockProcessor)
        {
            (IFlexiTableBlockOptions flexiTableBlockOptions, IFlexiTableBlocksExtensionOptions _) = _optionsService.
                                                                                                    CreateOptions(blockProcessor);

            // Block name
            string blockName = ResolveBlockName(flexiTableBlockOptions.BlockName);

            // Type
            FlexiTableType type = flexiTableBlockOptions.Type;

            ValidateType(type);

            // Create block
            return(CreateFlexiTableBlock(blockName, type, flexiTableBlockOptions.Attributes, proxyTableBlock, blockProcessor));
        }
        /// <summary>
        /// Opens a <see cref="ProxyTableBlock"/> if a line is a column definitions line or a row line.
        /// </summary>
        /// <param name="blockProcessor">The <see cref="BlockProcessor"/> for the document that contains a line starting with '|'.</param>
        /// <returns>
        /// <see cref="BlockState.None"/> if the current line has code indent.
        /// <see cref="BlockState.None"/> if the current line is neither a column definitions line nor a row line.
        /// <see cref="BlockState.ContinueDiscard"/> if a <see cref="ProxyTableBlock"/> is opened.
        /// </returns>
        protected override BlockState TryOpenBlock(BlockProcessor blockProcessor)
        {
            // A grid table cannot start more than an indent
            if (blockProcessor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            // Parse line
            Row                     row  = null;
            StringSlice             line = blockProcessor.Line;
            List <ColumnDefinition> columnDefinitions;

            if ((columnDefinitions = TryParseColumnDefinitionsLine(line, '|', -1)) == null &&
                (row = TryParseRowLine(blockProcessor, 0, -1)) == null)
            {
                return(BlockState.None);
            }

            // Create ProxyTableBlock
            ProxyTableBlock proxyTableBlock = _flexiTableBlockFactory.CreateProxy(blockProcessor, this);

            if (columnDefinitions != null)
            {
                proxyTableBlock.ColumnDefinitions = columnDefinitions;
                proxyTableBlock.NumColumns        = columnDefinitions.Count;
            }
            else
            {
                proxyTableBlock.Rows.Add(row);
                proxyTableBlock.NumColumns = row.Count;
            }
            blockProcessor.NewBlocks.Push(proxyTableBlock);

            // Store current line in case the grid FlexiTableBlock is invalid
            proxyTableBlock.Lines.Add(line);

            return(BlockState.ContinueDiscard);
        }
        /// <summary>
        /// Continues a <see cref="ProxyTableBlock"/> if the current line is a column definitions line or a row line.
        /// </summary>
        /// <param name="blockProcessor">The <see cref="BlockProcessor"/> for the <see cref="ProxyTableBlock"/> to try continuing.</param>
        /// <param name="block">The <see cref="ProxyTableBlock"/> to try continuing.</param>
        /// <returns>
        /// <see cref="BlockState.Break"/> if the current line does not start with '|'. This closes the <see cref="ProxyTableBlock"/>.
        /// <see cref="BlockState.Break"/> if the current line starts with '|' but is not a column definitions line or row line. The <see cref="ProxyTableBlock"/> is replaced with a paragraph block.
        /// <see cref="BlockState.ContinueDiscard"/> if the <see cref="ProxyTableBlock"/> remains open.
        /// </returns>
        protected override BlockState TryContinueBlock(BlockProcessor blockProcessor, ProxyTableBlock block)
        {
            if (blockProcessor.CurrentChar != '|') // No longer in table
            {
                return(BlockState.Break);
            }

            // Parse line
            StringSlice             line       = blockProcessor.Line;
            List <Row>              rows       = block.Rows;
            bool                    undo       = true;
            int                     numColumns = block.NumColumns;
            List <ColumnDefinition> columnDefinitions;

            if (block.ColumnDefinitions == null &&
                (columnDefinitions = TryParseColumnDefinitionsLine(line, '|', numColumns)) != null)
            {
                undo = false;
                block.ColumnDefinitions = columnDefinitions;

                foreach (Row headerRow in rows)
                {
                    headerRow.IsHeaderRow = true;
                }
            }

            Row row;

            if (undo && (row = TryParseRowLine(blockProcessor, rows.Count, numColumns)) != null)
            {
                undo = false;
                rows.Add(row);
            }

            if (undo)
            {
                Undo(blockProcessor, block);

                return(BlockState.Break);
            }

            // Update span end
            block.UpdateSpanEnd(line.End);

            // Store current line in case the grid FlexiTableBlock is invalid
            block.Lines.Add(line);

            return(BlockState.ContinueDiscard);
        }
        /// <summary>
        /// Continues a <see cref="ProxyTableBlock"/> if the current line is a content line or a separator line.
        /// </summary>
        /// <param name="blockProcessor">The <see cref="BlockProcessor"/> processing the <see cref="ProxyTableBlock"/> to try continuing.</param>
        /// <param name="block">The <see cref="ProxyTableBlock"/> to try continuing.</param>
        /// <returns>
        /// <see cref="BlockState.Break"/> if the current line does not start with '|' or '+'. This closes the <see cref="ProxyTableBlock"/>.
        /// <see cref="BlockState.Break"/> if the current line starts with '|' or '+' but is not a content line or separator line. The <see cref="ProxyTableBlock"/> is replaced with a paragraph block.
        /// <see cref="BlockState.ContinueDiscard"/> if the <see cref="ProxyTableBlock"/> remains open.
        /// </returns>
        protected override BlockState TryContinueBlock(BlockProcessor blockProcessor, ProxyTableBlock block)
        {
            // Parse line
            bool undo;

            if (blockProcessor.CurrentChar == '|') // Content line
            {
                undo = !TryParseContentLine(blockProcessor, block);
            }
            else if (blockProcessor.CurrentChar == '+') // Row separator line
            {
                undo = !TryParseSeparatorLine(blockProcessor.Line, block);
            }
            else // No longer in table
            {
                return(BlockState.Break);
            }

            if (undo)
            {
                Undo(blockProcessor, block);
                return(BlockState.Break);
            }

            // Update span end
            block.UpdateSpanEnd(blockProcessor.Line.End);

            // Store current line in case the grid FlexiTableBlock is invalid
            block.Lines.Add(blockProcessor.Line);

            return(BlockState.ContinueDiscard);
        }
 // A content line is <content> one or more times followed by '|'.
 // <content> is '|' followed by a series of characters, where '|' is aligned with a '+' in the column definitions line.
 //
 // Example content lines: | content |
 internal virtual bool TryParseContentLine(BlockProcessor blockProcessor, ProxyTableBlock proxyTableBlock)
 {
     ref StringSlice         line = ref blockProcessor.Line;