Beispiel #1
0
        private void ProcessEmphasis(InlineProcessor processor, List <EmphasisDelimiterInline> delimiters)
        {
            // The following method is inspired by the "An algorithm for parsing nested emphasis and links"
            // at the end of the CommonMark specs.

            // TODO: Benchmark difference between using List and LinkedList here since there could be a few Remove calls

            // Move current_position forward in the delimiter stack (if needed) until
            // we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input – the first one in parse order.)
            for (int i = 0; i < delimiters.Count; i++)
            {
                var closeDelimiter = delimiters[i];
                // Skip delimiters not supported by this instance
                EmphasisDescriptor emphasisDesc = emphasisMap[closeDelimiter.DelimiterChar];
                if (emphasisDesc == null)
                {
                    continue;
                }

                if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount)
                {
                    while (true)
                    {
                        // Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type)
                        // for the first matching potential opener (“matching” means same delimiter).
                        EmphasisDelimiterInline openDelimiter = null;
                        int openDelimiterIndex = -1;
                        for (int j = i - 1; j >= 0; j--)
                        {
                            var previousOpenDelimiter = delimiters[j];

                            var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 ||
                                              (previousOpenDelimiter.Type & DelimiterType.Close) != 0) &&
                                             previousOpenDelimiter.DelimiterCount != closeDelimiter.DelimiterCount &&
                                             (previousOpenDelimiter.DelimiterCount + closeDelimiter.DelimiterCount) % 3 == 0 &&
                                             (previousOpenDelimiter.DelimiterCount % 3 != 0 || closeDelimiter.DelimiterCount % 3 != 0);

                            if (previousOpenDelimiter.DelimiterChar == closeDelimiter.DelimiterChar &&
                                (previousOpenDelimiter.Type & DelimiterType.Open) != 0 &&
                                previousOpenDelimiter.DelimiterCount >= emphasisDesc.MinimumCount && !isOddMatch)
                            {
                                openDelimiter      = previousOpenDelimiter;
                                openDelimiterIndex = j;
                                break;
                            }
                        }

                        if (openDelimiter != null)
                        {
process_delims:
                            Debug.Assert(openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount, "Extra emphasis should have been discarded by now");
                            Debug.Assert(closeDelimiter.DelimiterCount >= emphasisDesc.MinimumCount, "Extra emphasis should have been discarded by now");
                            int delimiterDelta = Math.Min(Math.Min(openDelimiter.DelimiterCount, closeDelimiter.DelimiterCount), emphasisDesc.MaximumCount);

                            // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener.
                            EmphasisInline emphasis = null;
                            {
                                if (delimiterDelta <= 2)           // We can try using the legacy delegate
                                {
                                    #pragma warning disable CS0618 // Support fields marked as obsolete
                                    emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong: delimiterDelta == 2);
                                    #pragma warning restore CS0618 // Support fields marked as obsolete
                                }
                                if (emphasis == null)
                                {
                                    // Go in backwards order to give priority to newer delegates
                                    for (int delegateIndex = TryCreateEmphasisInlineList.Count - 1; delegateIndex >= 0; delegateIndex--)
                                    {
                                        emphasis = TryCreateEmphasisInlineList[delegateIndex].Invoke(closeDelimiter.DelimiterChar, delimiterDelta);
                                        if (emphasis != null)
                                        {
                                            break;
                                        }
                                    }

                                    if (emphasis == null)
                                    {
                                        emphasis = new EmphasisInline()
                                        {
                                            DelimiterChar  = closeDelimiter.DelimiterChar,
                                            DelimiterCount = delimiterDelta
                                        };
                                    }
                                }
                            }
                            Debug.Assert(emphasis != null);

                            // Update position for emphasis
                            var openDelimitercount  = openDelimiter.DelimiterCount;
                            var closeDelimitercount = closeDelimiter.DelimiterCount;

                            emphasis.Span.Start = openDelimiter.Span.Start;
                            emphasis.Line       = openDelimiter.Line;
                            emphasis.Column     = openDelimiter.Column;
                            emphasis.Span.End   = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta;

                            openDelimiter.Span.Start  += delimiterDelta;
                            openDelimiter.Column      += delimiterDelta;
                            closeDelimiter.Span.Start += delimiterDelta;
                            closeDelimiter.Column     += delimiterDelta;

                            openDelimiter.DelimiterCount  -= delimiterDelta;
                            closeDelimiter.DelimiterCount -= delimiterDelta;

                            var embracer = (ContainerInline)openDelimiter;

                            // Copy attributes attached to delimiter to the emphasis
                            var attributes = closeDelimiter.TryGetAttributes();
                            if (attributes != null)
                            {
                                emphasis.SetAttributes(attributes);
                            }

                            // Embrace all delimiters
                            embracer.EmbraceChildrenBy(emphasis);

                            // Remove any intermediate emphasis
                            for (int k = i - 1; k >= openDelimiterIndex + 1; k--)
                            {
                                var literalDelimiter = delimiters[k];
                                literalDelimiter.ReplaceBy(literalDelimiter.AsLiteralInline());
                                delimiters.RemoveAt(k);
                                i--;
                            }

                            if (closeDelimiter.DelimiterCount == 0)
                            {
                                var newParent = openDelimiter.DelimiterCount > 0 ? emphasis : emphasis.Parent;
                                closeDelimiter.MoveChildrenAfter(newParent);
                                closeDelimiter.Remove();
                                delimiters.RemoveAt(i);
                                i--;

                                // Remove the open delimiter if it is also empty
                                if (openDelimiter.DelimiterCount == 0)
                                {
                                    openDelimiter.MoveChildrenAfter(openDelimiter);
                                    openDelimiter.Remove();
                                    delimiters.RemoveAt(openDelimiterIndex);
                                    i--;
                                }
                                break;
                            }

                            // The current delimiters are matching
                            if (openDelimiter.DelimiterCount >= emphasisDesc.MinimumCount)
                            {
                                goto process_delims;
                            }
                            else if (openDelimiter.DelimiterCount > 0)
                            {
                                // There are still delimiter characters left, there's just not enough of them
                                openDelimiter.ReplaceBy(openDelimiter.AsLiteralInline());
                                delimiters.RemoveAt(openDelimiterIndex);
                                i--;
                            }
                            else
                            {
                                // Remove the open delimiter if it is also empty
                                var firstChild = openDelimiter.FirstChild;
                                firstChild.Remove();
                                openDelimiter.ReplaceBy(firstChild);
                                firstChild.IsClosed = true;
                                closeDelimiter.Remove();
                                firstChild.InsertAfter(closeDelimiter);
                                delimiters.RemoveAt(openDelimiterIndex);
                                i--;
                            }
                        }
                        else if ((closeDelimiter.Type & DelimiterType.Open) == 0)
                        {
                            closeDelimiter.ReplaceBy(closeDelimiter.AsLiteralInline());
                            delimiters.RemoveAt(i);
                            i--;
                            break;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            // Any delimiters left must be literal
            for (int i = 0; i < delimiters.Count; i++)
            {
                var delimiter = delimiters[i];
                delimiter.ReplaceBy(delimiter.AsLiteralInline());
            }
            delimiters.Clear();
        }
        private void ProcessEmphasis(List <EmphasisDelimiterInline> delimiters)
        {
            // The following method is inspired by the "An algorithm for parsing nested emphasis and links"
            // at the end of the CommonMark specs.

            // Move current_position forward in the delimiter stack (if needed) until
            // we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input – the first one in parse order.)
            for (int i = 0; i < delimiters.Count; i++)
            {
                var closeDelimiter = delimiters[i];
                // Skip delimiters not supported by this instance
                if (emphasisMap[closeDelimiter.DelimiterChar] == null)
                {
                    continue;
                }

                if ((closeDelimiter.Type & DelimiterType.Close) != 0 && closeDelimiter.DelimiterCount > 0)
                {
                    while (true)
                    {
                        // Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type)
                        // for the first matching potential opener (“matching” means same delimiter).
                        EmphasisDelimiterInline openDelimiter = null;
                        int openDelimiterIndex = -1;
                        for (int j = i - 1; j >= 0; j--)
                        {
                            var previousOpenDelimiter = delimiters[j];

                            var isOddMatch = ((closeDelimiter.Type & DelimiterType.Open) != 0 ||
                                              (previousOpenDelimiter.Type & DelimiterType.Close) != 0) &&
                                             previousOpenDelimiter.DelimiterCount != closeDelimiter.DelimiterCount &&
                                             (previousOpenDelimiter.DelimiterCount + closeDelimiter.DelimiterCount) % 3 == 0;

                            if (previousOpenDelimiter.DelimiterChar == closeDelimiter.DelimiterChar &&
                                (previousOpenDelimiter.Type & DelimiterType.Open) != 0 &&
                                previousOpenDelimiter.DelimiterCount > 0 && !isOddMatch)
                            {
                                openDelimiter      = previousOpenDelimiter;
                                openDelimiterIndex = j;
                                break;
                            }
                        }

                        if (openDelimiter != null)
                        {
process_delims:
                            bool isStrong = openDelimiter.DelimiterCount >= 2 && closeDelimiter.DelimiterCount >= 2;

                            // Insert an emph or strong emph node accordingly, after the text node corresponding to the opener.
                            var emphasis = CreateEmphasisInline?.Invoke(closeDelimiter.DelimiterChar, isStrong)
                                           ?? new EmphasisInline()
                            {
                                DelimiterChar = closeDelimiter.DelimiterChar,
                                IsDouble      = isStrong
                            };

                            // Update position for emphasis
                            var closeDelimitercount = closeDelimiter.DelimiterCount;
                            var delimiterDelta      = isStrong ? 2 : 1;

                            emphasis.Span.Start = openDelimiter.Span.Start;
                            emphasis.Line       = openDelimiter.Line;
                            emphasis.Column     = openDelimiter.Column;
                            emphasis.Span.End   = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta;

                            openDelimiter.Span.Start  += delimiterDelta;
                            openDelimiter.Column      += delimiterDelta;
                            closeDelimiter.Span.Start += delimiterDelta;
                            closeDelimiter.Column     += delimiterDelta;

                            openDelimiter.DelimiterCount  -= delimiterDelta;
                            closeDelimiter.DelimiterCount -= delimiterDelta;

                            var embracer = (ContainerInline)openDelimiter;

                            // Copy attributes attached to delimiter to the emphasis
                            var attributes = closeDelimiter.TryGetAttributes();
                            if (attributes != null)
                            {
                                emphasis.SetAttributes(attributes);
                            }

                            // Embrace all delimiters
                            embracer.EmbraceChildrenBy(emphasis);

                            // Remove any intermediate emphasis
                            for (int k = i - 1; k >= openDelimiterIndex + 1; k--)
                            {
                                var literalDelimiter = delimiters[k];
                                var literal          = new LiteralInline()
                                {
                                    Content  = new StringSlice(literalDelimiter.ToLiteral()),
                                    IsClosed = true,
                                    Span     = literalDelimiter.Span,
                                    Line     = literalDelimiter.Line,
                                    Column   = literalDelimiter.Column
                                };

                                literalDelimiter.ReplaceBy(literal);

                                delimiters.RemoveAt(k);
                                i--;
                            }

                            if (closeDelimiter.DelimiterCount == 0)
                            {
                                var newParent = openDelimiter.DelimiterCount > 0 ? emphasis : emphasis.Parent;
                                closeDelimiter.MoveChildrenAfter(newParent);
                                closeDelimiter.Remove();
                                delimiters.RemoveAt(i);
                                i--;

                                // Remove the open delimiter if it is also empty
                                if (openDelimiter.DelimiterCount == 0)
                                {
                                    openDelimiter.MoveChildrenAfter(openDelimiter);
                                    openDelimiter.Remove();
                                    delimiters.RemoveAt(openDelimiterIndex);
                                    i--;
                                }
                                break;
                            }

                            // The current delimiters are matching
                            if (openDelimiter.DelimiterCount > 0)
                            {
                                goto process_delims;
                            }
                            else
                            {
                                // Remove the open delimiter if it is also empty
                                var firstChild = openDelimiter.FirstChild;
                                firstChild.Remove();
                                openDelimiter.ReplaceBy(firstChild);
                                firstChild.IsClosed = true;
                                closeDelimiter.Remove();
                                firstChild.InsertAfter(closeDelimiter);
                                delimiters.RemoveAt(openDelimiterIndex);
                                i--;
                            }
                        }
                        else if ((closeDelimiter.Type & DelimiterType.Open) == 0)
                        {
                            var literal = new LiteralInline()
                            {
                                Content  = new StringSlice(closeDelimiter.ToLiteral()),
                                IsClosed = true,
                                Span     = closeDelimiter.Span,
                                Line     = closeDelimiter.Line,
                                Column   = closeDelimiter.Column
                            };

                            closeDelimiter.ReplaceBy(literal);
                            // Notifies processor as we are creating an inline locally
                            delimiters.RemoveAt(i);
                            i--;
                            break;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            // Any delimiters left must be literal
            for (int i = 0; i < delimiters.Count; i++)
            {
                var delimiter = delimiters[i];
                var literal   = new LiteralInline()
                {
                    Content  = new StringSlice(delimiter.ToLiteral()),
                    IsClosed = true,
                    Span     = delimiter.Span,
                    Line     = delimiter.Line,
                    Column   = delimiter.Column
                };

                delimiter.ReplaceBy(literal);
            }
            delimiters.Clear();
        }