Represents a list, with each list item proceeded by either a number or a bullet.
Inheritance: MarkdownBlock
Beispiel #1
0
        /// <summary>
        /// Parsing helper.
        /// </summary>
        /// <param name="list"></param>
        /// <returns> <c>true</c> if any of the list items were parsed using the block parser. </returns>
        private static bool ReplaceStringBuilders(ListBlock list)
        {
            bool usedBlockParser = false;

            foreach (var listItem in list.Items)
            {
                // Use the inline parser if there is one paragraph, use the block parser otherwise.
                var useBlockParser = listItem.Blocks.Count(block => block.Type == MarkdownBlockType.ListItemBuilder) > 1;

                // Recursively replace any child lists.
                foreach (var block in listItem.Blocks)
                {
                    if (block is ListBlock && ReplaceStringBuilders((ListBlock)block))
                    {
                        useBlockParser = true;
                    }
                }

                // Parse the text content of the list items.
                var newBlockList = new List <MarkdownBlock>();
                foreach (var block in listItem.Blocks)
                {
                    if (block is ListItemBuilder)
                    {
                        var blockText = ((ListItemBuilder)block).Builder.ToString();
                        if (blockText.TrimStart().StartsWith("```"))
                        {
                            useBlockParser = true;
                        }
                        if (useBlockParser)
                        {
                            // Parse the list item as a series of blocks.
                            int actualEnd;
                            newBlockList.AddRange(MarkdownDocument.Parse(blockText, 0, blockText.Length, quoteDepth: 0, actualEnd: out actualEnd));
                            usedBlockParser = true;
                        }
                        else
                        {
                            // Don't allow blocks.
                            var paragraph = new ParagraphBlock();
                            paragraph.Inlines = Common.ParseInlineChildren(blockText, 0, blockText.Length);
                            newBlockList.Add(paragraph);
                        }
                    }
                    else
                    {
                        newBlockList.Add(block);
                    }
                }
                listItem.Blocks = newBlockList;
            }
            return(usedBlockParser);
        }
        /// <summary>
        /// Parsing helper.
        /// </summary>
        /// <param name="list"></param>
        /// <returns> <c>true</c> if any of the list items were parsed using the block parser. </returns>
        private static bool ReplaceStringBuilders(ListBlock list)
        {
            bool usedBlockParser = false;
            foreach (var listItem in list.Items)
            {
                // Use the inline parser if there is one paragraph, use the block parser otherwise.
                var useBlockParser = listItem.Blocks.Count(block => block.Type == MarkdownBlockType.ListItemBuilder) > 1;

                // Recursively replace any child lists.
                foreach (var block in listItem.Blocks)
                    if (block is ListBlock && ReplaceStringBuilders((ListBlock)block))
                        useBlockParser = true;

                // Parse the text content of the list items.
                var newBlockList = new List<MarkdownBlock>();
                foreach (var block in listItem.Blocks)
                {
                    if (block is ListItemBuilder)
                    {
                        var blockText = ((ListItemBuilder)block).Builder.ToString();
                        if (useBlockParser)
                        {
                            // Parse the list item as a series of blocks.
                            int actualEnd;
                            newBlockList.AddRange(MarkdownDocument.Parse(blockText, 0, blockText.Length, quoteDepth: 0, actualEnd: out actualEnd));
                            usedBlockParser = true;
                        }
                        else
                        {
                            // Don't allow blocks.
                            var paragraph = new ParagraphBlock();
                            paragraph.Inlines = Common.ParseInlineChildren(blockText, 0, blockText.Length);
                            newBlockList.Add(paragraph);
                        }
                    }
                    else
                        newBlockList.Add(block);
                }
                listItem.Blocks = newBlockList;
            }
            return usedBlockParser;
        }
        /// <summary>
        /// Parses a list block.
        /// </summary>
        /// <param name="markdown"> The markdown text. </param>
        /// <param name="start"> The location of the first character in the block. </param>
        /// <param name="maxEnd"> The location to stop parsing. </param>
        /// <param name="quoteDepth"> The current nesting level for block quoting. </param>
        /// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param>
        /// <returns> A parsed list block, or <c>null</c> if this is not a list block. </returns>
        internal static ListBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd)
        {
            var russianDolls = new List<NestedListInfo>();
            int russianDollIndex = -1;
            bool previousLineWasBlank = false;
            ListItemBlock currentListItem = null;
            actualEnd = start;

            foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth))
            {
                // Is this line blank?
                if (lineInfo.IsLineBlank)
                {
                    // The line is blank, which means the next line which contains text may end the list (or it may not...).
                    previousLineWasBlank = true;
                }
                else
                {
                    // Does the line contain a list item?
                    ListItemPreamble listItemPreamble = null;
                    if (lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine < (russianDollIndex + 2) * 4)
                        listItemPreamble = ParseItemPreamble(markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine);
                    if (listItemPreamble != null)
                    {
                        // Yes, this line contains a list item.

                        // Determining the nesting level is done as follows:
                        // 1. If this is the first line, then the list is not nested.
                        // 2. If the number of spaces at the start of the line is equal to that of
                        //    an existing list, then the nesting level is the same as that list.
                        // 3. Otherwise, if the number of spaces is 0-4, then the nesting level
                        //    is one level deep.
                        // 4. Otherwise, if the number of spaces is 5-8, then the nesting level
                        //    is two levels deep (but no deeper than one level more than the
                        //    previous list item).
                        // 5. Etcetera.
                        ListBlock listToAddTo = null;
                        int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine;
                        russianDollIndex = russianDolls.FindIndex(rd => rd.SpaceCount == spaceCount);
                        if (russianDollIndex >= 0)
                        {
                            // Add the new list item to an existing list.
                            listToAddTo = russianDolls[russianDollIndex].List;

                            // Don't add new list items to items higher up in the list.
                            russianDolls.RemoveRange(russianDollIndex + 1, russianDolls.Count - (russianDollIndex + 1));
                        }
                        else
                        {
                            russianDollIndex = Math.Max(1, 1 + (spaceCount - 1) / 4);
                            if (russianDollIndex < russianDolls.Count)
                            {
                                // Add the new list item to an existing list.
                                listToAddTo = russianDolls[russianDollIndex].List;

                                // Don't add new list items to items higher up in the list.
                                russianDolls.RemoveRange(russianDollIndex + 1, russianDolls.Count - (russianDollIndex + 1));
                            }
                            else
                            {
                                // Create a new list.
                                listToAddTo = new ListBlock { Style = listItemPreamble.Style, Items = new List<ListItemBlock>() };
                                if (russianDolls.Count > 0)
                                    currentListItem.Blocks.Add(listToAddTo);
                                russianDollIndex = russianDolls.Count;
                                russianDolls.Add(new NestedListInfo { List = listToAddTo, SpaceCount = spaceCount });
                            }
                        }

                        // Add a new list item.
                        currentListItem = new ListItemBlock() { Blocks = new List<MarkdownBlock>() };
                        listToAddTo.Items.Add(currentListItem);

                        // Add the rest of the line to the builder.
                        AppendTextToListItem(currentListItem, markdown, listItemPreamble.ContentStartPos, lineInfo.EndOfLine);
                    }
                    else
                    {
                        // No, this line contains text.

                        // Is there even a list in progress?
                        if (currentListItem == null)
                        {
                            actualEnd = start;
                            return null;
                        }

                        // 0 spaces = end of the list.
                        // 1-4 spaces = first level.
                        // 5-8 spaces = second level, etc.
                        if (previousLineWasBlank)
                        {
                            // This is the start of a new paragraph.
                            int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine;
                            if (spaceCount == 0)
                                break;
                            russianDollIndex = Math.Min(russianDollIndex, (spaceCount - 1) / 4);
                            ListBlock listToAddTo = russianDolls[russianDollIndex].List;
                            currentListItem = listToAddTo.Items[listToAddTo.Items.Count - 1];
                            currentListItem.Blocks.Add(new ListItemBuilder());
                            AppendTextToListItem(currentListItem, markdown, Math.Min(lineInfo.FirstNonWhitespaceChar, lineInfo.StartOfLine + (russianDollIndex + 1) * 4), lineInfo.EndOfLine);
                        }
                        else
                        {
                            // Inline text.
                            AppendTextToListItem(currentListItem, markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine);
                        }
                    }

                    // The line was not blank.
                    previousLineWasBlank = false;
                }

                // Go to the next line.
                actualEnd = lineInfo.EndOfLine;
            }

            var result = russianDolls[0].List;
            ReplaceStringBuilders(result);
            return result;
        }
