Example #1
0
        public void EnsureChildrenAreWritten()
        {
            string text = "link description/title";

            inline.AppendChild(new LiteralInline(text));
            renderer.Write(pdfBuilder, inline);

            Assert.AreEqual(1, document.LastSection.Elements.Count);
            Paragraph paragraph = (Paragraph)document.LastSection.Elements[0];

            Assert.AreEqual(1, paragraph.Elements.Count);
            Hyperlink hyperlink = (Hyperlink)paragraph.Elements[0];

            Assert.AreEqual(1, hyperlink.Elements.Count);
            FormattedText formatted = (FormattedText)hyperlink.Elements[0];

            Assert.AreEqual(1, formatted.Elements.Count);
            Text plainText = (Text)formatted.Elements[0];

            Assert.AreEqual(text, plainText.Content);
        }
Example #2
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            // Previous char must be a whitespace or a punctuation
            var previousChar = slice.PeekCharExtra(-1);

            if (!previousChar.IsAsciiPunctuation() && !previousChar.IsWhiteSpaceOrZero())
            {
                return(false);
            }

            List <char> pendingEmphasis;

            // Check that an autolink is possible in the current context
            if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
            {
                return(false);
            }

            var startPosition = slice.Start;

            var c = slice.CurrentChar;

            // Precheck URL
            switch (c)
            {
            case 'h':
                if (!slice.MatchLowercase("ttp://", 1) && !slice.MatchLowercase("ttps://", 1))
                {
                    return(false);
                }
                break;

            case 'f':
                if (!slice.MatchLowercase("tp://", 1))
                {
                    return(false);
                }
                break;

            case 'm':
                if (!slice.MatchLowercase("ailto:", 1))
                {
                    return(false);
                }
                break;

            case 'w':
                if (!slice.MatchLowercase("ww.", 1) || previousChar == '/')     // We won't match http:/www. or /www.xxx
                {
                    return(false);
                }
                break;
            }

            // Parse URL
            string link;

            if (!LinkHelper.TryParseUrl(ref slice, out link))
            {
                return(false);
            }


            // If we have any pending emphasis, remove any pending emphasis characters from the end of the link
            if (pendingEmphasis != null)
            {
                for (int i = link.Length - 1; i >= 0; i--)
                {
                    if (pendingEmphasis.Contains(link[i]))
                    {
                        slice.Start--;
                    }
                    else
                    {
                        if (i < link.Length - 1)
                        {
                            link = link.Substring(0, i + 1);
                        }
                        break;
                    }
                }
            }

            // Post-check URL
            switch (c)
            {
            case 'h':
                if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
                {
                    return(false);
                }
                break;

            case 'f':
                if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
                {
                    return(false);
                }
                break;

            case 'm':
                if (string.Equals(link, "mailto:", StringComparison.OrdinalIgnoreCase) || !link.Contains("@"))
                {
                    return(false);
                }
                break;

            case 'w':
                // We require at least two .
                if (link.Length <= "www.x.y".Length || link.IndexOf(".", 4, StringComparison.Ordinal) < 0)
                {
                    return(false);
                }
                break;
            }

            int line;
            int column;
            var inline = new LinkInline()
            {
                Span =
                {
                    Start = processor.GetSourcePosition(startPosition, out line, out column),
                },
                Line     = line,
                Column   = column,
                Url      = c == 'w' ? "http://" + link : link,
                IsClosed = true,
            };

            inline.Span.End = inline.Span.Start + link.Length - 1;
            inline.UrlSpan  = inline.Span;
            inline.AppendChild(new LiteralInline()
            {
                Span     = inline.Span,
                Line     = line,
                Column   = column,
                Content  = new StringSlice(slice.Text, startPosition, startPosition + link.Length - 1),
                IsClosed = true
            });
            processor.Inline = inline;

            return(true);
        }
Example #3
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            // TODO

            var startPosition = slice.Start;

            var c = slice.CurrentChar;

            if (c != '#')
            {
                return(false);
            }

            slice.NextChar();

            var buffer = StringBuilderCache.Local();

            while (true)
            {
                c = slice.CurrentChar;

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

                buffer.Append(c);

                slice.NextChar();
            }

            var tagContent = buffer.ToString();

            var tag = new TagInline
            {
                Content = tagContent,
                Span    = new SourceSpan(processor.GetSourcePosition(startPosition + 1, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
                Line    = line,
                Column  = column
            };

            // Maintain a list of all tags at document level
            var tags = processor.Document.GetData(DocumentKey) as TagGroup;

            if (tags == null)
            {
                tags = new TagGroup();
                processor.Document.SetData(DocumentKey, tags);
            }

            tags.Add(tag);

            // TODO add setting

            var linkInline = new LinkInline
            {
                Span       = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.Start - 1)),
                Line       = line,
                Column     = column,
                Url        = $"#{tagContent}",
                Label      = tagContent,
                IsClosed   = true,
                IsAutoLink = false
            };

            linkInline.UrlSpan = linkInline.Span;
            linkInline.AppendChild(new LiteralInline
            {
                Span     = linkInline.Span,
                Line     = line,
                Column   = column,
                Content  = new StringSlice(slice.Text, startPosition, startPosition + tagContent.Length),
                IsClosed = true
            });


            processor.Inline = linkInline;

            return(true);
        }
    }
Example #4
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);
        }
