Beispiel #1
0
        private static Inline HandleLeftSquareBracket(Subject subj, bool isImage)
        {
            Inline inlText;

            if (isImage)
            {
                inlText = new Inline("![", subj.Position - 1, subj.Position + 1);
            }
            else
            {
                inlText = new Inline("[", subj.Position, subj.Position + 1);
            }

            // move past the '['
            subj.Position++;

            var istack = new InlineStack();

            istack.Delimiter      = '[';
            istack.StartingInline = inlText;
            istack.StartPosition  = subj.Position;
            istack.Priority       = InlineStack.InlineStackPriority.Links;
            istack.Flags          = InlineStack.InlineStackFlags.Opener | (isImage ? InlineStack.InlineStackFlags.ImageLink : InlineStack.InlineStackFlags.None);

            InlineStack.AppendStackEntry(istack, subj);

            return(inlText);
        }
Beispiel #2
0
        public static Inline parse_inlines(Subject subj, Func <Subject, Inline>[] parsers, char[] specialCharacters)
        {
            var len = subj.Length;

            if (len == 0)
            {
                return(null);
            }

            var first = ParseInline(subj, parsers, specialCharacters);

            subj.LastInline = first.LastSibling;

            Inline cur;

            while (subj.Position < len)
            {
                cur = ParseInline(subj, parsers, specialCharacters);
                if (cur != null)
                {
                    subj.LastInline.NextSibling = cur;
                    subj.LastInline             = cur.LastSibling;
                }
            }

            InlineStack.PostProcessInlineStack(subj, subj.FirstPendingInline, subj.LastPendingInline, InlineStack.InlineStackPriority.Maximum);

            return(first);
        }
Beispiel #3
0
        private static Inline HandleOpenerCloser(Subject subj, InlineTag singleCharTag, InlineTag doubleCharTag)
        {
            bool canOpen, canClose;
            var  c         = subj.Buffer[subj.Position];
            var  numdelims = ScanEmphasisDelimiters(subj, c, out canOpen, out canClose);

            if (canClose)
            {
                // walk the stack and find a matching opener, if there is one
                var istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Emphasis, c, numdelims, canOpen, out canClose);
                if (istack != null)
                {
                    var useDelims = MatchInlineStack(istack, subj, numdelims, null, singleCharTag, doubleCharTag);

                    if (useDelims > 0)
                    {
                        // if the closer was not fully used, move back a char or two and try again.
                        if (useDelims < numdelims)
                        {
                            subj.Position = subj.Position - numdelims + useDelims;

                            // use recursion only if it will not be very deep.
                            // however it cannot be used if the single char will not be parsed.
                            if (numdelims < 10)
                            {
                                return(HandleOpenerCloser(subj, singleCharTag, doubleCharTag));
                            }
                        }

                        return(null);
                    }
                }
            }

            var inlText = new Inline(subj.Buffer, subj.Position - numdelims, numdelims, subj.Position - numdelims, subj.Position, c);

            if (canOpen || canClose)
            {
                var istack = new InlineStack();
                istack.DelimiterCount = numdelims;
                istack.Delimiter      = c;
                istack.StartingInline = inlText;
                istack.Priority       = InlineStack.InlineStackPriority.Emphasis;
                istack.Flags          = (canOpen ? InlineStack.InlineStackFlags.Opener : 0)
                                        | (canClose ? (InlineStack.InlineStackFlags.Closer | InlineStack.InlineStackFlags.CloserOriginally) : 0);

                InlineStack.AppendStackEntry(istack, subj);
            }

            return(inlText);
        }