Beispiel #4
0
        /// <summary>
        /// Parses a list block.
        /// </summary>
        /// <param name="markdown"> The markdown text. </param>
        /// <param name="start"> The location of the first character in the block. </param>
        /// <param name="maxEnd"> The location to stop parsing. </param>
        /// <param name="quoteDepth"> The current nesting level for block quoting. </param>
        /// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param>
        /// <returns> A parsed list block, or <c>null</c> if this is not a list block. </returns>
        internal static ListBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd)
        {
            actualEnd = start;

            ListItemBlock currentListItem           = null;
            ListBlock     listToAddTo               = null;
            var           listStack                 = new Stack <ListBlock>();
            var           level                     = 0;
            var           lastLevel                 = -1;
            var           previousLineWasBlank      = false;
            var           previousLineWasListHeader = false;
            var           isCodeBlock               = false;
            var           lastStype                 = ListStyle.Bulleted;

            foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth))
            {
                // Is this line blank?
                if (lineInfo.IsLineBlank)
                {
                    // The line is blank, which means the next line
                    // which contains text may end the list (or it may not...).
                    previousLineWasBlank = true;
                }
                else
                {
                    // Does the line contain a list item?
                    ListItemPreamble listItemPreamble = null;
                    if (lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine < (level + 2) * 4)
                    {
                        listItemPreamble = ParseItemPreamble(markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine);
                    }

                    if (listItemPreamble != null)
                    {
                        previousLineWasListHeader = true;
                        // Yes, this line contains a list item.

                        // Determining the nesting level is done as follows:
                        // 1. If this is the first line, then the list is not nested.
                        // 2. If the number of spaces at the start of the line is equal to that of
                        //    an existing list, then the nesting level is the same as that list.
                        // 3. Otherwise, if the number of spaces is 0-4, then the nesting level
                        //    is one level deep.
                        // 4. Otherwise, if the number of spaces is 5-8, then the nesting level
                        //    is two levels deep (but no deeper than one level more than the
                        //    previous list item).
                        // 5. Etcetera.

                        var info = markdown.Substring(lineInfo.StartOfLine, lineInfo.EndOfLine - lineInfo.StartOfLine);
                        //Console.WriteLine(info);

                        // Stack
                        if (markdown.Substring(lineInfo.StartOfLine).TrimStart().StartsWith("- [gdb with lua"))
                        {
                            int jj = 0;
                        }
                        int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine;
                        level = GetLevel(markdown.Substring(lineInfo.StartOfLine, spaceCount));
                        if (level > lastLevel)
                        {
                            // Create a new list.

                            var newlistToAddTo = new ListBlock {
                                Style = listItemPreamble.Style,
                                Items = new List <ListItemBlock>()
                            };
                            lastStype = newlistToAddTo.Style;
                            listStack.Push(newlistToAddTo);
                            if (currentListItem != null)
                            {
                                currentListItem.Blocks.Add(newlistToAddTo);
                            }

                            listToAddTo = newlistToAddTo;
                        }
                        else if (level < lastLevel)
                        {
                            int popCount = lastLevel - level;
                            while (popCount > 0)
                            {
                                popCount--;
                                listStack.Pop();
                            }
                            listToAddTo = listStack.Peek();
                        }

                        // Add a new list item.
                        currentListItem = new ListItemBlock {
                            Blocks = new List <MarkdownBlock>()
                        };
                        listToAddTo.Items.Add(currentListItem);

                        // Add the rest of the line to the builder.
                        AppendTextToListItem(currentListItem, markdown, listItemPreamble.ContentStartPos, lineInfo.EndOfLine);

                        lastLevel = level;
                    }
                    else
                    {
                        // No, this line contains text.

                        // Is there even a list in progress?
                        if (currentListItem == null)
                        {
                            actualEnd = start;
                            return(null);
                        }

                        // -------------------------------- //
                        // 0 spaces   = end of the list.    //
                        // 1-4 spaces = first level.        //
                        // 5-8 spaces = second level, etc.  //
                        // -------------------------------- //
                        bool codeTag = false;
                        if ((previousLineWasBlank) && (!isCodeBlock))
                        {
                            // This is the start of a new paragraph.
                            int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine;
                            if (spaceCount == 0)
                            {
                                break;
                            }
                            level = GetLevel(markdown.Substring(lineInfo.StartOfLine, spaceCount));
                            int popCount = lastLevel - level;
                            while (popCount > 0)
                            {
                                popCount--;
                                listStack.Pop();
                            }
                            listToAddTo     = listStack.Peek();
                            currentListItem = listToAddTo.Items[listToAddTo.Items.Count - 1];
                            currentListItem.Blocks.Add(new ListItemBuilder());
                            codeTag = AppendTextToListItem(currentListItem, markdown, Math.Min(lineInfo.FirstNonWhitespaceChar, lineInfo.StartOfLine + (level + 1) * 4), lineInfo.EndOfLine);
                            if (codeTag)
                            {
                                isCodeBlock = !isCodeBlock;
                            }
                        }
                        else
                        {
                            // Inline text.
                            if (previousLineWasListHeader)
                            {
                                //currentListItem = new ListItemBlock {
                                //    Blocks = new List<MarkdownBlock>()
                                //};
                                //listToAddTo.Items.Add(currentListItem);
                                var newlistToAddTo = new ListBlock {
                                    Style = lastStype,
                                    Items = new List <ListItemBlock>()
                                };
                                listStack.Push(newlistToAddTo);
                                if (currentListItem != null)
                                {
                                    currentListItem.Blocks.Add(newlistToAddTo);
                                }

                                listToAddTo = newlistToAddTo;

                                // Add a new list item.
                                currentListItem = new ListItemBlock {
                                    Blocks = new List <MarkdownBlock>()
                                };
                                listToAddTo.Items.Add(currentListItem);

                                lastLevel = lastLevel + 1;
                            }
                            codeTag = AppendTextToListItem(currentListItem, markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine);
                        }
                        previousLineWasListHeader = false;
                        if (codeTag)
                        {
                            isCodeBlock = !isCodeBlock;
                        }
                    }

                    // The line was not blank.
                    previousLineWasBlank = false;
                }

                // Go to the next line.
                actualEnd = lineInfo.EndOfLine;
            }

            var result = listStack.Last();

            ReplaceStringBuilders(result);
            return(result);
        }
