/// <summary>
        /// Renders a horizontal rule element.
        /// </summary>
        /// <param name="element"></param>
        /// <param name="currentBlocks"></param>
        private void RenderHorizontalRule(HorizontalRuleBlock element, BlockCollection currentBlocks)
        {
            // This is going to be weird. To make this work we need to make a UI element
            // and fill it with text to make it stretch. If we don't fill it with text I can't
            // make it stretch the width of the box, so for now this is an "ok" hack.
            InlineUIContainer contianer = new InlineUIContainer();
            Grid grid = new Grid();

            grid.Height     = 2;
            grid.Background = new SolidColorBrush(Color.FromArgb(255, 153, 153, 153));

            // Add the expanding text block.
            TextBlock magicExpandingTextBlock = new TextBlock();

            magicExpandingTextBlock.Foreground = new SolidColorBrush(Color.FromArgb(255, 153, 153, 153));
            magicExpandingTextBlock.Text       = "This is Quinn writing magic text. You will never see this. Like a ghost! I love Marilyn Welniak! This needs to be really long! RRRRREEEEEAAAAALLLLYYYYY LLLOOOONNNGGGG. This is Quinn writing magic text. You will never see this. Like a ghost! I love Marilyn Welniak! This needs to be really long! RRRRREEEEEAAAAALLLLYYYYY LLLOOOONNNGGGG";
            grid.Children.Add(magicExpandingTextBlock);

            // Add the grid.
            contianer.Child = grid;

            // Make the new horizontal rule paragraph
            Paragraph horzPara = new Paragraph();

            horzPara.Margin = new Thickness(0, 12, 0, 12);
            horzPara.Inlines.Add(contianer);

            // Add it
            currentBlocks.Add(horzPara);
        }
Beispiel #2
0
        // Root,                           --- Done
        // Paragraph,                      --- Done
        // Header,// #(1-6)、===、---      --- Done
        // HorizontalRule,// ---、***
        // List, // +、-、*、number.       --- Done
        // Code,  // Four spaces or \t
        // Quote, // >
        // ListItemBuilder,
        // Table, // 表 | 1 | 2 |
        // LinkReference, // [name](url)

        internal static MarkdownBlock ParseBlock(string markdownText, int start, int end, out int actualEnd)
        {
            actualEnd = start;
            MarkdownBlock block        = null;
            char          nonSpaceChar = GetNonSpaceChar(markdownText, start, end, out int nonSpacePos);

            if (nonSpaceChar == '#' && nonSpacePos == start)
            {
                block = HeaderBlock.Parse(markdownText, start, end, out actualEnd);
            }

            if (block == null && (nonSpaceChar == '*' || nonSpaceChar == '-' || nonSpaceChar == '_'))
            {
                block = HorizontalRuleBlock.Parse(markdownText, start, end, out actualEnd);
            }

            if (block == null && (nonSpaceChar == '*' || nonSpaceChar == '+' || nonSpaceChar == '-' || (nonSpaceChar >= '0' && nonSpaceChar <= '9')))
            {
                block = ListElement.Parse(markdownText, start, end, out actualEnd);
            }

            if (block == null)
            {
                block = ParagraphBlock.Parse(markdownText, start, end, out actualEnd);
            }

            return(block);
        }
Beispiel #3
0
        /// <summary>
        /// Renders a horizontal rule element.
        /// </summary>
        /// <param name="element"></param>
        /// <param name="blockUIElementCollection"></param>
        /// <param name="context"></param>
        private void RenderHorizontalRule(HorizontalRuleBlock element, UIElementCollection blockUIElementCollection, RenderContext context)
        {
            var rectangle = new Rectangle();

            rectangle.HorizontalAlignment = HorizontalAlignment.Stretch;
            rectangle.Height = HorizontalRuleThickness;
            rectangle.Fill   = HorizontalRuleBrush ?? context.Foreground;
            rectangle.Margin = HorizontalRuleMargin;

            blockUIElementCollection.Add(rectangle);
        }