Beispiel #4
0
        internal static void MatchSquareBracketStack(InlineStack opener, Subject subj, Reference details)
        {
            if (details != null)
            {
                var inl     = opener.StartingInline;
                var isImage = 0 != (opener.Flags & InlineStack.InlineStackFlags.ImageLink);
                inl.Tag                = isImage ? InlineTag.Image : (details.IsPlaceholder ? InlineTag.Placeholder : InlineTag.Link);
                inl.FirstChild         = inl.NextSibling;
                inl.NextSibling        = null;
                inl.SourceLastPosition = subj.Position;

                inl.TargetUrl      = details.Url;
                inl.LiteralContent = details.Title;

                if (!isImage && !details.IsPlaceholder)
                {
                    // since there cannot be nested links, remove any other link openers before this
                    var temp = opener.Previous;
                    while (temp != null && temp.Priority <= InlineStack.InlineStackPriority.Links)
                    {
                        if (temp.Delimiter == '[' && temp.Flags == opener.Flags)
                        {
                            // mark the previous entries as "inactive"
                            if (temp.DelimiterCount == -1)
                            {
                                break;
                            }

                            temp.DelimiterCount = -1;
                        }

                        temp = temp.Previous;
                    }
                }

                InlineStack.RemoveStackEntry(opener, subj, null);

                subj.LastInline = inl;
            }
            else
            {
                // this looked like a link, but was not.
                // remove the opener stack entry but leave the inbetween intact
                InlineStack.RemoveStackEntry(opener, subj, opener);

                var inl = new Inline("]", subj.Position - 1, subj.Position);
                subj.LastInline.LastSibling.NextSibling = inl;
                subj.LastInline = inl;
            }
        }
Beispiel #5
0
        public static void AppendStackEntry(InlineStack entry, Subject subj)
        {
            if (subj.LastPendingInline != null)
            {
                entry.Previous = subj.LastPendingInline;
                subj.LastPendingInline.Next = entry;
            }

            if (subj.FirstPendingInline == null)
            {
                subj.FirstPendingInline = entry;
            }

            subj.LastPendingInline = entry;
        }
Beispiel #6
0
        public static InlineStack FindMatchingOpener(InlineStack searchBackwardsFrom, InlineStackPriority priority,
                                                     char delimiter, int closerDelimiterCount, bool closerCanOpen, out bool canClose)
        {
            canClose = true;
            var istack = searchBackwardsFrom;

            while (true)
            {
                if (istack == null)
                {
                    // this cannot be a closer since there is no opener available.
                    canClose = false;
                    return(null);
                }

                if (istack.Priority > priority ||
                    (istack.Delimiter == delimiter && 0 != (istack.Flags & InlineStackFlags.Closer)))
                {
                    // there might be a closer further back but we cannot go there yet because a higher priority element is blocking
                    // the other option is that the stack entry could be a closer for the same char - this means
                    // that any opener we might find would first have to be matched against this closer.
                    return(null);
                }

                if (istack.Delimiter == delimiter)
                {
                    // interior closer of size 2 does not match opener of size 1 and vice versa.
                    // for more details, see https://github.com/jgm/cmark/commit/c50197bab81d7105c9c790548821b61bcb97a62a
                    var oddMatch = (closerCanOpen || (istack.Flags & InlineStackFlags.CloserOriginally) > 0) &&
                                   istack.DelimiterCount != closerDelimiterCount &&
                                   ((istack.DelimiterCount + closerDelimiterCount) % 3 == 0);

                    if (!oddMatch)
                    {
                        return(istack);
                    }
                }

                istack = istack.Previous;
            }
        }
Beispiel #7
0
        private static Inline HandleRightSquareBracket(Subject subj, bool supportPlaceholderBrackets)
        {
            // move past ']'
            subj.Position++;

            bool canClose;
            var  istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Links, '[', 1, false, out canClose);

            if (istack != null)
            {
                // if the opener is "inactive" then it means that there was a nested link
                if (istack.DelimiterCount == -1)
                {
                    InlineStack.RemoveStackEntry(istack, subj, istack);
                    return(new Inline("]", subj.Position - 1, subj.Position));
                }

                var endpos = subj.Position;

                // try parsing details for '[foo](/url "title")' or '[foo][bar]'
                //var details = ParseLinkDetails(subj);
                var details = ParseLinkDetails(subj, supportPlaceholderBrackets);

                // try lookup of the brackets themselves
                if (details == null || details == Reference.SelfReference)
                {
                    var startpos = istack.StartPosition;
                    var label    = new StringPart(subj.Buffer, startpos, endpos - startpos - 1);

                    details = LookupReference(subj.DocumentData, label);
                }

                if (details == Reference.InvalidReference)
                {
                    details = null;
                }

                MatchSquareBracketStack(istack, subj, details);
                return(null);
            }

            var inlText = new Inline("]", subj.Position - 1, subj.Position);

            if (canClose)
            {
                // note that the current implementation will not work if there are other inlines with priority
                // higher than Links.
                // to fix this the parsed link details should be added to the closer element in the stack.

                throw new NotSupportedException("It is not supported to have inline stack priority higher than Links.");

                ////istack = new InlineStack();
                ////istack.Delimiter = '[';
                ////istack.StartingInline = inlText;
                ////istack.StartPosition = subj.Position;
                ////istack.Priority = InlineStack.InlineStackPriority.Links;
                ////istack.Flags = InlineStack.InlineStackFlags.Closer;

                ////InlineStack.AppendStackEntry(istack, subj);
            }

            return(inlText);
        }
