Пример #1
0
        private static void SetRowSpanState(List <GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
        {
            var lineStart = line.Start;

            isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
            hasRowSpan  = false;
            foreach (var columnSlice in columns)
            {
                if (columnSlice.CurrentCell != null)
                {
                    line.Start = lineStart + columnSlice.Start + 1;
                    line.End   = lineStart + columnSlice.End - 1;
                    line.Trim();
                    if (line.IsEmptyOrWhitespace() || !IsRowSeperator(line))
                    {
                        hasRowSpan = true;
                        columnSlice.CurrentCell.RowSpan++;
                        columnSlice.CurrentCell.AllowClose = false;
                    }
                    else
                    {
                        columnSlice.CurrentCell.AllowClose = true;
                    }
                }
            }
        }
Пример #2
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            if (slice.CurrentChar == '$' && slice.PeekChar(1) == '(')
            {
                slice.NextChar();
                slice.NextChar();

                int start = slice.Start;
                int end   = start;

                while (slice.CurrentChar != ')')
                {
                    end = slice.Start;
                    slice.NextChar();
                }

                processor.GetSourcePosition(slice.Start, out int line, out int column);

                processor.Inline = new VariableInline
                {
                    Line         = line,
                    Column       = column,
                    VariableName = new StringSlice(slice.Text, start, end)
                };

                slice.NextChar();

                return(true);
            }

            return(false);
        }
Пример #3
0
        /// <summary>
        /// Splits a slice into an IEnumerable of slices split on newlines
        /// </summary>
        /// <param name="slice">The slice to split</param>
        /// <returns>The line split into lines</returns>
        public static IEnumerable <StringSlice> SplitSliceToLines(StringSlice slice)
        {
            var sliceStart = slice.Start;

            while (!slice.IsEmpty)
            {
                if (slice.CurrentChar == '\n')
                {
                    slice.NextChar();
                    yield return(new StringSlice(slice.Text, sliceStart, slice.Start - 1));

                    sliceStart = slice.Start;
                    continue;
                }

                if (slice.CurrentChar == '\r' && slice.PeekChar(1) == '\n')
                {
                    slice.Start += 2;
                    yield return(new StringSlice(slice.Text, sliceStart, slice.Start - 1));

                    sliceStart = slice.Start;
                    continue;
                }

                slice.NextChar();
            }

            if (sliceStart != slice.Start)
            {
                yield return(new StringSlice(slice.Text, sliceStart, slice.Start - 1));
            }
        }
Пример #4
0
        private static void SetColumnSpanState(List <GridTableState.ColumnSlice> columns, StringSlice line)
        {
            foreach (var columnSlice in columns)
            {
                columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
                columnSlice.CurrentColumnSpan  = 0;
            }
            // | ------------- | ------------ | ---------------------------------------- |
            // Calculate the colspan for the new row
            int columnIndex = -1;

            for (int i = 0; i < columns.Count; i++)
            {
                var columnSlice = columns[i];
                var peek        = line.PeekChar(columnSlice.Start);
                if (peek == '|' || peek == '+')
                {
                    columnIndex = i;
                }
                if (columnIndex >= 0)
                {
                    columns[columnIndex].CurrentColumnSpan++;
                }
            }
        }
Пример #5
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            var startPosition = slice.Start;
            // Go to escape character
            var c = slice.NextChar();
            int line;
            int column;

            if (c.IsAsciiPunctuation())
            {
                processor.Inline = new LiteralInline()
                {
                    Content = new StringSlice(slice.Text, slice.Start, slice.Start),
                    Span    = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
                    Line    = line,
                    Column  = column,
                    IsFirstCharacterEscaped = true,
                };
                processor.Inline.Span.End = processor.Inline.Span.Start + 1;
                slice.SkipChar();
                return(true);
            }

            // A backslash at the end of the line is a [hard line break]:
            if (c == '\n' || c == '\r')
            {
                var newLine = c == '\n' ? NewLine.LineFeed : NewLine.CarriageReturn;
                if (c == '\r' && slice.PeekChar() == '\n')
                {
                    newLine = NewLine.CarriageReturnLineFeed;
                }
                var inline = new LineBreakInline()
                {
                    IsHard      = true,
                    IsBackslash = true,
                    Span        = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
                    Line        = line,
                    Column      = column,
                };
                processor.Inline = inline;

                if (processor.TrackTrivia)
                {
                    inline.NewLine = newLine;
                }

                inline.Span.End = inline.Span.Start + 1;
                slice.SkipChar(); // Skip \n or \r alone
                if (newLine == NewLine.CarriageReturnLineFeed)
                {
                    slice.SkipChar(); // Skip \r\n
                }
                return(true);
            }

            return(false);
        }