Example #5
0
        public override bool Match(InlineProcessor processor, ref StringSlice slice)
        {
            // Previous char must be a whitespace or a punctuation
            var previousChar = slice.PeekCharExtra(-1);

            if (!previousChar.IsWhiteSpaceOrZero() && ValidPreviousCharacters.IndexOf(previousChar) == -1)
            {
                return(false);
            }

            List <char> pendingEmphasis;

            // Check that an autolink is possible in the current context
            if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
            {
                return(false);
            }

            var startPosition = slice.Start;
            int domainOffset  = 0;

            var c = slice.CurrentChar;

            // Precheck URL
            switch (c)
            {
            case 'h':
                if (slice.MatchLowercase("ttp://", 1))
                {
                    domainOffset = 7;     // http://
                }
                else if (slice.MatchLowercase("ttps://", 1))
                {
                    domainOffset = 8;     // https://
                }
                else
                {
                    return(false);
                }
                break;

            case 'f':
                if (!slice.MatchLowercase("tp://", 1))
                {
                    return(false);
                }
                domainOffset = 6;     // ftp://
                break;

            case 'm':
                if (!slice.MatchLowercase("ailto:", 1))
                {
                    return(false);
                }
                break;

            case 'w':
                if (!slice.MatchLowercase("ww.", 1))     // We won't match http:/www. or /www.xxx
                {
                    return(false);
                }
                domainOffset = 4;     // www.
                break;
            }

            // Parse URL
            string link;

            if (!LinkHelper.TryParseUrl(ref slice, out link, true))
            {
                return(false);
            }


            // If we have any pending emphasis, remove any pending emphasis characters from the end of the link
            if (pendingEmphasis != null)
            {
                for (int i = link.Length - 1; i >= 0; i--)
                {
                    if (pendingEmphasis.Contains(link[i]))
                    {
                        slice.Start--;
                    }
                    else
                    {
                        if (i < link.Length - 1)
                        {
                            link = link.Substring(0, i + 1);
                        }
                        break;
                    }
                }
            }

            // Post-check URL
            switch (c)
            {
            case 'h':
                if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
                    string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
                {
                    return(false);
                }
                break;

            case 'f':
                if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
                {
                    return(false);
                }
                break;

            case 'm':
                int atIndex = link.IndexOf('@');
                if (atIndex == -1 ||
                    atIndex == 7)     // mailto:@ - no email part
                {
                    return(false);
                }
                domainOffset = atIndex + 1;
                break;
            }

            if (!LinkHelper.IsValidDomain(link, domainOffset))
            {
                return(false);
            }

            int line;
            int column;
            var inline = new LinkInline()
            {
                Span =
                {
                    Start = processor.GetSourcePosition(startPosition, out line, out column),
                },
                Line       = line,
                Column     = column,
                Url        = c == 'w' ? "http://" + link : link,
                IsClosed   = true,
                IsAutoLink = true,
            };

            var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content

            inline.Span.End = inline.Span.Start + link.Length - 1;
            inline.UrlSpan  = inline.Span;
            inline.AppendChild(new LiteralInline()
            {
                Span     = inline.Span,
                Line     = line,
                Column   = column,
                Content  = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
                IsClosed = true
            });
            processor.Inline = inline;

            return(true);
        }
Example #6
0
    public string Transform(string source)
    {
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/YamlSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/BootstrapSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/EmphasisExtraSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/DefinitionListSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/FootnotesSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/AutoLinks.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/ListExtraSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/MediaSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/AbbreviationSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/HardlineBreakSpecs.md
        // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md
        // https://github.com/ilich/Markdig.Prism/blob/main/src/Markdig.Prism/PrismCodeBlockRenderer.cs

        var pipeline = new MarkdownPipelineBuilder()
                       .UseYamlFrontMatter()
                       // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/EmojiSpecs.md
                       //.UseEmojiAndSmiley(new Markdig.Extensions.Emoji.EmojiMapping(new Dictionary<string, string>() { { ":smiley:", "♥" } }, new Dictionary<string, string>()))

                       // UseAdvancedExtensions 2021-01-25
                       // .UseAbbreviations()
                       // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/AutoIdentifierSpecs.md
                       .UseAutoIdentifiers()
                       // .UseCitations()
                       // .UseCustomContainers()
                       // .UseDefinitionLists()
                       // .UseEmphasisExtras()
                       // .UseFigures()
                       // .UseFooters()
                       // .UseFootnotes()
                       //.UseGridTables()
                       // .UseMathematics()
                       // .UseMediaLinks()
                       // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/PipeTableSpecs.md
                       .UsePipeTables()
                       // .UseListExtras()
                       // .UseTaskLists()
                       // .UseDiagrams()
                       // .UseAutoLinks()
                       // https://github.com/xoofx/markdig/blob/master/src/Markdig.Tests/Specs/GenericAttributesSpecs.md
                       .UseGenericAttributes()
                       .Build();

        var doc = Markdown.Parse(source, pipeline);

        // Process headings to insert an intermediate LinkInline
        foreach (var headingBlock in doc.Descendants <HeadingBlock>())
        {
            var inline         = new LinkInline($"#{headingBlock.GetAttributes().Id}", null);
            var previousInline = headingBlock.Inline;
            headingBlock.Inline = null;
            inline.AppendChild(previousInline);
            headingBlock.Inline = inline;
        }

        var anchorTags = doc.Descendants <LinkInline>();

        foreach (var anchor in anchorTags)
        {
            if (anchor is LinkInline link && !link.IsImage)
            {
                if (!anchor.Url.StartsWith(GlobalFunctions.Instance.Url))
                {
                    link.GetAttributes().AddClass("external");
                }
            }

            // TODO disable pending Medium response...
            // if (anchor is LinkInline imageLink && imageLink.IsImage)
            // {
            //     if (imageLink.Url.StartsWith("/assets"))
            //     {
            //         imageLink.Url = GlobalFunctions.Instance.Url + imageLink.Url;
            //     }
            // }
        }

        // Render the doc
        var writer   = new StringWriter();
        var renderer = new HtmlRenderer(writer);

        pipeline.Setup(renderer);
        renderer.Render(doc);

        return(writer.ToString().Trim());
    }