Example #1
0
 internal void FreeBlock(Block b)
 {
     m_SpareBlocks.Push(b);
 }
Example #2
0
 bool IsSectionHeader(Block b)
 {
     return b.blockType >= BlockType.h1 && b.blockType <= BlockType.h3;
 }
Example #3
0
 public Block CopyFrom(Block other)
 {
     blockType = other.blockType;
     buf = other.buf;
     contentStart = other.contentStart;
     contentLen = other.contentLen;
     lineStart = other.lineStart;
     lineLen = other.lineLen;
     return this;
 }
Example #4
0
 internal void AddFootnote(Block footnote)
 {
     m_Footnotes[(string)footnote.data] = footnote;
 }
        // Scan from the current position to the end of the html section
        internal bool ScanHtml(Block b)
        {
            // Remember start of html
            int posStartPiece = this.position;

            // Parse a HTML tag
            HtmlTag openingTag = HtmlTag.Parse(this);
            if (openingTag == null)
                return false;

            // Closing tag?
            if (openingTag.closing)
                return false;

            // Safe mode?
            bool bHasUnsafeContent = false;
            if (m_markdown.SafeMode && !openingTag.IsSafe())
                bHasUnsafeContent = true;

            HtmlTagFlags flags = openingTag.Flags;

            // Is it a block level tag?
            if ((flags & HtmlTagFlags.Block) == 0)
                return false;

            // Closed tag, hr or comment?
            if ((flags & HtmlTagFlags.NoClosing) != 0 || openingTag.closed)
            {
                SkipLinespace();
                SkipEol();

                b.contentEnd = position;
                b.blockType = bHasUnsafeContent ? BlockType.unsafe_html : BlockType.html;
                return true;
            }

            // Can it also be an inline tag?
            if ((flags & HtmlTagFlags.Inline) != 0)
            {
                // Yes, opening tag must be on a line by itself
                SkipLinespace();
                if (!eol)
                    return false;
            }

            // Head block extraction?
            bool bHeadBlock = m_markdown.ExtractHeadBlocks && string.Compare(openingTag.name, "head", true) == 0;
            int headStart = this.position;

            // Work out the markdown mode for this element
            if (!bHeadBlock && m_markdown.ExtraMode)
            {
                MarkdownInHtmlMode MarkdownMode = this.GetMarkdownMode(openingTag);
                if (MarkdownMode != MarkdownInHtmlMode.NA)
                {
                    return this.ProcessMarkdownEnabledHtml(b, openingTag, MarkdownMode);
                }
            }

            List<Block> childBlocks = null;

            // Now capture everything up to the closing tag and put it all in a single HTML block
            int depth = 1;

            while (!eof)
            {
                // Find next angle bracket
                if (!Find('<'))
                    break;

                // Save position of current tag
                int posStartCurrentTag = position;

                // Is it a html tag?
                HtmlTag tag = HtmlTag.Parse(this);
                if (tag == null)
                {
                    // Nope, skip it
                    SkipForward(1);
                    continue;
                }

                // Safe mode checks
                if (m_markdown.SafeMode && !tag.IsSafe())
                    bHasUnsafeContent = true;

                // Ignore self closing tags
                if (tag.closed)
                    continue;

                // Markdown enabled content?
                if (!bHeadBlock && !tag.closing && m_markdown.ExtraMode && !bHasUnsafeContent)
                {
                    MarkdownInHtmlMode MarkdownMode = this.GetMarkdownMode(tag);
                    if (MarkdownMode != MarkdownInHtmlMode.NA)
                    {
                        Block markdownBlock = this.CreateBlock();
                        if (this.ProcessMarkdownEnabledHtml(markdownBlock, tag, MarkdownMode))
                        {
                            if (childBlocks==null)
                            {
                                childBlocks = new List<Block>();
                            }

                            // Create a block for everything before the markdown tag
                            if (posStartCurrentTag > posStartPiece)
                            {
                                Block htmlBlock = this.CreateBlock();
                                htmlBlock.buf = input;
                                htmlBlock.blockType = BlockType.html;
                                htmlBlock.contentStart = posStartPiece;
                                htmlBlock.contentLen = posStartCurrentTag - posStartPiece;

                                childBlocks.Add(htmlBlock);
                            }

                            // Add the markdown enabled child block
                            childBlocks.Add(markdownBlock);

                            // Remember start of the next piece
                            posStartPiece = position;

                            continue;
                        }
                        else
                        {
                            this.FreeBlock(markdownBlock);
                        }
                    }
                }

                // Same tag?
                if (tag.name == openingTag.name)
                {
                    if (tag.closing)
                    {
                        depth--;
                        if (depth == 0)
                        {
                            // End of tag?
                            SkipLinespace();
                            SkipEol();

                            // If anything unsafe detected, just encode the whole block
                            if (bHasUnsafeContent)
                            {
                                b.blockType = BlockType.unsafe_html;
                                b.contentEnd = position;
                                return true;
                            }

                            // Did we create any child blocks
                            if (childBlocks != null)
                            {
                                // Create a block for the remainder
                                if (position > posStartPiece)
                                {
                                    Block htmlBlock = this.CreateBlock();
                                    htmlBlock.buf = input;
                                    htmlBlock.blockType = BlockType.html;
                                    htmlBlock.contentStart = posStartPiece;
                                    htmlBlock.contentLen = position - posStartPiece;

                                    childBlocks.Add(htmlBlock);
                                }

                                // Return a composite block
                                b.blockType = BlockType.Composite;
                                b.contentEnd = position;
                                b.children = childBlocks;
                                return true;
                            }

                            // Extract the head block content
                            if (bHeadBlock)
                            {
                                var content = this.Substring(headStart, posStartCurrentTag - headStart);
                                m_markdown.HeadBlockContent = (m_markdown.HeadBlockContent ?? "") + content.Trim() + "\n";
                                b.blockType = BlockType.html;
                                b.contentStart = position;
                                b.contentEnd = position;
                                b.lineStart = position;
                                return true;
                            }

                            // Straight html block
                            b.blockType = BlockType.html;
                            b.contentEnd = position;
                            return true;
                        }
                    }
                    else
                    {
                        depth++;
                    }
                }
            }

            // Rewind to just after the tag
            return false;
        }
        internal void CollapseLines(List<Block> blocks, List<Block> lines)
        {
            // Remove trailing blank lines
            while (lines.Count>0 && lines.Last().blockType == BlockType.Blank)
            {
                FreeBlock(lines.Pop());
            }

            // Quit if empty
            if (lines.Count == 0)
            {
                return;
            }

            // What sort of block?
            switch (lines[0].blockType)
            {
                case BlockType.p:
                {
                    // Collapse all lines into a single paragraph
                    var para = CreateBlock();
                    para.blockType = BlockType.p;
                    para.buf = lines[0].buf;
                    para.contentStart = lines[0].contentStart;
                    para.contentEnd = lines.Last().contentEnd;
                    blocks.Add(para);
                    FreeBlocks(lines);
                    break;
                }

                case BlockType.quote:
                {
                    // Create a new quote block
                    var quote = new Block(BlockType.quote);
                    quote.children = new BlockProcessor(m_markdown, m_bMarkdownInHtml, BlockType.quote).Process(RenderLines(lines));
                    FreeBlocks(lines);
                    blocks.Add(quote);
                    break;
                }

                case BlockType.ol_li:
                case BlockType.ul_li:
                    blocks.Add(BuildList(lines));
                    break;

                case BlockType.dd:
                    if (blocks.Count > 0)
                    {
                        var prev=blocks[blocks.Count-1];
                        switch (prev.blockType)
                        {
                            case BlockType.p:
                                prev.blockType = BlockType.dt;
                                break;

                            case BlockType.dd:
                                break;

                            default:
                                var wrapper = CreateBlock();
                                wrapper.blockType = BlockType.dt;
                                wrapper.children = new List<Block>();
                                wrapper.children.Add(prev);
                                blocks.Pop();
                                blocks.Add(wrapper);
                                break;
                        }

                    }
                    blocks.Add(BuildDefinition(lines));
                    break;

                case BlockType.footnote:
                    m_markdown.AddFootnote(BuildFootnote(lines));
                    break;

                case BlockType.indent:
                {
                    var codeblock = new Block(BlockType.codeblock);
                    /*
                    if (m_markdown.FormatCodeBlockAttributes != null)
                    {
                        // Does the line first line look like a syntax specifier
                        var firstline = lines[0].Content;
                        if (firstline.StartsWith("{{") && firstline.EndsWith("}}"))
                        {
                            codeblock.data = firstline.Substring(2, firstline.Length - 4);
                            lines.RemoveAt(0);
                        }
                    }
                     */
                    codeblock.children = new List<Block>();
                    codeblock.children.AddRange(lines);
                    blocks.Add(codeblock);
                    lines.Clear();
                    break;
                }
            }
        }
        internal bool ProcessMarkdownEnabledHtml(Block b, HtmlTag openingTag, MarkdownInHtmlMode mode)
        {
            // Current position is just after the opening tag

            // Scan until we find matching closing tag
            int inner_pos = position;
            int depth = 1;
            bool bHasUnsafeContent = false;
            while (!eof)
            {
                // Find next angle bracket
                if (!Find('<'))
                    break;

                // Is it a html tag?
                int tagpos = position;
                HtmlTag tag = HtmlTag.Parse(this);
                if (tag == null)
                {
                    // Nope, skip it
                    SkipForward(1);
                    continue;
                }

                // In markdown off mode, we need to check for unsafe tags
                if (m_markdown.SafeMode && mode == MarkdownInHtmlMode.Off && !bHasUnsafeContent)
                {
                    if (!tag.IsSafe())
                        bHasUnsafeContent = true;
                }

                // Ignore self closing tags
                if (tag.closed)
                    continue;

                // Same tag?
                if (tag.name == openingTag.name)
                {
                    if (tag.closing)
                    {
                        depth--;
                        if (depth == 0)
                        {
                            // End of tag?
                            SkipLinespace();
                            SkipEol();

                            b.blockType = BlockType.HtmlTag;
                            b.data = openingTag;
                            b.contentEnd = position;

                            switch (mode)
                            {
                                case MarkdownInHtmlMode.Span:
                                {
                                    Block span = this.CreateBlock();
                                    span.buf = input;
                                    span.blockType = BlockType.span;
                                    span.contentStart = inner_pos;
                                    span.contentLen = tagpos - inner_pos;

                                    b.children = new List<Block>();
                                    b.children.Add(span);
                                    break;
                                }

                                case MarkdownInHtmlMode.Block:
                                case MarkdownInHtmlMode.Deep:
                                {
                                    // Scan the internal content
                                    var bp = new BlockProcessor(m_markdown, mode == MarkdownInHtmlMode.Deep);
                                    b.children = bp.ScanLines(input, inner_pos, tagpos - inner_pos);
                                    break;
                                }

                                case MarkdownInHtmlMode.Off:
                                {
                                    if (bHasUnsafeContent)
                                    {
                                        b.blockType = BlockType.unsafe_html;
                                        b.contentEnd = position;
                                    }
                                    else
                                    {
                                        Block span = this.CreateBlock();
                                        span.buf = input;
                                        span.blockType = BlockType.html;
                                        span.contentStart = inner_pos;
                                        span.contentLen = tagpos - inner_pos;

                                        b.children = new List<Block>();
                                        b.children.Add(span);
                                    }
                                    break;
                                }
                            }

                            return true;
                        }
                    }
                    else
                    {
                        depth++;
                    }
                }
            }

            // Missing closing tag(s).
            return false;
        }
 internal void FreeBlock(Block b)
 {
     m_markdown.FreeBlock(b);
 }
        bool ProcessFencedCodeBlock(Block b)
        {
            // Extract the fence
            Mark();
            while (current == '~')
                SkipForward(1);
            string strFence = Extract();

            // Must be at least 3 long
            if (strFence.Length < 3)
                return false;

            // Rest of line must be blank
            SkipLinespace();
            if (!eol)
                return false;

            // Skip the eol and remember start of code
            SkipEol();
            int startCode = position;

            // Find the end fence
            if (!Find(strFence))
                return false;

            // Character before must be a eol char
            if (!IsLineEnd(CharAtOffset(-1)))
                return false;

            int endCode = position;

            // Skip the fence
            SkipForward(strFence.Length);

            // Whitespace allowed at end
            SkipLinespace();
            if (!eol)
                return false;

            // Create the code block
            b.blockType = BlockType.codeblock;
            b.children = new List<Block>();

            // Remove the trailing line end
            if (input[endCode - 1] == '\r' && input[endCode - 2] == '\n')
                endCode -= 2;
            else if (input[endCode - 1] == '\n' && input[endCode - 2] == '\r')
                endCode -= 2;
            else
                endCode--;

            // Create the child block with the entire content
            var child = CreateBlock();
            child.blockType = BlockType.indent;
            child.buf = input;
            child.contentStart = startCode;
            child.contentEnd = endCode;
            b.children.Add(child);

            return true;
        }
        BlockType EvaluateLine(Block b)
        {
            // Empty line?
            if (eol)
                return BlockType.Blank;

            // Save start of line position
            int line_start= position;

            // ## Heading ##
            char ch=current;
            if (ch == '#')
            {
                // Work out heading level
                int level = 1;
                SkipForward(1);
                while (current == '#')
                {
                    level++;
                    SkipForward(1);
                }

                // Limit of 6
                if (level > 6)
                    level = 6;

                // Skip any whitespace
                SkipLinespace();

                // Save start position
                b.contentStart = position;

                // Jump to end
                SkipToEol();

                // In extra mode, check for a trailing HTML ID
                if (m_markdown.ExtraMode && !m_markdown.SafeMode)
                {
                    int end=position;
                    string strID = Utils.StripHtmlID(input, b.contentStart, ref end);
                    if (strID!=null)
                    {
                        b.data = strID;
                        position = end;
                    }
                }

                // Rewind over trailing hashes
                while (position>b.contentStart && CharAtOffset(-1) == '#')
                {
                    SkipForward(-1);
                }

                // Rewind over trailing spaces
                while (position>b.contentStart && char.IsWhiteSpace(CharAtOffset(-1)))
                {
                    SkipForward(-1);
                }

                // Create the heading block
                b.contentEnd = position;

                SkipToEol();
                return BlockType.h1 + (level - 1);
            }

            // Check for entire line as - or = for setext h1 and h2
            if (ch=='-' || ch=='=')
            {
                // Skip all matching characters
                char chType = ch;
                while (current==chType)
                {
                    SkipForward(1);
                }

                // Trailing whitespace allowed
                SkipLinespace();

                // If not at eol, must have found something other than setext header
                if (eol)
                {
                    return chType == '=' ? BlockType.post_h1 : BlockType.post_h2;
                }

                position = line_start;
            }

            // MarkdownExtra Table row indicator?
            if (m_markdown.ExtraMode)
            {
                TableSpec spec = TableSpec.Parse(this);
                if (spec!=null)
                {
                    b.data = spec;
                    return BlockType.table_spec;
                }

                position = line_start;
            }

            // Fenced code blocks?
            if (m_markdown.ExtraMode && ch == '~')
            {
                if (ProcessFencedCodeBlock(b))
                    return b.blockType;

                // Rewind
                position = line_start;
            }

            // Scan the leading whitespace, remembering how many spaces and where the first tab is
            int tabPos = -1;
            int leadingSpaces = 0;
            while (!eol)
            {
                if (current == ' ')
                {
                    if (tabPos < 0)
                        leadingSpaces++;
                }
                else if (current == '\t')
                {
                    if (tabPos < 0)
                        tabPos = position;
                }
                else
                {
                    // Something else, get out
                    break;
                }
                SkipForward(1);
            }

            // Blank line?
            if (eol)
            {
                b.contentEnd = b.contentStart;
                return BlockType.Blank;
            }

            // 4 leading spaces?
            if (leadingSpaces >= 4)
            {
                b.contentStart = line_start + 4;
                return BlockType.indent;
            }

            // Tab in the first 4 characters?
            if (tabPos >= 0 && tabPos - line_start<4)
            {
                b.contentStart = tabPos + 1;
                return BlockType.indent;
            }

            // Treat start of line as after leading whitespace
            b.contentStart = position;

            // Get the next character
            ch = current;

            // Html block?
            if (ch == '<')
            {
                // Scan html block
                if (ScanHtml(b))
                    return b.blockType;

                // Rewind
                position = b.contentStart;
            }

            // Block quotes start with '>' and have one space or one tab following
            if (ch == '>')
            {
                // Block quote followed by space
                if (IsLineSpace(CharAtOffset(1)))
                {
                    // Skip it and create quote block
                    SkipForward(2);
                    b.contentStart = position;
                    return BlockType.quote;
                }

                SkipForward(1);
                b.contentStart = position;
                return BlockType.quote;
            }

            // Horizontal rule - a line consisting of 3 or more '-', '_' or '*' with optional spaces and nothing else
            if (ch == '-' || ch == '_' || ch == '*')
            {
                int count = 0;
                while (!eol)
                {
                    char chType = current;
                    if (current == ch)
                    {
                        count++;
                        SkipForward(1);
                        continue;
                    }

                    if (IsLineSpace(current))
                    {
                        SkipForward(1);
                        continue;
                    }

                    break;
                }

                if (eol && count >= 3)
                {
                    if (m_markdown.UserBreaks)
                        return BlockType.user_break;
                    else
                        return BlockType.hr;
                }

                // Rewind
                position = b.contentStart;
            }

            // Abbreviation definition?
            if (m_markdown.ExtraMode && ch == '*' && CharAtOffset(1) == '[')
            {
                SkipForward(2);
                SkipLinespace();

                Mark();
                while (!eol && current != ']')
                {
                    SkipForward(1);
                }

                var abbr = Extract().Trim();
                if (current == ']' && CharAtOffset(1) == ':' && !string.IsNullOrEmpty(abbr))
                {
                    SkipForward(2);
                    SkipLinespace();

                    Mark();

                    SkipToEol();

                    var title = Extract();

                    m_markdown.AddAbbreviation(abbr, title);

                    return BlockType.Blank;
                }

                position = b.contentStart;
            }

            // Unordered list
            if ((ch == '*' || ch == '+' || ch == '-') && IsLineSpace(CharAtOffset(1)))
            {
                // Skip it
                SkipForward(1);
                SkipLinespace();
                b.contentStart = position;
                return BlockType.ul_li;
            }

            // Definition
            if (ch == ':' && m_markdown.ExtraMode && IsLineSpace(CharAtOffset(1)))
            {
                SkipForward(1);
                SkipLinespace();
                b.contentStart = position;
                return BlockType.dd;
            }

            // Ordered list
            if (char.IsDigit(ch))
            {
                // Ordered list?  A line starting with one or more digits, followed by a '.' and a space or tab

                // Skip all digits
                SkipForward(1);
                while (char.IsDigit(current))
                    SkipForward(1);

                if (SkipChar('.') && SkipLinespace())
                {
                    b.contentStart = position;
                    return BlockType.ol_li;
                }

                position=b.contentStart;
            }

            // Reference link definition?
            if (ch == '[')
            {
                // Footnote definition?
                if (m_markdown.ExtraMode && CharAtOffset(1) == '^')
                {
                    var savepos = position;

                    SkipForward(2);

                    string id;
                    if (SkipFootnoteID(out id) && SkipChar(']') && SkipChar(':'))
                    {
                        SkipLinespace();
                        b.contentStart = position;
                        b.data = id;
                        return BlockType.footnote;
                    }

                    position = savepos;
                }

                // Parse a link definition
                LinkDefinition l = LinkDefinition.ParseLinkDefinition(this, m_markdown.ExtraMode);
                if (l!=null)
                {
                    m_markdown.AddLinkDefinition(l);
                    return BlockType.Blank;
                }
            }

            // Nothing special
            return BlockType.p;
        }
        /*
         * Spacing
         *
         * 1-3 spaces - Promote to indented if more spaces than original item
         *
         */
        /*
         * BuildList - build a single <ol> or <ul> list
         */
        private Block BuildList(List<Block> lines)
        {
            // What sort of list are we dealing with
            BlockType listType = lines[0].blockType;
            System.Diagnostics.Debug.Assert(listType == BlockType.ul_li || listType == BlockType.ol_li);

            // Preprocess
            // 1. Collapse all plain lines (ie: handle hardwrapped lines)
            // 2. Promote any unindented lines that have more leading space
            //    than the original list item to indented, including leading
            //    special chars
            int leadingSpace = lines[0].leadingSpaces;
            for (int i = 1; i < lines.Count; i++)
            {
                // Join plain paragraphs
                if ((lines[i].blockType == BlockType.p) &&
                    (lines[i - 1].blockType == BlockType.p || lines[i - 1].blockType == BlockType.ul_li || lines[i - 1].blockType==BlockType.ol_li))
                {
                    lines[i - 1].contentEnd = lines[i].contentEnd;
                    FreeBlock(lines[i]);
                    lines.RemoveAt(i);
                    i--;
                    continue;
                }

                if (lines[i].blockType != BlockType.indent && lines[i].blockType != BlockType.Blank)
                {
                    int thisLeadingSpace = lines[i].leadingSpaces;
                    if (thisLeadingSpace > leadingSpace)
                    {
                        // Change line to indented, including original leading chars
                        // (eg: '* ', '>', '1.' etc...)
                        lines[i].blockType = BlockType.indent;
                        int saveend = lines[i].contentEnd;
                        lines[i].contentStart = lines[i].lineStart + thisLeadingSpace;
                        lines[i].contentEnd = saveend;
                    }
                }
            }

            // Create the wrapping list item
            var List = new Block(listType == BlockType.ul_li ? BlockType.ul : BlockType.ol);
            List.children = new List<Block>();

            // Process all lines in the range
            for (int i = 0; i < lines.Count; i++)
            {
                System.Diagnostics.Debug.Assert(lines[i].blockType == BlockType.ul_li || lines[i].blockType==BlockType.ol_li);

                // Find start of item, including leading blanks
                int start_of_li = i;
                while (start_of_li > 0 && lines[start_of_li - 1].blockType == BlockType.Blank)
                    start_of_li--;

                // Find end of the item, including trailing blanks
                int end_of_li = i;
                while (end_of_li < lines.Count - 1 && lines[end_of_li + 1].blockType != BlockType.ul_li && lines[end_of_li + 1].blockType != BlockType.ol_li)
                    end_of_li++;

                // Is this a simple or complex list item?
                if (start_of_li == end_of_li)
                {
                    // It's a simple, single line item item
                    System.Diagnostics.Debug.Assert(start_of_li == i);
                    List.children.Add(CreateBlock().CopyFrom(lines[i]));
                }
                else
                {
                    // Build a new string containing all child items
                    bool bAnyBlanks = false;
                    StringBuilder sb = m_markdown.GetStringBuilder();
                    for (int j = start_of_li; j <= end_of_li; j++)
                    {
                        var l = lines[j];
                        sb.Append(l.buf, l.contentStart, l.contentLen);
                        sb.Append('\n');

                        if (lines[j].blockType == BlockType.Blank)
                        {
                            bAnyBlanks = true;
                        }
                    }

                    // Create the item and process child blocks
                    var item = new Block(BlockType.li);
                    item.children = new BlockProcessor(m_markdown, m_bMarkdownInHtml, listType).Process(sb.ToString());

                    // If no blank lines, change all contained paragraphs to plain text
                    if (!bAnyBlanks)
                    {
                        foreach (var child in item.children)
                        {
                            if (child.blockType == BlockType.p)
                            {
                                child.blockType = BlockType.span;
                            }
                        }
                    }

                    // Add the complex item
                    List.children.Add(item);
                }

                // Continue processing from end of li
                i = end_of_li;
            }

            FreeBlocks(lines);
            lines.Clear();

            // Continue processing after this item
            return List;
        }