Пример #6
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            var nextChar = slice.PeekChar();

            if (nextChar != '{')
            {
                return(false);
            }

            var start = slice.Start;

            slice.NextChar();
            nextChar = slice.NextChar();

            var contentBuilder = processor.StringBuilders.Get();

            while (nextChar != '}')
            {
                contentBuilder.Append(nextChar);
                nextChar = slice.NextChar();
            }

            if (slice.PeekChar() != ']')
            {
                processor.StringBuilders.Release(contentBuilder);
                return(false);
            }

            slice.NextChar();
            slice.NextChar();

            processor.Inline = new CodeInclude
            {
                DisplayName = contentBuilder.ToString(),
                Span        = new SourceSpan(processor.GetSourcePosition(
                                                 start, out var line, out var column),
                                             processor.GetSourcePosition(slice.End)),
                Line   = line,
                Column = column
            };

            return(true);
        }
    }
Пример #7
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            // Hard line breaks are for separating inline content within a block. Neither syntax for hard line breaks works at the end of a paragraph or other block element:
            if (!processor.Block !.IsParagraphBlock)
            {
                return(false);
            }

            var startPosition         = slice.Start;
            var hasDoubleSpacesBefore = slice.PeekCharExtra(-1).IsSpace() && slice.PeekCharExtra(-2).IsSpace();
            var newLine = NewLine.LineFeed;

            if (processor.TrackTrivia)
            {
                if (slice.CurrentChar == '\r')
                {
                    if (slice.PeekChar() == '\n')
                    {
                        newLine = NewLine.CarriageReturnLineFeed;
                        slice.SkipChar(); // Skip \n
                    }
                    else
                    {
                        newLine = NewLine.CarriageReturn;
                    }
                }
                else
                {
                    newLine = NewLine.LineFeed;
                }
            }
            else
            {
                if (slice.CurrentChar == '\r' && slice.PeekChar() == '\n')
                {
                    slice.SkipChar(); // Skip \n
                }
            }
            slice.SkipChar(); // Skip \r or \n

            processor.Inline = new LineBreakInline
            {
                Span    = { Start = processor.GetSourcePosition(startPosition, out int line, out int column) },
Пример #8
0
    /// <summary>
    /// Tries to match the specified slice.
    /// </summary>
    /// <param name="processor">The parser processor.</param>
    /// <param name="slice">The text slice.</param>
    /// <returns><c>true</c> if this parser found a match; <c>false</c> otherwise</returns>
    public override bool Match(InlineProcessor processor, ref StringSlice slice)
    {
        var c             = slice.CurrentChar;
        var startPosition = processor.GetSourcePosition(slice.Start, out var line, out var column);

        var isImage = false;

        if (c == '!')
        {
            isImage = true;
            c       = slice.NextChar();
            if (c != LinkOpenChar)
            {
                return(false);
            }
        }

        switch (c)
        {
        case LinkOpenChar:
            if (slice.PeekChar() != LinkOpenChar)
            {
                return(false);
            }
            var saved           = slice;
            var currentPosition = slice.Start;
            if (TryParseLink(ref slice, out var title, out var display, out var hasDisplay, out var autoDisplay, out var endPosition))
            {
                processor.Inline = new WikiLinkDelimiterInline(this)
                {
                    Type        = DelimiterType.Open,
                    AutoDisplay = autoDisplay,
                    Display     = display,
                    HasDisplay  = hasDisplay,
                    Title       = title,
                    IsImage     = isImage,
                    Span        = new SourceSpan(startPosition, processor.GetSourcePosition(slice.Start - 1)),
                    Line        = line,
                    Column      = column,
                };
                slice = saved;
                if (endPosition == currentPosition)
                {
                    slice.NextChar();
                    slice.NextChar();
                }
                else
                {
                    slice.Start = endPosition;
                    slice.NextChar();
                }
            }
        /// <inheritdoc />
        public virtual void Collapse(ref StringSlice line, float collapseRatio)
        {
            if (collapseRatio < 0 || collapseRatio > 1)
            {
                throw new ArgumentOutOfRangeException(nameof(collapseRatio),
                                                      string.Format(Strings.ArgumentOutOfRangeException_Shared_ValueMustBeWithinRange, "[0, 1]", collapseRatio));
            }

            if (line.IsEmpty || collapseRatio == 1)
            {
                return;
            }

            if (collapseRatio == 0)
            {
                line.TrimStart(); // Remove all leading whitespace
            }
            else
            {
                int leadingWhitespaceCount = 0;
                while (line.PeekChar(leadingWhitespaceCount).IsWhitespace())
                {
                    leadingWhitespaceCount++;
                }

                if (leadingWhitespaceCount == 0)
                {
                    return;
                }

                // collapseRatio is defined as finalLeadingWhitespaceCount/initialLeadingWhitespaceCount,
                // so collapseLength = initialLeadingWhitespaceCount - finalLeadingWhitespaceCount = initialLeadingWhitespaceCount - initialLeadingWhitespaceCount*collapseRatio
                int collapseLength = leadingWhitespaceCount - (int)Math.Round(leadingWhitespaceCount * collapseRatio);

                for (int start = 0; start < collapseLength; start++)
                {
                    line.NextChar();
                }
            }
        }
        /// <inheritdoc />
        public virtual void Dedent(ref StringSlice line, int dedentLength)
        {
            if (dedentLength < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(dedentLength), string.Format(Strings.ArgumentOutOfRangeException_Shared_ValueCannotBeNegative, dedentLength));
            }

            if (line.IsEmpty || dedentLength == 0)
            {
                return;
            }

            for (int offset = 0; offset < dedentLength; offset++)
            {
                if (!line.PeekChar(offset).IsWhitespace())
                {
                    line.Start += offset;
                    return; // No more whitespace to dedent or collapse
                }
            }

            line.Start += dedentLength;
        }
Пример #11
0
        private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn, int startPosition)
        {
            char c;

            c = line.CurrentChar;
            if (c == '!')
            {
                c = line.NextChar();
                if (c == '-' && line.PeekChar() == '-')
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition)); // group 2
                }
                if (c.IsAlphaUpper())
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn, startPosition)); // group 4
                }
                if (c == '[' && line.Match("CDATA[", 1))
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.CData, startColumn, startPosition)); // group 5
                }

                return(BlockState.None);
            }

            if (c == '?')
            {
                return(CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn, startPosition)); // group 3
            }

            var hasLeadingClose = c == '/';

            if (hasLeadingClose)
            {
                c = line.NextChar();
            }

            var tag   = new char[10];
            var count = 0;

            for (; count < tag.Length; count++)
            {
                if (!c.IsAlphaNumeric())
                {
                    break;
                }
                tag[count] = char.ToLowerInvariant(c);
                c          = line.NextChar();
            }

            if (
                !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() ||
                  c == '\0'))
            {
                return(BlockState.None);
            }

            if (count == 0)
            {
                return(BlockState.None);
            }

            var tagName  = new string(tag, 0, count);
            var tagIndex = Array.BinarySearch(HtmlTags, tagName, StringComparer.Ordinal);

            if (tagIndex < 0)
            {
                return(BlockState.None);
            }

            // Cannot start with </script </pre or </style
            if ((tagIndex == 49 || tagIndex == 50 || tagIndex == 53))
            {
                if (c == '/' || hasLeadingClose)
                {
                    return(BlockState.None);
                }
                return(CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn, startPosition));
            }

            return(CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn, startPosition));
        }
