예제 #1
0
        public override Boolean Match(InlineProcessor processor, ref StringSlice slice)
        {
            var startPosition = processor.GetSourcePosition(slice.Start, out int line, out int column);
            var last          = slice.PeekCharExtra(-1);
            var current       = slice.CurrentChar;

            if (current != '[' || last == '\\')
            {
                return(false);
            }

            // Link reference
            if (LinkHelper.TryParseLabel(ref slice, out String label, out SourceSpan labelSpan))
            {
                if (processor.Document.ContainsLinkReferenceDefinition(label))
                {
                    return(false);
                }
            }

            // Explicit link
            if (slice.CurrentChar.IsOneOf('[', '(', ':'))
            {
                return(false);
            }

            // Shorthand
            processor.Inline = new LinkInline(label, "")
            {
                LabelSpan = processor.GetSourcePositionFromLocalSpan(labelSpan),
                IsImage   = false,
                Span      = new SourceSpan(
                    startPosition,
                    processor.GetSourcePosition(slice.Start - 1)),
                Line     = line,
                Column   = column,
                IsClosed = true,
            }.AppendChild(new LiteralInline(label));

            return(true);
        }
예제 #2
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            // The following methods are inspired by the "An algorithm for parsing nested emphasis and links"
            // at the end of the CommonMark specs.

            var c = slice.CurrentChar;

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

            bool isImage = false;

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

            switch (c)
            {
            case '[':
                // If this is not an image, we may have a reference link shortcut
                // so we try to resolve it here
                var    saved = slice;
                string label;

                SourceSpan labelSpan;
                // If the label is followed by either a ( or a [, this is not a shortcut
                if (LinkHelper.TryParseLabel(ref slice, out label, out labelSpan))
                {
                    if (!processor.Document.ContainsLinkReferenceDefinition(label))
                    {
                        label = null;
                    }
                }
                slice = saved;

                // Else we insert a LinkDelimiter
                slice.NextChar();
                processor.Inline = new LinkDelimiterInline(this)
                {
                    Type      = DelimiterType.Open,
                    Label     = label,
                    LabelSpan = processor.GetSourcePositionFromLocalSpan(labelSpan),
                    IsImage   = isImage,
                    Span      = new SourceSpan(startPosition, processor.GetSourcePosition(slice.Start - 1)),
                    Line      = line,
                    Column    = column
                };
                return(true);

            case ']':
                slice.NextChar();
                if (processor.Inline != null)
                {
                    if (TryProcessLinkOrImage(processor, ref slice))
                    {
                        return(true);
                    }
                }

                // If we don’t find one, we return a literal slice node ].
                // (Done after by the LiteralInline parser)
                return(false);
            }

            // We don't have an emphasis
            return(false);
        }
예제 #3
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);
        }
예제 #4
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);
                }
            }