Beispiel #4
0
        /// <summary>
        /// Called by all elements to find the next element to parse out of the markdown given a startingPos and an ending Pos
        /// </summary>
        /// <param name="markdown"></param>
        /// <param name="startingPos"></param>
        /// <param name="endingPost"></param>
        /// <returns></returns>
        public static MarkdownBlock FindNextBlock(ref string markdown, ref int startingPos, int endingPos)
        {
            // We need to look at the start of this current block and figure out what type it is.
            // Find the next char that isn't a \n, \r, or ' ', keep track of white space
            int spaceCount = 0;

            while (markdown.Length > startingPos && endingPos > startingPos && (markdown[startingPos] == '\r' || markdown[startingPos] == '\n' || Char.IsWhiteSpace(markdown[startingPos])))
            {
                // If we find a space count it for the indent rules. If not reset the count.
                spaceCount = markdown[startingPos] == ' ' ? spaceCount + 1 : 0;
                startingPos++;
            }

            if (CodeBlock.CanHandleBlock(ref markdown, startingPos, endingPos, spaceCount))
            {
                return(new CodeBlock());
            }
            if (QuoteBlock.CanHandleBlock(ref markdown, startingPos, endingPos))
            {
                return(new QuoteBlock());
            }
            if (HeaderBlock.CanHandleBlock(ref markdown, startingPos, endingPos))
            {
                return(new HeaderBlock());
            }
            if (ListElementBlock.CanHandleBlock(ref markdown, startingPos, endingPos))
            {
                return(new ListElementBlock());
            }
            if (HorizontalRuleBlock.CanHandleBlock(ref markdown, startingPos, endingPos))
            {
                return(new HorizontalRuleBlock());
            }
            if (LineBreakBlock.CanHandleBlock(ref markdown, startingPos, endingPos))
            {
                return(new LineBreakBlock());
            }

            // If we can't match any of these just make a new paragraph.
            return(new ParagraphBlock());
        }