Пример #12
0
        private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text)
        {
            LinkDelimiterInline openParent = null;

            foreach (var parent in inlineState.Inline.FindParentOfType <LinkDelimiterInline>())
            {
                openParent = parent;
                break;
            }

            if (openParent != null)
            {
                // If we do find one, but it’s not active,
                // we remove the inactive delimiter from the stack,
                // and return a literal text node ].
                if (!openParent.IsActive)
                {
                    inlineState.Inline = new LiteralInline()
                    {
                        Content = new StringSlice("["),
                        Span    = openParent.Span,
                        Line    = openParent.Line,
                        Column  = openParent.Column,
                    };
                    openParent.ReplaceBy(inlineState.Inline);
                    return(false);
                }

                // If we find one and it’s active,
                // then we parse ahead to see if we have
                // an inline link/image, reference link/image,
                // compact reference link/image,
                // or shortcut reference link/image
                var parentDelimiter = openParent.Parent;
                switch (text.CurrentChar)
                {
                case '(':
                    string     url;
                    string     title;
                    SourceSpan linkSpan;
                    SourceSpan titleSpan;
                    if (LinkHelper.TryParseInlineLink(ref text, out url, out title, out linkSpan, out titleSpan))
                    {
                        // Inline Link
                        var link = new LinkInline()
                        {
                            Url       = HtmlHelper.Unescape(url),
                            Title     = HtmlHelper.Unescape(title),
                            IsImage   = openParent.IsImage,
                            LabelSpan = openParent.LabelSpan,
                            UrlSpan   = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
                            TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan),
                            Span      = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)),
                            Line      = openParent.Line,
                            Column    = openParent.Column,
                        };

                        openParent.ReplaceBy(link);
                        // Notifies processor as we are creating an inline locally
                        inlineState.Inline = link;

                        // Process emphasis delimiters
                        inlineState.PostProcessInlines(0, link, null, false);

                        // If we have a link (and not an image),
                        // we also set all [ delimiters before the opening delimiter to inactive.
                        // (This will prevent us from getting links within links.)
                        if (!openParent.IsImage)
                        {
                            MarkParentAsInactive(parentDelimiter);
                        }

                        link.IsClosed = true;

                        return(true);
                    }
                    break;

                default:

                    var    labelSpan        = SourceSpan.Empty;
                    string label            = null;
                    bool   isLabelSpanLocal = true;
                    // Handle Collapsed links
                    if (text.CurrentChar == '[')
                    {
                        if (text.PeekChar(1) == ']')
                        {
                            label            = openParent.Label;
                            labelSpan        = openParent.LabelSpan;
                            isLabelSpanLocal = false;
                            text.NextChar();     // Skip [
                            text.NextChar();     // Skip ]
                        }
                    }
                    else
                    {
                        label = openParent.Label;
                    }

                    if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
                    {
                        if (isLabelSpanLocal)
                        {
                            labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
                        }

                        if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
                        {
                            // Remove the open parent
                            openParent.Remove();
                            if (!openParent.IsImage)
                            {
                                MarkParentAsInactive(parentDelimiter);
                            }
                        }
                        else
                        {
                            return(false);
                        }
                        return(true);
                    }
                    break;
                }

                // We have a nested [ ]
                // firstParent.Remove();
                // The opening [ will be transformed to a literal followed by all the childrens of the [

                var literal = new LiteralInline()
                {
                    Span    = openParent.Span,
                    Content = new StringSlice(openParent.IsImage ? "![" : "[")
                };

                inlineState.Inline = openParent.ReplaceBy(literal);
                return(false);
            }

            return(false);
        }