Beispiel #8
0
        internal static int MatchInlineStack(InlineStack opener, Subject subj, int closingDelimiterCount, InlineStack closer, InlineTag singleCharTag, InlineTag doubleCharTag)
        {
            // calculate the actual number of delimiters used from this closer
            int useDelims;
            var openerDelims = opener.DelimiterCount;

            if (closingDelimiterCount < 3 || openerDelims < 3)
            {
                useDelims = closingDelimiterCount <= openerDelims ? closingDelimiterCount : openerDelims;
                if (useDelims == 1 && singleCharTag == 0)
                {
                    return(0);
                }
            }
            else if (singleCharTag == 0)
            {
                useDelims = 2;
            }
            else if (doubleCharTag == 0)
            {
                useDelims = 1;
            }
            else
            {
                useDelims = closingDelimiterCount % 2 == 0 ? 2 : 1;
            }

            Inline    inl = opener.StartingInline;
            InlineTag tag = useDelims == 1 ? singleCharTag : doubleCharTag;

            if (openerDelims == useDelims)
            {
                // the opener is completely used up - remove the stack entry and reuse the inline element
                inl.Tag            = tag;
                inl.LiteralContent = null;
                inl.FirstChild     = inl.NextSibling;
                inl.NextSibling    = null;

                InlineStack.RemoveStackEntry(opener, subj, closer?.Previous);
            }
            else
            {
                // the opener will only partially be used - stack entry remains (truncated) and a new inline is added.
                opener.DelimiterCount  -= useDelims;
                inl.LiteralContent      = inl.LiteralContent.Substring(0, opener.DelimiterCount);
                inl.SourceLastPosition -= useDelims;

                inl.NextSibling = new Inline(tag, inl.NextSibling);
                inl             = inl.NextSibling;

                inl.SourcePosition = opener.StartingInline.SourcePosition + opener.DelimiterCount;
            }

            // there are two callers for this method, distinguished by the `closer` argument.
            // if closer == null it means the method is called during the initial subject parsing and the closer
            //   characters are at the current position in the subject. The main benefit is that there is nothing
            //   parsed that is located after the matched inline element.
            // if closer != null it means the method is called when the second pass for previously unmatched
            //   stack elements is done. The drawback is that there can be other elements after the closer.
            if (closer != null)
            {
                var clInl = closer.StartingInline;
                if ((closer.DelimiterCount -= useDelims) > 0)
                {
                    // a new inline element must be created because the old one has to be the one that
                    // finalizes the children of the emphasis
                    var newCloserInline = new Inline(clInl.LiteralContent.Substring(useDelims));
                    newCloserInline.SourcePosition = inl.SourceLastPosition = clInl.SourcePosition + useDelims;
                    newCloserInline.SourceLength   = closer.DelimiterCount;
                    newCloserInline.NextSibling    = clInl.NextSibling;

                    clInl.LiteralContent = null;
                    clInl.NextSibling    = null;
                    inl.NextSibling      = closer.StartingInline = newCloserInline;
                }
                else
                {
                    inl.SourceLastPosition = clInl.SourceLastPosition;

                    clInl.LiteralContent = null;
                    inl.NextSibling      = clInl.NextSibling;
                    clInl.NextSibling    = null;
                }
            }
            else if (subj != null)
            {
                inl.SourceLastPosition = subj.Position - closingDelimiterCount + useDelims;
                subj.LastInline        = inl;
            }

            return(useDelims);
        }