Beispiel #5
0
        /// <summary>
        /// Parses a markdown document.
        /// </summary>
        /// <param name="markdown"> The markdown text. </param>
        /// <param name="start"> The position to start parsing. </param>
        /// <param name="end"> The position to stop parsing. </param>
        /// <param name="quoteDepth"> The current nesting level for block quoting. </param>
        /// <param name="actualEnd"> Set to the position at which parsing ended.  This can be
        /// different from <paramref name="end"/> when the parser is being called recursively.
        /// </param>
        /// <returns> A list of parsed blocks. </returns>
        internal static List <MarkdownBlock> Parse(string markdown, int start, int end, int quoteDepth, out int actualEnd)
        {
            // We need to parse out the list of blocks.
            // Some blocks need to start on a new paragraph (code, lists and tables) while other
            // blocks can start on any line (headers, horizontal rules and quotes).
            // Text that is outside of any other block becomes a paragraph.
            var  blocks                 = new List <MarkdownBlock>();
            int  startOfLine            = start;
            bool lineStartsNewParagraph = true;
            var  paragraphText          = new StringBuilder();

            // These are needed to parse underline-style header blocks.
            int previousRealtStartOfLine = start;
            int previousStartOfLine      = start;
            int previousEndOfLine        = start;

            // Go line by line.
            while (startOfLine < end)
            {
                // Find the first non-whitespace character.
                int  nonSpacePos             = startOfLine;
                char nonSpaceChar            = '\0';
                int  realStartOfLine         = startOfLine; // i.e. including quotes.
                int  expectedQuotesRemaining = quoteDepth;
                while (true)
                {
                    while (nonSpacePos < end)
                    {
                        char c = markdown[nonSpacePos];
                        if (c == '\r' || c == '\n')
                        {
                            // The line is either entirely whitespace, or is empty.
                            break;
                        }

                        if (c != ' ' && c != '\t')
                        {
                            // The line has content.
                            nonSpaceChar = c;
                            break;
                        }

                        nonSpacePos++;
                    }

                    // When parsing blocks in a blockquote context, we need to count the number of
                    // quote characters ('>').  If there are less than expected AND this is the
                    // start of a new paragraph, then stop parsing.
                    if (expectedQuotesRemaining == 0)
                    {
                        break;
                    }

                    if (nonSpaceChar == '>')
                    {
                        // Expected block quote characters should be ignored.
                        expectedQuotesRemaining--;
                        nonSpacePos++;
                        nonSpaceChar = '\0';
                        startOfLine  = nonSpacePos;

                        // Ignore the first space after the quote character, if there is one.
                        if (startOfLine < end && markdown[startOfLine] == ' ')
                        {
                            startOfLine++;
                            nonSpacePos++;
                        }
                    }
                    else
                    {
                        int    lastIndentation = 0;
                        string lastline        = null;

                        // Determines how many Quote levels were in the last line.
                        if (realStartOfLine > 0)
                        {
                            lastline        = markdown.Substring(previousRealtStartOfLine, previousEndOfLine - previousRealtStartOfLine);
                            lastIndentation = lastline.Count(c => c == '>');
                        }

                        var currentEndOfLine   = Common.FindNextSingleNewLine(markdown, nonSpacePos, end, out _);
                        var currentline        = markdown.Substring(realStartOfLine, currentEndOfLine - realStartOfLine);
                        var currentIndentation = currentline.Count(c => c == '>');
                        var firstChar          = markdown[realStartOfLine];

                        // This is a quote that doesn't start with a Quote marker, but carries on from the last line.
                        if (lastIndentation == 1)
                        {
                            if (nonSpaceChar != '\0' && firstChar != '>')
                            {
                                break;
                            }
                        }

                        // Collapse down a level of quotes if the current indentation is greater than the last indentation.
                        // Only if the last indentation is greater than 1, and the current indentation is greater than 0
                        if (lastIndentation > 1 && currentIndentation > 0 && currentIndentation < lastIndentation)
                        {
                            break;
                        }

                        // This must be the end of the blockquote.  End the current paragraph, if any.
                        actualEnd = realStartOfLine;

                        if (paragraphText.Length > 0)
                        {
                            blocks.Add(ParagraphBlock.Parse(paragraphText.ToString()));
                        }

                        return(blocks);
                    }
                }

                // Find the end of the current line.
                int startOfNextLine;
                int endOfLine = Common.FindNextSingleNewLine(markdown, nonSpacePos, end, out startOfNextLine);

                if (nonSpaceChar == '\0')
                {
                    // The line is empty or nothing but whitespace.
                    lineStartsNewParagraph = true;

                    // End the current paragraph.
                    if (paragraphText.Length > 0)
                    {
                        blocks.Add(ParagraphBlock.Parse(paragraphText.ToString()));
                        paragraphText.Clear();
                    }
                }
                else
                {
                    // This is a header if the line starts with a hash character,
                    // or if the line starts with '-' or a '=' character and has no other characters.
                    // Or a quote if the line starts with a greater than character (optionally preceded by whitespace).
                    // Or a horizontal rule if the line contains nothing but 3 '*', '-' or '_' characters (with optional whitespace).
                    MarkdownBlock newBlockElement = null;
                    if (nonSpaceChar == '#' && nonSpacePos == startOfLine)
                    {
                        // Hash-prefixed header.
                        newBlockElement = HeaderBlock.ParseHashPrefixedHeader(markdown, startOfLine, endOfLine);
                    }
                    else if ((nonSpaceChar == '-' || nonSpaceChar == '=') && nonSpacePos == startOfLine && paragraphText.Length > 0)
                    {
                        // Underline style header. These are weird because you don't know you've
                        // got one until you've gone past it.
                        // Note: we intentionally deviate from reddit here in that we only
                        // recognize this type of header if the previous line is part of a
                        // paragraph.  For example if you have this, the header at the bottom is
                        // ignored:
                        //   a|b
                        //   -|-
                        //   1|2
                        //   ===
                        newBlockElement = HeaderBlock.ParseUnderlineStyleHeader(markdown, previousStartOfLine, previousEndOfLine, startOfLine, endOfLine);

                        if (newBlockElement != null)
                        {
                            // We're going to have to remove the header text from the pending
                            // paragraph by prematurely ending the current paragraph.
                            // We already made sure that there is a paragraph in progress.
                            paragraphText.Length = paragraphText.Length - (previousEndOfLine - previousStartOfLine);
                        }
                    }

                    // These characters overlap with the underline-style header - this check should go after that one.
                    if (newBlockElement == null && (nonSpaceChar == '*' || nonSpaceChar == '-' || nonSpaceChar == '_'))
                    {
                        newBlockElement = HorizontalRuleBlock.Parse(markdown, startOfLine, endOfLine);
                    }

                    if (newBlockElement == null && lineStartsNewParagraph)
                    {
                        // Some block elements must start on a new paragraph (tables, lists and code).
                        int endOfBlock = startOfNextLine;
                        if (nonSpaceChar == '*' || nonSpaceChar == '+' || nonSpaceChar == '-' || (nonSpaceChar >= '0' && nonSpaceChar <= '9'))
                        {
                            newBlockElement = ListBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out endOfBlock);
                        }

                        if (newBlockElement == null && (nonSpacePos > startOfLine || nonSpaceChar == '`'))
                        {
                            newBlockElement = CodeBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out endOfBlock);
                        }

                        if (newBlockElement == null)
                        {
                            newBlockElement = TableBlock.Parse(markdown, realStartOfLine, endOfLine, end, quoteDepth, out endOfBlock);
                        }

                        if (newBlockElement != null)
                        {
                            startOfNextLine = endOfBlock;
                        }
                    }

                    // This check needs to go after the code block check.
                    if (newBlockElement == null && nonSpaceChar == '>')
                    {
                        newBlockElement = QuoteBlock.Parse(markdown, realStartOfLine, end, quoteDepth, out startOfNextLine);
                    }

                    // This check needs to go after the code block check.
                    if (newBlockElement == null && nonSpaceChar == '[')
                    {
                        newBlockElement = LinkReferenceBlock.Parse(markdown, startOfLine, endOfLine);
                    }

                    // Block elements start new paragraphs.
                    lineStartsNewParagraph = newBlockElement != null;

                    if (newBlockElement == null)
                    {
                        // The line contains paragraph text.
                        if (paragraphText.Length > 0)
                        {
                            // If the previous two characters were both spaces, then append a line break.
                            if (paragraphText.Length > 2 && paragraphText[paragraphText.Length - 1] == ' ' && paragraphText[paragraphText.Length - 2] == ' ')
                            {
                                // Replace the two spaces with a line break.
                                paragraphText[paragraphText.Length - 2] = '\r';
                                paragraphText[paragraphText.Length - 1] = '\n';
                            }
                            else
                            {
                                paragraphText.Append(" ");
                            }
                        }

                        // Add the last paragraph if we are at the end of the input text.
                        if (startOfNextLine >= end)
                        {
                            if (paragraphText.Length == 0)
                            {
                                // Optimize for single line paragraphs.
                                blocks.Add(ParagraphBlock.Parse(markdown.Substring(startOfLine, endOfLine - startOfLine)));
                            }
                            else
                            {
                                // Slow path.
                                paragraphText.Append(markdown.Substring(startOfLine, endOfLine - startOfLine));
                                blocks.Add(ParagraphBlock.Parse(paragraphText.ToString()));
                            }
                        }
                        else
                        {
                            paragraphText.Append(markdown.Substring(startOfLine, endOfLine - startOfLine));
                        }
                    }
                    else
                    {
                        // The line contained a block.  End the current paragraph, if any.
                        if (paragraphText.Length > 0)
                        {
                            blocks.Add(ParagraphBlock.Parse(paragraphText.ToString()));
                            paragraphText.Clear();
                        }

                        blocks.Add(newBlockElement);
                    }
                }

                // Repeat.
                previousRealtStartOfLine = realStartOfLine;
                previousStartOfLine      = startOfLine;
                previousEndOfLine        = endOfLine;
                startOfLine = startOfNextLine;
            }

            actualEnd = startOfLine;
            return(blocks);
        }