Пример #13
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            // We are matching the following characters:
            //
            // '    ‘ ’     &lsquo; &rsquo;     'left-single-quote', 'right-single-quote'
            // ''   “ ”     &ldquo; &rdquo;     'left-double-quote', 'right-double-quote'
            // "    “ ”     &ldquo; &rdquo;     'left-double-quote', 'right-double-quote'
            // << >>    « »     &laquo; &raquo;     'left-angle-quote', 'right-angle-quote'
            // ...  …   &hellip;    'ellipsis'

            // Special case: &ndash; and &mdash; are handle as a PostProcess step to avoid conflicts with pipetables header separator row
            // --   –   &ndash;     'ndash'
            // ---  —   &mdash;     'mdash'

            var pc          = slice.PeekCharExtra(-1);
            var c           = slice.CurrentChar;
            var openingChar = c;

            var startingPosition = slice.Start;

            // undefined first
            var type = (SmartyPantType)0;

            switch (c)
            {
            case '\'':
                type = SmartyPantType.Quote;     // We will resolve them at the end of parsing all inlines
                if (slice.PeekChar(1) == '\'')
                {
                    slice.NextChar();
                    type = SmartyPantType.DoubleQuote;     // We will resolve them at the end of parsing all inlines
                }
                break;

            case '"':
                type = SmartyPantType.DoubleQuote;
                break;

            case '<':
                if (slice.NextChar() == '<')
                {
                    type = SmartyPantType.LeftAngleQuote;
                }
                break;

            case '>':
                if (slice.NextChar() == '>')
                {
                    type = SmartyPantType.RightAngleQuote;
                }
                break;

            case '.':
                if (slice.NextChar() == '.' && slice.NextChar() == '.')
                {
                    type = SmartyPantType.Ellipsis;
                }
                break;

            case '-':
                if (slice.NextChar() == '-')
                {
                    var quotePants = GetOrCreateState(processor);
                    quotePants.HasDash = true;
                    return(false);
                }
                break;
            }

            // If it is not matched, early exit
            if (type == 0)
            {
                return(false);
            }

            // Skip char
            c = slice.NextChar();

            bool canOpen;
            bool canClose;

            CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);

            bool postProcess = false;

            switch (type)
            {
            case SmartyPantType.Quote:
                postProcess = true;
                if (canOpen && !canClose)
                {
                    type = SmartyPantType.LeftQuote;
                }
                else if (!canOpen && canClose)
                {
                    type = SmartyPantType.RightQuote;
                }
                else
                {
                    return(false);
                }
                break;

            case SmartyPantType.DoubleQuote:
                postProcess = true;
                if (canOpen && !canClose)
                {
                    type = SmartyPantType.LeftDoubleQuote;
                }
                else if (!canOpen && canClose)
                {
                    type = SmartyPantType.RightDoubleQuote;
                }
                else
                {
                    return(false);
                }
                break;

            case SmartyPantType.LeftAngleQuote:
                postProcess = true;
                if (!canOpen || canClose)
                {
                    return(false);
                }
                break;

            case SmartyPantType.RightAngleQuote:
                postProcess = true;
                if (canOpen || !canClose)
                {
                    return(false);
                }
                break;

            case SmartyPantType.Ellipsis:
                if (canOpen || !canClose)
                {
                    return(false);
                }
                break;
            }

            // Create the SmartyPant inline
            int line;
            int column;
            var pant = new SmartyPant()
            {
                Span             = { Start = processor.GetSourcePosition(startingPosition, out line, out column) },
                Line             = line,
                Column           = column,
                OpeningCharacter = openingChar,
                Type             = type
            };

            pant.Span.End = pant.Span.Start + slice.Start - startingPosition - 1;

            // We will check in a post-process step for balanced open/close quotes
            if (postProcess)
            {
                var quotePants = GetOrCreateState(processor);

                // Register only if we don't have yet any quotes
                if (quotePants.Count == 0)
                {
                    processor.Block.ProcessInlinesEnd += BlockOnProcessInlinesEnd;
                }
                quotePants.Add(pant);
            }

            processor.Inline = pant;
            return(true);
        }