Beispiel #9
0
        public static void PostProcessInlineStack(Subject subj, InlineStack first, InlineStack last, InlineStackPriority ignorePriority)
        {
            while (ignorePriority > 0)
            {
                var istack = first;
                while (istack != null)
                {
                    if (istack.Priority >= ignorePriority)
                    {
                        RemoveStackEntry(istack, subj, istack);
                    }
                    else if (0 != (istack.Flags & InlineStackFlags.Closer))
                    {
                        bool canClose;
                        var  iopener = FindMatchingOpener(istack.Previous, istack.Priority, istack.Delimiter, istack.DelimiterCount, (istack.Flags & InlineStackFlags.Opener) > 0, out canClose);
                        if (iopener != null)
                        {
                            bool retry = false;
                            if (iopener.Delimiter == '~')
                            {
                                InlineMethods.MatchInlineStack(iopener, subj, istack.DelimiterCount, istack, (InlineTag)0, InlineTag.Strikethrough);
                                if (istack.DelimiterCount > 1)
                                {
                                    retry = true;
                                }
                            }
                            else
                            {
                                var useDelims = InlineMethods.MatchInlineStack(iopener, subj, istack.DelimiterCount, istack, InlineTag.Emphasis, InlineTag.Strong);
                                if (istack.DelimiterCount > 0)
                                {
                                    retry = true;
                                }
                            }

                            if (retry)
                            {
                                // remove everything between opened and closer (not inclusive).
                                if (istack.Previous != null && iopener.Next != istack.Previous)
                                {
                                    RemoveStackEntry(iopener.Next, subj, istack.Previous);
                                }

                                continue;
                            }
                            else
                            {
                                // remove opener, everything in between, and the closer
                                RemoveStackEntry(iopener, subj, istack);
                            }
                        }
                        else if (!canClose)
                        {
                            // this case means that a matching opener does not exist
                            // remove the Closer flag so that a future Opener can be matched against it.
                            istack.Flags &= ~InlineStackFlags.Closer;
                        }
                    }

                    if (istack == last)
                    {
                        break;
                    }

                    istack = istack.Next;
                }

                ignorePriority--;
            }
        }
Beispiel #10
0
        /// <summary>
        /// Removes a subset of the stack.
        /// </summary>
        /// <param name="first">The first entry to be removed.</param>
        /// <param name="subj">The subject associated with this stack. Can be <see langword="null"/> if the pointers in the subject should not be updated.</param>
        /// <param name="last">The last entry to be removed. Can be <see langword="null"/> if everything starting from <paramref name="first"/> has to be removed.</param>
        public static void RemoveStackEntry(InlineStack first, Subject subj, InlineStack last)
        {
            var curPriority = first.Priority;

            if (last == null)
            {
                if (first.Previous != null)
                {
                    first.Previous.Next = null;
                }
                else if (subj != null)
                {
                    subj.FirstPendingInline = null;
                }

                if (subj != null)
                {
                    last = subj.LastPendingInline;
                    subj.LastPendingInline = first.Previous;
                }

                first = first.Next;
            }
            else
            {
                if (first.Previous != null)
                {
                    first.Previous.Next = last.Next;
                }
                else if (subj != null)
                {
                    subj.FirstPendingInline = last.Next;
                }

                if (last.Next != null)
                {
                    last.Next.Previous = first.Previous;
                }
                else if (subj != null)
                {
                    subj.LastPendingInline = first.Previous;
                }

                if (first == last)
                {
                    return;
                }

                first = first.Next;
                last  = last.Previous;
            }

            if (last == null || first == null)
            {
                return;
            }

            first.Previous = null;
            last.Next      = null;

            // handle case like [*b*] (the whole [..] is being removed but the inner *..* must still be matched).
            // this is not done automatically because the initial * is recognized as a potential closer (assuming
            // potential scenario '*[*' ).
            if (curPriority > 0)
            {
                PostProcessInlineStack(null, first, last, curPriority);
            }
        }