Beispiel #5
0
        /// <summary>
        /// Parses a list block.
        /// </summary>
        /// <param name="markdown"> The markdown text. </param>
        /// <param name="start"> The location of the first character in the block. </param>
        /// <param name="maxEnd"> The location to stop parsing. </param>
        /// <param name="quoteDepth"> The current nesting level for block quoting. </param>
        /// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param>
        /// <returns> A parsed list block, or <c>null</c> if this is not a list block. </returns>
        internal static ListBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd)
        {
            var           russianDolls         = new List <NestedListInfo>();
            int           russianDollIndex     = -1;
            bool          previousLineWasBlank = false;
            ListItemBlock currentListItem      = null;

            actualEnd = start;

            foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth))
            {
                // Is this line blank?
                if (lineInfo.IsLineBlank)
                {
                    // The line is blank, which means the next line which contains text may end the list (or it may not...).
                    previousLineWasBlank = true;
                }
                else
                {
                    // Does the line contain a list item?
                    ListItemPreamble listItemPreamble = null;
                    if (lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine < (russianDollIndex + 2) * 4)
                    {
                        listItemPreamble = ParseItemPreamble(markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine);
                    }
                    if (listItemPreamble != null)
                    {
                        // Yes, this line contains a list item.

                        // Determining the nesting level is done as follows:
                        // 1. If this is the first line, then the list is not nested.
                        // 2. If the number of spaces at the start of the line is equal to that of
                        //    an existing list, then the nesting level is the same as that list.
                        // 3. Otherwise, if the number of spaces is 0-4, then the nesting level
                        //    is one level deep.
                        // 4. Otherwise, if the number of spaces is 5-8, then the nesting level
                        //    is two levels deep (but no deeper than one level more than the
                        //    previous list item).
                        // 5. Etcetera.
                        ListBlock listToAddTo = null;
                        int       spaceCount  = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine;
                        russianDollIndex = russianDolls.FindIndex(rd => rd.SpaceCount == spaceCount);
                        if (russianDollIndex >= 0)
                        {
                            // Add the new list item to an existing list.
                            listToAddTo = russianDolls[russianDollIndex].List;
                        }
                        else
                        {
                            russianDollIndex = Math.Max(1, 1 + (spaceCount - 1) / 4);
                            if (russianDollIndex < russianDolls.Count)
                            {
                                // Add the new list item to an existing list.
                                listToAddTo = russianDolls[russianDollIndex].List;
                            }
                            else
                            {
                                // Create a new list.
                                listToAddTo = new ListBlock {
                                    Style = listItemPreamble.Style, Items = new List <ListItemBlock>()
                                };
                                if (russianDolls.Count > 0)
                                {
                                    currentListItem.Blocks.Add(listToAddTo);
                                }
                                russianDollIndex = russianDolls.Count;
                                russianDolls.Add(new NestedListInfo {
                                    List = listToAddTo, SpaceCount = spaceCount
                                });
                            }
                        }

                        // Add a new list item.
                        currentListItem = new ListItemBlock()
                        {
                            Blocks = new List <MarkdownBlock>()
                        };
                        listToAddTo.Items.Add(currentListItem);

                        // Add the rest of the line to the builder.
                        AppendTextToListItem(currentListItem, markdown, listItemPreamble.ContentStartPos, lineInfo.EndOfLine);
                    }
                    else
                    {
                        // No, this line contains text.

                        // Is there even a list in progress?
                        if (currentListItem == null)
                        {
                            actualEnd = start;
                            return(null);
                        }

                        // 0 spaces = end of the list.
                        // 1-4 spaces = first level.
                        // 5-8 spaces = second level, etc.
                        if (previousLineWasBlank)
                        {
                            // This is the start of a new paragraph.
                            int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine;
                            if (spaceCount == 0)
                            {
                                break;
                            }
                            russianDollIndex = Math.Min(russianDollIndex, (spaceCount - 1) / 4);
                            ListBlock listToAddTo = russianDolls[russianDollIndex].List;
                            currentListItem = listToAddTo.Items[listToAddTo.Items.Count - 1];
                            currentListItem.Blocks.Add(new ListItemBuilder());
                            AppendTextToListItem(currentListItem, markdown, Math.Min(lineInfo.FirstNonWhitespaceChar, lineInfo.StartOfLine + (russianDollIndex + 1) * 4), lineInfo.EndOfLine);
                        }
                        else
                        {
                            // Inline text.
                            AppendTextToListItem(currentListItem, markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine);
                        }
                    }

                    // The line was not blank.
                    previousLineWasBlank = false;
                }

                // Go to the next line.
                actualEnd = lineInfo.EndOfLine;
            }

            var result = russianDolls[0].List;

            ReplaceStringBuilders(result);
            return(result);
        }
        /// <summary>
        /// Renders a list element.
        /// </summary>
        /// <param name="element"></param>
        /// <param name="blockUIElementCollection"></param>
        /// <param name="context"></param>
        private void RenderListElement(ListBlock element, UIElementCollection blockUIElementCollection, RenderContext context)
        {
            // Create a grid with two columns.
            Grid grid = new Grid();
            grid.Margin = ListMargin;

            // The first column for the bullet (or number) and the second for the text.
            grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(ListGutterWidth) });
            grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });

            for (int rowIndex = 0; rowIndex < element.Items.Count; rowIndex ++)
            {
                var listItem = element.Items[rowIndex];

                // Add a row definition.
                grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

                // Add the bullet or number.
                var bullet = CreateTextBlock(context);
                bullet.Margin = ParagraphMargin;
                switch (element.Style)
                {
                    case ListStyle.Bulleted:
                        bullet.Text = "•";
                        break;
                    case ListStyle.Numbered:
                        bullet.Text = $"{rowIndex + 1}.";
                        break;
                }
                bullet.HorizontalAlignment = HorizontalAlignment.Right;
                bullet.Margin = new Thickness(0, 0, ListBulletSpacing, 0);
                Grid.SetRow(bullet, rowIndex);
                grid.Children.Add(bullet);

                // Add the list item content.
                var content = new StackPanel();
                RenderBlocks(listItem.Blocks, content.Children, context);
                Grid.SetColumn(content, 1);
                Grid.SetRow(content, rowIndex);
                grid.Children.Add(content);
            }

            blockUIElementCollection.Add(grid);
        }