Пример #14
0
        /// <summary>
        /// Tries to match a block opening.
        /// </summary>
        /// <param name="processor">The parser processor.</param>
        /// <returns>The result of the match</returns>
        public override BlockState TryOpen(BlockProcessor processor)
        {
            // We expect no indentation for a fenced code block.
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            // Only accept a frontmatter at the beginning of the file
            if (processor.Start != 0)
            {
                return(BlockState.None);
            }

            int  count = 0;
            var  line  = processor.Line;
            char c     = line.CurrentChar;

            // Must consist of exactly three dashes
            while (c == '-' && count < 4)
            {
                count++;
                c = line.NextChar();
            }

            // If three dashes (optionally followed by whitespace)
            // this is a YAML front matter block
            if (count == 3 && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
            {
                bool hasFullYamlFrontMatter = false;
                // We make sure that there is a closing frontmatter somewhere in the document
                // so here we work on the full document instead of just the line
                var fullLine = new StringSlice(line.Text, line.Start, line.Text.Length - 1);
                c = fullLine.CurrentChar;
                while (c != '\0')
                {
                    c = fullLine.NextChar();
                    if (c == '\n' || c == '\r')
                    {
                        var nc = fullLine.PeekChar();
                        if (c == '\r' && nc == '\n')
                        {
                            c = fullLine.NextChar();
                        }
                        nc = fullLine.PeekChar();
                        if (nc == '-')
                        {
                            if (fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
                            {
                                hasFullYamlFrontMatter = true;
                                break;
                            }
                        }
                        else if (nc == '.')
                        {
                            if (fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
                            {
                                hasFullYamlFrontMatter = true;
                                break;
                            }
                        }
                    }
                }

                if (hasFullYamlFrontMatter)
                {
                    // Create a front matter block
                    var block = this.CreateFrontMatterBlock(processor);
                    block.Column     = processor.Column;
                    block.Span.Start = 0;
                    block.Span.End   = line.Start;

                    // Store the number of matched string into the context
                    processor.NewBlocks.Push(block);

                    // Discard the current line as it is already parsed
                    return(BlockState.ContinueDiscard);
                }
            }

            return(BlockState.None);
        }
Пример #15
0
 /// <summary>
 /// Peeks a character at the specified offset from the current position in the line.
 /// </summary>
 /// <param name="offset">The offset.</param>
 /// <returns>A character peeked at the specified offset</returns>
 public char PeekChar(int offset)
 {
     return(Line.PeekChar(offset));
 }
Пример #16
0
        private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn, int startPosition)
        {
            char c;

            c = line.CurrentChar;
            if (c == '!')
            {
                c = line.NextChar();
                if (c == '-' && line.PeekChar() == '-')
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition)); // group 2
                }
                if (c.IsAlphaUpper())
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn, startPosition)); // group 4
                }
                if (c == '[' && line.Match("CDATA[", 1))
                {
                    return(CreateHtmlBlock(state, HtmlBlockType.CData, startColumn, startPosition)); // group 5
                }

                return(BlockState.None);
            }

            if (c == '?')
            {
                return(CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn, startPosition)); // group 3
            }

            var hasLeadingClose = c == '/';

            if (hasLeadingClose)
            {
                c = line.NextChar();
            }

            Span <char> tag   = stackalloc char[10];
            var         count = 0;

            for (; count < tag.Length; count++)
            {
                if (!c.IsAlphaNumeric())
                {
                    break;
                }
                tag[count] = char.ToLowerInvariant(c);
                c          = line.NextChar();
            }

            if (
                !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() ||
                  c == '\0'))
            {
                return(BlockState.None);
            }

            if (count == 0)
            {
                return(BlockState.None);
            }

            if (!HtmlTags.TryMatchExact(tag.Slice(0, count), out var match))
            {
                return(BlockState.None);
            }

            int tagIndex = match.Value;

            // Cannot start with </script </pre or </style or </textArea
            if ((tagIndex == 49 || tagIndex == 50 || tagIndex == 53 || tagIndex == 56))
            {
                if (c == '/' || hasLeadingClose)
                {
                    return(BlockState.None);
                }
                return(CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn, startPosition));
            }

            return(CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn, startPosition));
        }
