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); }
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); }
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); } }
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); }
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); }
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()); }