Пример #17
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            var c = slice.CurrentChar;
            var p = processor.GetSourcePosition(slice.Start, out var line, out var column);

            // consume first '['
            var c2 = slice.NextChar();

            if (c2 != '[')
            {
                return(false);
            }

            var start      = slice.Start + 1;
            var labelStart = start;

            var        hasEscape    = false;
            SourceSpan?labelSpan    = null;
            string     label        = null;
            SourceSpan?urlSpan      = null;
            string     url          = null;
            var        success      = false;
            var        hasSeparator = false;

            while (true)
            {
                c = slice.NextChar();

                if (c == '\0' || c == '\r' || c == '\n')
                {
                    break;
                }

                if (hasEscape)
                {
                    // ignore escaped characters
                    hasEscape = false;
                }
                else
                {
                    if (c == '\\')
                    {
                        hasEscape = true;
                    }
                    else if (c == '|')
                    {
                        var span = new SourceSpan(start, slice.Start - 1);
                        if (span.Length != 0)
                        {
                            urlSpan = span;
                            url     = slice.Text.Substring(span.Start, span.Length);
                        }

                        hasSeparator = true;
                        labelStart   = slice.Start + 1;
                    }
                    else if (c == ']')
                    {
                        c2 = slice.PeekChar(1);
                        if (c2 == ']')
                        {
                            // end of link
                            success = true;
                            c       = slice.NextChar();

                            var span             = new SourceSpan(labelStart, slice.Start - 2);
                            var trailingCharsLen = 0;
                            var linkEnd          = -1;
                            if (!hasSeparator && IncludeTrailingCharacters)
                            {
                                c2 = slice.PeekChar(1);
                                if (char.IsLetter(c2) || c2 == '\'')
                                {
                                    linkEnd = slice.Start - 1;
                                    c       = slice.NextChar();
                                    while (char.IsLetter(c) || c == '\'')
                                    {
                                        trailingCharsLen++;
                                        c = slice.NextChar();
                                    }
                                }
                            }

                            slice.NextChar();

                            if (linkEnd != -1)
                            {
                                url     = slice.Text.Substring(labelStart, linkEnd - 2);
                                urlSpan = span;

                                label = slice.Text.Substring(labelStart, linkEnd - 2)
                                        + slice.Text.Substring(linkEnd + 2, trailingCharsLen);
                            }

                            if (span.Length != 0)
                            {
                                labelSpan = span;
                                if (label == null)
                                {
                                    label = slice.Text.Substring(span.Start, span.Length);
                                }
                            }
                            break;
                        }
                    }
                }
            }

            if (success)
            {
                if (url == null)
                {
                    // occurs when no separator were used
                    // copy label as url
                    url     = label;
                    urlSpan = labelSpan;

                    // keep only the page name
                    var lastSegment = label.LastIndexOf('/');
                    if (lastSegment != -1)
                    {
                        label = label.Substring(lastSegment + 1);
                    }

                    // remove any hash
                    var hash = label.LastIndexOf('#');
                    if (hash != -1)
                    {
                        label = label.Substring(0, hash);
                    }
                }

                if (label == null)
                {
                    label = url;

                    // keep only the page name
                    var lastSegment = label.LastIndexOf('/');
                    if (lastSegment != -1)
                    {
                        label = label.Substring(lastSegment + 1);
                    }

                    // remove any hash
                    var hash = label.LastIndexOf('#');
                    if (hash != -1)
                    {
                        label = label.Substring(0, hash);
                    }

                    labelSpan = urlSpan;
                }

                if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
                    !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
                {
                    // adapt relative url
                    url = Regex.Replace(url, "[ ]", $"{WhiteSpaceUrlChar}");
                    if (!string.IsNullOrEmpty(Extension))
                    {
                        url += Extension;
                    }
                }

                var link = new LinkInline()
                {
                    Column    = column,
                    Line      = line,
                    LabelSpan = labelSpan,
                    Label     = label,
                    Url       = url,
                    UrlSpan   = urlSpan,
                    IsClosed  = true,
                    //IsShortcut = false,
                    IsImage = false,
                };

                link.AppendChild(new LiteralInline(label));

                processor.Inline = link;
            }

            return(success);
        }
Пример #18
0
        private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text)
        {
            LinkDelimiterInline?openParent = inlineState.Inline !.FirstParentOfType <LinkDelimiterInline>();

            if (openParent is null)
            {
                return(false);
            }

            // If we do find one, but it’s not active,
            // we remove the inactive delimiter from the stack,
            // and return a literal text node ].
            if (!openParent.IsActive)
            {
                inlineState.Inline = new LiteralInline()
                {
                    Content = new StringSlice("["),
                    Span    = openParent.Span,
                    Line    = openParent.Line,
                    Column  = openParent.Column,
                };
                openParent.ReplaceBy(inlineState.Inline);
                return(false);
            }

            // If we find one and it’s active,
            // then we parse ahead to see if we have
            // an inline link/image, reference link/image,
            // compact reference link/image,
            // or shortcut reference link/image
            var parentDelimiter = openParent.Parent;
            var savedText       = text;

            if (text.CurrentChar == '(')
            {
                if (inlineState.TrackTrivia)
                {
                    if (LinkHelper.TryParseInlineLinkTrivia(
                            ref text,
                            out string?url,
                            out SourceSpan unescapedUrlSpan,
                            out string?title,
                            out SourceSpan unescapedTitleSpan,
                            out char titleEnclosingCharacter,
                            out SourceSpan linkSpan,
                            out SourceSpan titleSpan,
                            out SourceSpan triviaBeforeLink,
                            out SourceSpan triviaAfterLink,
                            out SourceSpan triviaAfterTitle,
                            out bool urlHasPointyBrackets))
                    {
                        var wsBeforeLink   = new StringSlice(text.Text, triviaBeforeLink.Start, triviaBeforeLink.End);
                        var wsAfterLink    = new StringSlice(text.Text, triviaAfterLink.Start, triviaAfterLink.End);
                        var wsAfterTitle   = new StringSlice(text.Text, triviaAfterTitle.Start, triviaAfterTitle.End);
                        var unescapedUrl   = new StringSlice(text.Text, unescapedUrlSpan.Start, unescapedUrlSpan.End);
                        var unescapedTitle = new StringSlice(text.Text, unescapedTitleSpan.Start, unescapedTitleSpan.End);
                        // Inline Link
                        var link = new LinkInline()
                        {
                            TriviaBeforeUrl         = wsBeforeLink,
                            Url                     = HtmlHelper.Unescape(url),
                            UnescapedUrl            = unescapedUrl,
                            UrlHasPointyBrackets    = urlHasPointyBrackets,
                            TriviaAfterUrl          = wsAfterLink,
                            Title                   = HtmlHelper.Unescape(title),
                            UnescapedTitle          = unescapedTitle,
                            TitleEnclosingCharacter = titleEnclosingCharacter,
                            TriviaAfterTitle        = wsAfterTitle,
                            IsImage                 = openParent.IsImage,
                            LabelSpan               = openParent.LabelSpan,
                            UrlSpan                 = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
                            TitleSpan               = inlineState.GetSourcePositionFromLocalSpan(titleSpan),
                            Span                    = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)),
                            Line                    = openParent.Line,
                            Column                  = openParent.Column,
                        };

                        openParent.ReplaceBy(link);
                        // Notifies processor as we are creating an inline locally
                        inlineState.Inline = link;

                        // Process emphasis delimiters
                        inlineState.PostProcessInlines(0, link, null, false);

                        // If we have a link (and not an image),
                        // we also set all [ delimiters before the opening delimiter to inactive.
                        // (This will prevent us from getting links within links.)
                        if (!openParent.IsImage)
                        {
                            MarkParentAsInactive(parentDelimiter);
                        }

                        link.IsClosed = true;

                        return(true);
                    }
                }
                else
                {
                    if (LinkHelper.TryParseInlineLink(ref text, out string?url, out string?title, out SourceSpan linkSpan, out SourceSpan titleSpan))
                    {
                        // Inline Link
                        var link = new LinkInline()
                        {
                            Url       = HtmlHelper.Unescape(url),
                            Title     = HtmlHelper.Unescape(title),
                            IsImage   = openParent.IsImage,
                            LabelSpan = openParent.LabelSpan,
                            UrlSpan   = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
                            TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan),
                            Span      = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)),
                            Line      = openParent.Line,
                            Column    = openParent.Column,
                        };

                        openParent.ReplaceBy(link);
                        // Notifies processor as we are creating an inline locally
                        inlineState.Inline = link;

                        // Process emphasis delimiters
                        inlineState.PostProcessInlines(0, link, null, false);

                        // If we have a link (and not an image),
                        // we also set all [ delimiters before the opening delimiter to inactive.
                        // (This will prevent us from getting links within links.)
                        if (!openParent.IsImage)
                        {
                            MarkParentAsInactive(parentDelimiter);
                        }

                        link.IsClosed = true;

                        return(true);
                    }
                }

                text = savedText;
            }

            var        labelSpan        = SourceSpan.Empty;
            string?    label            = null;
            SourceSpan labelWithTrivia  = SourceSpan.Empty;
            bool       isLabelSpanLocal = true;

            bool       isShortcut = false;
            LocalLabel localLabel = LocalLabel.Local;

            // Handle Collapsed links
            if (text.CurrentChar == '[')
            {
                if (text.PeekChar() == ']')
                {
                    label            = openParent.Label;
                    labelSpan        = openParent.LabelSpan;
                    isLabelSpanLocal = false;
                    localLabel       = LocalLabel.Empty;
                    text.SkipChar(); // Skip [
                    text.SkipChar(); // Skip ]
                }
            }
            else
            {
                localLabel = LocalLabel.None;
                label      = openParent.Label;
                isShortcut = true;
            }
            if (label != null || LinkHelper.TryParseLabelTrivia(ref text, true, out label, out labelSpan))
            {
                labelWithTrivia = new SourceSpan(labelSpan.Start, labelSpan.End);
                if (isLabelSpanLocal)
                {
                    labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
                }

                if (ProcessLinkReference(inlineState, text, label !, labelWithTrivia, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1), localLabel))
                {
                    // Remove the open parent
                    openParent.Remove();
                    if (!openParent.IsImage)
                    {
                        MarkParentAsInactive(parentDelimiter);
                    }
                    return(true);
                }
                else if (text.CurrentChar != ']' && text.CurrentChar != '[')
                {
                    return(false);
                }
            }