diff --git a/README.md b/README.md index 7238b7a4..fcaa8775 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ If you have used and benefitted from this library. Please feel free to sponsor m **Markdown flavors** - GitHub Flavoured Markdown conversion for br, pre, tasklists, and table. Use `var config = new ReverseMarkdown.Config(githubFlavoured:true);`. By default the table will always be converted to Github flavored markdown immaterial of this flag - Slack Flavoured Markdown conversion. Use `var config = new ReverseMarkdown.Config { SlackFlavored = true };` +- CommonMark-focused output with opt-in flags to preserve compatibility. Use `var config = new ReverseMarkdown.Config { CommonMark = true };` This mode may emit inline HTML for tricky emphasis/link cases unless you disable `CommonMarkUseHtmlInlineTags`. **Tables** - Support for nested tables (converted as HTML inside markdown) @@ -89,6 +90,10 @@ var converter = new ReverseMarkdown.Converter(config); * `DefaultCodeBlockLanguage` - Option to set the default code block language for Github style markdown if class based language markers are not available * `GithubFlavored` - Github style markdown for br, pre and table. Default is false * `SlackFlavored` - Slack style markdown formatting. When enabled, uses `*` for bold, `_` for italic, `~` for strikethrough, and `•` for list bullets. Default is false +* `CommonMark` - Enable CommonMark-focused output rules. Default is false +* `CommonMarkUseHtmlInlineTags` - When CommonMark is enabled, emit HTML for inline tags (`em`, `strong`, `a`, `img`) to avoid delimiter edge cases. Default is true +* `CommonMarkIntrawordEmphasisSpacing` - When CommonMark is enabled, insert spaces to avoid intraword emphasis. Default is false + * Note: CommonMark is best used on its own. Combining `CommonMark` with `GithubFlavored` can produce mixed output; keep them separate unless you explicitly want that behavior. * `CleanupUnnecessarySpaces` - Cleanup unnecessary spaces in the output. Default is true * `SuppressDivNewlines` - Removes prefixed newlines from `div` tags. Default is false * `ListBulletChar` - Allows you to change the bullet character. Default value is `-`. Some systems expect the bullet character to be `*` rather than `-`, this config allows you to change it. Note: This option is ignored when `SlackFlavored` is enabled diff --git a/src/ReverseMarkdown.Test/CommonMarkSpecTests.cs b/src/ReverseMarkdown.Test/CommonMarkSpecTests.cs new file mode 100644 index 00000000..4841f0f7 --- /dev/null +++ b/src/ReverseMarkdown.Test/CommonMarkSpecTests.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using Markdig; +using Xunit; +using Xunit.Abstractions; + +namespace ReverseMarkdown.Test +{ + public class CommonMarkSpecTests + { + private readonly ITestOutputHelper _output; + + public CommonMarkSpecTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void CommonMark_Spec_Examples_RoundTripHtml() + { + var specPath = GetSpecPath(); + Assert.True( + File.Exists(specPath), + "CommonMark spec file not found. Download commonmark.json to src/ReverseMarkdown.Test/TestData/commonmark.json" + ); + + var json = File.ReadAllText(specPath); + var examples = JsonSerializer.Deserialize>( + json, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + if (examples == null || examples.Count == 0) { + throw new InvalidOperationException("CommonMark spec file is empty or invalid."); + } + + var maxExamples = GetIntEnvironmentVariable("COMMONMARK_MAX_EXAMPLES"); + var selected = maxExamples > 0 ? examples.Take(maxExamples).ToList() : examples; + + var converter = new Converter(new Config { CommonMark = true }); + var pipeline = new MarkdownPipelineBuilder().Build(); + var failures = new List(); + + foreach (var example in selected) { + if (string.IsNullOrEmpty(example.Html)) { + continue; + } + + var markdown = converter.Convert(example.Html); + var roundTripHtml = Markdown.ToHtml(markdown, pipeline); + + var expected = NormalizeHtml(example.Html); + var actual = NormalizeHtml(roundTripHtml); + + if (!string.Equals(expected, actual, StringComparison.Ordinal)) { + failures.Add(FormatFailure(example, markdown, expected, actual)); + if (failures.Count >= 10) { + break; + } + } + } + + if (failures.Count > 0) { + var message = $"CommonMark failures: {failures.Count}/{selected.Count}"; + _output.WriteLine(message); + foreach (var failure in failures) { + _output.WriteLine(failure); + } + + Assert.Fail(message); + } + } + + private static string NormalizeHtml(string html) + { + if (string.IsNullOrEmpty(html)) { + return string.Empty; + } + + var normalized = html.Replace("\r\n", "\n").TrimEnd(); + normalized = normalized.Replace("
", "
"); + normalized = normalized.Replace("
", "
"); + normalized = normalized.Replace("
", "
"); + normalized = normalized.Replace("
", "
"); + normalized = normalized.Replace(" ", "\n"); + normalized = normalized.Replace(" ", "\n"); + normalized = System.Text.RegularExpressions.Regex.Replace(normalized, @">\s+<", "><"); + + var doc = new HtmlAgilityPack.HtmlDocument(); + doc.LoadHtml(normalized); + normalized = doc.DocumentNode.InnerHtml; + normalized = normalized.Replace("\u00A0", " "); + + return normalized; + } + + private static string FormatFailure(CommonMarkExample example, string markdown, string expected, string actual) + { + return $"Example {example.Example} ({example.Section}):\n" + + $"Markdown:\n{markdown}\n" + + $"Expected HTML:\n{expected}\n" + + $"Actual HTML:\n{actual}\n"; + } + + private static int GetIntEnvironmentVariable(string name) + { + var value = Environment.GetEnvironmentVariable(name); + return int.TryParse(value, out var result) ? result : 0; + } + + private static string GetSpecPath() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory != null && !File.Exists(Path.Combine(directory.FullName, "ReverseMarkdown.Test.csproj"))) { + directory = directory.Parent; + } + + if (directory == null) { + throw new DirectoryNotFoundException("Could not locate test project directory."); + } + + return Path.Combine(directory.FullName, "TestData", "commonmark.json"); + } + + private sealed class CommonMarkExample + { + public int Example { get; set; } + public string Section { get; set; } = string.Empty; + public string Html { get; set; } = string.Empty; + } + } +} diff --git a/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsCodeTag_ThenConvertToMarkdownWithBackTick.verified.md b/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsCodeTag_ThenConvertToMarkdownWithBackTick.verified.md deleted file mode 100644 index 0772d76f..00000000 --- a/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsCodeTag_ThenConvertToMarkdownWithBackTick.verified.md +++ /dev/null @@ -1 +0,0 @@ -This text has code `alert();` \ No newline at end of file diff --git a/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsEmTag_ThenConvertToMarkdownSingleAsterisks.verified.md b/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsEmTag_ThenConvertToMarkdownSingleAsterisks.verified.md deleted file mode 100644 index 8296644f..00000000 --- a/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsEmTag_ThenConvertToMarkdownSingleAsterisks.verified.md +++ /dev/null @@ -1 +0,0 @@ -This is a *sample* paragraph \ No newline at end of file diff --git a/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsITag_ThenConvertToMarkdownSingleAsterisks.verified.md b/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsITag_ThenConvertToMarkdownSingleAsterisks.verified.md deleted file mode 100644 index 8296644f..00000000 --- a/src/ReverseMarkdown.Test/ConverterTests.WhenThereIsITag_ThenConvertToMarkdownSingleAsterisks.verified.md +++ /dev/null @@ -1 +0,0 @@ -This is a *sample* paragraph \ No newline at end of file diff --git a/src/ReverseMarkdown.Test/ConverterTests.cs b/src/ReverseMarkdown.Test/ConverterTests.cs index 1adb7ee3..7d22662f 100644 --- a/src/ReverseMarkdown.Test/ConverterTests.cs +++ b/src/ReverseMarkdown.Test/ConverterTests.cs @@ -1,1679 +1,537 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using VerifyTests; -using VerifyXunit; -using Xunit; -using Xunit.Abstractions; - -namespace ReverseMarkdown.Test -{ - public class ConverterTests - { - private readonly ITestOutputHelper _testOutputHelper; - private readonly VerifySettings _verifySettings; - - public ConverterTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - _verifySettings = new VerifySettings(); - _verifySettings.DisableRequireUniquePrefix(); - } - - [Fact] - public Task WhenThereIsAsideTag() +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using VerifyTests; +using VerifyXunit; +using Xunit; +using Xunit.Abstractions; + +namespace ReverseMarkdown.Test +{ + public class ConverterTests + { + private readonly ITestOutputHelper _testOutputHelper; + private readonly VerifySettings _verifySettings; + + public ConverterTests(ITestOutputHelper testOutputHelper) { - var html = " This text appears after aside."; - return CheckConversion(html); + _testOutputHelper = testOutputHelper; + _verifySettings = new VerifySettings(); + _verifySettings.DisableRequireUniquePrefix(); } - [Fact] - public Task WhenThereAreSemanticContainerTags() + private static readonly Lazy> CaseHtmlById = new(() => { - var html = "
Whatever Inc.

Thanks for your enquiry.

Section intro
Article text
Diagram
Figure caption
Mail us
"; - return CheckConversion(html, new Config - { - UnknownTags = Config.UnknownTagsOption.PassThrough - }); + var path = Path.Combine(GetProjectDirectory().FullName, "TestData", "cases.json"); + var json = File.ReadAllText(path); + var cases = JsonSerializer.Deserialize>(json, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); + return cases.Where(item => !string.IsNullOrWhiteSpace(item.Id)) + .ToDictionary(item => item.Id, item => item.Html); + }); + + private static string LoadHtml(string id) + { + if (!CaseHtmlById.Value.TryGetValue(id, out var html)) { + throw new InvalidOperationException($"Missing html for case '{id}'."); + } + + return html; } - - [Fact] - public Task WhenThereIsHtmlLink_ThenConvertToMarkdownLink() - { - var html = @"This is a link"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsHtmlLinkWithTitle_ThenConvertToMarkdownLink() - { - var html = @"This is a link"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereAreMultipleLinks_ThenConvertThemToMarkdownLinks() - { - var html = - @"This is first link and second link"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsHtmlLinkNotWhitelisted_ThenBypass() - { - var html = - @"Leave http, https, ftp, ftps, file. Remove data, tel and whatever"; - return CheckConversion(html, new Config - { - WhitelistUriSchemes = {"http", "https", "ftp", "ftps", "file"} - }); - } - - [Fact] - public Task WhenThereHtmlWithHrefAndNoSchema_WhitelistedEmptyString_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"yeah", - config: new Config - { - WhitelistUriSchemes = {""} - } - ); - } - - [Fact] - public Task WhenThereHtmlWithHrefAndNoSchema_NotWhitelisted_ThenConvertToPlain() - { - return CheckConversion( - html: @"yeah", - config: new Config - { - WhitelistUriSchemes = {"whatever"} - } - ); - } - - [Fact] - public Task WhenThereIsHtmlLinkWithDisallowedCharsInChildren_ThenEscapeTextInMarkdown() - { - return CheckConversion( - html: @"this ]( might break things", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public Task WhenThereIsHtmlLinkWithParensInHref_ThenEscapeHrefInMarkdown() - { - return CheckConversion( - html: @"link", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public Task WhenThereIsHtmlWithProtocolRelativeUrlHrefAndNameNotMatching_SmartHandling_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"example.com", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public Task WhenThereIsHtmlWithHrefAndNameNotMatching_SmartHandling_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"Something intact", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public Task WhenThereIsHtmlWithHrefAndNameMatching_SmartHandling_ThenConvertToPlain() - { - return CheckConversion( - html: @"http://example.com/abc?x", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public void WhenThereIsHtmlWithHttpSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain() - { - var config = new Config() - { - SmartHrefHandling = true - }; - var converter = new Converter(config); - var result = converter.Convert(@"example.com"); - Assert.Equal("http://example.com", result, StringComparer.OrdinalIgnoreCase); - - var result1 = converter.Convert(@"example.com"); - Assert.Equal("https://example.com", result1, StringComparer.OrdinalIgnoreCase); - } - - [Fact] - public Task WhenThereIsHtmlWithMailtoSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain() - { - return CheckConversion( - html: @"george@example.com", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public Task WhenThereIsHtmlWithTelSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain() - { - return CheckConversion( - html: @"+1123-45678", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public void WhenThereIsHtmlLinkWithHttpSchemaAndNameWithout_SmartHandling_ThenOutputOnlyHref() - { - var config = new Config - { - SmartHrefHandling = true - }; - var converter = new Converter(config); - var result = converter.Convert(@"example.com"); - Assert.Equal("http://example.com", result, StringComparer.OrdinalIgnoreCase); - var result1 = converter.Convert(@"example.com"); - Assert.Equal("https://example.com", result1, StringComparer.OrdinalIgnoreCase); - } - - [Fact] - public void WhenThereIsHtmlNonWellFormedLinkLink_SmartHandling_ThenConvertToMarkdown() - { - var config = new Config - { - SmartHrefHandling = true - }; - - //The string is not correctly escaped. - var converter = new Converter(config); - var result = - converter.Convert( - @"http://example.com/path/file name.docx"); - Assert.Equal("[http://example.com/path/file name.docx](http://example.com/path/file%20name.docx)", result, - StringComparer.OrdinalIgnoreCase); - - //The string is an absolute Uri that represents an implicit file Uri. - var result1 = converter.Convert(@" c:\\directory\filename"); - Assert.Equal(@"[c:\\directory\filename](c:\\directory\filename)", result1, - StringComparer.OrdinalIgnoreCase); - - //The string is an absolute URI that is missing a slash before the path. - var result2 = - converter.Convert(@"file://c:/directory/filename"); - Assert.Equal("[file://c:/directory/filename](file://c:/directory/filename)", result2, - StringComparer.OrdinalIgnoreCase); - - //The string contains unescaped backslashes even if they are treated as forward slashes. - var result3 = converter.Convert(@"http:\\host/path/file"); - Assert.Equal(@"[http:\\host/path/file](http:\\host/path/file)", result3, StringComparer.OrdinalIgnoreCase); - } - - [Fact] - public Task WhenThereIsHtmlLinkWithoutHttpSchemaAndNameWithoutScheme_SmartHandling_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"example.com", - config: new Config - { - SmartHrefHandling = true - } - ); - } - - [Fact] - public Task WhenThereAreStrongTag_ThenConvertToMarkdownDoubleAsterisks() - { - var html = "This paragraph contains bold text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereAreBTag_ThenConvertToMarkdownDoubleAsterisks() - { - var html = "This paragraph contains bold text"; - return CheckConversion(html); - } - - [Fact] - public Task - WhenThereIsEncompassingStrongOrBTag_ThenConvertToMarkdownDoubleAsterisks_AnyStrongOrBTagsInsideAreIgnored() - { - var html = - "Paragraph is encompassed with strong tag and also has bold text words within it"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsSingleAsteriskInText_ThenConvertToMarkdownEscapedAsterisk() - { - var html = "This is a sample(*) paragraph"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsEmTag_ThenConvertToMarkdownSingleAsterisks() - { - var html = "This is a sample paragraph"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsITag_ThenConvertToMarkdownSingleAsterisks() - { - var html = "This is a sample paragraph"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsEncompassingEmOrITag_ThenConvertToMarkdownSingleAsterisks_AnyEmOrITagsInsideAreIgnored() - { - var html = "This is a sample paragraph"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsBreakTag_ThenConvertToMarkdownDoubleSpacesCarriageReturn() - { - var html = "This is a paragraph.
This line appears after break."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsCodeTag_ThenConvertToMarkdownWithBackTick() - { - var html = "This text has code alert();"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsH1Tag_ThenConvertToMarkdownHeader() - { - var html = "This text has

header

. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsH2Tag_ThenConvertToMarkdownHeader() - { - var html = "This text has

header

. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsH3Tag_ThenConvertToMarkdownHeader() - { - var html = "This text has

header

. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsH4Tag_ThenConvertToMarkdownHeader() - { - var html = "This text has

header

. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsH5Tag_ThenConvertToMarkdownHeader() - { - var html = "This text has
header
. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsH6Tag_ThenConvertToMarkdownHeader() - { - var html = "This text has
header
. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsHeadingInsideTable_ThenIgnoreHeadingLevel() - { - var html = - $"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}

Heading text

Content
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsBlockquoteTag_ThenConvertToMarkdownBlockquote() - { - var html = "This text has
blockquote
. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsEmptyBlockquoteTag_ThenConvertToMarkdownBlockquote() - { - var html = "This text has
. This text appear after header."; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsParagraphTag_ThenConvertToMarkdownDoubleLineBreakBeforeAndAfter() - { - var html = "This text has markup

paragraph.

Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsHorizontalRule_ThenConvertToMarkdownHorizontalRule() - { - var html = "This text has horizontal rule.
Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsImgTag_ThenConvertToMarkdownImage() - { - var html = - @"This text has image . Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsImgTagWithoutTitle_ThenConvertToMarkdownImageWithoutTitle() - { - var html = - @"This text has image . Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsImgTagWithoutAltText_ThenConvertToMarkdownImageWithoutAltText() - { - var html = - @"This text has image . Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsImgTagWithMultilineAltText_ThenEnsureNoBlankLinesInMarkdownAltText() - { - var html = - $@"This text has image . Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsImgTagWithBracesInAltText_ThenEnsureAltTextIsEscapedInMarkdown() - { - var html = - @"This text has image . Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsImgTag_SchemeNotWhitelisted_ThenEmptyOutput() - { - return CheckConversion( - html: @"", - config: new Config - { - WhitelistUriSchemes = {"http"} - } - ); - } - - [Fact] - public Task WhenThereIsImgTag_SchemeIsWhitelisted_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"", - config: new Config() - { - WhitelistUriSchemes = {"data"} - } - ); - } - - [Fact] - public Task WhenThereIsImgTagAndSrcWithNoSchema_WhitelistedEmptyString_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"", - config: new Config() - { - WhitelistUriSchemes = {""} - } - ); - } - - [Fact] - public Task WhenThereIsImgTagAndSrcWithNoSchema_NotWhitelisted_ThenConvertToPlain() - { - return CheckConversion( - html: @"", - config: new Config() - { - WhitelistUriSchemes = {"whatever"} - } - ); - } - - [Fact] - public Task WhenThereIsImgTagWithRelativeUrl_NotWhitelisted_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"", - config: new Config() - { - WhitelistUriSchemes = {"data"} - } - ); - } - - [Fact] - public Task WhenThereIsImgTagWithUnixUrl_ConfigHasWhitelist_ThenConvertToMarkdown() - { - return CheckConversion( - html: @"", - config: new Config() - { - WhitelistUriSchemes = {"file"} - } - ); - } - - [Fact] - public Task WhenThereIsImgTagWithHttpProtocolRelativeUrl_ConfigHasWhitelist_ThenConvertToMarkdown() - { - var html = @""; - var config = new Config - { - WhitelistUriSchemes = {"http"} - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenThereIsBase64ImgTag_WithDefaultConfig_ThenIncludeInMarkdown() - { - var html = @"

Before

After

"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsBase64ImgTag_WithSkipConfig_ThenSkipImage() - { - var html = @"

Before

After

"; - var config = new Config { Base64Images = Config.Base64ImageHandling.Skip }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenThereIsBase64JpegImgTag_WithSkipConfig_ThenSkipImage() - { - var html = @""; - var config = new Config { Base64Images = Config.Base64ImageHandling.Skip }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenThereIsBase64ImgTag_WithSaveToFileConfigButNoDirectory_ThenSkipImage() - { - var html = @"

Before

After

"; - var config = new Config - { - Base64Images = Config.Base64ImageHandling.SaveToFile - // Base64ImageSaveDirectory is not set - }; - return CheckConversion(html, config); - } - - [Fact] - public void WhenThereIsBase64PngImgTag_WithSaveToFileConfigAndValidDirectory_ThenSaveImageAndReferenceFilePath() - { - var tempDir = Path.Combine(Path.GetTempPath(), "reversemarkdown_test_" + Guid.NewGuid()); - try - { - var html = @"

Before

After

"; - var config = new Config - { - Base64Images = Config.Base64ImageHandling.SaveToFile, - Base64ImageSaveDirectory = tempDir - }; - - var converter = new Converter(config); - var result = converter.Convert(html); - - // Verify the image file was created - var imageFiles = Directory.GetFiles(tempDir, "image_*.png"); - Assert.Single(imageFiles); - Assert.True(File.Exists(imageFiles[0])); - - // Verify the markdown references the saved file path - Assert.Contains(imageFiles[0], result); - Assert.Contains("Before", result); - Assert.Contains("After", result); - Assert.Contains("![test]", result); - } - finally - { - // Clean up temp directory - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir, true); - } - } - } - - [Fact] - public void WhenThereIsBase64JpegImgTag_WithSaveToFileConfigAndValidDirectory_ThenSaveImageWithJpgExtension() - { - var tempDir = Path.Combine(Path.GetTempPath(), "reversemarkdown_test_" + Guid.NewGuid()); - try - { - var html = @""; - var config = new Config - { - Base64Images = Config.Base64ImageHandling.SaveToFile, - Base64ImageSaveDirectory = tempDir - }; - - var converter = new Converter(config); - var result = converter.Convert(html); - - // Verify the image file was created with .jpg extension - var imageFiles = Directory.GetFiles(tempDir, "image_*.jpg"); - Assert.Single(imageFiles); - Assert.True(File.Exists(imageFiles[0])); - Assert.EndsWith(".jpg", imageFiles[0]); - - // Verify the markdown references the saved file path - Assert.Contains(imageFiles[0], result); - Assert.Contains("![jpeg]", result); - } - finally - { - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir, true); - } - } - } - - [Fact] - public void WhenMultipleBase64ImgTags_WithSaveToFileConfig_ThenSaveAllImagesWithUniqueNames() - { - var tempDir = Path.Combine(Path.GetTempPath(), "reversemarkdown_test_" + Guid.NewGuid()); - try - { - var html = @""; - var config = new Config - { - Base64Images = Config.Base64ImageHandling.SaveToFile, - Base64ImageSaveDirectory = tempDir - }; - - var converter = new Converter(config); - var result = converter.Convert(html); - - // Verify both image files were created with unique names - var imageFiles = Directory.GetFiles(tempDir, "image_*.png"); - Assert.Equal(2, imageFiles.Length); - - // Verify both images are referenced in the markdown - Assert.Contains("![first]", result); - Assert.Contains("![second]", result); - } - finally - { - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir, true); - } - } - } - - [Fact] - public void WhenBase64ImgTag_WithCustomFileNameGenerator_ThenUseCustomFileName() - { - var tempDir = Path.Combine(Path.GetTempPath(), "reversemarkdown_test_" + Guid.NewGuid()); - try - { - var html = @""; - var config = new Config - { - Base64Images = Config.Base64ImageHandling.SaveToFile, - Base64ImageSaveDirectory = tempDir, - Base64ImageFileNameGenerator = (index, mimeType) => $"custom_image_{index}" - }; - - var converter = new Converter(config); - var result = converter.Convert(html); - - // Verify the custom filename was used - var imageFiles = Directory.GetFiles(tempDir, "custom_image_*.png"); - Assert.Single(imageFiles); - Assert.Contains("custom_image_0", imageFiles[0]); - } - finally - { - if (Directory.Exists(tempDir)) - { - Directory.Delete(tempDir, true); - } - } - } - - [Fact] - public void WhenBase64ImgTag_WithSaveToFileAndNonExistentDirectory_ThenCreateDirectoryAndSaveImage() - { - var tempDir = Path.Combine(Path.GetTempPath(), "reversemarkdown_test_" + Guid.NewGuid(), "nested", "path"); - try - { - // Ensure the directory does not exist - Assert.False(Directory.Exists(tempDir)); - - var html = @""; - var config = new Config - { - Base64Images = Config.Base64ImageHandling.SaveToFile, - Base64ImageSaveDirectory = tempDir - }; - - var converter = new Converter(config); - var result = converter.Convert(html); - - // Verify the directory was created - Assert.True(Directory.Exists(tempDir)); - - // Verify the image file was created - var imageFiles = Directory.GetFiles(tempDir, "image_*.png"); - Assert.Single(imageFiles); - } - finally - { - // Clean up the entire temp directory tree - var rootTempDir = Path.Combine(Path.GetTempPath(), Path.GetFileName(Path.GetDirectoryName(Path.GetDirectoryName(tempDir)))); - if (Directory.Exists(rootTempDir)) - { - Directory.Delete(rootTempDir, true); - } - } - } - - [Fact] - public Task WhenThereIsPreTag_ThenConvertToMarkdownPre() - { - var html = "This text has pre tag content
Predefined text
Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsEmptyPreTag_ThenConvertToMarkdownPre() - { - var html = "This text has pre tag content

Next line of text"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsEmptyPreTag_ThenConvertToMarkdownPre_GFM() - { - var html = "This text has pre tag content

Next line of text"; - return CheckConversion(html, new Config {GithubFlavored = true}); - } - - [Fact] - public Task WhenThereIsUnorderedList_ThenConvertToMarkdownList() - { - var html = "This text has unordered list.
  • Item1
  • Item2
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsUnorderedListAndBulletIsAsterisk_ThenConvertToMarkdownList() - { - var html = "This text has unordered list.
  • Item1
  • Item2
"; - return CheckConversion(html, new Config {ListBulletChar = '*'}); - } - - [Fact] - public Task WhenThereIsInputListWithGithubFlavoredEnabled_ThenConvertToMarkdownCheckList() - { - var html = "
  • Unchecked
  • Checked
"; - - var config = new Config - { - GithubFlavored = true, - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task WhenThereIsInputListWithGithubFlavoredDisabled_ThenConvertToTypicalMarkdownList() - { - var html = "
  • Unchecked
  • Checked
"; - - var config = new Config - { - GithubFlavored = false, - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task WhenThereIsOrderedList_ThenConvertToMarkdownList() - { - var html = "This text has ordered list.
  1. Item1
  2. Item2
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsOrderedListWithNestedUnorderedList_ThenConvertToMarkdownListWithNestedList() - { - var html = - "This text has ordered list.
  1. OuterItem1
    • InnerItem1
    • InnerItem2
  2. Item2
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsUnorderedListWithNestedOrderedList_ThenConvertToMarkdownListWithNestedList() - { - var html = - "This text has ordered list.
  • OuterItem1
    1. InnerItem1
    2. InnerItem2
  • Item2
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenThereIsWhitespaceAroundNestedLists_PreventBlankLinesWhenConvertingToMarkdownList() - { - var html = $"
    {Environment.NewLine} "; - html += - $"
  • OuterItem1{Environment.NewLine}
      {Environment.NewLine}
    1. InnerItem1
    2. {Environment.NewLine}
    {Environment.NewLine}
  • {Environment.NewLine}"; - html += $"
  • Item2
  • {Environment.NewLine}"; - html += - $"
      {Environment.NewLine}
    1. InnerItem2
    2. {Environment.NewLine}
    {Environment.NewLine}"; - html += $"
  • Item3
  • {Environment.NewLine}"; - html += "
"; - - return CheckConversion(html); - } - - [Fact] - public Task - WhenListItemTextContainsLeadingAndTrailingSpacesAndTabs_ThenConvertToMarkdownListItemWithSpacesAndTabsStripped() - { - var html = @"
  1. This is a text with leading and trailing spaces and tabs
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenListContainsNewlineAndTabBetweenTagBorders_CleanupAndConvertToMarkdown() - { - var html = - $"
    {Environment.NewLine}\t
  1. {Environment.NewLine}\t\tItem1
  2. {Environment.NewLine}\t
  3. {Environment.NewLine}\t\tItem2
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenListContainsMultipleParagraphs_ConvertToMarkdownAndIndentSiblings() - { - var html = - @"
    -
  1. -

    Paragraph 1

    -

    Paragraph 1.1

    -

    Paragraph 1.2

  2. -
  3. -

    Paragraph 3

"; - - return CheckConversion(html); - } - - [Fact] - public Task WhenListContainsParagraphsOutsideItems_ConvertToMarkdownAndIndentSiblings() - { - var html = - @"
    -
  1. Item1
  2. -

    Item 1 additional info

    -
  3. Item2
  4. -
"; - - return CheckConversion(html); - } - - [Fact] - public Task When_OrderedListIsInTable_LeaveListAsHtml() - { - var html = "
Heading
  1. Item1
"; - return CheckConversion(html); - } - + + private static DirectoryInfo GetProjectDirectory() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory != null && !File.Exists(Path.Combine(directory.FullName, "ReverseMarkdown.Test.csproj"))) { + directory = directory.Parent; + } + + if (directory == null) { + throw new DirectoryNotFoundException("Could not locate test project directory."); + } + + return directory; + } + + private sealed class CaseHtml + { + public string Id { get; set; } = string.Empty; + public string Html { get; set; } = string.Empty; + } + + + + + + + + + + + + + + + + + + + + + + + + + + + [Fact] - public Task When_UnorderedListIsInTable_LeaveListAsHtml() + public void WhenThereIsHtmlWithHttpSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain() { - var html = "
Heading
  • Item1
"; - return CheckConversion(html); + var config = new Config() + { + SmartHrefHandling = true + }; + var converter = new Converter(config); + var result = converter.Convert(LoadHtml("SmartHandling_HttpScheme_Http")); + Assert.Equal("http://example.com", result, StringComparer.OrdinalIgnoreCase); + + var result1 = converter.Convert(LoadHtml("SmartHandling_HttpScheme_Https")); + Assert.Equal("https://example.com", result1, StringComparer.OrdinalIgnoreCase); } + + + + [Fact] - public Task When_Underline_Tag_With_UnknownTagsReplacer_ThenConvertToItalics() + public void WhenThereIsHtmlLinkWithHttpSchemaAndNameWithout_SmartHandling_ThenOutputOnlyHref() { - var html = "This is underline text."; var config = new Config { - UnknownTagsReplacer = { ["u"] = "*" } + SmartHrefHandling = true }; - return CheckConversion(html, config); + var converter = new Converter(config); + var result = converter.Convert(LoadHtml("SmartHandling_OutputOnlyHref_Http")); + Assert.Equal("http://example.com", result, StringComparer.OrdinalIgnoreCase); + var result1 = converter.Convert(LoadHtml("SmartHandling_OutputOnlyHref_Https")); + Assert.Equal("https://example.com", result1, StringComparer.OrdinalIgnoreCase); } [Fact] - public Task When_Underline_Tag_With_TagAlias_ThenConvertToItalics() + public void WhenThereIsHtmlNonWellFormedLinkLink_SmartHandling_ThenConvertToMarkdown() { - var html = "This is underline text."; var config = new Config { - TagAliases = { ["u"] = "em" } + SmartHrefHandling = true }; - return CheckConversion(html, config); + + //The string is not correctly escaped. + var converter = new Converter(config); + var result = converter.Convert(LoadHtml("SmartHandling_NonWellFormed")); + Assert.Equal("[http://example.com/path/file name.docx](http://example.com/path/file%20name.docx)", result, + StringComparer.OrdinalIgnoreCase); + + //The string is an absolute Uri that represents an implicit file Uri. + var result1 = converter.Convert(LoadHtml("SmartHandling_ImplicitFile")); + Assert.Equal(@"[c:\\directory\filename](c:\\directory\filename)", result1, + StringComparer.OrdinalIgnoreCase); + + //The string is an absolute URI that is missing a slash before the path. + var result2 = converter.Convert(LoadHtml("SmartHandling_FileMissingSlash")); + Assert.Equal("[file://c:/directory/filename](file://c:/directory/filename)", result2, + StringComparer.OrdinalIgnoreCase); + + //The string contains unescaped backslashes even if they are treated as forward slashes. + var result3 = converter.Convert(LoadHtml("SmartHandling_UnescapedBackslashes")); + Assert.Equal(@"[http:\\host/path/file](http:\\host/path/file)", result3, StringComparer.OrdinalIgnoreCase); } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Fact] - public Task When_Underline_Tag_With_AliasConverter_Register_ThenConvertToItalics() + public void WhenThereIsBase64PngImgTag_WithSaveToFileConfigAndValidDirectory_ThenSaveImageAndReferenceFilePath() { - var html = "This is underline text."; - var converter = new Converter(); - converter.Register("u", new ReverseMarkdown.Converters.AliasConverter(converter, "em")); - var result = converter.Convert(html); - var settings = new VerifySettings(); - settings.DisableRequireUniquePrefix(); - return Verifier.Verify(result, settings: settings, extension: "md"); + var tempDir = Path.Combine(Path.GetTempPath(), "reversemarkdown_test_" + Guid.NewGuid()); + try + { + var html = LoadHtml("Base64_SaveToFile_Png"); + var config = new Config + { + Base64Images = Config.Base64ImageHandling.SaveToFile, + Base64ImageSaveDirectory = tempDir + }; + + var converter = new Converter(config); + var result = converter.Convert(html); + + // Verify the image file was created + var imageFiles = Directory.GetFiles(tempDir, "image_*.png"); + Assert.Single(imageFiles); + Assert.True(File.Exists(imageFiles[0])); + + // Verify the markdown references the saved file path + Assert.Contains(imageFiles[0], result); + Assert.Contains("Before", result); + Assert.Contains("After", result); + Assert.Contains("![test]", result); + } + finally + { + // Clean up temp directory + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } } [Fact] - public Task Check_Converter_With_Unknown_Tag_ByPass_Option() + public void WhenThereIsBase64JpegImgTag_WithSaveToFileConfigAndValidDirectory_ThenSaveImageWithJpgExtension() { - var html = "text in unknown tag"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenStyletagWithBypassOption_ReturnEmpty() - { - var html = @""; - var config = new Config() - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - return CheckConversion(html, config); - } - - [Fact] - public Task Check_Converter_With_Unknown_Tag_Drop_Option() - { - var html = "text in unknown tag

paragraph text

"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Drop - }; - return CheckConversion(html, config); - } - - [Fact] - public Task Check_Converter_With_Unknown_Tag_PassThrough_Option() - { - var html = "text in unknown tag

paragraph text

"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.PassThrough - }; - return CheckConversion(html, config); - } - - [Fact] - public Task Check_Converter_With_Unknown_Tag_Raise_Option() - { - var html = "text in unknown tag

paragraph text

"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Raise - }; - var converter = new Converter(config); - return Verifier.Throws(() => converter.Convert(html), settings: _verifySettings) - .IgnoreMember(e => e.StackTrace); - } - - [Fact] - public Task WhenTable_ThenConvertToGFMTable() - { - var html = - "
col1col2col3
data1data2data3
"; - - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - return CheckConversion(html, config); - } - - [Fact] - public Task - WhenTable_WithoutHeaderRow_With_TableWithoutHeaderRowHandlingOptionEmptyRow_ThenConvertToGFMTable_WithEmptyHeaderRow() - { - var html = - "
data1data2data3
data4data5data6
"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass, - TableWithoutHeaderRowHandling = Config.TableWithoutHeaderRowHandlingOption.EmptyRow - }; - return CheckConversion(html, config); - } - - [Fact] - public Task - WhenTable_WithoutHeaderRow_With_TableWithoutHeaderRowHandlingOptionDefault_ThenConvertToGFMTable_WithFirstRowAsHeaderRow() - { - var html = - "
data1data2data3
data4data5data6
"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass, - // TableWithoutHeaderRowHandling = Config.TableWithoutHeaderRowHandlingOption.Default - this is default - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_Cell_Content_WithNewline_Add_BR_ThenConvertToGFMTable() - { - var html = - $"
col1col2col3
data line1{Environment.NewLine}line2data2data3
"; - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_CellContainsParagraph_AddBrThenConvertToGFMTable() - { - var html = "
col1

line1

line2

"; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_ContainsTheadTh_ConvertToGFMTable() - { - var html = - "
col1col2
data1data2
"; - var config = new Config - { - GithubFlavored = true, - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_ContainsTheadTd_ConvertToGFMTable() - { - var html = - "
col1col2
data1data2
"; - var config = new Config - { - GithubFlavored = true, - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_CellContainsBr_PreserveBrAndConvertToGFMTable() - { - var html = "
col1
line 1
line 2
"; - var config = new Config - { - GithubFlavored = true, - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_HasEmptyRow_DropsEmptyRow() - { - var html = "
abc
"; - var config = new Config - { - GithubFlavored = true, - TableWithoutHeaderRowHandling = Config.TableWithoutHeaderRowHandlingOption.EmptyRow, - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_BR_With_GitHubFlavored_Config_ThenConvertToGFM_BR() - { - var html = "First part
Second part"; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_PRE_With_GitHubFlavored_Config_ThenConvertToGFM_PRE() - { - var html = "
var test = 'hello world';
"; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_PRE_With_Confluence_Lang_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE() - { - var html = @"
var test = 'hello world';
"; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_PRE_With_Github_Site_DIV_Parent_And_GitHubFlavored_Config_ThenConvertToGFM_PRE() - { - var html = @"
var test = ""hello world"";
"; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_PRE_With_HighlightJs_Lang_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE() - { - var html = @"
var test = ""hello world"";
"; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_PRE_With_Lang_Highlight_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE() - { - var html = @"
var test = 'hello world';
"; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenRemovedCommentsIsEnabled_CommentsAreRemoved() - { - var html = - "Hello there content

"; - - var config = new Config - { - RemoveComments = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenBoldTagContainsBRTag_ThenConvertToMarkdown() - { - var html = "test
test
"; - return CheckConversion(html); - } - - [Fact] - public Task WhenAnchorTagContainsImgTag_LinkTextShouldNotBeEscaped() - { - var html = ""; - return CheckConversion(html); - } - - [Fact] - public Task - When_PRE_Without_Lang_Marker_Class_Att_And_GitHubFlavored_Config_With_DefaultCodeBlockLanguage_ThenConvertToGFM_PRE() - { - var html = @"
var test = ""hello world"";
"; - var config = new Config - { - GithubFlavored = true, - DefaultCodeBlockLanguage = "csharp" - }; - return CheckConversion(html, config); - } - - [Fact] - public Task - When_PRE_With_Parent_DIV_And_Non_GitHubFlavored_Config_FirstLine_CodeBlock_SpaceIndent_Should_Be_Retained() - { - var html = @"
var test = ""hello world"";
"; - return CheckConversion(html); - } - - [Fact] - public Task When_Converting_HTML_Ensure_To_Process_Only_Body() - { - var html = - "sample text"; - return CheckConversion(html); - } - - [Fact] - public Task When_Html_Containing_Nested_DIVs_Process_ONLY_Inner_Most_DIV() - { - var html = "
sample text
"; - return CheckConversion(html); - } - - [Fact] - public Task When_SingleChild_BlockTag_With_Parent_DIV_Ignore_Processing_DIV() - { - var html = "

sample text

"; - return CheckConversion(html); - } - - [Fact] - public Task When_Table_Within_List_Should_Be_Indented() - { - var html = - "
  1. Item1
  2. Item2
    col1col2col3
    data1data2data3
  3. Item3
"; - return CheckConversion(html); - } - - [Fact] - public Task When_Tag_In_PassThoughTags_List_Then_Use_PassThroughConverter() - { - var html = - @"This text has image . Next line of text"; - var config = new Config - { - PassThroughTags = ["img"] - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_CodeContainsSpaces_ShouldPreserveSpaces() - { - var html = "A JavaScript function ..."; - return CheckConversion(html); - } - - [Fact] - public Task When_CodeContainsSpanWithExtraSpaces_Should_NotNormalizeSpaces() - { - var html = "A JavaScript function ..."; - return CheckConversion(html); - } - - - [Fact] - public Task When_CodeContainsSpacesAndIsSurroundedByWhitespace_Should_NotRemoveSpaces() - { - var html = "A JavaScript function ..."; - return CheckConversion(html); - } - - [Fact] - public Task When_PreTag_Contains_IndentedFirstLine_Should_PreserveIndentation() - { - var html = "
    function foo {
"; - return CheckConversion(html); - } - - [Fact] - public Task When_PreTag_Contains_IndentedFirstLine_Should_PreserveIndentation_GFM() - { - var html = "
    function foo {
"; - - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_PreTag_Within_List_Should_Be_Indented() - { - var html = - $"
  1. Item1
  2. Item2
     test
    {Environment.NewLine} test
  3. Item3
"; - return CheckConversion(html); - } - - [Fact] - public Task When_PreTag_Within_List_Should_Be_Indented_With_GitHub_FlavouredMarkdown() - { - var html = - $"
  1. Item1
  2. Item2
     test
    {Environment.NewLine} test
  3. Item3
"; - - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task When_Text_Contains_NewLineChars_Should_Not_Convert_To_BR() - { - var html = "

line 1
line 2
"; - return CheckConversion(html); - } - - [Fact] - public Task When_Text_Contains_NewLineChars_Should_Not_Convert_To_BR_GitHub_Flavoured() - { - var html = "

line 1
line 2
"; - return CheckConversion(html, new Config - { - GithubFlavored = true - }); - } - - [Fact] - public Task When_Consecutive_Strong_Tags_Should_Convert_Properly() - { - var html = "block1block2block3block4"; - return CheckConversion(html); - } - - [Fact] - public Task When_Consecutive_Em_Tags_Should_Convert_Properly() - { - var html = "block1block2block3block4"; - return CheckConversion(html); - } - - [Fact] - public Task Li_With_No_Parent() - { - var html = "

  • item
  • "; - return CheckConversion(html); - } - - [Fact] - public Task When_Span_with_newline_Should_Convert_Properly() - { - var html = $"2 sets{Environment.NewLine}30 mountain climbers"; - return CheckConversion(html); - } - - [Fact] - public Task Bug255_table_newline_char_issue() - { - var html = - $"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}
    ProgressionFocus
    "; - return CheckConversion(html); - } - - [Fact] - public Task When_Content_Contains_script_tags_ignore_it() - { - var html = - $"

    simple paragraph

    "; - return CheckConversion(html); - } - - [Fact] - public Task When_DescriptionListTag_ThenConvertToMarkdown_List() - { - var html = - "
    Coffee
    Filter Coffee
    Hot Black Coffee
    Milk
    White Cold Drink
    "; - return CheckConversion(html); - } - - [Fact] - public Task Bug294_Table_bug_with_row_superfluous_newlines() - { - var html = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    比较wordpresshexo & hugo
    搭建要求一台服务器以及运行环境静态生成页面,无需服务器。
    性能由于是动态生成页面,可以通过自行配置提高性能,但是仍然无法媲美静态页面几乎无需考虑性能问题
    访问速度依赖于服务器配置以及cdn加速。只需考虑cdn加速
    功能完善作为强大的cms功能很完善,需要的功能基本可以插件下载直接实现。额外功能也可以通过插件实现,不过稍微需要自行查找以及diy
    后台管理现成的后台管理功能,开箱即用由于静态博客,本身没有后台管理,有需求需要自行搜索实现
    "; - - return CheckConversion(html); - } - - [Fact] - public Task WhenTableHeadingWithAlignmentStyles_ThenTableHeaderShouldHaveProperAlignment() - { - var html = - $"
    Col1Col2Col2
    123
    "; - return CheckConversion(html); - } - - [Fact] - public Task When_Sup_And_Nested_Sup() - { - var html = $"This is the 1st sentence to test the sup tag conversion"; - return CheckConversion(html); - } - - [Fact] - public Task When_Anchor_Text_with_Underscore_Do_Not_Escape() - { - var html = $"This a sample paragraph from https://www.w3schools.com/html/mov_bbb.mp4"; - return CheckConversion(html); - } - - [Fact] - public Task When_Strikethrough_And_Nested_Strikethrough() - { - var html = $"This is the 1st sentence to test the strikethrough tag conversion"; - return CheckConversion(html); - } - - [Fact] - public Task When_Spaces_In_Inline_Tags_Should_Be_Retained() - { - var html = $"... example html code block"; - return CheckConversion(html); - } - - [Fact] - public Task When_SuppressNewlineFlag_PrefixDiv_Should_Be_Empty() - { - var html = $"
    the
    fox
    jumps
    over
    "; - return CheckConversion(html, new Config - { - SuppressDivNewlines = true - }); - } - - [Fact] - public Task WhenTable_WithColSpan_TableHeaderColumnSpansHandling_ThenConvertToGFMTable() - { - var html = - "
    col1col2col3
    data1data2.1data2.2data3
    "; - - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - return CheckConversion(html, config); - } - - [Fact] - public Task Bug391_AnchorTagUnnecessarilyIndented() - { - var html = - "

    \n\n

    \n\n\n
    \nAn error occurred while importing data from feed 'FBA Producten'. More details can be found in the latest \">feed validation report.\n
    \n\n\n\n\n\" class=\"btn btn-primary btn-sm my-2\" target=\"_blank\">View feed 4"; - var config = new Config - { - GithubFlavored = true, - }; - - return CheckConversion(html, config); - } - + var html = LoadHtml("Underline_Tag_Alias"); + var converter = new Converter(); + converter.Register("u", new ReverseMarkdown.Converters.AliasConverter(converter, "em")); + var result = converter.Convert(html); + var settings = new VerifySettings(); + settings.DisableRequireUniquePrefix(); + return Verifier.Verify(result, settings: settings, extension: "md"); + } + + + + + + + + + [Fact] - public Task Bug393_RegressionWithVaryingNewLines() + public Task Check_Converter_With_Unknown_Tag_Raise_Option() { - const string html = "This is regular text\r\n

    This is HTML:

    • Line 1
    • Line 2
    • Line 3 has an unknown tag

    "; - var config = new Config { UnknownTags = Config.UnknownTagsOption.Bypass, ListBulletChar = '*' }; - return CheckConversion(html, config); + var html = LoadHtml("Unknown_Tag_Raise"); + var config = new Config + { + UnknownTags = Config.UnknownTagsOption.Raise + }; + var converter = new Converter(config); + return Verifier.Throws(() => converter.Convert(html), settings: _verifySettings) + .IgnoreMember(e => e.StackTrace); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Fact] + public void TestConversionWithPastedHtmlContainingUnicodeSpaces() + { + var html = LoadHtml("PastedHtmlUnicodeSpaces"); + + var config = new Config + { + GithubFlavored = true, + UnknownTags = + Config.UnknownTagsOption + .PassThrough, // Include the unknown tag completely in the result (default as well) + SmartHrefHandling = true // remove markdown output for links where appropriate + }; + var converter = new Converter(config); + var expected = converter.Convert(html); + + _testOutputHelper.WriteLine("Below is the generated markdown:"); + _testOutputHelper.WriteLine(expected); + + Assert.Contains("and **Weblog Publisher** for Windows", expected); } [Fact] public async Task Converter_Is_Thread_Safe_For_Concurrent_Use() { - var html = - "
    Head
    1. Item1
    2. Item2

    Tail

    "; + var html = LoadHtml("Converter_Is_Thread_Safe_For_Concurrent_Use"); var converter = new Converter(new Config { GithubFlavored = true }); var expected = converter.Convert(html); @@ -1690,364 +548,358 @@ public async Task Converter_Is_Thread_Safe_For_Concurrent_Use() Assert.Equal(expected, result); } } - - [Fact] - public Task SlackFlavored_Bold() - { - const string html = "test | test"; - var config = new Config { SlackFlavored = true }; - return CheckConversion(html, config); - } - - [Fact] - public Task SlackFlavored_Italic() - { - const string html = "test | test"; - var config = new Config { SlackFlavored = true }; - return CheckConversion(html, config); - } - - [Fact] - public Task SlackFlavored_Strikethrough() - { - const string html = "test"; - var config = new Config { SlackFlavored = true }; - return CheckConversion(html, config); - } - - [Fact] - public Task SlackFlavored_Bullets() - { - const string html = "
      \n
    • Item 1
    • \n
    • Item 2
    • \n
    • Item 3
    • \n
    "; - var config = new Config { SlackFlavored = true }; - return CheckConversion(html, config); - } - - [Fact] - public void SlackFlavored_Unsupported_Hr() - { - const string html = "
    "; - var config = new Config { SlackFlavored = true }; - var converter = new Converter(config); - Assert.Throws(() => converter.Convert(html)); - } - - [Fact] - public void SlackFlavored_Unsupported_Img() - { - const string html = ""; - var config = new Config { SlackFlavored = true }; - var converter = new Converter(config); - Assert.Throws(() => converter.Convert(html)); - } - - [Fact] - public void SlackFlavored_Unsupported_Sup() - { - const string html = "test"; - var config = new Config { SlackFlavored = true }; - var converter = new Converter(config); - Assert.Throws(() => converter.Convert(html)); - } - - [Fact] - public void SlackFlavored_Unsupported_Table() - { - const string html = "
    "; - var config = new Config { SlackFlavored = true }; - var converter = new Converter(config); - Assert.Throws(() => converter.Convert(html)); - } - - [Fact] - public void SlackFlavored_Unsupported_Table_Td() - { - const string html = ""; - var config = new Config { SlackFlavored = true }; - var converter = new Converter(config); - Assert.Throws(() => converter.Convert(html)); - } - - [Fact] - public void SlackFlavored_Unsupported_Table_Tr() - { - const string html = ""; - var config = new Config { SlackFlavored = true }; - var converter = new Converter(config); - Assert.Throws(() => converter.Convert(html)); - } - - [Fact] - public Task Bug403_unexpectedBehaviourWhenTableBodyRowsWithTHCells() - { - var html = $"{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}
    Heading1Heading2
    data 1data 2
    data 3data 4
    "; - var config = new Config { UnknownTags = Config.UnknownTagsOption.Bypass, ListBulletChar = '*', GithubFlavored = true}; - return CheckConversion(html, config); - } - - [Fact] - public Task EscapeMarkdownCharsInTextProperly() - { - var html = "[a-z]([0-9]){0,4}"; - return CheckConversion(html); - } - - [Fact] - public Task Bug400_MissingSpanSpaceWithItalics() - { - var html = "

    What we thought: When we built Pages, we assumed that customers would use them like newsletters to share relevant, continually-updated information with field teams.

    "; - return CheckConversion(html); - } - - [Fact] - public Task When_NestedParagraphs_FiveLevelsDeep_ThenConvertCorrectly() - { - // Tests moderately nested

    tags where HtmlAgilityPack creates nested structure - var html = "

    Level1

    Level2

    Level3

    Level4

    Level5

    "; - - var config = new Config - { - GithubFlavored = true, - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_NestedSpans_FiveLevelsDeep_ThenConvertCorrectly() - { - // Tests moderately nested tags to ensure span bypass converter handles nesting - var html = "Level1Level2Level3Level4Level5"; - - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_InterleavedParagraphsAndSpans_ThenConvertCorrectly() - { - // Tests the interleaved

    pattern common in malformed HTML - var html = "

    Text1

    Text2

    Text3

    Text4

    "; - - var config = new Config - { - GithubFlavored = true, - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_ManySequentialUnclosedParagraphs_ThenConvertCorrectly() - { - // Tests sequential unclosed

    tags as found in user-generated content - var html = "

    Part1

    Part2

    Part3

    Part4

    Part5

    Part6

    Part7

    Part8"; - - var config = new Config - { - GithubFlavored = true, - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_UnclosedParagraphsWithSpansAndTextNodes_ThenConvertCorrectly() - { - // Tests mixed content: properly closed tags, text nodes, and unclosed nested tags - var html = @"

    Intro

    Filler text here.

    Section1

    Section2

    Section3"; - - var config = new Config - { - GithubFlavored = true, - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_EmptyNestedParagraphs_ThenConvertCorrectly() - { - // Tests deeply nested empty

    tags - var html = "

    "; - - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_AlternatingEmptyAndFilledNestedParagraphs_ThenConvertCorrectly() - { - // Tests combination of empty and filled nested

    tags - var html = "

    A

    B

    C

    D

    E

    "; - - var config = new Config - { - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_NestedParagraphs_TenLevelsDeep_ThenConvertCorrectly() - { - // Tests deeper nesting (10 levels) to ensure performance remains linear after fix - var html = "

    L1

    L2

    L3

    L4

    L5

    L6

    L7

    L8

    L9

    L10

    "; - - var config = new Config - { - GithubFlavored = true, - UnknownTags = Config.UnknownTagsOption.Bypass - }; - - return CheckConversion(html, config); - } - - [Fact] - public Task When_NestedTableIsInTable_LeaveNestedTableAsHtml() - { - // Issue #411: Nested tables should be left as HTML, similar to lists in tables - var html = "
    StepInstructions
    1
    ConditionAction
    ADo X
    "; - return CheckConversion(html); - } - - [Fact] - public Task When_ComplexNestedTableIsInTable_LeaveNestedTableAsHtml() - { - // More complex nested table scenario - var html = @" - - - - - - - - -
    CategoryDetails
    Products - - - - -
    NamePrice
    Item 1$10
    Item 2$20
    -
    "; - return CheckConversion(html); - } - - [Fact] - public Task When_MultipleNestedTablesInTable_LeaveAllNestedTablesAsHtml() - { - // Multiple nested tables in different cells - var html = @" - - - - - - - - -
    Column 1Column 2
    - - -
    Nested 1
    -
    - - -
    Nested 2
    -
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithCaption_ThenCaptionAppearsAboveTable() - { - var html = "
    Monthly Sales Report
    MonthSales
    January$1000
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithoutCaption_ThenConvertNormally() - { - var html = "
    MonthSales
    January$1000
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithEmptyCaption_ThenConvertNormally() - { - var html = "
    MonthSales
    January$1000
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithCaptionContainingWhitespace_ThenTrimWhitespace() - { - var html = "
    Table Caption
    Col1
    Data1
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithCaptionContainingMarkdownChars_ThenHandleProperly() - { - var html = "
    Sales Report [2024] - **Important**
    MonthSales
    Jan$100
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithCaptionAndNoHeaderRow_EmptyRowHandling_ThenCaptionAppearsAboveTable() - { - var html = "
    Data Table
    data1data2
    data3data4
    "; - var config = new Config - { - TableWithoutHeaderRowHandling = Config.TableWithoutHeaderRowHandlingOption.EmptyRow - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_WithCaptionContainingNestedTags_ThenExtractTextOnly() - { - var html = "
    Sales Report for 2024
    Month
    Jan
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTable_WithCaptionAndGithubFlavored_ThenCaptionAppearsAboveTable() - { - var html = "
    Code Review Summary
    PRStatus
    #123Approved
    "; - var config = new Config - { - GithubFlavored = true - }; - return CheckConversion(html, config); - } - - [Fact] - public Task WhenTable_WithCaptionContainingNewlines_ThenHandleNewlines() - { - var html = $"
    Multi{Environment.NewLine}Line{Environment.NewLine}Caption
    Col
    Data
    "; - return CheckConversion(html); - } - - [Fact] - public Task WhenTableRowWithDuplicateStyleKeysAfterTrimming_ThenConvertWithoutException() - { - var html = "
    Header
    Data
    "; - return CheckConversion(html); - } - } -} + + [Fact] + public void SlackFlavored_Unsupported_Hr() + { + var html = LoadHtml("SlackFlavored_Unsupported_Hr"); + var config = new Config { SlackFlavored = true }; + var converter = new Converter(config); + Assert.Throws(() => converter.Convert(html)); + } + + [Fact] + public void SlackFlavored_Unsupported_Img() + { + var html = LoadHtml("SlackFlavored_Unsupported_Img"); + var config = new Config { SlackFlavored = true }; + var converter = new Converter(config); + Assert.Throws(() => converter.Convert(html)); + } + + [Fact] + public void SlackFlavored_Unsupported_Sup() + { + var html = LoadHtml("SlackFlavored_Unsupported_Sup"); + var config = new Config { SlackFlavored = true }; + var converter = new Converter(config); + Assert.Throws(() => converter.Convert(html)); + } + + [Fact] + public void SlackFlavored_Unsupported_Table() + { + var html = LoadHtml("SlackFlavored_Unsupported_Table"); + var config = new Config { SlackFlavored = true }; + var converter = new Converter(config); + Assert.Throws(() => converter.Convert(html)); + } + + [Fact] + public void SlackFlavored_Unsupported_Table_Td() + { + var html = LoadHtml("SlackFlavored_Unsupported_Table_Td"); + var config = new Config { SlackFlavored = true }; + var converter = new Converter(config); + Assert.Throws(() => converter.Convert(html)); + } + + [Fact] + public void SlackFlavored_Unsupported_Table_Tr() + { + var html = LoadHtml("SlackFlavored_Unsupported_Table_Tr"); + var config = new Config { SlackFlavored = true }; + var converter = new Converter(config); + Assert.Throws(() => converter.Convert(html)); + } + + + + + + + + + + + + // [Fact] + // public Task When_TextWithinParagraphContainsNewlineChars_ConvertNewlineCharsToSpace() + // { + // // note that the string also has a tab space + // var html = + // $"

    This service will be{Environment.NewLine}temporarily unavailable due to planned maintenance{Environment.NewLine}from 02:00-04:00 on 30/01/2020

    "; + // return CheckConversion(html); + // } + + // [Fact] + // public Task WhenTableCellsWithPWithMarkupNewlines_ThenRenderBr() + // { + // var html = + // $"{Environment.NewLine}\t{Environment.NewLine}\t\t{Environment.NewLine}\t{Environment.NewLine}\t\t\t
    {Environment.NewLine}\t\t\t

    {Environment.NewLine}col1{Environment.NewLine}

    {Environment.NewLine}\t\t
    {Environment.NewLine}\t\t\t

    {Environment.NewLine}data1{Environment.NewLine}

    {Environment.NewLine}\t\t
    "; + // + // return CheckConversion(html); + // } + + } + + public class DataDrivenCasesTests + { + public static IEnumerable Cases => LoadCases() + .Select(testCase => new object[] { testCase }); + + [Theory] + [MemberData(nameof(Cases))] + public void CaseRuns(CaseData testCase) + { + var config = BuildConfig(testCase); + var converter = new Converter(config); + var result = converter.Convert(testCase.Html); + var expected = LoadExpected(testCase); + Assert.Equal(expected, result); + } + + private static IEnumerable LoadCases() + { + var path = GetCasesPath(); + if (!File.Exists(path)) { + throw new FileNotFoundException("cases.json not found", path); + } + + var json = File.ReadAllText(path); + var cases = JsonSerializer.Deserialize>( + json, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true } + ); + + var filtered = (cases ?? new List()).Where(testCase => !testCase.DataOnly); + return ApplyTagFilter(filtered); + } + + private static IEnumerable ApplyTagFilter(IEnumerable cases) + { + var filter = Environment.GetEnvironmentVariable("RM_TEST_TAGS"); + if (string.IsNullOrWhiteSpace(filter)) { + return cases; + } + + var tags = filter.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + return cases.Where(testCase => testCase.Tags.Any(tag => tags.Contains(tag, StringComparer.OrdinalIgnoreCase))); + } + + private static string LoadExpected(CaseData testCase) + { + if (!string.IsNullOrWhiteSpace(testCase.Expected)) { + return testCase.Expected; + } + + if (string.IsNullOrWhiteSpace(testCase.ExpectedFile)) { + throw new InvalidOperationException($"Case '{testCase.Id}' missing expected output."); + } + + var projectDir = GetProjectDirectory(); + var path = Path.Combine(projectDir.FullName, testCase.ExpectedFile); + if (!File.Exists(path)) { + throw new FileNotFoundException($"Expected file not found for '{testCase.Id}'", path); + } + + var expected = File.ReadAllText(path); + if (expected.EndsWith("\r\n", StringComparison.Ordinal)) { + return expected[..^2]; + } + + if (expected.EndsWith("\n", StringComparison.Ordinal)) { + return expected[..^1]; + } + + return expected; + } + + private static Config BuildConfig(CaseData testCase) + { + if (!string.IsNullOrWhiteSpace(testCase.ConfigPreset)) { + return ConfigPresets.Get(testCase.ConfigPreset); + } + + var config = new Config(); + var overrides = testCase.Config; + if (overrides == null) { + return config; + } + + ApplyBool(overrides.GithubFlavored, value => config.GithubFlavored = value); + ApplyBool(overrides.SlackFlavored, value => config.SlackFlavored = value); + ApplyBool(overrides.CommonMark, value => config.CommonMark = value); + ApplyBool(overrides.CommonMarkIntrawordEmphasisSpacing, + value => config.CommonMarkIntrawordEmphasisSpacing = value); + ApplyBool(overrides.CommonMarkUseHtmlInlineTags, + value => config.CommonMarkUseHtmlInlineTags = value); + ApplyBool(overrides.SuppressDivNewlines, value => config.SuppressDivNewlines = value); + ApplyBool(overrides.RemoveComments, value => config.RemoveComments = value); + ApplyBool(overrides.SmartHrefHandling, value => config.SmartHrefHandling = value); + ApplyBool(overrides.CleanupUnnecessarySpaces, value => config.CleanupUnnecessarySpaces = value); + ApplyBool(overrides.TableHeaderColumnSpanHandling, + value => config.TableHeaderColumnSpanHandling = value); + + if (!string.IsNullOrWhiteSpace(overrides.DefaultCodeBlockLanguage)) { + config.DefaultCodeBlockLanguage = overrides.DefaultCodeBlockLanguage; + } + + if (!string.IsNullOrWhiteSpace(overrides.ListBulletChar)) { + config.ListBulletChar = overrides.ListBulletChar[0]; + } + + ApplyEnum(overrides.UnknownTags, + value => config.UnknownTags = value); + ApplyEnum(overrides.TableWithoutHeaderRowHandling, + value => config.TableWithoutHeaderRowHandling = value); + ApplyEnum(overrides.Base64Images, + value => config.Base64Images = value); + + if (!string.IsNullOrWhiteSpace(overrides.Base64ImageSaveDirectory)) { + config.Base64ImageSaveDirectory = overrides.Base64ImageSaveDirectory; + } + + if (overrides.WhitelistUriSchemes.Length > 0) { + foreach (var scheme in overrides.WhitelistUriSchemes) { + config.WhitelistUriSchemes.Add(scheme); + } + } + + if (overrides.PassThroughTags.Length > 0) { + foreach (var tag in overrides.PassThroughTags) { + config.PassThroughTags.Add(tag); + } + } + + if (overrides.UnknownTagsReplacer.Count > 0) { + foreach (var entry in overrides.UnknownTagsReplacer) { + config.UnknownTagsReplacer[entry.Key] = entry.Value; + } + } + + if (overrides.TagAliases.Count > 0) { + foreach (var entry in overrides.TagAliases) { + config.TagAliases[entry.Key] = entry.Value; + } + } + + return config; + } + + private static void ApplyBool(bool? value, Action apply) + { + if (value.HasValue) { + apply(value.Value); + } + } + + private static void ApplyEnum(string value, Action apply) where T : struct + { + if (string.IsNullOrWhiteSpace(value)) { + return; + } + + if (Enum.TryParse(value, out var parsed)) { + apply(parsed); + } + } + + private static DirectoryInfo GetProjectDirectory() + { + var directory = new DirectoryInfo(AppContext.BaseDirectory); + while (directory != null && !File.Exists(Path.Combine(directory.FullName, "ReverseMarkdown.Test.csproj"))) { + directory = directory.Parent; + } + + if (directory == null) { + throw new DirectoryNotFoundException("Could not locate test project directory."); + } + + return directory; + } + + private static string GetCasesPath() + { + return Path.Combine(GetProjectDirectory().FullName, "TestData", "cases.json"); + } + + public class CaseData + { + public string Id { get; set; } = string.Empty; + public string Html { get; set; } = string.Empty; + public string Expected { get; set; } = string.Empty; + public string ExpectedFile { get; set; } = string.Empty; + public CaseConfig Config { get; set; } = new CaseConfig(); + public string ConfigPreset { get; set; } = string.Empty; + public string[] Tags { get; set; } = Array.Empty(); + public bool DataOnly { get; set; } + + public override string ToString() + { + return Id; + } + } + + public class CaseConfig + { + public bool? GithubFlavored { get; set; } + public bool? SlackFlavored { get; set; } + public bool? CommonMark { get; set; } + public bool? CommonMarkIntrawordEmphasisSpacing { get; set; } + public bool? CommonMarkUseHtmlInlineTags { get; set; } + public bool? SuppressDivNewlines { get; set; } + public bool? RemoveComments { get; set; } + public bool? SmartHrefHandling { get; set; } + public bool? CleanupUnnecessarySpaces { get; set; } + public bool? TableHeaderColumnSpanHandling { get; set; } + public string DefaultCodeBlockLanguage { get; set; } = string.Empty; + public string ListBulletChar { get; set; } = string.Empty; + public string UnknownTags { get; set; } = string.Empty; + public string TableWithoutHeaderRowHandling { get; set; } = string.Empty; + public string Base64Images { get; set; } = string.Empty; + public string Base64ImageSaveDirectory { get; set; } = string.Empty; + public string[] WhitelistUriSchemes { get; set; } = Array.Empty(); + public string[] PassThroughTags { get; set; } = Array.Empty(); + public Dictionary UnknownTagsReplacer { get; set; } = new(); + public Dictionary TagAliases { get; set; } = new(); + } + + private static class ConfigPresets + { + public static Config Get(string name) + { + throw new InvalidOperationException($"Unknown config preset '{name}'."); + } + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj b/src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj index d829d174..a7be9035 100644 --- a/src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj +++ b/src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj @@ -17,6 +17,7 @@ all + diff --git a/src/ReverseMarkdown.Test/TestData/README.md b/src/ReverseMarkdown.Test/TestData/README.md new file mode 100644 index 00000000..6f3f1d46 --- /dev/null +++ b/src/ReverseMarkdown.Test/TestData/README.md @@ -0,0 +1,24 @@ +CommonMark test data + +Download commonmark.json from the CommonMark spec repository and save it here: + +https://raw.githubusercontent.com/commonmark/commonmark-spec/master/commonmark.json + +Local data-driven test cases + +Add cases to cases.json with the following fields: + +- id: unique identifier, used for test naming +- html: source HTML input +- expected: expected markdown output (inline) +- expectedFile: filename of a snapshot in src/ReverseMarkdown.Test (optional alternative to expected) +- config: optional Config values (CommonMark, SlackFlavored, etc.) +- tags: optional tags for filtering via RM_TEST_TAGS + +Example: + +{ + "id": "basic-strong", + "html": "This is a sample paragraph", + "expected": "This is a **sample** paragraph" +} diff --git a/src/ReverseMarkdown.Test/TestData/cases.json b/src/ReverseMarkdown.Test/TestData/cases.json new file mode 100644 index 00000000..6a2f4d43 --- /dev/null +++ b/src/ReverseMarkdown.Test/TestData/cases.json @@ -0,0 +1,1238 @@ +[ + { + "id": "WhenThereIsAsideTag", + "html": " This text appears after aside.", + "expectedFile": "ConverterTests.WhenThereIsAsideTag.verified.md" + }, + { + "id": "WhenThereAreSemanticContainerTags", + "html": "
    Whatever Inc.

    Thanks for your enquiry.

    Section intro
    Article text
    Diagram
    Figure caption
    Mail us
    ", + "expectedFile": "ConverterTests.WhenThereAreSemanticContainerTags.verified.md", + "config": { + "UnknownTags": "PassThrough" + } + }, + { + "id": "WhenThereIsHtmlLink_ThenConvertToMarkdownLink", + "html": "This is a link", + "expectedFile": "ConverterTests.WhenThereIsHtmlLink_ThenConvertToMarkdownLink.verified.md" + }, + { + "id": "WhenThereIsHtmlLinkWithTitle_ThenConvertToMarkdownLink", + "html": "This is a link", + "expectedFile": "ConverterTests.WhenThereIsHtmlLinkWithTitle_ThenConvertToMarkdownLink.verified.md" + }, + { + "id": "WhenThereAreMultipleLinks_ThenConvertThemToMarkdownLinks", + "html": "This is first link and second link", + "expectedFile": "ConverterTests.WhenThereAreMultipleLinks_ThenConvertThemToMarkdownLinks.verified.md" + }, + { + "id": "WhenThereIsHtmlLinkNotWhitelisted_ThenBypass", + "html": "Leave http, https, ftp, ftps, file. Remove data, tel and whatever", + "expectedFile": "ConverterTests.WhenThereIsHtmlLinkNotWhitelisted_ThenBypass.verified.md", + "config": { + "WhitelistUriSchemes": [ + "http", + "https", + "ftp", + "ftps", + "file" + ] + } + }, + { + "id": "WhenThereHtmlWithHrefAndNoSchema_WhitelistedEmptyString_ThenConvertToMarkdown", + "html": "yeah", + "expectedFile": "ConverterTests.WhenThereHtmlWithHrefAndNoSchema_WhitelistedEmptyString_ThenConvertToMarkdown.verified.md", + "config": { + "WhitelistUriSchemes": [ + "" + ] + } + }, + { + "id": "WhenThereHtmlWithHrefAndNoSchema_NotWhitelisted_ThenConvertToPlain", + "html": "yeah", + "expectedFile": "ConverterTests.WhenThereHtmlWithHrefAndNoSchema_NotWhitelisted_ThenConvertToPlain.verified.md", + "config": { + "WhitelistUriSchemes": [ + "whatever" + ] + } + }, + { + "id": "WhenThereIsHtmlLinkWithDisallowedCharsInChildren_ThenEscapeTextInMarkdown", + "html": "this ]( might break things", + "expectedFile": "ConverterTests.WhenThereIsHtmlLinkWithDisallowedCharsInChildren_ThenEscapeTextInMarkdown.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlLinkWithParensInHref_ThenEscapeHrefInMarkdown", + "html": "link", + "expectedFile": "ConverterTests.WhenThereIsHtmlLinkWithParensInHref_ThenEscapeHrefInMarkdown.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlWithProtocolRelativeUrlHrefAndNameNotMatching_SmartHandling_ThenConvertToMarkdown", + "html": "example.com", + "expectedFile": "ConverterTests.WhenThereIsHtmlWithProtocolRelativeUrlHrefAndNameNotMatching_SmartHandling_ThenConvertToMarkdown.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlWithHrefAndNameNotMatching_SmartHandling_ThenConvertToMarkdown", + "html": "Something intact", + "expectedFile": "ConverterTests.WhenThereIsHtmlWithHrefAndNameNotMatching_SmartHandling_ThenConvertToMarkdown.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlWithHrefAndNameMatching_SmartHandling_ThenConvertToPlain", + "html": "http://example.com/abc?x", + "expectedFile": "ConverterTests.WhenThereIsHtmlWithHrefAndNameMatching_SmartHandling_ThenConvertToPlain.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlWithMailtoSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain", + "html": "george@example.com", + "expectedFile": "ConverterTests.WhenThereIsHtmlWithMailtoSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlWithTelSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain", + "html": "+1123-45678", + "expectedFile": "ConverterTests.WhenThereIsHtmlWithTelSchemeAndNameWithoutScheme_SmartHandling_ThenConvertToPlain.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereIsHtmlLinkWithoutHttpSchemaAndNameWithoutScheme_SmartHandling_ThenConvertToMarkdown", + "html": "example.com", + "expectedFile": "ConverterTests.WhenThereIsHtmlLinkWithoutHttpSchemaAndNameWithoutScheme_SmartHandling_ThenConvertToMarkdown.verified.md", + "config": { + "SmartHrefHandling": true + } + }, + { + "id": "WhenThereAreStrongTag_ThenConvertToMarkdownDoubleAsterisks", + "html": "This paragraph contains bold text", + "expectedFile": "ConverterTests.WhenThereAreStrongTag_ThenConvertToMarkdownDoubleAsterisks.verified.md" + }, + { + "id": "WhenThereAreBTag_ThenConvertToMarkdownDoubleAsterisks", + "html": "This paragraph contains bold text", + "expectedFile": "ConverterTests.WhenThereAreBTag_ThenConvertToMarkdownDoubleAsterisks.verified.md" + }, + { + "id": "WhenThereIsEncompassingStrongOrBTag_ThenConvertToMarkdownDoubleAsterisks_AnyStrongOrBTagsInsideAreIgnored", + "html": "Paragraph is encompassed with strong tag and also has bold text words within it", + "expectedFile": "ConverterTests.WhenThereIsEncompassingStrongOrBTag_ThenConvertToMarkdownDoubleAsterisks_AnyStrongOrBTagsInsideAreIgnored.verified.md" + }, + { + "id": "WhenThereIsSingleAsteriskInText_ThenConvertToMarkdownEscapedAsterisk", + "html": "This is a sample(*) paragraph", + "expectedFile": "ConverterTests.WhenThereIsSingleAsteriskInText_ThenConvertToMarkdownEscapedAsterisk.verified.md" + }, + { + "id": "WhenThereIsEmTag_ThenConvertToMarkdownSingleAsterisks", + "html": "This is a sample paragraph", + "expected": "This is a *sample* paragraph" + }, + { + "id": "WhenThereIsITag_ThenConvertToMarkdownSingleAsterisks", + "html": "This is a sample paragraph", + "expected": "This is a *sample* paragraph" + }, + { + "id": "When_CommonMark_Enabled_InlineEmphasisInsideWord_ThenInsertSpaces", + "html": "hello and thing", + "expected": "he **ll** o and th *in* g", + "config": { + "CommonMark": true, + "CommonMarkIntrawordEmphasisSpacing": true, + "CommonMarkUseHtmlInlineTags": false + } + }, + { + "id": "WhenThereIsEncompassingEmOrITag_ThenConvertToMarkdownSingleAsterisks_AnyEmOrITagsInsideAreIgnored", + "html": "This is a sample paragraph", + "expectedFile": "ConverterTests.WhenThereIsEncompassingEmOrITag_ThenConvertToMarkdownSingleAsterisks_AnyEmOrITagsInsideAreIgnored.verified.md" + }, + { + "id": "WhenThereIsBreakTag_ThenConvertToMarkdownDoubleSpacesCarriageReturn", + "html": "This is a paragraph.
    This line appears after break.", + "expectedFile": "ConverterTests.WhenThereIsBreakTag_ThenConvertToMarkdownDoubleSpacesCarriageReturn.verified.md" + }, + { + "id": "WhenThereIsCodeTag_ThenConvertToMarkdownWithBackTick", + "html": "This text has code alert();", + "expected": "This text has code `alert();`" + }, + { + "id": "WhenThereIsH1Tag_ThenConvertToMarkdownHeader", + "html": "This text has

    header

    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsH1Tag_ThenConvertToMarkdownHeader.verified.md" + }, + { + "id": "WhenThereIsH2Tag_ThenConvertToMarkdownHeader", + "html": "This text has

    header

    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsH2Tag_ThenConvertToMarkdownHeader.verified.md" + }, + { + "id": "WhenThereIsH3Tag_ThenConvertToMarkdownHeader", + "html": "This text has

    header

    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsH3Tag_ThenConvertToMarkdownHeader.verified.md" + }, + { + "id": "WhenThereIsH4Tag_ThenConvertToMarkdownHeader", + "html": "This text has

    header

    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsH4Tag_ThenConvertToMarkdownHeader.verified.md" + }, + { + "id": "WhenThereIsH5Tag_ThenConvertToMarkdownHeader", + "html": "This text has
    header
    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsH5Tag_ThenConvertToMarkdownHeader.verified.md" + }, + { + "id": "WhenThereIsH6Tag_ThenConvertToMarkdownHeader", + "html": "This text has
    header
    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsH6Tag_ThenConvertToMarkdownHeader.verified.md" + }, + { + "id": "WhenThereIsHeadingInsideTable_ThenIgnoreHeadingLevel", + "html": "\n\n\n

    Heading text

    Content
    ", + "expectedFile": "ConverterTests.WhenThereIsHeadingInsideTable_ThenIgnoreHeadingLevel.verified.md" + }, + { + "id": "WhenThereIsBlockquoteTag_ThenConvertToMarkdownBlockquote", + "html": "This text has
    blockquote
    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsBlockquoteTag_ThenConvertToMarkdownBlockquote.verified.md" + }, + { + "id": "WhenThereIsEmptyBlockquoteTag_ThenConvertToMarkdownBlockquote", + "html": "This text has
    . This text appear after header.", + "expectedFile": "ConverterTests.WhenThereIsEmptyBlockquoteTag_ThenConvertToMarkdownBlockquote.verified.md" + }, + { + "id": "WhenThereIsParagraphTag_ThenConvertToMarkdownDoubleLineBreakBeforeAndAfter", + "html": "This text has markup

    paragraph.

    Next line of text", + "expectedFile": "ConverterTests.WhenThereIsParagraphTag_ThenConvertToMarkdownDoubleLineBreakBeforeAndAfter.verified.md" + }, + { + "id": "WhenThereIsHorizontalRule_ThenConvertToMarkdownHorizontalRule", + "html": "This text has horizontal rule.
    Next line of text", + "expectedFile": "ConverterTests.WhenThereIsHorizontalRule_ThenConvertToMarkdownHorizontalRule.verified.md" + }, + { + "id": "WhenThereIsImgTag_ThenConvertToMarkdownImage", + "html": "This text has image \"alt\". Next line of text", + "expectedFile": "ConverterTests.WhenThereIsImgTag_ThenConvertToMarkdownImage.verified.md" + }, + { + "id": "WhenThereIsImgTagWithoutTitle_ThenConvertToMarkdownImageWithoutTitle", + "html": "This text has image \"alt\". Next line of text", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithoutTitle_ThenConvertToMarkdownImageWithoutTitle.verified.md" + }, + { + "id": "WhenThereIsImgTagWithoutAltText_ThenConvertToMarkdownImageWithoutAltText", + "html": "This text has image . Next line of text", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithoutAltText_ThenConvertToMarkdownImageWithoutAltText.verified.md" + }, + { + "id": "WhenThereIsImgTagWithMultilineAltText_ThenEnsureNoBlankLinesInMarkdownAltText", + "html": "This text has image \"cat\n\ndog\". Next line of text", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithMultilineAltText_ThenEnsureNoBlankLinesInMarkdownAltText.verified.md" + }, + { + "id": "WhenThereIsImgTagWithBracesInAltText_ThenEnsureAltTextIsEscapedInMarkdown", + "html": "This text has image \"a]b\". Next line of text", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithBracesInAltText_ThenEnsureAltTextIsEscapedInMarkdown.verified.md" + }, + { + "id": "WhenThereIsImgTag_SchemeNotWhitelisted_ThenEmptyOutput", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTag_SchemeNotWhitelisted_ThenEmptyOutput.verified.md", + "config": { + "WhitelistUriSchemes": [ + "http" + ] + } + }, + { + "id": "WhenThereIsImgTag_SchemeIsWhitelisted_ThenConvertToMarkdown", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTag_SchemeIsWhitelisted_ThenConvertToMarkdown.verified.md", + "config": { + "WhitelistUriSchemes": [ + "data" + ] + } + }, + { + "id": "WhenThereIsImgTagAndSrcWithNoSchema_WhitelistedEmptyString_ThenConvertToMarkdown", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTagAndSrcWithNoSchema_WhitelistedEmptyString_ThenConvertToMarkdown.verified.md", + "config": { + "WhitelistUriSchemes": [ + "" + ] + } + }, + { + "id": "WhenThereIsImgTagAndSrcWithNoSchema_NotWhitelisted_ThenConvertToPlain", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTagAndSrcWithNoSchema_NotWhitelisted_ThenConvertToPlain.verified.md", + "config": { + "WhitelistUriSchemes": [ + "whatever" + ] + } + }, + { + "id": "WhenThereIsImgTagWithRelativeUrl_NotWhitelisted_ThenConvertToMarkdown", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithRelativeUrl_NotWhitelisted_ThenConvertToMarkdown.verified.md", + "config": { + "WhitelistUriSchemes": [ + "data" + ] + } + }, + { + "id": "WhenThereIsImgTagWithUnixUrl_ConfigHasWhitelist_ThenConvertToMarkdown", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithUnixUrl_ConfigHasWhitelist_ThenConvertToMarkdown.verified.md", + "config": { + "WhitelistUriSchemes": [ + "file" + ] + } + }, + { + "id": "WhenThereIsImgTagWithHttpProtocolRelativeUrl_ConfigHasWhitelist_ThenConvertToMarkdown", + "html": "", + "expectedFile": "ConverterTests.WhenThereIsImgTagWithHttpProtocolRelativeUrl_ConfigHasWhitelist_ThenConvertToMarkdown.verified.md", + "config": { + "WhitelistUriSchemes": [ + "http" + ] + } + }, + { + "id": "WhenThereIsBase64ImgTag_WithDefaultConfig_ThenIncludeInMarkdown", + "html": "

    Before

    \"test\"

    After

    ", + "expectedFile": "ConverterTests.WhenThereIsBase64ImgTag_WithDefaultConfig_ThenIncludeInMarkdown.verified.md" + }, + { + "id": "WhenThereIsBase64ImgTag_WithSkipConfig_ThenSkipImage", + "html": "

    Before

    \"test\"

    After

    ", + "expectedFile": "ConverterTests.WhenThereIsBase64ImgTag_WithSkipConfig_ThenSkipImage.verified.md", + "config": { + "Base64Images": "Skip" + } + }, + { + "id": "WhenThereIsBase64JpegImgTag_WithSkipConfig_ThenSkipImage", + "html": "\"jpeg\"", + "expectedFile": "ConverterTests.WhenThereIsBase64JpegImgTag_WithSkipConfig_ThenSkipImage.verified.md", + "config": { + "Base64Images": "Skip" + } + }, + { + "id": "WhenThereIsBase64ImgTag_WithSaveToFileConfigButNoDirectory_ThenSkipImage", + "html": "

    Before

    \"test\"

    After

    ", + "expectedFile": "ConverterTests.WhenThereIsBase64ImgTag_WithSaveToFileConfigButNoDirectory_ThenSkipImage.verified.md", + "config": { + "Base64Images": "SaveToFile" + } + }, + { + "id": "WhenThereIsPreTag_ThenConvertToMarkdownPre", + "html": "This text has pre tag content
    Predefined text
    Next line of text", + "expectedFile": "ConverterTests.WhenThereIsPreTag_ThenConvertToMarkdownPre.verified.md" + }, + { + "id": "WhenThereIsEmptyPreTag_ThenConvertToMarkdownPre", + "html": "This text has pre tag content

    Next line of text", + "expectedFile": "ConverterTests.WhenThereIsEmptyPreTag_ThenConvertToMarkdownPre.verified.md" + }, + { + "id": "WhenThereIsEmptyPreTag_ThenConvertToMarkdownPre_GFM", + "html": "This text has pre tag content

    Next line of text", + "expectedFile": "ConverterTests.WhenThereIsEmptyPreTag_ThenConvertToMarkdownPre_GFM.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenThereIsUnorderedList_ThenConvertToMarkdownList", + "html": "This text has unordered list.
    • Item1
    • Item2
    ", + "expectedFile": "ConverterTests.WhenThereIsUnorderedList_ThenConvertToMarkdownList.verified.md" + }, + { + "id": "WhenThereIsUnorderedListAndBulletIsAsterisk_ThenConvertToMarkdownList", + "html": "This text has unordered list.
    • Item1
    • Item2
    ", + "expectedFile": "ConverterTests.WhenThereIsUnorderedListAndBulletIsAsterisk_ThenConvertToMarkdownList.verified.md", + "config": { + "ListBulletChar": "*" + } + }, + { + "id": "WhenThereIsInputListWithGithubFlavoredEnabled_ThenConvertToMarkdownCheckList", + "html": "
    • Unchecked
    • Checked
    ", + "expectedFile": "ConverterTests.WhenThereIsInputListWithGithubFlavoredEnabled_ThenConvertToMarkdownCheckList.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenThereIsInputListWithGithubFlavoredDisabled_ThenConvertToTypicalMarkdownList", + "html": "
    • Unchecked
    • Checked
    ", + "expectedFile": "ConverterTests.WhenThereIsInputListWithGithubFlavoredDisabled_ThenConvertToTypicalMarkdownList.verified.md", + "config": { + "GithubFlavored": false + } + }, + { + "id": "WhenThereIsOrderedList_ThenConvertToMarkdownList", + "html": "This text has ordered list.
    1. Item1
    2. Item2
    ", + "expectedFile": "ConverterTests.WhenThereIsOrderedList_ThenConvertToMarkdownList.verified.md" + }, + { + "id": "WhenThereIsOrderedListWithNestedUnorderedList_ThenConvertToMarkdownListWithNestedList", + "html": "This text has ordered list.
    1. OuterItem1
      • InnerItem1
      • InnerItem2
    2. Item2
    ", + "expectedFile": "ConverterTests.WhenThereIsOrderedListWithNestedUnorderedList_ThenConvertToMarkdownListWithNestedList.verified.md" + }, + { + "id": "WhenThereIsUnorderedListWithNestedOrderedList_ThenConvertToMarkdownListWithNestedList", + "html": "This text has ordered list.
    • OuterItem1
      1. InnerItem1
      2. InnerItem2
    • Item2
    ", + "expectedFile": "ConverterTests.WhenThereIsUnorderedListWithNestedOrderedList_ThenConvertToMarkdownListWithNestedList.verified.md" + }, + { + "id": "WhenThereIsWhitespaceAroundNestedLists_PreventBlankLinesWhenConvertingToMarkdownList", + "html": "
      \n
    • OuterItem1\n
        \n
      1. InnerItem1
      2. \n
      \n
    • \n
    • Item2
    • \n
        \n
      1. InnerItem2
      2. \n
      \n
    • Item3
    • \n
    ", + "expectedFile": "ConverterTests.WhenThereIsWhitespaceAroundNestedLists_PreventBlankLinesWhenConvertingToMarkdownList.verified.md" + }, + { + "id": "WhenListItemTextContainsLeadingAndTrailingSpacesAndTabs_ThenConvertToMarkdownListItemWithSpacesAndTabsStripped", + "html": "
    1. \t This is a text with leading and trailing spaces and tabs\t\t
    ", + "expectedFile": "ConverterTests.WhenListItemTextContainsLeadingAndTrailingSpacesAndTabs_ThenConvertToMarkdownListItemWithSpacesAndTabsStripped.verified.md" + }, + { + "id": "WhenListContainsNewlineAndTabBetweenTagBorders_CleanupAndConvertToMarkdown", + "html": "
      \n\t
    1. \n\t\tItem1
    2. \n\t
    3. \n\t\tItem2
    ", + "expectedFile": "ConverterTests.WhenListContainsNewlineAndTabBetweenTagBorders_CleanupAndConvertToMarkdown.verified.md" + }, + { + "id": "WhenListContainsMultipleParagraphs_ConvertToMarkdownAndIndentSiblings", + "html": "
      \n\t
    1. \n\t\t

      Paragraph 1

      \n

      Paragraph 1.1

      \n

      Paragraph 1.2

    2. \n\t
    3. \n\t\t

      Paragraph 3

    ", + "expectedFile": "ConverterTests.WhenListContainsMultipleParagraphs_ConvertToMarkdownAndIndentSiblings.verified.md" + }, + { + "id": "WhenListContainsParagraphsOutsideItems_ConvertToMarkdownAndIndentSiblings", + "html": "
      \n\t
    1. Item1
    2. \n\t

      Item 1 additional info

      \n\t
    3. Item2
    4. \n
    ", + "expectedFile": "ConverterTests.WhenListContainsParagraphsOutsideItems_ConvertToMarkdownAndIndentSiblings.verified.md" + }, + { + "id": "When_OrderedListIsInTable_LeaveListAsHtml", + "html": "
    Heading
    1. Item1
    ", + "expectedFile": "ConverterTests.When_OrderedListIsInTable_LeaveListAsHtml.verified.md" + }, + { + "id": "When_UnorderedListIsInTable_LeaveListAsHtml", + "html": "
    Heading
    • Item1
    ", + "expectedFile": "ConverterTests.When_UnorderedListIsInTable_LeaveListAsHtml.verified.md" + }, + { + "id": "When_Underline_Tag_With_UnknownTagsReplacer_ThenConvertToItalics", + "html": "This is underline text.", + "expectedFile": "ConverterTests.When_Underline_Tag_With_UnknownTagsReplacer_ThenConvertToItalics.verified.md", + "config": { + "UnknownTagsReplacer": { + "u": "*" + } + } + }, + { + "id": "When_Underline_Tag_With_TagAlias_ThenConvertToItalics", + "html": "This is underline text.", + "expectedFile": "ConverterTests.When_Underline_Tag_With_TagAlias_ThenConvertToItalics.verified.md", + "config": { + "TagAliases": { + "u": "em" + } + } + }, + { + "id": "Check_Converter_With_Unknown_Tag_ByPass_Option", + "html": "text in unknown tag", + "expectedFile": "ConverterTests.Check_Converter_With_Unknown_Tag_ByPass_Option.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "WhenStyletagWithBypassOption_ReturnEmpty", + "html": "", + "expectedFile": "ConverterTests.WhenStyletagWithBypassOption_ReturnEmpty.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "Check_Converter_With_Unknown_Tag_Drop_Option", + "html": "text in unknown tag

    paragraph text

    ", + "expectedFile": "ConverterTests.Check_Converter_With_Unknown_Tag_Drop_Option.verified.md", + "config": { + "UnknownTags": "Drop" + } + }, + { + "id": "Check_Converter_With_Unknown_Tag_PassThrough_Option", + "html": "text in unknown tag

    paragraph text

    ", + "expectedFile": "ConverterTests.Check_Converter_With_Unknown_Tag_PassThrough_Option.verified.md", + "config": { + "UnknownTags": "PassThrough" + } + }, + { + "id": "WhenTable_ThenConvertToGFMTable", + "html": "
    col1col2col3
    data1data2data3
    ", + "expectedFile": "ConverterTests.WhenTable_ThenConvertToGFMTable.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "WhenTable_WithoutHeaderRow_With_TableWithoutHeaderRowHandlingOptionEmptyRow_ThenConvertToGFMTable_WithEmptyHeaderRow", + "html": "
    data1data2data3
    data4data5data6
    ", + "expectedFile": "ConverterTests.WhenTable_WithoutHeaderRow_With_TableWithoutHeaderRowHandlingOptionEmptyRow_ThenConvertToGFMTable_WithEmptyHeaderRow.verified.md", + "config": { + "UnknownTags": "Bypass", + "TableWithoutHeaderRowHandling": "EmptyRow" + } + }, + { + "id": "WhenTable_WithoutHeaderRow_With_TableWithoutHeaderRowHandlingOptionDefault_ThenConvertToGFMTable_WithFirstRowAsHeaderRow", + "html": "
    data1data2data3
    data4data5data6
    ", + "expectedFile": "ConverterTests.WhenTable_WithoutHeaderRow_With_TableWithoutHeaderRowHandlingOptionDefault_ThenConvertToGFMTable_WithFirstRowAsHeaderRow.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "WhenTable_Cell_Content_WithNewline_Add_BR_ThenConvertToGFMTable", + "html": "
    col1col2col3
    data line1\nline2data2data3
    ", + "expectedFile": "ConverterTests.WhenTable_Cell_Content_WithNewline_Add_BR_ThenConvertToGFMTable.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "WhenTable_CellContainsParagraph_AddBrThenConvertToGFMTable", + "html": "
    col1

    line1

    line2

    ", + "expectedFile": "ConverterTests.WhenTable_CellContainsParagraph_AddBrThenConvertToGFMTable.verified.md" + }, + { + "id": "WhenTable_ContainsTheadTh_ConvertToGFMTable", + "html": "
    col1col2
    data1data2
    ", + "expectedFile": "ConverterTests.WhenTable_ContainsTheadTh_ConvertToGFMTable.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenTable_ContainsTheadTd_ConvertToGFMTable", + "html": "
    col1col2
    data1data2
    ", + "expectedFile": "ConverterTests.WhenTable_ContainsTheadTd_ConvertToGFMTable.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenTable_CellContainsBr_PreserveBrAndConvertToGFMTable", + "html": "
    col1
    line 1
    line 2
    ", + "expectedFile": "ConverterTests.WhenTable_CellContainsBr_PreserveBrAndConvertToGFMTable.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenTable_HasEmptyRow_DropsEmptyRow", + "html": "
    abc
    ", + "expectedFile": "ConverterTests.WhenTable_HasEmptyRow_DropsEmptyRow.verified.md", + "config": { + "GithubFlavored": true, + "TableWithoutHeaderRowHandling": "EmptyRow" + } + }, + { + "id": "When_BR_With_GitHubFlavored_Config_ThenConvertToGFM_BR", + "html": "First part
    Second part", + "expectedFile": "ConverterTests.When_BR_With_GitHubFlavored_Config_ThenConvertToGFM_BR.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_PRE_With_GitHubFlavored_Config_ThenConvertToGFM_PRE", + "html": "
    var test = 'hello world';
    ", + "expectedFile": "ConverterTests.When_PRE_With_GitHubFlavored_Config_ThenConvertToGFM_PRE.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_PRE_With_Confluence_Lang_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE", + "html": "
    var test = 'hello world';
    ", + "expectedFile": "ConverterTests.When_PRE_With_Confluence_Lang_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_PRE_With_Github_Site_DIV_Parent_And_GitHubFlavored_Config_ThenConvertToGFM_PRE", + "html": "
    var test = \"hello world\";
    ", + "expectedFile": "ConverterTests.When_PRE_With_Github_Site_DIV_Parent_And_GitHubFlavored_Config_ThenConvertToGFM_PRE.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_PRE_With_HighlightJs_Lang_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE", + "html": "
    var test = \"hello world\";
    ", + "expectedFile": "ConverterTests.When_PRE_With_HighlightJs_Lang_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_PRE_With_Lang_Highlight_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE", + "html": "
    var test = 'hello world';
    ", + "expectedFile": "ConverterTests.When_PRE_With_Lang_Highlight_Class_Att_And_GitHubFlavored_Config_ThenConvertToGFM_PRE.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenRemovedCommentsIsEnabled_CommentsAreRemoved", + "html": "Hello there content

    ", + "expectedFile": "ConverterTests.WhenCommentOverlapTag_WithRemoveComments_ThenDoNotStripContentBetweenComments.verified.md", + "config": { + "RemoveComments": true + } + }, + { + "id": "WhenBoldTagContainsBRTag_ThenConvertToMarkdown", + "html": "test
    test
    ", + "expectedFile": "ConverterTests.WhenBoldTagContainsBRTag_ThenConvertToMarkdown.verified.md" + }, + { + "id": "WhenAnchorTagContainsImgTag_LinkTextShouldNotBeEscaped", + "html": "", + "expectedFile": "ConverterTests.WhenAnchorTagContainsImgTag_LinkTextShouldNotBeEscaped.verified.md" + }, + { + "id": "When_PRE_Without_Lang_Marker_Class_Att_And_GitHubFlavored_Config_With_DefaultCodeBlockLanguage_ThenConvertToGFM_PRE", + "html": "
    var test = \"hello world\";
    ", + "expectedFile": "ConverterTests.When_PRE_Without_Lang_Marker_Class_Att_And_GitHubFlavored_Config_With_DefaultCodeBlockLanguage_ThenConvertToGFM_PRE.verified.md", + "config": { + "GithubFlavored": true, + "DefaultCodeBlockLanguage": "csharp" + } + }, + { + "id": "When_PRE_With_Parent_DIV_And_Non_GitHubFlavored_Config_FirstLine_CodeBlock_SpaceIndent_Should_Be_Retained", + "html": "
    var test = \"hello world\";
    ", + "expectedFile": "ConverterTests.When_PRE_With_Parent_DIV_And_Non_GitHubFlavored_Config_FirstLine_CodeBlock_SpaceIndent_Should_Be_Retained.verified.md" + }, + { + "id": "When_Converting_HTML_Ensure_To_Process_Only_Body", + "html": "sample text", + "expectedFile": "ConverterTests.When_Converting_HTML_Ensure_To_Process_Only_Body.verified.md" + }, + { + "id": "When_Html_Containing_Nested_DIVs_Process_ONLY_Inner_Most_DIV", + "html": "
    sample text
    ", + "expectedFile": "ConverterTests.When_Html_Containing_Nested_DIVs_Process_ONLY_Inner_Most_DIV.verified.md" + }, + { + "id": "When_SingleChild_BlockTag_With_Parent_DIV_Ignore_Processing_DIV", + "html": "

    sample text

    ", + "expectedFile": "ConverterTests.When_SingleChild_BlockTag_With_Parent_DIV_Ignore_Processing_DIV.verified.md" + }, + { + "id": "When_Table_Within_List_Should_Be_Indented", + "html": "
    1. Item1
    2. Item2
      col1col2col3
      data1data2data3
    3. Item3
    ", + "expectedFile": "ConverterTests.When_Table_Within_List_Should_Be_Indented.verified.md" + }, + { + "id": "When_Tag_In_PassThoughTags_List_Then_Use_PassThroughConverter", + "html": "This text has image \"alt\". Next line of text", + "expectedFile": "ConverterTests.When_Tag_In_PassThoughTags_List_Then_Use_PassThroughConverter.verified.md", + "config": { + "PassThroughTags": [ + "img" + ] + } + }, + { + "id": "When_CodeContainsSpaces_ShouldPreserveSpaces", + "html": "A JavaScript function ...", + "expectedFile": "ConverterTests.When_CodeContainsSpaces_ShouldPreserveSpaces.verified.md" + }, + { + "id": "When_CodeContainsSpanWithExtraSpaces_Should_NotNormalizeSpaces", + "html": "A JavaScript function ...", + "expectedFile": "ConverterTests.When_CodeContainsSpanWithExtraSpaces_Should_NotNormalizeSpaces.verified.md" + }, + { + "id": "When_CodeContainsSpacesAndIsSurroundedByWhitespace_Should_NotRemoveSpaces", + "html": "A JavaScript function ...", + "expectedFile": "ConverterTests.When_CodeContainsSpacesAndIsSurroundedByWhitespace_Should_NotRemoveSpaces.verified.md" + }, + { + "id": "When_PreTag_Contains_IndentedFirstLine_Should_PreserveIndentation", + "html": "
        function foo {
    ", + "expectedFile": "ConverterTests.When_PreTag_Contains_IndentedFirstLine_Should_PreserveIndentation.verified.md" + }, + { + "id": "When_PreTag_Contains_IndentedFirstLine_Should_PreserveIndentation_GFM", + "html": "
        function foo {
    ", + "expectedFile": "ConverterTests.When_PreTag_Contains_IndentedFirstLine_Should_PreserveIndentation_GFM.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_PreTag_Within_List_Should_Be_Indented", + "html": "
    1. Item1
    2. Item2
       test
      \n test
    3. Item3
    ", + "expectedFile": "ConverterTests.When_PreTag_Within_List_Should_Be_Indented.verified.md" + }, + { + "id": "When_PreTag_Within_List_Should_Be_Indented_With_GitHub_FlavouredMarkdown", + "html": "
    1. Item1
    2. Item2
       test
      \n test
    3. Item3
    ", + "expectedFile": "ConverterTests.When_PreTag_Within_List_Should_Be_Indented_With_GitHub_FlavouredMarkdown.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_Text_Contains_NewLineChars_Should_Not_Convert_To_BR", + "html": "

    line 1
    line 2
    ", + "expectedFile": "ConverterTests.When_Text_Contains_NewLineChars_Should_Not_Convert_To_BR.verified.md" + }, + { + "id": "When_Text_Contains_NewLineChars_Should_Not_Convert_To_BR_GitHub_Flavoured", + "html": "

    line 1
    line 2
    ", + "expectedFile": "ConverterTests.When_Text_Contains_NewLineChars_Should_Not_Convert_To_BR_GitHub_Flavoured.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "When_Consecutive_Strong_Tags_Should_Convert_Properly", + "html": "block1block2block3block4", + "expectedFile": "ConverterTests.When_Consecutive_Strong_Tags_Should_Convert_Properly.verified.md" + }, + { + "id": "When_Consecutive_Em_Tags_Should_Convert_Properly", + "html": "block1block2block3block4", + "expectedFile": "ConverterTests.When_Consecutive_Em_Tags_Should_Convert_Properly.verified.md" + }, + { + "id": "Li_With_No_Parent", + "html": "

  • item
  • ", + "expectedFile": "ConverterTests.Li_With_No_Parent.verified.md" + }, + { + "id": "When_Span_with_newline_Should_Convert_Properly", + "html": "2 sets\n30 mountain climbers", + "expectedFile": "ConverterTests.When_Span_with_newline_Should_Convert_Properly.verified.md" + }, + { + "id": "Bug255_table_newline_char_issue", + "html": "\n\n\n\n\n
    ProgressionFocus
    ", + "expectedFile": "ConverterTests.Bug255_table_newline_char_issue.verified.md" + }, + { + "id": "When_Content_Contains_script_tags_ignore_it", + "html": "

    simple paragraph

    ", + "expectedFile": "ConverterTests.When_Content_Contains_script_tags_ignore_it.verified.md" + }, + { + "id": "When_DescriptionListTag_ThenConvertToMarkdown_List", + "html": "
    Coffee
    Filter Coffee
    Hot Black Coffee
    Milk
    White Cold Drink
    ", + "expectedFile": "ConverterTests.When_DescriptionListTag_ThenConvertToMarkdown_List.verified.md" + }, + { + "id": "Bug294_Table_bug_with_row_superfluous_newlines", + "html": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    \u6bd4\u8f83wordpresshexo & hugo
    \u642d\u5efa\u8981\u6c42\u4e00\u53f0\u670d\u52a1\u5668\u4ee5\u53ca\u8fd0\u884c\u73af\u5883\u9759\u6001\u751f\u6210\u9875\u9762\uff0c\u65e0\u9700\u670d\u52a1\u5668\u3002
    \u6027\u80fd\u7531\u4e8e\u662f\u52a8\u6001\u751f\u6210\u9875\u9762\uff0c\u53ef\u4ee5\u901a\u8fc7\u81ea\u884c\u914d\u7f6e\u63d0\u9ad8\u6027\u80fd\uff0c\u4f46\u662f\u4ecd\u7136\u65e0\u6cd5\u5ab2\u7f8e\u9759\u6001\u9875\u9762\u51e0\u4e4e\u65e0\u9700\u8003\u8651\u6027\u80fd\u95ee\u9898
    \u8bbf\u95ee\u901f\u5ea6\u4f9d\u8d56\u4e8e\u670d\u52a1\u5668\u914d\u7f6e\u4ee5\u53cacdn\u52a0\u901f\u3002\u53ea\u9700\u8003\u8651cdn\u52a0\u901f
    \u529f\u80fd\u5b8c\u5584\u4f5c\u4e3a\u5f3a\u5927\u7684cms\u529f\u80fd\u5f88\u5b8c\u5584\uff0c\u9700\u8981\u7684\u529f\u80fd\u57fa\u672c\u53ef\u4ee5\u63d2\u4ef6\u4e0b\u8f7d\u76f4\u63a5\u5b9e\u73b0\u3002\u989d\u5916\u529f\u80fd\u4e5f\u53ef\u4ee5\u901a\u8fc7\u63d2\u4ef6\u5b9e\u73b0\uff0c\u4e0d\u8fc7\u7a0d\u5fae\u9700\u8981\u81ea\u884c\u67e5\u627e\u4ee5\u53cadiy
    \u540e\u53f0\u7ba1\u7406\u73b0\u6210\u7684\u540e\u53f0\u7ba1\u7406\u529f\u80fd\uff0c\u5f00\u7bb1\u5373\u7528\u7531\u4e8e\u9759\u6001\u535a\u5ba2\uff0c\u672c\u8eab\u6ca1\u6709\u540e\u53f0\u7ba1\u7406\uff0c\u6709\u9700\u6c42\u9700\u8981\u81ea\u884c\u641c\u7d22\u5b9e\u73b0
    ", + "expectedFile": "ConverterTests.Bug294_Table_bug_with_row_superfluous_newlines.verified.md" + }, + { + "id": "WhenTableHeadingWithAlignmentStyles_ThenTableHeaderShouldHaveProperAlignment", + "html": "
    Col1Col2Col2
    123
    ", + "expectedFile": "ConverterTests.WhenTableHeadingWithAlignmentStyles_ThenTableHeaderShouldHaveProperAlignment.verified.md" + }, + { + "id": "When_Sup_And_Nested_Sup", + "html": "This is the 1st sentence to test the sup tag conversion", + "expectedFile": "ConverterTests.When_Sup_And_Nested_Sup.verified.md" + }, + { + "id": "When_Anchor_Text_with_Underscore_Do_Not_Escape", + "html": "This a sample paragraph from https://www.w3schools.com/html/mov_bbb.mp4", + "expectedFile": "ConverterTests.When_Anchor_Text_with_Underscore_Do_Not_Escape.verified.md" + }, + { + "id": "When_Strikethrough_And_Nested_Strikethrough", + "html": "This is the 1st sentence to test the strikethrough tag conversion", + "expectedFile": "ConverterTests.When_Strikethrough_And_Nested_Strikethrough.verified.md" + }, + { + "id": "When_Spaces_In_Inline_Tags_Should_Be_Retained", + "html": "... example html code block", + "expectedFile": "ConverterTests.When_Spaces_In_Inline_Tags_Should_Be_Retained.verified.md" + }, + { + "id": "When_SuppressNewlineFlag_PrefixDiv_Should_Be_Empty", + "html": "
    the
    fox
    jumps
    over
    ", + "expectedFile": "ConverterTests.When_SuppressNewlineFlag_PrefixDiv_Should_Be_Empty.verified.md", + "config": { + "SuppressDivNewlines": true + } + }, + { + "id": "WhenTable_WithColSpan_TableHeaderColumnSpansHandling_ThenConvertToGFMTable", + "html": "
    col1col2col3
    data1data2.1data2.2data3
    ", + "expectedFile": "ConverterTests.WhenTable_WithColSpan_TableHeaderColumnSpansHandling_ThenConvertToGFMTable.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "Bug391_AnchorTagUnnecessarilyIndented", + "html": "

    \n\n

    \n\n\n
    \nAn error occurred while importing data from feed 'FBA Producten'. More details can be found in the latest \">feed validation report.\n
    \n\n\n\n\n\" class=\"btn btn-primary btn-sm my-2\" target=\"_blank\">View feed 4", + "expectedFile": "ConverterTests.Bug391_AnchorTagUnnecessarilyIndented.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "Bug403_unexpectedBehaviourWhenTableBodyRowsWithTHCells", + "html": "\n\n\n\n
    Heading1Heading2
    data 1data 2
    data 3data 4
    ", + "expectedFile": "ConverterTests.Bug403_unexpectedBehaviourWhenTableBodyRowsWithTHCells.verified.md", + "config": { + "UnknownTags": "Bypass", + "ListBulletChar": "*", + "GithubFlavored": true + } + }, + { + "id": "EscapeMarkdownCharsInTextProperly", + "html": "[a-z]([0-9]){0,4}", + "expectedFile": "ConverterTests.EscapeMarkdownCharsInTextProperly.verified.md" + }, + { + "id": "Bug400_MissingSpanSpaceWithItalics", + "html": "

    What we thought: When we built Pages, we assumed that customers would use them like newsletters to share relevant, continually-updated information with field teams.

    ", + "expectedFile": "ConverterTests.Bug400_MissingSpanSpaceWithItalics.verified.md" + }, + { + "id": "When_NestedParagraphs_FiveLevelsDeep_ThenConvertCorrectly", + "html": "

    Level1

    Level2

    Level3

    Level4

    Level5

    ", + "expectedFile": "ConverterTests.When_NestedParagraphs_FiveLevelsDeep_ThenConvertCorrectly.verified.md", + "config": { + "GithubFlavored": true, + "UnknownTags": "Bypass" + } + }, + { + "id": "When_NestedSpans_FiveLevelsDeep_ThenConvertCorrectly", + "html": "Level1Level2Level3Level4Level5", + "expectedFile": "ConverterTests.When_NestedSpans_FiveLevelsDeep_ThenConvertCorrectly.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "When_InterleavedParagraphsAndSpans_ThenConvertCorrectly", + "html": "

    Text1

    Text2

    Text3

    Text4

    ", + "expectedFile": "ConverterTests.When_InterleavedParagraphsAndSpans_ThenConvertCorrectly.verified.md", + "config": { + "GithubFlavored": true, + "UnknownTags": "Bypass" + } + }, + { + "id": "When_ManySequentialUnclosedParagraphs_ThenConvertCorrectly", + "html": "

    Part1

    Part2

    Part3

    Part4

    Part5

    Part6

    Part7

    Part8", + "expectedFile": "ConverterTests.When_ManySequentialUnclosedParagraphs_ThenConvertCorrectly.verified.md", + "config": { + "GithubFlavored": true, + "UnknownTags": "Bypass" + } + }, + { + "id": "When_UnclosedParagraphsWithSpansAndTextNodes_ThenConvertCorrectly", + "html": "

    Intro

    Filler text here.

    Section1

    Section2

    Section3", + "expectedFile": "ConverterTests.When_UnclosedParagraphsWithSpansAndTextNodes_ThenConvertCorrectly.verified.md", + "config": { + "GithubFlavored": true, + "UnknownTags": "Bypass" + } + }, + { + "id": "When_EmptyNestedParagraphs_ThenConvertCorrectly", + "html": "

    ", + "expectedFile": "ConverterTests.When_EmptyNestedParagraphs_ThenConvertCorrectly.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "When_AlternatingEmptyAndFilledNestedParagraphs_ThenConvertCorrectly", + "html": "

    A

    B

    C

    D

    E

    ", + "expectedFile": "ConverterTests.When_AlternatingEmptyAndFilledNestedParagraphs_ThenConvertCorrectly.verified.md", + "config": { + "UnknownTags": "Bypass" + } + }, + { + "id": "When_NestedParagraphs_TenLevelsDeep_ThenConvertCorrectly", + "html": "

    L1

    L2

    L3

    L4

    L5

    L6

    L7

    L8

    L9

    L10

    ", + "expectedFile": "ConverterTests.When_NestedParagraphs_TenLevelsDeep_ThenConvertCorrectly.verified.md", + "config": { + "GithubFlavored": true, + "UnknownTags": "Bypass" + } + }, + { + "id": "When_NestedTableIsInTable_LeaveNestedTableAsHtml", + "html": "
    StepInstructions
    1
    ConditionAction
    ADo X
    ", + "expectedFile": "ConverterTests.When_NestedTableIsInTable_LeaveNestedTableAsHtml.verified.md" + }, + { + "id": "When_ComplexNestedTableIsInTable_LeaveNestedTableAsHtml", + "html": "\n \n \n \n \n \n \n \n \n
    CategoryDetails
    Products\n \n \n \n \n
    NamePrice
    Item 1$10
    Item 2$20
    \n
    ", + "expectedFile": "ConverterTests.When_ComplexNestedTableIsInTable_LeaveNestedTableAsHtml.verified.md" + }, + { + "id": "When_MultipleNestedTablesInTable_LeaveAllNestedTablesAsHtml", + "html": "\n \n \n \n \n \n \n \n \n
    Column 1Column 2
    \n \n \n
    Nested 1
    \n
    \n \n \n
    Nested 2
    \n
    ", + "expectedFile": "ConverterTests.When_MultipleNestedTablesInTable_LeaveAllNestedTablesAsHtml.verified.md" + }, + { + "id": "WhenTable_WithCaption_ThenCaptionAppearsAboveTable", + "html": "
    Monthly Sales Report
    MonthSales
    January$1000
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaption_ThenCaptionAppearsAboveTable.verified.md" + }, + { + "id": "WhenTable_WithoutCaption_ThenConvertNormally", + "html": "
    MonthSales
    January$1000
    ", + "expectedFile": "ConverterTests.WhenTable_WithoutCaption_ThenConvertNormally.verified.md" + }, + { + "id": "WhenTable_WithEmptyCaption_ThenConvertNormally", + "html": "
    MonthSales
    January$1000
    ", + "expectedFile": "ConverterTests.WhenTable_WithEmptyCaption_ThenConvertNormally.verified.md" + }, + { + "id": "WhenTable_WithCaptionContainingWhitespace_ThenTrimWhitespace", + "html": "
    Table Caption
    Col1
    Data1
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaptionContainingWhitespace_ThenTrimWhitespace.verified.md" + }, + { + "id": "WhenTable_WithCaptionContainingMarkdownChars_ThenHandleProperly", + "html": "
    Sales Report [2024] - **Important**
    MonthSales
    Jan$100
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaptionContainingMarkdownChars_ThenHandleProperly.verified.md" + }, + { + "id": "WhenTable_WithCaptionAndNoHeaderRow_EmptyRowHandling_ThenCaptionAppearsAboveTable", + "html": "
    Data Table
    data1data2
    data3data4
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaptionAndNoHeaderRow_EmptyRowHandling_ThenCaptionAppearsAboveTable.verified.md", + "config": { + "TableWithoutHeaderRowHandling": "EmptyRow" + } + }, + { + "id": "WhenTable_WithCaptionContainingNestedTags_ThenExtractTextOnly", + "html": "
    Sales Report for 2024
    Month
    Jan
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaptionContainingNestedTags_ThenExtractTextOnly.verified.md" + }, + { + "id": "WhenTable_WithCaptionAndGithubFlavored_ThenCaptionAppearsAboveTable", + "html": "
    Code Review Summary
    PRStatus
    #123Approved
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaptionAndGithubFlavored_ThenCaptionAppearsAboveTable.verified.md", + "config": { + "GithubFlavored": true + } + }, + { + "id": "WhenTable_WithCaptionContainingNewlines_ThenHandleNewlines", + "html": "
    Multi\nLine\nCaption
    Col
    Data
    ", + "expectedFile": "ConverterTests.WhenTable_WithCaptionContainingNewlines_ThenHandleNewlines.verified.md" + }, + { + "id": "WhenTableRowWithDuplicateStyleKeysAfterTrimming_ThenConvertWithoutException", + "html": "
    Header
    Data
    ", + "expectedFile": "ConverterTests.WhenTableRowWithDuplicateStyleKeysAfterTrimming_ThenConvertWithoutException.verified.md" + }, + { + "id": "SmartHandling_HttpScheme_Http", + "html": "example.com", + "dataOnly": true + }, + { + "id": "SmartHandling_HttpScheme_Https", + "html": "example.com", + "dataOnly": true + }, + { + "id": "SmartHandling_OutputOnlyHref_Http", + "html": "example.com", + "dataOnly": true + }, + { + "id": "SmartHandling_OutputOnlyHref_Https", + "html": "example.com", + "dataOnly": true + }, + { + "id": "SmartHandling_NonWellFormed", + "html": "http://example.com/path/file name.docx", + "dataOnly": true + }, + { + "id": "SmartHandling_ImplicitFile", + "html": "\tc:\\\\directory\\filename", + "dataOnly": true + }, + { + "id": "SmartHandling_FileMissingSlash", + "html": "file://c:/directory/filename", + "dataOnly": true + }, + { + "id": "SmartHandling_UnescapedBackslashes", + "html": "http:\\\\host/path/file", + "dataOnly": true + }, + { + "id": "Base64_SaveToFile_Png", + "html": "

    Before

    \"test\"

    After

    ", + "dataOnly": true + }, + { + "id": "Base64_SaveToFile_Jpeg", + "html": "\"jpeg\"", + "dataOnly": true + }, + { + "id": "Base64_SaveToFile_Multiple", + "html": "\"first\"\"second\"", + "dataOnly": true + }, + { + "id": "Base64_SaveToFile_CustomName", + "html": "\"test\"", + "dataOnly": true + }, + { + "id": "Base64_SaveToFile_NonExistentDir", + "html": "\"test\"", + "dataOnly": true + }, + { + "id": "Underline_Tag_Alias", + "html": "This is underline text.", + "dataOnly": true + }, + { + "id": "Unknown_Tag_Raise", + "html": "text in unknown tag

    paragraph text

    ", + "dataOnly": true + }, + { + "id": "PastedHtmlUnicodeSpaces", + "html": "Markdown Monster is an easy to use and extensible\u00a0Markdown Editor,\u00a0Viewer\u00a0and\u00a0Weblog Publisher\u00a0for Windows. Our goal is to provide the best Markdown specific editor for Windows and make it as easy as possible to create Markdown documents. We provide a core editor and previewer, and a number of non-intrusive helpers to help embed content like images, links, tables, code and more into your documents with minimal effort.", + "dataOnly": true + }, + { + "id": "Bug393_RegressionWithVaryingNewLines", + "html": "This is regular text\n

    This is HTML:

    • Line 1
    • Line 2
    • Line 3 has an unknown tag

    ", + "expectedFile": "ConverterTests.Bug393_RegressionWithVaryingNewLines.verified.md", + "config": { + "UnknownTags": "Bypass", + "ListBulletChar": "*" + } + }, + { + "id": "SlackFlavored_Bold", + "html": "test | test", + "expectedFile": "ConverterTests.SlackFlavored_Bold.verified.md", + "config": { + "SlackFlavored": true + } + }, + { + "id": "SlackFlavored_Bullets", + "html": "
      \n
    • Item 1
    • \n
    • Item 2
    • \n
    • Item 3
    • \n
    ", + "expectedFile": "ConverterTests.SlackFlavored_Bullets.verified.md", + "config": { + "SlackFlavored": true + } + }, + { + "id": "SlackFlavored_Italic", + "html": "test | test", + "expectedFile": "ConverterTests.SlackFlavored_Italic.verified.md", + "config": { + "SlackFlavored": true + } + }, + { + "id": "SlackFlavored_Strikethrough", + "html": "test", + "expectedFile": "ConverterTests.SlackFlavored_Strikethrough.verified.md", + "config": { + "SlackFlavored": true + } + }, + { + "id": "Converter_Is_Thread_Safe_For_Concurrent_Use", + "html": "
    Head
    1. Item1
    2. Item2

    Tail

    ", + "dataOnly": true + }, + { + "id": "SlackFlavored_Unsupported_Hr", + "html": "
    ", + "dataOnly": true + }, + { + "id": "SlackFlavored_Unsupported_Img", + "html": "", + "dataOnly": true + }, + { + "id": "SlackFlavored_Unsupported_Sup", + "html": "test", + "dataOnly": true + }, + { + "id": "SlackFlavored_Unsupported_Table", + "html": "
    ", + "dataOnly": true + }, + { + "id": "SlackFlavored_Unsupported_Table_Td", + "html": "", + "dataOnly": true + }, + { + "id": "SlackFlavored_Unsupported_Table_Tr", + "html": "", + "dataOnly": true + } +] \ No newline at end of file diff --git a/src/ReverseMarkdown.Test/TestData/commonmark.json b/src/ReverseMarkdown.Test/TestData/commonmark.json new file mode 100644 index 00000000..1f89e66f --- /dev/null +++ b/src/ReverseMarkdown.Test/TestData/commonmark.json @@ -0,0 +1,5218 @@ +[ + { + "markdown": "\tfoo\tbaz\t\tbim\n", + "html": "
    foo\tbaz\t\tbim\n
    \n", + "example": 1, + "start_line": 355, + "end_line": 360, + "section": "Tabs" + }, + { + "markdown": " \tfoo\tbaz\t\tbim\n", + "html": "
    foo\tbaz\t\tbim\n
    \n", + "example": 2, + "start_line": 362, + "end_line": 367, + "section": "Tabs" + }, + { + "markdown": " a\ta\n ὐ\ta\n", + "html": "
    a\ta\nὐ\ta\n
    \n", + "example": 3, + "start_line": 369, + "end_line": 376, + "section": "Tabs" + }, + { + "markdown": " - foo\n\n\tbar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 4, + "start_line": 382, + "end_line": 393, + "section": "Tabs" + }, + { + "markdown": "- foo\n\n\t\tbar\n", + "html": "
      \n
    • \n

      foo

      \n
        bar\n
      \n
    • \n
    \n", + "example": 5, + "start_line": 395, + "end_line": 407, + "section": "Tabs" + }, + { + "markdown": ">\t\tfoo\n", + "html": "
    \n
      foo\n
    \n
    \n", + "example": 6, + "start_line": 418, + "end_line": 425, + "section": "Tabs" + }, + { + "markdown": "-\t\tfoo\n", + "html": "
      \n
    • \n
        foo\n
      \n
    • \n
    \n", + "example": 7, + "start_line": 427, + "end_line": 436, + "section": "Tabs" + }, + { + "markdown": " foo\n\tbar\n", + "html": "
    foo\nbar\n
    \n", + "example": 8, + "start_line": 439, + "end_line": 446, + "section": "Tabs" + }, + { + "markdown": " - foo\n - bar\n\t - baz\n", + "html": "
      \n
    • foo\n
        \n
      • bar\n
          \n
        • baz
        • \n
        \n
      • \n
      \n
    • \n
    \n", + "example": 9, + "start_line": 448, + "end_line": 464, + "section": "Tabs" + }, + { + "markdown": "#\tFoo\n", + "html": "

    Foo

    \n", + "example": 10, + "start_line": 466, + "end_line": 470, + "section": "Tabs" + }, + { + "markdown": "*\t*\t*\t\n", + "html": "
    \n", + "example": 11, + "start_line": 472, + "end_line": 476, + "section": "Tabs" + }, + { + "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n", + "html": "

    !"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~

    \n", + "example": 12, + "start_line": 489, + "end_line": 493, + "section": "Backslash escapes" + }, + { + "markdown": "\\\t\\A\\a\\ \\3\\φ\\«\n", + "html": "

    \\\t\\A\\a\\ \\3\\φ\\«

    \n", + "example": 13, + "start_line": 499, + "end_line": 503, + "section": "Backslash escapes" + }, + { + "markdown": "\\*not emphasized*\n\\
    not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\ö not a character entity\n", + "html": "

    *not emphasized*\n<br/> not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url "not a reference"\n&ouml; not a character entity

    \n", + "example": 14, + "start_line": 509, + "end_line": 529, + "section": "Backslash escapes" + }, + { + "markdown": "\\\\*emphasis*\n", + "html": "

    \\emphasis

    \n", + "example": 15, + "start_line": 534, + "end_line": 538, + "section": "Backslash escapes" + }, + { + "markdown": "foo\\\nbar\n", + "html": "

    foo
    \nbar

    \n", + "example": 16, + "start_line": 543, + "end_line": 549, + "section": "Backslash escapes" + }, + { + "markdown": "`` \\[\\` ``\n", + "html": "

    \\[\\`

    \n", + "example": 17, + "start_line": 555, + "end_line": 559, + "section": "Backslash escapes" + }, + { + "markdown": " \\[\\]\n", + "html": "
    \\[\\]\n
    \n", + "example": 18, + "start_line": 562, + "end_line": 567, + "section": "Backslash escapes" + }, + { + "markdown": "~~~\n\\[\\]\n~~~\n", + "html": "
    \\[\\]\n
    \n", + "example": 19, + "start_line": 570, + "end_line": 577, + "section": "Backslash escapes" + }, + { + "markdown": "\n", + "html": "

    https://example.com?find=\\*

    \n", + "example": 20, + "start_line": 580, + "end_line": 584, + "section": "Backslash escapes" + }, + { + "markdown": "\n", + "html": "\n", + "example": 21, + "start_line": 587, + "end_line": 591, + "section": "Backslash escapes" + }, + { + "markdown": "[foo](/bar\\* \"ti\\*tle\")\n", + "html": "

    foo

    \n", + "example": 22, + "start_line": 597, + "end_line": 601, + "section": "Backslash escapes" + }, + { + "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n", + "html": "

    foo

    \n", + "example": 23, + "start_line": 604, + "end_line": 610, + "section": "Backslash escapes" + }, + { + "markdown": "``` foo\\+bar\nfoo\n```\n", + "html": "
    foo\n
    \n", + "example": 24, + "start_line": 613, + "end_line": 620, + "section": "Backslash escapes" + }, + { + "markdown": "  & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸\n", + "html": "

      & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸

    \n", + "example": 25, + "start_line": 649, + "end_line": 657, + "section": "Entity and numeric character references" + }, + { + "markdown": "# Ӓ Ϡ �\n", + "html": "

    # Ӓ Ϡ �

    \n", + "example": 26, + "start_line": 668, + "end_line": 672, + "section": "Entity and numeric character references" + }, + { + "markdown": "" ആ ಫ\n", + "html": "

    " ആ ಫ

    \n", + "example": 27, + "start_line": 681, + "end_line": 685, + "section": "Entity and numeric character references" + }, + { + "markdown": "  &x; &#; &#x;\n�\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n", + "html": "

    &nbsp &x; &#; &#x;\n&#87654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;

    \n", + "example": 28, + "start_line": 690, + "end_line": 700, + "section": "Entity and numeric character references" + }, + { + "markdown": "©\n", + "html": "

    &copy

    \n", + "example": 29, + "start_line": 707, + "end_line": 711, + "section": "Entity and numeric character references" + }, + { + "markdown": "&MadeUpEntity;\n", + "html": "

    &MadeUpEntity;

    \n", + "example": 30, + "start_line": 717, + "end_line": 721, + "section": "Entity and numeric character references" + }, + { + "markdown": "\n", + "html": "\n", + "example": 31, + "start_line": 728, + "end_line": 732, + "section": "Entity and numeric character references" + }, + { + "markdown": "[foo](/föö \"föö\")\n", + "html": "

    foo

    \n", + "example": 32, + "start_line": 735, + "end_line": 739, + "section": "Entity and numeric character references" + }, + { + "markdown": "[foo]\n\n[foo]: /föö \"föö\"\n", + "html": "

    foo

    \n", + "example": 33, + "start_line": 742, + "end_line": 748, + "section": "Entity and numeric character references" + }, + { + "markdown": "``` föö\nfoo\n```\n", + "html": "
    foo\n
    \n", + "example": 34, + "start_line": 751, + "end_line": 758, + "section": "Entity and numeric character references" + }, + { + "markdown": "`föö`\n", + "html": "

    f&ouml;&ouml;

    \n", + "example": 35, + "start_line": 764, + "end_line": 768, + "section": "Entity and numeric character references" + }, + { + "markdown": " föfö\n", + "html": "
    f&ouml;f&ouml;\n
    \n", + "example": 36, + "start_line": 771, + "end_line": 776, + "section": "Entity and numeric character references" + }, + { + "markdown": "*foo*\n*foo*\n", + "html": "

    *foo*\nfoo

    \n", + "example": 37, + "start_line": 783, + "end_line": 789, + "section": "Entity and numeric character references" + }, + { + "markdown": "* foo\n\n* foo\n", + "html": "

    * foo

    \n
      \n
    • foo
    • \n
    \n", + "example": 38, + "start_line": 791, + "end_line": 800, + "section": "Entity and numeric character references" + }, + { + "markdown": "foo bar\n", + "html": "

    foo\n\nbar

    \n", + "example": 39, + "start_line": 802, + "end_line": 808, + "section": "Entity and numeric character references" + }, + { + "markdown": " foo\n", + "html": "

    \tfoo

    \n", + "example": 40, + "start_line": 810, + "end_line": 814, + "section": "Entity and numeric character references" + }, + { + "markdown": "[a](url "tit")\n", + "html": "

    [a](url "tit")

    \n", + "example": 41, + "start_line": 817, + "end_line": 821, + "section": "Entity and numeric character references" + }, + { + "markdown": "- `one\n- two`\n", + "html": "
      \n
    • `one
    • \n
    • two`
    • \n
    \n", + "example": 42, + "start_line": 840, + "end_line": 848, + "section": "Precedence" + }, + { + "markdown": "***\n---\n___\n", + "html": "
    \n
    \n
    \n", + "example": 43, + "start_line": 879, + "end_line": 887, + "section": "Thematic breaks" + }, + { + "markdown": "+++\n", + "html": "

    +++

    \n", + "example": 44, + "start_line": 892, + "end_line": 896, + "section": "Thematic breaks" + }, + { + "markdown": "===\n", + "html": "

    ===

    \n", + "example": 45, + "start_line": 899, + "end_line": 903, + "section": "Thematic breaks" + }, + { + "markdown": "--\n**\n__\n", + "html": "

    --\n**\n__

    \n", + "example": 46, + "start_line": 908, + "end_line": 916, + "section": "Thematic breaks" + }, + { + "markdown": " ***\n ***\n ***\n", + "html": "
    \n
    \n
    \n", + "example": 47, + "start_line": 921, + "end_line": 929, + "section": "Thematic breaks" + }, + { + "markdown": " ***\n", + "html": "
    ***\n
    \n", + "example": 48, + "start_line": 934, + "end_line": 939, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n ***\n", + "html": "

    Foo\n***

    \n", + "example": 49, + "start_line": 942, + "end_line": 948, + "section": "Thematic breaks" + }, + { + "markdown": "_____________________________________\n", + "html": "
    \n", + "example": 50, + "start_line": 953, + "end_line": 957, + "section": "Thematic breaks" + }, + { + "markdown": " - - -\n", + "html": "
    \n", + "example": 51, + "start_line": 962, + "end_line": 966, + "section": "Thematic breaks" + }, + { + "markdown": " ** * ** * ** * **\n", + "html": "
    \n", + "example": 52, + "start_line": 969, + "end_line": 973, + "section": "Thematic breaks" + }, + { + "markdown": "- - - -\n", + "html": "
    \n", + "example": 53, + "start_line": 976, + "end_line": 980, + "section": "Thematic breaks" + }, + { + "markdown": "- - - - \n", + "html": "
    \n", + "example": 54, + "start_line": 985, + "end_line": 989, + "section": "Thematic breaks" + }, + { + "markdown": "_ _ _ _ a\n\na------\n\n---a---\n", + "html": "

    _ _ _ _ a

    \n

    a------

    \n

    ---a---

    \n", + "example": 55, + "start_line": 994, + "end_line": 1004, + "section": "Thematic breaks" + }, + { + "markdown": " *-*\n", + "html": "

    -

    \n", + "example": 56, + "start_line": 1010, + "end_line": 1014, + "section": "Thematic breaks" + }, + { + "markdown": "- foo\n***\n- bar\n", + "html": "
      \n
    • foo
    • \n
    \n
    \n
      \n
    • bar
    • \n
    \n", + "example": 57, + "start_line": 1019, + "end_line": 1031, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n***\nbar\n", + "html": "

    Foo

    \n
    \n

    bar

    \n", + "example": 58, + "start_line": 1036, + "end_line": 1044, + "section": "Thematic breaks" + }, + { + "markdown": "Foo\n---\nbar\n", + "html": "

    Foo

    \n

    bar

    \n", + "example": 59, + "start_line": 1053, + "end_line": 1060, + "section": "Thematic breaks" + }, + { + "markdown": "* Foo\n* * *\n* Bar\n", + "html": "
      \n
    • Foo
    • \n
    \n
    \n
      \n
    • Bar
    • \n
    \n", + "example": 60, + "start_line": 1066, + "end_line": 1078, + "section": "Thematic breaks" + }, + { + "markdown": "- Foo\n- * * *\n", + "html": "
      \n
    • Foo
    • \n
    • \n
      \n
    • \n
    \n", + "example": 61, + "start_line": 1083, + "end_line": 1093, + "section": "Thematic breaks" + }, + { + "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n", + "html": "

    foo

    \n

    foo

    \n

    foo

    \n

    foo

    \n
    foo
    \n
    foo
    \n", + "example": 62, + "start_line": 1112, + "end_line": 1126, + "section": "ATX headings" + }, + { + "markdown": "####### foo\n", + "html": "

    ####### foo

    \n", + "example": 63, + "start_line": 1131, + "end_line": 1135, + "section": "ATX headings" + }, + { + "markdown": "#5 bolt\n\n#hashtag\n", + "html": "

    #5 bolt

    \n

    #hashtag

    \n", + "example": 64, + "start_line": 1146, + "end_line": 1153, + "section": "ATX headings" + }, + { + "markdown": "\\## foo\n", + "html": "

    ## foo

    \n", + "example": 65, + "start_line": 1158, + "end_line": 1162, + "section": "ATX headings" + }, + { + "markdown": "# foo *bar* \\*baz\\*\n", + "html": "

    foo bar *baz*

    \n", + "example": 66, + "start_line": 1167, + "end_line": 1171, + "section": "ATX headings" + }, + { + "markdown": "# foo \n", + "html": "

    foo

    \n", + "example": 67, + "start_line": 1176, + "end_line": 1180, + "section": "ATX headings" + }, + { + "markdown": " ### foo\n ## foo\n # foo\n", + "html": "

    foo

    \n

    foo

    \n

    foo

    \n", + "example": 68, + "start_line": 1185, + "end_line": 1193, + "section": "ATX headings" + }, + { + "markdown": " # foo\n", + "html": "
    # foo\n
    \n", + "example": 69, + "start_line": 1198, + "end_line": 1203, + "section": "ATX headings" + }, + { + "markdown": "foo\n # bar\n", + "html": "

    foo\n# bar

    \n", + "example": 70, + "start_line": 1206, + "end_line": 1212, + "section": "ATX headings" + }, + { + "markdown": "## foo ##\n ### bar ###\n", + "html": "

    foo

    \n

    bar

    \n", + "example": 71, + "start_line": 1217, + "end_line": 1223, + "section": "ATX headings" + }, + { + "markdown": "# foo ##################################\n##### foo ##\n", + "html": "

    foo

    \n
    foo
    \n", + "example": 72, + "start_line": 1228, + "end_line": 1234, + "section": "ATX headings" + }, + { + "markdown": "### foo ### \n", + "html": "

    foo

    \n", + "example": 73, + "start_line": 1239, + "end_line": 1243, + "section": "ATX headings" + }, + { + "markdown": "### foo ### b\n", + "html": "

    foo ### b

    \n", + "example": 74, + "start_line": 1250, + "end_line": 1254, + "section": "ATX headings" + }, + { + "markdown": "# foo#\n", + "html": "

    foo#

    \n", + "example": 75, + "start_line": 1259, + "end_line": 1263, + "section": "ATX headings" + }, + { + "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n", + "html": "

    foo ###

    \n

    foo ###

    \n

    foo #

    \n", + "example": 76, + "start_line": 1269, + "end_line": 1277, + "section": "ATX headings" + }, + { + "markdown": "****\n## foo\n****\n", + "html": "
    \n

    foo

    \n
    \n", + "example": 77, + "start_line": 1283, + "end_line": 1291, + "section": "ATX headings" + }, + { + "markdown": "Foo bar\n# baz\nBar foo\n", + "html": "

    Foo bar

    \n

    baz

    \n

    Bar foo

    \n", + "example": 78, + "start_line": 1294, + "end_line": 1302, + "section": "ATX headings" + }, + { + "markdown": "## \n#\n### ###\n", + "html": "

    \n

    \n

    \n", + "example": 79, + "start_line": 1307, + "end_line": 1315, + "section": "ATX headings" + }, + { + "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n", + "html": "

    Foo bar

    \n

    Foo bar

    \n", + "example": 80, + "start_line": 1347, + "end_line": 1356, + "section": "Setext headings" + }, + { + "markdown": "Foo *bar\nbaz*\n====\n", + "html": "

    Foo bar\nbaz

    \n", + "example": 81, + "start_line": 1361, + "end_line": 1368, + "section": "Setext headings" + }, + { + "markdown": " Foo *bar\nbaz*\t\n====\n", + "html": "

    Foo bar\nbaz

    \n", + "example": 82, + "start_line": 1375, + "end_line": 1382, + "section": "Setext headings" + }, + { + "markdown": "Foo\n-------------------------\n\nFoo\n=\n", + "html": "

    Foo

    \n

    Foo

    \n", + "example": 83, + "start_line": 1387, + "end_line": 1396, + "section": "Setext headings" + }, + { + "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n", + "html": "

    Foo

    \n

    Foo

    \n

    Foo

    \n", + "example": 84, + "start_line": 1402, + "end_line": 1415, + "section": "Setext headings" + }, + { + "markdown": " Foo\n ---\n\n Foo\n---\n", + "html": "
    Foo\n---\n\nFoo\n
    \n
    \n", + "example": 85, + "start_line": 1420, + "end_line": 1433, + "section": "Setext headings" + }, + { + "markdown": "Foo\n ---- \n", + "html": "

    Foo

    \n", + "example": 86, + "start_line": 1439, + "end_line": 1444, + "section": "Setext headings" + }, + { + "markdown": "Foo\n ---\n", + "html": "

    Foo\n---

    \n", + "example": 87, + "start_line": 1449, + "end_line": 1455, + "section": "Setext headings" + }, + { + "markdown": "Foo\n= =\n\nFoo\n--- -\n", + "html": "

    Foo\n= =

    \n

    Foo

    \n
    \n", + "example": 88, + "start_line": 1460, + "end_line": 1471, + "section": "Setext headings" + }, + { + "markdown": "Foo \n-----\n", + "html": "

    Foo

    \n", + "example": 89, + "start_line": 1476, + "end_line": 1481, + "section": "Setext headings" + }, + { + "markdown": "Foo\\\n----\n", + "html": "

    Foo\\

    \n", + "example": 90, + "start_line": 1486, + "end_line": 1491, + "section": "Setext headings" + }, + { + "markdown": "`Foo\n----\n`\n\n\n", + "html": "

    `Foo

    \n

    `

    \n

    <a title="a lot

    \n

    of dashes"/>

    \n", + "example": 91, + "start_line": 1497, + "end_line": 1510, + "section": "Setext headings" + }, + { + "markdown": "> Foo\n---\n", + "html": "
    \n

    Foo

    \n
    \n
    \n", + "example": 92, + "start_line": 1516, + "end_line": 1524, + "section": "Setext headings" + }, + { + "markdown": "> foo\nbar\n===\n", + "html": "
    \n

    foo\nbar\n===

    \n
    \n", + "example": 93, + "start_line": 1527, + "end_line": 1537, + "section": "Setext headings" + }, + { + "markdown": "- Foo\n---\n", + "html": "
      \n
    • Foo
    • \n
    \n
    \n", + "example": 94, + "start_line": 1540, + "end_line": 1548, + "section": "Setext headings" + }, + { + "markdown": "Foo\nBar\n---\n", + "html": "

    Foo\nBar

    \n", + "example": 95, + "start_line": 1555, + "end_line": 1562, + "section": "Setext headings" + }, + { + "markdown": "---\nFoo\n---\nBar\n---\nBaz\n", + "html": "
    \n

    Foo

    \n

    Bar

    \n

    Baz

    \n", + "example": 96, + "start_line": 1568, + "end_line": 1580, + "section": "Setext headings" + }, + { + "markdown": "\n====\n", + "html": "

    ====

    \n", + "example": 97, + "start_line": 1585, + "end_line": 1590, + "section": "Setext headings" + }, + { + "markdown": "---\n---\n", + "html": "
    \n
    \n", + "example": 98, + "start_line": 1597, + "end_line": 1603, + "section": "Setext headings" + }, + { + "markdown": "- foo\n-----\n", + "html": "
      \n
    • foo
    • \n
    \n
    \n", + "example": 99, + "start_line": 1606, + "end_line": 1614, + "section": "Setext headings" + }, + { + "markdown": " foo\n---\n", + "html": "
    foo\n
    \n
    \n", + "example": 100, + "start_line": 1617, + "end_line": 1624, + "section": "Setext headings" + }, + { + "markdown": "> foo\n-----\n", + "html": "
    \n

    foo

    \n
    \n
    \n", + "example": 101, + "start_line": 1627, + "end_line": 1635, + "section": "Setext headings" + }, + { + "markdown": "\\> foo\n------\n", + "html": "

    > foo

    \n", + "example": 102, + "start_line": 1641, + "end_line": 1646, + "section": "Setext headings" + }, + { + "markdown": "Foo\n\nbar\n---\nbaz\n", + "html": "

    Foo

    \n

    bar

    \n

    baz

    \n", + "example": 103, + "start_line": 1672, + "end_line": 1682, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n\n---\n\nbaz\n", + "html": "

    Foo\nbar

    \n
    \n

    baz

    \n", + "example": 104, + "start_line": 1688, + "end_line": 1700, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n* * *\nbaz\n", + "html": "

    Foo\nbar

    \n
    \n

    baz

    \n", + "example": 105, + "start_line": 1706, + "end_line": 1716, + "section": "Setext headings" + }, + { + "markdown": "Foo\nbar\n\\---\nbaz\n", + "html": "

    Foo\nbar\n---\nbaz

    \n", + "example": 106, + "start_line": 1721, + "end_line": 1731, + "section": "Setext headings" + }, + { + "markdown": " a simple\n indented code block\n", + "html": "
    a simple\n  indented code block\n
    \n", + "example": 107, + "start_line": 1749, + "end_line": 1756, + "section": "Indented code blocks" + }, + { + "markdown": " - foo\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 108, + "start_line": 1763, + "end_line": 1774, + "section": "Indented code blocks" + }, + { + "markdown": "1. foo\n\n - bar\n", + "html": "
      \n
    1. \n

      foo

      \n
        \n
      • bar
      • \n
      \n
    2. \n
    \n", + "example": 109, + "start_line": 1777, + "end_line": 1790, + "section": "Indented code blocks" + }, + { + "markdown": "
    \n *hi*\n\n - one\n", + "html": "
    <a/>\n*hi*\n\n- one\n
    \n", + "example": 110, + "start_line": 1797, + "end_line": 1808, + "section": "Indented code blocks" + }, + { + "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n", + "html": "
    chunk1\n\nchunk2\n\n\n\nchunk3\n
    \n", + "example": 111, + "start_line": 1813, + "end_line": 1830, + "section": "Indented code blocks" + }, + { + "markdown": " chunk1\n \n chunk2\n", + "html": "
    chunk1\n  \n  chunk2\n
    \n", + "example": 112, + "start_line": 1836, + "end_line": 1845, + "section": "Indented code blocks" + }, + { + "markdown": "Foo\n bar\n\n", + "html": "

    Foo\nbar

    \n", + "example": 113, + "start_line": 1851, + "end_line": 1858, + "section": "Indented code blocks" + }, + { + "markdown": " foo\nbar\n", + "html": "
    foo\n
    \n

    bar

    \n", + "example": 114, + "start_line": 1865, + "end_line": 1872, + "section": "Indented code blocks" + }, + { + "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n", + "html": "

    Heading

    \n
    foo\n
    \n

    Heading

    \n
    foo\n
    \n
    \n", + "example": 115, + "start_line": 1878, + "end_line": 1893, + "section": "Indented code blocks" + }, + { + "markdown": " foo\n bar\n", + "html": "
        foo\nbar\n
    \n", + "example": 116, + "start_line": 1898, + "end_line": 1905, + "section": "Indented code blocks" + }, + { + "markdown": "\n \n foo\n \n\n", + "html": "
    foo\n
    \n", + "example": 117, + "start_line": 1911, + "end_line": 1920, + "section": "Indented code blocks" + }, + { + "markdown": " foo \n", + "html": "
    foo  \n
    \n", + "example": 118, + "start_line": 1925, + "end_line": 1930, + "section": "Indented code blocks" + }, + { + "markdown": "```\n<\n >\n```\n", + "html": "
    <\n >\n
    \n", + "example": 119, + "start_line": 1980, + "end_line": 1989, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~\n<\n >\n~~~\n", + "html": "
    <\n >\n
    \n", + "example": 120, + "start_line": 1994, + "end_line": 2003, + "section": "Fenced code blocks" + }, + { + "markdown": "``\nfoo\n``\n", + "html": "

    foo

    \n", + "example": 121, + "start_line": 2007, + "end_line": 2013, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n~~~\n```\n", + "html": "
    aaa\n~~~\n
    \n", + "example": 122, + "start_line": 2018, + "end_line": 2027, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~\naaa\n```\n~~~\n", + "html": "
    aaa\n```\n
    \n", + "example": 123, + "start_line": 2030, + "end_line": 2039, + "section": "Fenced code blocks" + }, + { + "markdown": "````\naaa\n```\n``````\n", + "html": "
    aaa\n```\n
    \n", + "example": 124, + "start_line": 2044, + "end_line": 2053, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~\naaa\n~~~\n~~~~\n", + "html": "
    aaa\n~~~\n
    \n", + "example": 125, + "start_line": 2056, + "end_line": 2065, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n", + "html": "
    \n", + "example": 126, + "start_line": 2071, + "end_line": 2075, + "section": "Fenced code blocks" + }, + { + "markdown": "`````\n\n```\naaa\n", + "html": "
    \n```\naaa\n
    \n", + "example": 127, + "start_line": 2078, + "end_line": 2088, + "section": "Fenced code blocks" + }, + { + "markdown": "> ```\n> aaa\n\nbbb\n", + "html": "
    \n
    aaa\n
    \n
    \n

    bbb

    \n", + "example": 128, + "start_line": 2091, + "end_line": 2102, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n\n \n```\n", + "html": "
    \n  \n
    \n", + "example": 129, + "start_line": 2107, + "end_line": 2116, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n```\n", + "html": "
    \n", + "example": 130, + "start_line": 2121, + "end_line": 2126, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\naaa\n```\n", + "html": "
    aaa\naaa\n
    \n", + "example": 131, + "start_line": 2133, + "end_line": 2142, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\naaa\n aaa\naaa\n ```\n", + "html": "
    aaa\naaa\naaa\n
    \n", + "example": 132, + "start_line": 2145, + "end_line": 2156, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\n aaa\n aaa\n ```\n", + "html": "
    aaa\n aaa\naaa\n
    \n", + "example": 133, + "start_line": 2159, + "end_line": 2170, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\n aaa\n ```\n", + "html": "
    ```\naaa\n```\n
    \n", + "example": 134, + "start_line": 2175, + "end_line": 2184, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n ```\n", + "html": "
    aaa\n
    \n", + "example": 135, + "start_line": 2190, + "end_line": 2197, + "section": "Fenced code blocks" + }, + { + "markdown": " ```\naaa\n ```\n", + "html": "
    aaa\n
    \n", + "example": 136, + "start_line": 2200, + "end_line": 2207, + "section": "Fenced code blocks" + }, + { + "markdown": "```\naaa\n ```\n", + "html": "
    aaa\n    ```\n
    \n", + "example": 137, + "start_line": 2212, + "end_line": 2220, + "section": "Fenced code blocks" + }, + { + "markdown": "``` ```\naaa\n", + "html": "

    \naaa

    \n", + "example": 138, + "start_line": 2226, + "end_line": 2232, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~~~\naaa\n~~~ ~~\n", + "html": "
    aaa\n~~~ ~~\n
    \n", + "example": 139, + "start_line": 2235, + "end_line": 2243, + "section": "Fenced code blocks" + }, + { + "markdown": "foo\n```\nbar\n```\nbaz\n", + "html": "

    foo

    \n
    bar\n
    \n

    baz

    \n", + "example": 140, + "start_line": 2249, + "end_line": 2260, + "section": "Fenced code blocks" + }, + { + "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n", + "html": "

    foo

    \n
    bar\n
    \n

    baz

    \n", + "example": 141, + "start_line": 2266, + "end_line": 2278, + "section": "Fenced code blocks" + }, + { + "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n", + "html": "
    def foo(x)\n  return 3\nend\n
    \n", + "example": 142, + "start_line": 2288, + "end_line": 2299, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n", + "html": "
    def foo(x)\n  return 3\nend\n
    \n", + "example": 143, + "start_line": 2302, + "end_line": 2313, + "section": "Fenced code blocks" + }, + { + "markdown": "````;\n````\n", + "html": "
    \n", + "example": 144, + "start_line": 2316, + "end_line": 2321, + "section": "Fenced code blocks" + }, + { + "markdown": "``` aa ```\nfoo\n", + "html": "

    aa\nfoo

    \n", + "example": 145, + "start_line": 2326, + "end_line": 2332, + "section": "Fenced code blocks" + }, + { + "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n", + "html": "
    foo\n
    \n", + "example": 146, + "start_line": 2337, + "end_line": 2344, + "section": "Fenced code blocks" + }, + { + "markdown": "```\n``` aaa\n```\n", + "html": "
    ``` aaa\n
    \n", + "example": 147, + "start_line": 2349, + "end_line": 2356, + "section": "Fenced code blocks" + }, + { + "markdown": "
    \n
    \n**Hello**,\n\n_world_.\n
    \n
    \n", + "html": "
    \n
    \n**Hello**,\n

    world.\n

    \n
    \n", + "example": 148, + "start_line": 2428, + "end_line": 2443, + "section": "HTML blocks" + }, + { + "markdown": "\n \n \n \n
    \n hi\n
    \n\nokay.\n", + "html": "\n \n \n \n
    \n hi\n
    \n

    okay.

    \n", + "example": 149, + "start_line": 2457, + "end_line": 2476, + "section": "HTML blocks" + }, + { + "markdown": "
    \n*foo*\n", + "example": 151, + "start_line": 2492, + "end_line": 2498, + "section": "HTML blocks" + }, + { + "markdown": "
    \n\n*Markdown*\n\n
    \n", + "html": "
    \n

    Markdown

    \n
    \n", + "example": 152, + "start_line": 2503, + "end_line": 2513, + "section": "HTML blocks" + }, + { + "markdown": "
    \n
    \n", + "html": "
    \n
    \n", + "example": 153, + "start_line": 2519, + "end_line": 2527, + "section": "HTML blocks" + }, + { + "markdown": "
    \n
    \n", + "html": "
    \n
    \n", + "example": 154, + "start_line": 2530, + "end_line": 2538, + "section": "HTML blocks" + }, + { + "markdown": "
    \n*foo*\n\n*bar*\n", + "html": "
    \n*foo*\n

    bar

    \n", + "example": 155, + "start_line": 2542, + "end_line": 2551, + "section": "HTML blocks" + }, + { + "markdown": "
    \n", + "html": "\n", + "example": 159, + "start_line": 2591, + "end_line": 2595, + "section": "HTML blocks" + }, + { + "markdown": "
    \nfoo\n
    \n", + "html": "
    \nfoo\n
    \n", + "example": 160, + "start_line": 2598, + "end_line": 2606, + "section": "HTML blocks" + }, + { + "markdown": "
    \n``` c\nint x = 33;\n```\n", + "html": "
    \n``` c\nint x = 33;\n```\n", + "example": 161, + "start_line": 2615, + "end_line": 2625, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 162, + "start_line": 2632, + "end_line": 2640, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 163, + "start_line": 2645, + "end_line": 2653, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n\n", + "html": "\n*bar*\n\n", + "example": 164, + "start_line": 2656, + "end_line": 2664, + "section": "HTML blocks" + }, + { + "markdown": "\n*bar*\n", + "html": "\n*bar*\n", + "example": 165, + "start_line": 2667, + "end_line": 2673, + "section": "HTML blocks" + }, + { + "markdown": "\n*foo*\n\n", + "html": "\n*foo*\n\n", + "example": 166, + "start_line": 2682, + "end_line": 2690, + "section": "HTML blocks" + }, + { + "markdown": "\n\n*foo*\n\n\n", + "html": "\n

    foo

    \n
    \n", + "example": 167, + "start_line": 2697, + "end_line": 2707, + "section": "HTML blocks" + }, + { + "markdown": "*foo*\n", + "html": "

    foo

    \n", + "example": 168, + "start_line": 2715, + "end_line": 2719, + "section": "HTML blocks" + }, + { + "markdown": "
    \nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
    \nokay\n", + "html": "
    \nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n
    \n

    okay

    \n", + "example": 169, + "start_line": 2731, + "end_line": 2747, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

    okay

    \n", + "example": 170, + "start_line": 2752, + "end_line": 2766, + "section": "HTML blocks" + }, + { + "markdown": "\n", + "html": "\n", + "example": 171, + "start_line": 2771, + "end_line": 2787, + "section": "HTML blocks" + }, + { + "markdown": "\nh1 {color:red;}\n\np {color:blue;}\n\nokay\n", + "html": "\nh1 {color:red;}\n\np {color:blue;}\n\n

    okay

    \n", + "example": 172, + "start_line": 2791, + "end_line": 2807, + "section": "HTML blocks" + }, + { + "markdown": "\n\nfoo\n", + "html": "\n\nfoo\n", + "example": 173, + "start_line": 2814, + "end_line": 2824, + "section": "HTML blocks" + }, + { + "markdown": ">
    \n> foo\n\nbar\n", + "html": "
    \n
    \nfoo\n
    \n

    bar

    \n", + "example": 174, + "start_line": 2827, + "end_line": 2838, + "section": "HTML blocks" + }, + { + "markdown": "-
    \n- foo\n", + "html": "
      \n
    • \n
      \n
    • \n
    • foo
    • \n
    \n", + "example": 175, + "start_line": 2841, + "end_line": 2851, + "section": "HTML blocks" + }, + { + "markdown": "\n*foo*\n", + "html": "\n

    foo

    \n", + "example": 176, + "start_line": 2856, + "end_line": 2862, + "section": "HTML blocks" + }, + { + "markdown": "*bar*\n*baz*\n", + "html": "*bar*\n

    baz

    \n", + "example": 177, + "start_line": 2865, + "end_line": 2871, + "section": "HTML blocks" + }, + { + "markdown": "1. *bar*\n", + "html": "1. *bar*\n", + "example": 178, + "start_line": 2877, + "end_line": 2885, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

    okay

    \n", + "example": 179, + "start_line": 2890, + "end_line": 2902, + "section": "HTML blocks" + }, + { + "markdown": "';\n\n?>\nokay\n", + "html": "';\n\n?>\n

    okay

    \n", + "example": 180, + "start_line": 2908, + "end_line": 2922, + "section": "HTML blocks" + }, + { + "markdown": "\n", + "html": "\n", + "example": 181, + "start_line": 2927, + "end_line": 2931, + "section": "HTML blocks" + }, + { + "markdown": "\nokay\n", + "html": "\n

    okay

    \n", + "example": 182, + "start_line": 2936, + "end_line": 2964, + "section": "HTML blocks" + }, + { + "markdown": " \n\n \n", + "html": " \n
    <!-- foo -->\n
    \n", + "example": 183, + "start_line": 2970, + "end_line": 2978, + "section": "HTML blocks" + }, + { + "markdown": "
    \n\n
    \n", + "html": "
    \n
    <div>\n
    \n", + "example": 184, + "start_line": 2981, + "end_line": 2989, + "section": "HTML blocks" + }, + { + "markdown": "Foo\n
    \nbar\n
    \n", + "html": "

    Foo

    \n
    \nbar\n
    \n", + "example": 185, + "start_line": 2995, + "end_line": 3005, + "section": "HTML blocks" + }, + { + "markdown": "
    \nbar\n
    \n*foo*\n", + "html": "
    \nbar\n
    \n*foo*\n", + "example": 186, + "start_line": 3012, + "end_line": 3022, + "section": "HTML blocks" + }, + { + "markdown": "Foo\n\nbaz\n", + "html": "

    Foo\n\nbaz

    \n", + "example": 187, + "start_line": 3027, + "end_line": 3035, + "section": "HTML blocks" + }, + { + "markdown": "
    \n\n*Emphasized* text.\n\n
    \n", + "html": "
    \n

    Emphasized text.

    \n
    \n", + "example": 188, + "start_line": 3068, + "end_line": 3078, + "section": "HTML blocks" + }, + { + "markdown": "
    \n*Emphasized* text.\n
    \n", + "html": "
    \n*Emphasized* text.\n
    \n", + "example": 189, + "start_line": 3081, + "end_line": 3089, + "section": "HTML blocks" + }, + { + "markdown": "\n\n\n\n\n\n\n\n
    \nHi\n
    \n", + "html": "\n\n\n\n
    \nHi\n
    \n", + "example": 190, + "start_line": 3103, + "end_line": 3123, + "section": "HTML blocks" + }, + { + "markdown": "\n\n \n\n \n\n \n\n
    \n Hi\n
    \n", + "html": "\n \n
    <td>\n  Hi\n</td>\n
    \n \n
    \n", + "example": 191, + "start_line": 3130, + "end_line": 3151, + "section": "HTML blocks" + }, + { + "markdown": "[foo]: /url \"title\"\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 192, + "start_line": 3179, + "end_line": 3185, + "section": "Link reference definitions" + }, + { + "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n", + "html": "

    foo

    \n", + "example": 193, + "start_line": 3188, + "end_line": 3196, + "section": "Link reference definitions" + }, + { + "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n", + "html": "

    Foo*bar]

    \n", + "example": 194, + "start_line": 3199, + "end_line": 3205, + "section": "Link reference definitions" + }, + { + "markdown": "[Foo bar]:\n\n'title'\n\n[Foo bar]\n", + "html": "

    Foo bar

    \n", + "example": 195, + "start_line": 3208, + "end_line": 3216, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 196, + "start_line": 3221, + "end_line": 3235, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n", + "html": "

    [foo]: /url 'title

    \n

    with blank line'

    \n

    [foo]

    \n", + "example": 197, + "start_line": 3240, + "end_line": 3250, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]:\n/url\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 198, + "start_line": 3255, + "end_line": 3262, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]:\n\n[foo]\n", + "html": "

    [foo]:

    \n

    [foo]

    \n", + "example": 199, + "start_line": 3267, + "end_line": 3274, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: <>\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 200, + "start_line": 3279, + "end_line": 3285, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: (baz)\n\n[foo]\n", + "html": "

    [foo]: (baz)

    \n

    [foo]

    \n", + "example": 201, + "start_line": 3290, + "end_line": 3297, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n", + "html": "

    foo

    \n", + "example": 202, + "start_line": 3303, + "end_line": 3309, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n[foo]: url\n", + "html": "

    foo

    \n", + "example": 203, + "start_line": 3314, + "end_line": 3320, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n", + "html": "

    foo

    \n", + "example": 204, + "start_line": 3326, + "end_line": 3333, + "section": "Link reference definitions" + }, + { + "markdown": "[FOO]: /url\n\n[Foo]\n", + "html": "

    Foo

    \n", + "example": 205, + "start_line": 3339, + "end_line": 3345, + "section": "Link reference definitions" + }, + { + "markdown": "[ΑΓΩ]: /φου\n\n[αγω]\n", + "html": "

    αγω

    \n", + "example": 206, + "start_line": 3348, + "end_line": 3354, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n", + "html": "", + "example": 207, + "start_line": 3363, + "end_line": 3366, + "section": "Link reference definitions" + }, + { + "markdown": "[\nfoo\n]: /url\nbar\n", + "html": "

    bar

    \n", + "example": 208, + "start_line": 3371, + "end_line": 3378, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url \"title\" ok\n", + "html": "

    [foo]: /url "title" ok

    \n", + "example": 209, + "start_line": 3384, + "end_line": 3388, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n\"title\" ok\n", + "html": "

    "title" ok

    \n", + "example": 210, + "start_line": 3393, + "end_line": 3398, + "section": "Link reference definitions" + }, + { + "markdown": " [foo]: /url \"title\"\n\n[foo]\n", + "html": "
    [foo]: /url "title"\n
    \n

    [foo]

    \n", + "example": 211, + "start_line": 3404, + "end_line": 3412, + "section": "Link reference definitions" + }, + { + "markdown": "```\n[foo]: /url\n```\n\n[foo]\n", + "html": "
    [foo]: /url\n
    \n

    [foo]

    \n", + "example": 212, + "start_line": 3418, + "end_line": 3428, + "section": "Link reference definitions" + }, + { + "markdown": "Foo\n[bar]: /baz\n\n[bar]\n", + "html": "

    Foo\n[bar]: /baz

    \n

    [bar]

    \n", + "example": 213, + "start_line": 3433, + "end_line": 3442, + "section": "Link reference definitions" + }, + { + "markdown": "# [Foo]\n[foo]: /url\n> bar\n", + "html": "

    Foo

    \n
    \n

    bar

    \n
    \n", + "example": 214, + "start_line": 3448, + "end_line": 3457, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\nbar\n===\n[foo]\n", + "html": "

    bar

    \n

    foo

    \n", + "example": 215, + "start_line": 3459, + "end_line": 3467, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /url\n===\n[foo]\n", + "html": "

    ===\nfoo

    \n", + "example": 216, + "start_line": 3469, + "end_line": 3476, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n", + "html": "

    foo,\nbar,\nbaz

    \n", + "example": 217, + "start_line": 3482, + "end_line": 3495, + "section": "Link reference definitions" + }, + { + "markdown": "[foo]\n\n> [foo]: /url\n", + "html": "

    foo

    \n
    \n
    \n", + "example": 218, + "start_line": 3503, + "end_line": 3511, + "section": "Link reference definitions" + }, + { + "markdown": "aaa\n\nbbb\n", + "html": "

    aaa

    \n

    bbb

    \n", + "example": 219, + "start_line": 3525, + "end_line": 3532, + "section": "Paragraphs" + }, + { + "markdown": "aaa\nbbb\n\nccc\nddd\n", + "html": "

    aaa\nbbb

    \n

    ccc\nddd

    \n", + "example": 220, + "start_line": 3537, + "end_line": 3548, + "section": "Paragraphs" + }, + { + "markdown": "aaa\n\n\nbbb\n", + "html": "

    aaa

    \n

    bbb

    \n", + "example": 221, + "start_line": 3553, + "end_line": 3561, + "section": "Paragraphs" + }, + { + "markdown": " aaa\n bbb\n", + "html": "

    aaa\nbbb

    \n", + "example": 222, + "start_line": 3566, + "end_line": 3572, + "section": "Paragraphs" + }, + { + "markdown": "aaa\n bbb\n ccc\n", + "html": "

    aaa\nbbb\nccc

    \n", + "example": 223, + "start_line": 3578, + "end_line": 3586, + "section": "Paragraphs" + }, + { + "markdown": " aaa\nbbb\n", + "html": "

    aaa\nbbb

    \n", + "example": 224, + "start_line": 3592, + "end_line": 3598, + "section": "Paragraphs" + }, + { + "markdown": " aaa\nbbb\n", + "html": "
    aaa\n
    \n

    bbb

    \n", + "example": 225, + "start_line": 3601, + "end_line": 3608, + "section": "Paragraphs" + }, + { + "markdown": "aaa \nbbb \n", + "html": "

    aaa
    \nbbb

    \n", + "example": 226, + "start_line": 3615, + "end_line": 3621, + "section": "Paragraphs" + }, + { + "markdown": " \n\naaa\n \n\n# aaa\n\n \n", + "html": "

    aaa

    \n

    aaa

    \n", + "example": 227, + "start_line": 3632, + "end_line": 3644, + "section": "Blank lines" + }, + { + "markdown": "> # Foo\n> bar\n> baz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 228, + "start_line": 3700, + "end_line": 3710, + "section": "Block quotes" + }, + { + "markdown": "># Foo\n>bar\n> baz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 229, + "start_line": 3715, + "end_line": 3725, + "section": "Block quotes" + }, + { + "markdown": " > # Foo\n > bar\n > baz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 230, + "start_line": 3730, + "end_line": 3740, + "section": "Block quotes" + }, + { + "markdown": " > # Foo\n > bar\n > baz\n", + "html": "
    > # Foo\n> bar\n> baz\n
    \n", + "example": 231, + "start_line": 3745, + "end_line": 3754, + "section": "Block quotes" + }, + { + "markdown": "> # Foo\n> bar\nbaz\n", + "html": "
    \n

    Foo

    \n

    bar\nbaz

    \n
    \n", + "example": 232, + "start_line": 3760, + "end_line": 3770, + "section": "Block quotes" + }, + { + "markdown": "> bar\nbaz\n> foo\n", + "html": "
    \n

    bar\nbaz\nfoo

    \n
    \n", + "example": 233, + "start_line": 3776, + "end_line": 3786, + "section": "Block quotes" + }, + { + "markdown": "> foo\n---\n", + "html": "
    \n

    foo

    \n
    \n
    \n", + "example": 234, + "start_line": 3800, + "end_line": 3808, + "section": "Block quotes" + }, + { + "markdown": "> - foo\n- bar\n", + "html": "
    \n
      \n
    • foo
    • \n
    \n
    \n
      \n
    • bar
    • \n
    \n", + "example": 235, + "start_line": 3820, + "end_line": 3832, + "section": "Block quotes" + }, + { + "markdown": "> foo\n bar\n", + "html": "
    \n
    foo\n
    \n
    \n
    bar\n
    \n", + "example": 236, + "start_line": 3838, + "end_line": 3848, + "section": "Block quotes" + }, + { + "markdown": "> ```\nfoo\n```\n", + "html": "
    \n
    \n
    \n

    foo

    \n
    \n", + "example": 237, + "start_line": 3851, + "end_line": 3861, + "section": "Block quotes" + }, + { + "markdown": "> foo\n - bar\n", + "html": "
    \n

    foo\n- bar

    \n
    \n", + "example": 238, + "start_line": 3867, + "end_line": 3875, + "section": "Block quotes" + }, + { + "markdown": ">\n", + "html": "
    \n
    \n", + "example": 239, + "start_line": 3891, + "end_line": 3896, + "section": "Block quotes" + }, + { + "markdown": ">\n> \n> \n", + "html": "
    \n
    \n", + "example": 240, + "start_line": 3899, + "end_line": 3906, + "section": "Block quotes" + }, + { + "markdown": ">\n> foo\n> \n", + "html": "
    \n

    foo

    \n
    \n", + "example": 241, + "start_line": 3911, + "end_line": 3919, + "section": "Block quotes" + }, + { + "markdown": "> foo\n\n> bar\n", + "html": "
    \n

    foo

    \n
    \n
    \n

    bar

    \n
    \n", + "example": 242, + "start_line": 3924, + "end_line": 3935, + "section": "Block quotes" + }, + { + "markdown": "> foo\n> bar\n", + "html": "
    \n

    foo\nbar

    \n
    \n", + "example": 243, + "start_line": 3946, + "end_line": 3954, + "section": "Block quotes" + }, + { + "markdown": "> foo\n>\n> bar\n", + "html": "
    \n

    foo

    \n

    bar

    \n
    \n", + "example": 244, + "start_line": 3959, + "end_line": 3968, + "section": "Block quotes" + }, + { + "markdown": "foo\n> bar\n", + "html": "

    foo

    \n
    \n

    bar

    \n
    \n", + "example": 245, + "start_line": 3973, + "end_line": 3981, + "section": "Block quotes" + }, + { + "markdown": "> aaa\n***\n> bbb\n", + "html": "
    \n

    aaa

    \n
    \n
    \n
    \n

    bbb

    \n
    \n", + "example": 246, + "start_line": 3987, + "end_line": 3999, + "section": "Block quotes" + }, + { + "markdown": "> bar\nbaz\n", + "html": "
    \n

    bar\nbaz

    \n
    \n", + "example": 247, + "start_line": 4005, + "end_line": 4013, + "section": "Block quotes" + }, + { + "markdown": "> bar\n\nbaz\n", + "html": "
    \n

    bar

    \n
    \n

    baz

    \n", + "example": 248, + "start_line": 4016, + "end_line": 4025, + "section": "Block quotes" + }, + { + "markdown": "> bar\n>\nbaz\n", + "html": "
    \n

    bar

    \n
    \n

    baz

    \n", + "example": 249, + "start_line": 4028, + "end_line": 4037, + "section": "Block quotes" + }, + { + "markdown": "> > > foo\nbar\n", + "html": "
    \n
    \n
    \n

    foo\nbar

    \n
    \n
    \n
    \n", + "example": 250, + "start_line": 4044, + "end_line": 4056, + "section": "Block quotes" + }, + { + "markdown": ">>> foo\n> bar\n>>baz\n", + "html": "
    \n
    \n
    \n

    foo\nbar\nbaz

    \n
    \n
    \n
    \n", + "example": 251, + "start_line": 4059, + "end_line": 4073, + "section": "Block quotes" + }, + { + "markdown": "> code\n\n> not code\n", + "html": "
    \n
    code\n
    \n
    \n
    \n

    not code

    \n
    \n", + "example": 252, + "start_line": 4081, + "end_line": 4093, + "section": "Block quotes" + }, + { + "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n", + "html": "

    A paragraph\nwith two lines.

    \n
    indented code\n
    \n
    \n

    A block quote.

    \n
    \n", + "example": 253, + "start_line": 4135, + "end_line": 4150, + "section": "List items" + }, + { + "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 254, + "start_line": 4157, + "end_line": 4176, + "section": "List items" + }, + { + "markdown": "- one\n\n two\n", + "html": "
      \n
    • one
    • \n
    \n

    two

    \n", + "example": 255, + "start_line": 4190, + "end_line": 4199, + "section": "List items" + }, + { + "markdown": "- one\n\n two\n", + "html": "
      \n
    • \n

      one

      \n

      two

      \n
    • \n
    \n", + "example": 256, + "start_line": 4202, + "end_line": 4213, + "section": "List items" + }, + { + "markdown": " - one\n\n two\n", + "html": "
      \n
    • one
    • \n
    \n
     two\n
    \n", + "example": 257, + "start_line": 4216, + "end_line": 4226, + "section": "List items" + }, + { + "markdown": " - one\n\n two\n", + "html": "
      \n
    • \n

      one

      \n

      two

      \n
    • \n
    \n", + "example": 258, + "start_line": 4229, + "end_line": 4240, + "section": "List items" + }, + { + "markdown": " > > 1. one\n>>\n>> two\n", + "html": "
    \n
    \n
      \n
    1. \n

      one

      \n

      two

      \n
    2. \n
    \n
    \n
    \n", + "example": 259, + "start_line": 4251, + "end_line": 4266, + "section": "List items" + }, + { + "markdown": ">>- one\n>>\n > > two\n", + "html": "
    \n
    \n
      \n
    • one
    • \n
    \n

    two

    \n
    \n
    \n", + "example": 260, + "start_line": 4278, + "end_line": 4291, + "section": "List items" + }, + { + "markdown": "-one\n\n2.two\n", + "html": "

    -one

    \n

    2.two

    \n", + "example": 261, + "start_line": 4297, + "end_line": 4304, + "section": "List items" + }, + { + "markdown": "- foo\n\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 262, + "start_line": 4310, + "end_line": 4322, + "section": "List items" + }, + { + "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n", + "html": "
      \n
    1. \n

      foo

      \n
      bar\n
      \n

      baz

      \n
      \n

      bam

      \n
      \n
    2. \n
    \n", + "example": 263, + "start_line": 4327, + "end_line": 4349, + "section": "List items" + }, + { + "markdown": "- Foo\n\n bar\n\n\n baz\n", + "html": "
      \n
    • \n

      Foo

      \n
      bar\n\n\nbaz\n
      \n
    • \n
    \n", + "example": 264, + "start_line": 4355, + "end_line": 4373, + "section": "List items" + }, + { + "markdown": "123456789. ok\n", + "html": "
      \n
    1. ok
    2. \n
    \n", + "example": 265, + "start_line": 4377, + "end_line": 4383, + "section": "List items" + }, + { + "markdown": "1234567890. not ok\n", + "html": "

    1234567890. not ok

    \n", + "example": 266, + "start_line": 4386, + "end_line": 4390, + "section": "List items" + }, + { + "markdown": "0. ok\n", + "html": "
      \n
    1. ok
    2. \n
    \n", + "example": 267, + "start_line": 4395, + "end_line": 4401, + "section": "List items" + }, + { + "markdown": "003. ok\n", + "html": "
      \n
    1. ok
    2. \n
    \n", + "example": 268, + "start_line": 4404, + "end_line": 4410, + "section": "List items" + }, + { + "markdown": "-1. not ok\n", + "html": "

    -1. not ok

    \n", + "example": 269, + "start_line": 4415, + "end_line": 4419, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n
      bar\n
      \n
    • \n
    \n", + "example": 270, + "start_line": 4438, + "end_line": 4450, + "section": "List items" + }, + { + "markdown": " 10. foo\n\n bar\n", + "html": "
      \n
    1. \n

      foo

      \n
      bar\n
      \n
    2. \n
    \n", + "example": 271, + "start_line": 4455, + "end_line": 4467, + "section": "List items" + }, + { + "markdown": " indented code\n\nparagraph\n\n more code\n", + "html": "
    indented code\n
    \n

    paragraph

    \n
    more code\n
    \n", + "example": 272, + "start_line": 4474, + "end_line": 4486, + "section": "List items" + }, + { + "markdown": "1. indented code\n\n paragraph\n\n more code\n", + "html": "
      \n
    1. \n
      indented code\n
      \n

      paragraph

      \n
      more code\n
      \n
    2. \n
    \n", + "example": 273, + "start_line": 4489, + "end_line": 4505, + "section": "List items" + }, + { + "markdown": "1. indented code\n\n paragraph\n\n more code\n", + "html": "
      \n
    1. \n
       indented code\n
      \n

      paragraph

      \n
      more code\n
      \n
    2. \n
    \n", + "example": 274, + "start_line": 4511, + "end_line": 4527, + "section": "List items" + }, + { + "markdown": " foo\n\nbar\n", + "html": "

    foo

    \n

    bar

    \n", + "example": 275, + "start_line": 4538, + "end_line": 4545, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
      \n
    • foo
    • \n
    \n

    bar

    \n", + "example": 276, + "start_line": 4548, + "end_line": 4557, + "section": "List items" + }, + { + "markdown": "- foo\n\n bar\n", + "html": "
      \n
    • \n

      foo

      \n

      bar

      \n
    • \n
    \n", + "example": 277, + "start_line": 4565, + "end_line": 4576, + "section": "List items" + }, + { + "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n", + "html": "
      \n
    • foo
    • \n
    • \n
      bar\n
      \n
    • \n
    • \n
      baz\n
      \n
    • \n
    \n", + "example": 278, + "start_line": 4592, + "end_line": 4613, + "section": "List items" + }, + { + "markdown": "- \n foo\n", + "html": "
      \n
    • foo
    • \n
    \n", + "example": 279, + "start_line": 4618, + "end_line": 4625, + "section": "List items" + }, + { + "markdown": "-\n\n foo\n", + "html": "
      \n
    • \n
    \n

    foo

    \n", + "example": 280, + "start_line": 4632, + "end_line": 4641, + "section": "List items" + }, + { + "markdown": "- foo\n-\n- bar\n", + "html": "
      \n
    • foo
    • \n
    • \n
    • bar
    • \n
    \n", + "example": 281, + "start_line": 4646, + "end_line": 4656, + "section": "List items" + }, + { + "markdown": "- foo\n- \n- bar\n", + "html": "
      \n
    • foo
    • \n
    • \n
    • bar
    • \n
    \n", + "example": 282, + "start_line": 4661, + "end_line": 4671, + "section": "List items" + }, + { + "markdown": "1. foo\n2.\n3. bar\n", + "html": "
      \n
    1. foo
    2. \n
    3. \n
    4. bar
    5. \n
    \n", + "example": 283, + "start_line": 4676, + "end_line": 4686, + "section": "List items" + }, + { + "markdown": "*\n", + "html": "
      \n
    • \n
    \n", + "example": 284, + "start_line": 4691, + "end_line": 4697, + "section": "List items" + }, + { + "markdown": "foo\n*\n\nfoo\n1.\n", + "html": "

    foo\n*

    \n

    foo\n1.

    \n", + "example": 285, + "start_line": 4701, + "end_line": 4712, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 286, + "start_line": 4723, + "end_line": 4742, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 287, + "start_line": 4747, + "end_line": 4766, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 288, + "start_line": 4771, + "end_line": 4790, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
    1.  A paragraph\n    with two lines.\n\n        indented code\n\n    > A block quote.\n
    \n", + "example": 289, + "start_line": 4795, + "end_line": 4810, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n", + "html": "
      \n
    1. \n

      A paragraph\nwith two lines.

      \n
      indented code\n
      \n
      \n

      A block quote.

      \n
      \n
    2. \n
    \n", + "example": 290, + "start_line": 4825, + "end_line": 4844, + "section": "List items" + }, + { + "markdown": " 1. A paragraph\n with two lines.\n", + "html": "
      \n
    1. A paragraph\nwith two lines.
    2. \n
    \n", + "example": 291, + "start_line": 4849, + "end_line": 4857, + "section": "List items" + }, + { + "markdown": "> 1. > Blockquote\ncontinued here.\n", + "html": "
    \n
      \n
    1. \n
      \n

      Blockquote\ncontinued here.

      \n
      \n
    2. \n
    \n
    \n", + "example": 292, + "start_line": 4862, + "end_line": 4876, + "section": "List items" + }, + { + "markdown": "> 1. > Blockquote\n> continued here.\n", + "html": "
    \n
      \n
    1. \n
      \n

      Blockquote\ncontinued here.

      \n
      \n
    2. \n
    \n
    \n", + "example": 293, + "start_line": 4879, + "end_line": 4893, + "section": "List items" + }, + { + "markdown": "- foo\n - bar\n - baz\n - boo\n", + "html": "
      \n
    • foo\n
        \n
      • bar\n
          \n
        • baz\n
            \n
          • boo
          • \n
          \n
        • \n
        \n
      • \n
      \n
    • \n
    \n", + "example": 294, + "start_line": 4907, + "end_line": 4928, + "section": "List items" + }, + { + "markdown": "- foo\n - bar\n - baz\n - boo\n", + "html": "
      \n
    • foo
    • \n
    • bar
    • \n
    • baz
    • \n
    • boo
    • \n
    \n", + "example": 295, + "start_line": 4933, + "end_line": 4945, + "section": "List items" + }, + { + "markdown": "10) foo\n - bar\n", + "html": "
      \n
    1. foo\n
        \n
      • bar
      • \n
      \n
    2. \n
    \n", + "example": 296, + "start_line": 4950, + "end_line": 4961, + "section": "List items" + }, + { + "markdown": "10) foo\n - bar\n", + "html": "
      \n
    1. foo
    2. \n
    \n
      \n
    • bar
    • \n
    \n", + "example": 297, + "start_line": 4966, + "end_line": 4976, + "section": "List items" + }, + { + "markdown": "- - foo\n", + "html": "
      \n
    • \n
        \n
      • foo
      • \n
      \n
    • \n
    \n", + "example": 298, + "start_line": 4981, + "end_line": 4991, + "section": "List items" + }, + { + "markdown": "1. - 2. foo\n", + "html": "
      \n
    1. \n
        \n
      • \n
          \n
        1. foo
        2. \n
        \n
      • \n
      \n
    2. \n
    \n", + "example": 299, + "start_line": 4994, + "end_line": 5008, + "section": "List items" + }, + { + "markdown": "- # Foo\n- Bar\n ---\n baz\n", + "html": "
      \n
    • \n

      Foo

      \n
    • \n
    • \n

      Bar

      \nbaz
    • \n
    \n", + "example": 300, + "start_line": 5013, + "end_line": 5027, + "section": "List items" + }, + { + "markdown": "- foo\n- bar\n+ baz\n", + "html": "
      \n
    • foo
    • \n
    • bar
    • \n
    \n
      \n
    • baz
    • \n
    \n", + "example": 301, + "start_line": 5249, + "end_line": 5261, + "section": "Lists" + }, + { + "markdown": "1. foo\n2. bar\n3) baz\n", + "html": "
      \n
    1. foo
    2. \n
    3. bar
    4. \n
    \n
      \n
    1. baz
    2. \n
    \n", + "example": 302, + "start_line": 5264, + "end_line": 5276, + "section": "Lists" + }, + { + "markdown": "Foo\n- bar\n- baz\n", + "html": "

    Foo

    \n
      \n
    • bar
    • \n
    • baz
    • \n
    \n", + "example": 303, + "start_line": 5283, + "end_line": 5293, + "section": "Lists" + }, + { + "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n", + "html": "

    The number of windows in my house is\n14. The number of doors is 6.

    \n", + "example": 304, + "start_line": 5360, + "end_line": 5366, + "section": "Lists" + }, + { + "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n", + "html": "

    The number of windows in my house is

    \n
      \n
    1. The number of doors is 6.
    2. \n
    \n", + "example": 305, + "start_line": 5370, + "end_line": 5378, + "section": "Lists" + }, + { + "markdown": "- foo\n\n- bar\n\n\n- baz\n", + "html": "
      \n
    • \n

      foo

      \n
    • \n
    • \n

      bar

      \n
    • \n
    • \n

      baz

      \n
    • \n
    \n", + "example": 306, + "start_line": 5384, + "end_line": 5403, + "section": "Lists" + }, + { + "markdown": "- foo\n - bar\n - baz\n\n\n bim\n", + "html": "
      \n
    • foo\n
        \n
      • bar\n
          \n
        • \n

          baz

          \n

          bim

          \n
        • \n
        \n
      • \n
      \n
    • \n
    \n", + "example": 307, + "start_line": 5405, + "end_line": 5427, + "section": "Lists" + }, + { + "markdown": "- foo\n- bar\n\n\n\n- baz\n- bim\n", + "html": "
      \n
    • foo
    • \n
    • bar
    • \n
    \n\n
      \n
    • baz
    • \n
    • bim
    • \n
    \n", + "example": 308, + "start_line": 5435, + "end_line": 5453, + "section": "Lists" + }, + { + "markdown": "- foo\n\n notcode\n\n- foo\n\n\n\n code\n", + "html": "
      \n
    • \n

      foo

      \n

      notcode

      \n
    • \n
    • \n

      foo

      \n
    • \n
    \n\n
    code\n
    \n", + "example": 309, + "start_line": 5456, + "end_line": 5479, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n", + "html": "
      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d
    • \n
    • e
    • \n
    • f
    • \n
    • g
    • \n
    \n", + "example": 310, + "start_line": 5487, + "end_line": 5505, + "section": "Lists" + }, + { + "markdown": "1. a\n\n 2. b\n\n 3. c\n", + "html": "
      \n
    1. \n

      a

      \n
    2. \n
    3. \n

      b

      \n
    4. \n
    5. \n

      c

      \n
    6. \n
    \n", + "example": 311, + "start_line": 5508, + "end_line": 5526, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n - d\n - e\n", + "html": "
      \n
    • a
    • \n
    • b
    • \n
    • c
    • \n
    • d\n- e
    • \n
    \n", + "example": 312, + "start_line": 5532, + "end_line": 5546, + "section": "Lists" + }, + { + "markdown": "1. a\n\n 2. b\n\n 3. c\n", + "html": "
      \n
    1. \n

      a

      \n
    2. \n
    3. \n

      b

      \n
    4. \n
    \n
    3. c\n
    \n", + "example": 313, + "start_line": 5552, + "end_line": 5569, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n- c\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n

      b

      \n
    • \n
    • \n

      c

      \n
    • \n
    \n", + "example": 314, + "start_line": 5575, + "end_line": 5592, + "section": "Lists" + }, + { + "markdown": "* a\n*\n\n* c\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n
    • \n

      c

      \n
    • \n
    \n", + "example": 315, + "start_line": 5597, + "end_line": 5612, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n c\n- d\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n

      b

      \n

      c

      \n
    • \n
    • \n

      d

      \n
    • \n
    \n", + "example": 316, + "start_line": 5619, + "end_line": 5638, + "section": "Lists" + }, + { + "markdown": "- a\n- b\n\n [ref]: /url\n- d\n", + "html": "
      \n
    • \n

      a

      \n
    • \n
    • \n

      b

      \n
    • \n
    • \n

      d

      \n
    • \n
    \n", + "example": 317, + "start_line": 5641, + "end_line": 5659, + "section": "Lists" + }, + { + "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n", + "html": "
      \n
    • a
    • \n
    • \n
      b\n\n\n
      \n
    • \n
    • c
    • \n
    \n", + "example": 318, + "start_line": 5664, + "end_line": 5683, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n\n c\n- d\n", + "html": "
      \n
    • a\n
        \n
      • \n

        b

        \n

        c

        \n
      • \n
      \n
    • \n
    • d
    • \n
    \n", + "example": 319, + "start_line": 5690, + "end_line": 5708, + "section": "Lists" + }, + { + "markdown": "* a\n > b\n >\n* c\n", + "html": "
      \n
    • a\n
      \n

      b

      \n
      \n
    • \n
    • c
    • \n
    \n", + "example": 320, + "start_line": 5714, + "end_line": 5728, + "section": "Lists" + }, + { + "markdown": "- a\n > b\n ```\n c\n ```\n- d\n", + "html": "
      \n
    • a\n
      \n

      b

      \n
      \n
      c\n
      \n
    • \n
    • d
    • \n
    \n", + "example": 321, + "start_line": 5734, + "end_line": 5752, + "section": "Lists" + }, + { + "markdown": "- a\n", + "html": "
      \n
    • a
    • \n
    \n", + "example": 322, + "start_line": 5757, + "end_line": 5763, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n", + "html": "
      \n
    • a\n
        \n
      • b
      • \n
      \n
    • \n
    \n", + "example": 323, + "start_line": 5766, + "end_line": 5777, + "section": "Lists" + }, + { + "markdown": "1. ```\n foo\n ```\n\n bar\n", + "html": "
      \n
    1. \n
      foo\n
      \n

      bar

      \n
    2. \n
    \n", + "example": 324, + "start_line": 5783, + "end_line": 5797, + "section": "Lists" + }, + { + "markdown": "* foo\n * bar\n\n baz\n", + "html": "
      \n
    • \n

      foo

      \n
        \n
      • bar
      • \n
      \n

      baz

      \n
    • \n
    \n", + "example": 325, + "start_line": 5802, + "end_line": 5817, + "section": "Lists" + }, + { + "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n", + "html": "
      \n
    • \n

      a

      \n
        \n
      • b
      • \n
      • c
      • \n
      \n
    • \n
    • \n

      d

      \n
        \n
      • e
      • \n
      • f
      • \n
      \n
    • \n
    \n", + "example": 326, + "start_line": 5820, + "end_line": 5845, + "section": "Lists" + }, + { + "markdown": "`hi`lo`\n", + "html": "

    hilo`

    \n", + "example": 327, + "start_line": 5854, + "end_line": 5858, + "section": "Inlines" + }, + { + "markdown": "`foo`\n", + "html": "

    foo

    \n", + "example": 328, + "start_line": 5886, + "end_line": 5890, + "section": "Code spans" + }, + { + "markdown": "`` foo ` bar ``\n", + "html": "

    foo ` bar

    \n", + "example": 329, + "start_line": 5897, + "end_line": 5901, + "section": "Code spans" + }, + { + "markdown": "` `` `\n", + "html": "

    ``

    \n", + "example": 330, + "start_line": 5907, + "end_line": 5911, + "section": "Code spans" + }, + { + "markdown": "` `` `\n", + "html": "

    ``

    \n", + "example": 331, + "start_line": 5915, + "end_line": 5919, + "section": "Code spans" + }, + { + "markdown": "` a`\n", + "html": "

    a

    \n", + "example": 332, + "start_line": 5924, + "end_line": 5928, + "section": "Code spans" + }, + { + "markdown": "` b `\n", + "html": "

     b 

    \n", + "example": 333, + "start_line": 5933, + "end_line": 5937, + "section": "Code spans" + }, + { + "markdown": "` `\n` `\n", + "html": "

     \n

    \n", + "example": 334, + "start_line": 5941, + "end_line": 5947, + "section": "Code spans" + }, + { + "markdown": "``\nfoo\nbar \nbaz\n``\n", + "html": "

    foo bar baz

    \n", + "example": 335, + "start_line": 5952, + "end_line": 5960, + "section": "Code spans" + }, + { + "markdown": "``\nfoo \n``\n", + "html": "

    foo

    \n", + "example": 336, + "start_line": 5962, + "end_line": 5968, + "section": "Code spans" + }, + { + "markdown": "`foo bar \nbaz`\n", + "html": "

    foo bar baz

    \n", + "example": 337, + "start_line": 5973, + "end_line": 5978, + "section": "Code spans" + }, + { + "markdown": "`foo\\`bar`\n", + "html": "

    foo\\bar`

    \n", + "example": 338, + "start_line": 5990, + "end_line": 5994, + "section": "Code spans" + }, + { + "markdown": "``foo`bar``\n", + "html": "

    foo`bar

    \n", + "example": 339, + "start_line": 6001, + "end_line": 6005, + "section": "Code spans" + }, + { + "markdown": "` foo `` bar `\n", + "html": "

    foo `` bar

    \n", + "example": 340, + "start_line": 6007, + "end_line": 6011, + "section": "Code spans" + }, + { + "markdown": "*foo`*`\n", + "html": "

    *foo*

    \n", + "example": 341, + "start_line": 6019, + "end_line": 6023, + "section": "Code spans" + }, + { + "markdown": "[not a `link](/foo`)\n", + "html": "

    [not a link](/foo)

    \n", + "example": 342, + "start_line": 6028, + "end_line": 6032, + "section": "Code spans" + }, + { + "markdown": "``\n", + "html": "

    <a href="">`

    \n", + "example": 343, + "start_line": 6038, + "end_line": 6042, + "section": "Code spans" + }, + { + "markdown": "
    `\n", + "html": "

    `

    \n", + "example": 344, + "start_line": 6047, + "end_line": 6051, + "section": "Code spans" + }, + { + "markdown": "``\n", + "html": "

    <https://foo.bar.baz>`

    \n", + "example": 345, + "start_line": 6056, + "end_line": 6060, + "section": "Code spans" + }, + { + "markdown": "`\n", + "html": "

    https://foo.bar.`baz`

    \n", + "example": 346, + "start_line": 6065, + "end_line": 6069, + "section": "Code spans" + }, + { + "markdown": "```foo``\n", + "html": "

    ```foo``

    \n", + "example": 347, + "start_line": 6075, + "end_line": 6079, + "section": "Code spans" + }, + { + "markdown": "`foo\n", + "html": "

    `foo

    \n", + "example": 348, + "start_line": 6082, + "end_line": 6086, + "section": "Code spans" + }, + { + "markdown": "`foo``bar``\n", + "html": "

    `foobar

    \n", + "example": 349, + "start_line": 6091, + "end_line": 6095, + "section": "Code spans" + }, + { + "markdown": "*foo bar*\n", + "html": "

    foo bar

    \n", + "example": 350, + "start_line": 6308, + "end_line": 6312, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a * foo bar*\n", + "html": "

    a * foo bar*

    \n", + "example": 351, + "start_line": 6318, + "end_line": 6322, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a*\"foo\"*\n", + "html": "

    a*"foo"*

    \n", + "example": 352, + "start_line": 6329, + "end_line": 6333, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "* a *\n", + "html": "

    * a *

    \n", + "example": 353, + "start_line": 6338, + "end_line": 6342, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*$*alpha.\n\n*£*bravo.\n\n*€*charlie.\n", + "html": "

    *$*alpha.

    \n

    *£*bravo.

    \n

    *€*charlie.

    \n", + "example": 354, + "start_line": 6347, + "end_line": 6357, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo*bar*\n", + "html": "

    foobar

    \n", + "example": 355, + "start_line": 6362, + "end_line": 6366, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5*6*78\n", + "html": "

    5678

    \n", + "example": 356, + "start_line": 6369, + "end_line": 6373, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo bar_\n", + "html": "

    foo bar

    \n", + "example": 357, + "start_line": 6378, + "end_line": 6382, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_ foo bar_\n", + "html": "

    _ foo bar_

    \n", + "example": 358, + "start_line": 6388, + "end_line": 6392, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a_\"foo\"_\n", + "html": "

    a_"foo"_

    \n", + "example": 359, + "start_line": 6398, + "end_line": 6402, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo_bar_\n", + "html": "

    foo_bar_

    \n", + "example": 360, + "start_line": 6407, + "end_line": 6411, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5_6_78\n", + "html": "

    5_6_78

    \n", + "example": 361, + "start_line": 6414, + "end_line": 6418, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "пристаням_стремятся_\n", + "html": "

    пристаням_стремятся_

    \n", + "example": 362, + "start_line": 6421, + "end_line": 6425, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "aa_\"bb\"_cc\n", + "html": "

    aa_"bb"_cc

    \n", + "example": 363, + "start_line": 6431, + "end_line": 6435, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo-_(bar)_\n", + "html": "

    foo-(bar)

    \n", + "example": 364, + "start_line": 6442, + "end_line": 6446, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo*\n", + "html": "

    _foo*

    \n", + "example": 365, + "start_line": 6454, + "end_line": 6458, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo bar *\n", + "html": "

    *foo bar *

    \n", + "example": 366, + "start_line": 6464, + "end_line": 6468, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo bar\n*\n", + "html": "

    *foo bar\n*

    \n", + "example": 367, + "start_line": 6473, + "end_line": 6479, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(*foo)\n", + "html": "

    *(*foo)

    \n", + "example": 368, + "start_line": 6486, + "end_line": 6490, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(*foo*)*\n", + "html": "

    (foo)

    \n", + "example": 369, + "start_line": 6496, + "end_line": 6500, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo*bar\n", + "html": "

    foobar

    \n", + "example": 370, + "start_line": 6505, + "end_line": 6509, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo bar _\n", + "html": "

    _foo bar _

    \n", + "example": 371, + "start_line": 6518, + "end_line": 6522, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(_foo)\n", + "html": "

    _(_foo)

    \n", + "example": 372, + "start_line": 6528, + "end_line": 6532, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(_foo_)_\n", + "html": "

    (foo)

    \n", + "example": 373, + "start_line": 6537, + "end_line": 6541, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo_bar\n", + "html": "

    _foo_bar

    \n", + "example": 374, + "start_line": 6546, + "end_line": 6550, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_пристаням_стремятся\n", + "html": "

    _пристаням_стремятся

    \n", + "example": 375, + "start_line": 6553, + "end_line": 6557, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo_bar_baz_\n", + "html": "

    foo_bar_baz

    \n", + "example": 376, + "start_line": 6560, + "end_line": 6564, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(bar)_.\n", + "html": "

    (bar).

    \n", + "example": 377, + "start_line": 6571, + "end_line": 6575, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo bar**\n", + "html": "

    foo bar

    \n", + "example": 378, + "start_line": 6580, + "end_line": 6584, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "** foo bar**\n", + "html": "

    ** foo bar**

    \n", + "example": 379, + "start_line": 6590, + "end_line": 6594, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a**\"foo\"**\n", + "html": "

    a**"foo"**

    \n", + "example": 380, + "start_line": 6601, + "end_line": 6605, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo**bar**\n", + "html": "

    foobar

    \n", + "example": 381, + "start_line": 6610, + "end_line": 6614, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo bar__\n", + "html": "

    foo bar

    \n", + "example": 382, + "start_line": 6619, + "end_line": 6623, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__ foo bar__\n", + "html": "

    __ foo bar__

    \n", + "example": 383, + "start_line": 6629, + "end_line": 6633, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__\nfoo bar__\n", + "html": "

    __\nfoo bar__

    \n", + "example": 384, + "start_line": 6637, + "end_line": 6643, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "a__\"foo\"__\n", + "html": "

    a__"foo"__

    \n", + "example": 385, + "start_line": 6649, + "end_line": 6653, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo__bar__\n", + "html": "

    foo__bar__

    \n", + "example": 386, + "start_line": 6658, + "end_line": 6662, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "5__6__78\n", + "html": "

    5__6__78

    \n", + "example": 387, + "start_line": 6665, + "end_line": 6669, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "пристаням__стремятся__\n", + "html": "

    пристаням__стремятся__

    \n", + "example": 388, + "start_line": 6672, + "end_line": 6676, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo, __bar__, baz__\n", + "html": "

    foo, bar, baz

    \n", + "example": 389, + "start_line": 6679, + "end_line": 6683, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo-__(bar)__\n", + "html": "

    foo-(bar)

    \n", + "example": 390, + "start_line": 6690, + "end_line": 6694, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo bar **\n", + "html": "

    **foo bar **

    \n", + "example": 391, + "start_line": 6703, + "end_line": 6707, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**(**foo)\n", + "html": "

    **(**foo)

    \n", + "example": 392, + "start_line": 6716, + "end_line": 6720, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*(**foo**)*\n", + "html": "

    (foo)

    \n", + "example": 393, + "start_line": 6726, + "end_line": 6730, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n", + "html": "

    Gomphocarpus (Gomphocarpus physocarpus, syn.\nAsclepias physocarpa)

    \n", + "example": 394, + "start_line": 6733, + "end_line": 6739, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo \"*bar*\" foo**\n", + "html": "

    foo "bar" foo

    \n", + "example": 395, + "start_line": 6742, + "end_line": 6746, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo**bar\n", + "html": "

    foobar

    \n", + "example": 396, + "start_line": 6751, + "end_line": 6755, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo bar __\n", + "html": "

    __foo bar __

    \n", + "example": 397, + "start_line": 6763, + "end_line": 6767, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__(__foo)\n", + "html": "

    __(__foo)

    \n", + "example": 398, + "start_line": 6773, + "end_line": 6777, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_(__foo__)_\n", + "html": "

    (foo)

    \n", + "example": 399, + "start_line": 6783, + "end_line": 6787, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__bar\n", + "html": "

    __foo__bar

    \n", + "example": 400, + "start_line": 6792, + "end_line": 6796, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__пристаням__стремятся\n", + "html": "

    __пристаням__стремятся

    \n", + "example": 401, + "start_line": 6799, + "end_line": 6803, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__bar__baz__\n", + "html": "

    foo__bar__baz

    \n", + "example": 402, + "start_line": 6806, + "end_line": 6810, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__(bar)__.\n", + "html": "

    (bar).

    \n", + "example": 403, + "start_line": 6817, + "end_line": 6821, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo [bar](/url)*\n", + "html": "

    foo bar

    \n", + "example": 404, + "start_line": 6829, + "end_line": 6833, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo\nbar*\n", + "html": "

    foo\nbar

    \n", + "example": 405, + "start_line": 6836, + "end_line": 6842, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo __bar__ baz_\n", + "html": "

    foo bar baz

    \n", + "example": 406, + "start_line": 6848, + "end_line": 6852, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo _bar_ baz_\n", + "html": "

    foo bar baz

    \n", + "example": 407, + "start_line": 6855, + "end_line": 6859, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo_ bar_\n", + "html": "

    foo bar

    \n", + "example": 408, + "start_line": 6862, + "end_line": 6866, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo *bar**\n", + "html": "

    foo bar

    \n", + "example": 409, + "start_line": 6869, + "end_line": 6873, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar** baz*\n", + "html": "

    foo bar baz

    \n", + "example": 410, + "start_line": 6876, + "end_line": 6880, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar**baz*\n", + "html": "

    foobarbaz

    \n", + "example": 411, + "start_line": 6882, + "end_line": 6886, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar*\n", + "html": "

    foo**bar

    \n", + "example": 412, + "start_line": 6906, + "end_line": 6910, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo** bar*\n", + "html": "

    foo bar

    \n", + "example": 413, + "start_line": 6919, + "end_line": 6923, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar***\n", + "html": "

    foo bar

    \n", + "example": 414, + "start_line": 6926, + "end_line": 6930, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**bar***\n", + "html": "

    foobar

    \n", + "example": 415, + "start_line": 6933, + "end_line": 6937, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo***bar***baz\n", + "html": "

    foobarbaz

    \n", + "example": 416, + "start_line": 6944, + "end_line": 6948, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo******bar*********baz\n", + "html": "

    foobar***baz

    \n", + "example": 417, + "start_line": 6950, + "end_line": 6954, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo **bar *baz* bim** bop*\n", + "html": "

    foo bar baz bim bop

    \n", + "example": 418, + "start_line": 6959, + "end_line": 6963, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo [*bar*](/url)*\n", + "html": "

    foo bar

    \n", + "example": 419, + "start_line": 6966, + "end_line": 6970, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "** is not an empty emphasis\n", + "html": "

    ** is not an empty emphasis

    \n", + "example": 420, + "start_line": 6975, + "end_line": 6979, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**** is not an empty strong emphasis\n", + "html": "

    **** is not an empty strong emphasis

    \n", + "example": 421, + "start_line": 6982, + "end_line": 6986, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo [bar](/url)**\n", + "html": "

    foo bar

    \n", + "example": 422, + "start_line": 6995, + "end_line": 6999, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo\nbar**\n", + "html": "

    foo\nbar

    \n", + "example": 423, + "start_line": 7002, + "end_line": 7008, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo _bar_ baz__\n", + "html": "

    foo bar baz

    \n", + "example": 424, + "start_line": 7014, + "end_line": 7018, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo __bar__ baz__\n", + "html": "

    foo bar baz

    \n", + "example": 425, + "start_line": 7021, + "end_line": 7025, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo__ bar__\n", + "html": "

    foo bar

    \n", + "example": 426, + "start_line": 7028, + "end_line": 7032, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo **bar****\n", + "html": "

    foo bar

    \n", + "example": 427, + "start_line": 7035, + "end_line": 7039, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar* baz**\n", + "html": "

    foo bar baz

    \n", + "example": 428, + "start_line": 7042, + "end_line": 7046, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo*bar*baz**\n", + "html": "

    foobarbaz

    \n", + "example": 429, + "start_line": 7049, + "end_line": 7053, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo* bar**\n", + "html": "

    foo bar

    \n", + "example": 430, + "start_line": 7056, + "end_line": 7060, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar***\n", + "html": "

    foo bar

    \n", + "example": 431, + "start_line": 7063, + "end_line": 7067, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo *bar **baz**\nbim* bop**\n", + "html": "

    foo bar baz\nbim bop

    \n", + "example": 432, + "start_line": 7072, + "end_line": 7078, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo [*bar*](/url)**\n", + "html": "

    foo bar

    \n", + "example": 433, + "start_line": 7081, + "end_line": 7085, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__ is not an empty emphasis\n", + "html": "

    __ is not an empty emphasis

    \n", + "example": 434, + "start_line": 7090, + "end_line": 7094, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____ is not an empty strong emphasis\n", + "html": "

    ____ is not an empty strong emphasis

    \n", + "example": 435, + "start_line": 7097, + "end_line": 7101, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo ***\n", + "html": "

    foo ***

    \n", + "example": 436, + "start_line": 7107, + "end_line": 7111, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *\\**\n", + "html": "

    foo *

    \n", + "example": 437, + "start_line": 7114, + "end_line": 7118, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *_*\n", + "html": "

    foo _

    \n", + "example": 438, + "start_line": 7121, + "end_line": 7125, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo *****\n", + "html": "

    foo *****

    \n", + "example": 439, + "start_line": 7128, + "end_line": 7132, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo **\\***\n", + "html": "

    foo *

    \n", + "example": 440, + "start_line": 7135, + "end_line": 7139, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo **_**\n", + "html": "

    foo _

    \n", + "example": 441, + "start_line": 7142, + "end_line": 7146, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo*\n", + "html": "

    *foo

    \n", + "example": 442, + "start_line": 7153, + "end_line": 7157, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo**\n", + "html": "

    foo*

    \n", + "example": 443, + "start_line": 7160, + "end_line": 7164, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo**\n", + "html": "

    *foo

    \n", + "example": 444, + "start_line": 7167, + "end_line": 7171, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "****foo*\n", + "html": "

    ***foo

    \n", + "example": 445, + "start_line": 7174, + "end_line": 7178, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo***\n", + "html": "

    foo*

    \n", + "example": 446, + "start_line": 7181, + "end_line": 7185, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo****\n", + "html": "

    foo***

    \n", + "example": 447, + "start_line": 7188, + "end_line": 7192, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo ___\n", + "html": "

    foo ___

    \n", + "example": 448, + "start_line": 7198, + "end_line": 7202, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _\\__\n", + "html": "

    foo _

    \n", + "example": 449, + "start_line": 7205, + "end_line": 7209, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _*_\n", + "html": "

    foo *

    \n", + "example": 450, + "start_line": 7212, + "end_line": 7216, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo _____\n", + "html": "

    foo _____

    \n", + "example": 451, + "start_line": 7219, + "end_line": 7223, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo __\\___\n", + "html": "

    foo _

    \n", + "example": 452, + "start_line": 7226, + "end_line": 7230, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "foo __*__\n", + "html": "

    foo *

    \n", + "example": 453, + "start_line": 7233, + "end_line": 7237, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo_\n", + "html": "

    _foo

    \n", + "example": 454, + "start_line": 7240, + "end_line": 7244, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo__\n", + "html": "

    foo_

    \n", + "example": 455, + "start_line": 7251, + "end_line": 7255, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "___foo__\n", + "html": "

    _foo

    \n", + "example": 456, + "start_line": 7258, + "end_line": 7262, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo_\n", + "html": "

    ___foo

    \n", + "example": 457, + "start_line": 7265, + "end_line": 7269, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo___\n", + "html": "

    foo_

    \n", + "example": 458, + "start_line": 7272, + "end_line": 7276, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo____\n", + "html": "

    foo___

    \n", + "example": 459, + "start_line": 7279, + "end_line": 7283, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo**\n", + "html": "

    foo

    \n", + "example": 460, + "start_line": 7289, + "end_line": 7293, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*_foo_*\n", + "html": "

    foo

    \n", + "example": 461, + "start_line": 7296, + "end_line": 7300, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__foo__\n", + "html": "

    foo

    \n", + "example": 462, + "start_line": 7303, + "end_line": 7307, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_*foo*_\n", + "html": "

    foo

    \n", + "example": 463, + "start_line": 7310, + "end_line": 7314, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "****foo****\n", + "html": "

    foo

    \n", + "example": 464, + "start_line": 7320, + "end_line": 7324, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "____foo____\n", + "html": "

    foo

    \n", + "example": 465, + "start_line": 7327, + "end_line": 7331, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "******foo******\n", + "html": "

    foo

    \n", + "example": 466, + "start_line": 7338, + "end_line": 7342, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "***foo***\n", + "html": "

    foo

    \n", + "example": 467, + "start_line": 7347, + "end_line": 7351, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_____foo_____\n", + "html": "

    foo

    \n", + "example": 468, + "start_line": 7354, + "end_line": 7358, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo _bar* baz_\n", + "html": "

    foo _bar baz_

    \n", + "example": 469, + "start_line": 7363, + "end_line": 7367, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo __bar *baz bim__ bam*\n", + "html": "

    foo bar *baz bim bam

    \n", + "example": 470, + "start_line": 7370, + "end_line": 7374, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**foo **bar baz**\n", + "html": "

    **foo bar baz

    \n", + "example": 471, + "start_line": 7379, + "end_line": 7383, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*foo *bar baz*\n", + "html": "

    *foo bar baz

    \n", + "example": 472, + "start_line": 7386, + "end_line": 7390, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*[bar*](/url)\n", + "html": "

    *bar*

    \n", + "example": 473, + "start_line": 7395, + "end_line": 7399, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_foo [bar_](/url)\n", + "html": "

    _foo bar_

    \n", + "example": 474, + "start_line": 7402, + "end_line": 7406, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*\n", + "html": "

    *

    \n", + "example": 475, + "start_line": 7409, + "end_line": 7413, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**\n", + "html": "

    **

    \n", + "example": 476, + "start_line": 7416, + "end_line": 7420, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__\n", + "html": "

    __

    \n", + "example": 477, + "start_line": 7423, + "end_line": 7427, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "*a `*`*\n", + "html": "

    a *

    \n", + "example": 478, + "start_line": 7430, + "end_line": 7434, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "_a `_`_\n", + "html": "

    a _

    \n", + "example": 479, + "start_line": 7437, + "end_line": 7441, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "**a\n", + "html": "

    **ahttps://foo.bar/?q=**

    \n", + "example": 480, + "start_line": 7444, + "end_line": 7448, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "__a\n", + "html": "

    __ahttps://foo.bar/?q=__

    \n", + "example": 481, + "start_line": 7451, + "end_line": 7455, + "section": "Emphasis and strong emphasis" + }, + { + "markdown": "[link](/uri \"title\")\n", + "html": "

    link

    \n", + "example": 482, + "start_line": 7539, + "end_line": 7543, + "section": "Links" + }, + { + "markdown": "[link](/uri)\n", + "html": "

    link

    \n", + "example": 483, + "start_line": 7549, + "end_line": 7553, + "section": "Links" + }, + { + "markdown": "[](./target.md)\n", + "html": "

    \n", + "example": 484, + "start_line": 7555, + "end_line": 7559, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    link

    \n", + "example": 485, + "start_line": 7562, + "end_line": 7566, + "section": "Links" + }, + { + "markdown": "[link](<>)\n", + "html": "

    link

    \n", + "example": 486, + "start_line": 7569, + "end_line": 7573, + "section": "Links" + }, + { + "markdown": "[]()\n", + "html": "

    \n", + "example": 487, + "start_line": 7576, + "end_line": 7580, + "section": "Links" + }, + { + "markdown": "[link](/my uri)\n", + "html": "

    [link](/my uri)

    \n", + "example": 488, + "start_line": 7585, + "end_line": 7589, + "section": "Links" + }, + { + "markdown": "[link](
    )\n", + "html": "

    link

    \n", + "example": 489, + "start_line": 7591, + "end_line": 7595, + "section": "Links" + }, + { + "markdown": "[link](foo\nbar)\n", + "html": "

    [link](foo\nbar)

    \n", + "example": 490, + "start_line": 7600, + "end_line": 7606, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    [link]()

    \n", + "example": 491, + "start_line": 7608, + "end_line": 7614, + "section": "Links" + }, + { + "markdown": "[a]()\n", + "html": "

    a

    \n", + "example": 492, + "start_line": 7619, + "end_line": 7623, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    [link](<foo>)

    \n", + "example": 493, + "start_line": 7627, + "end_line": 7631, + "section": "Links" + }, + { + "markdown": "[a](\n[a](c)\n", + "html": "

    [a](<b)c\n[a](<b)c>\n[a](c)

    \n", + "example": 494, + "start_line": 7636, + "end_line": 7644, + "section": "Links" + }, + { + "markdown": "[link](\\(foo\\))\n", + "html": "

    link

    \n", + "example": 495, + "start_line": 7648, + "end_line": 7652, + "section": "Links" + }, + { + "markdown": "[link](foo(and(bar)))\n", + "html": "

    link

    \n", + "example": 496, + "start_line": 7657, + "end_line": 7661, + "section": "Links" + }, + { + "markdown": "[link](foo(and(bar))\n", + "html": "

    [link](foo(and(bar))

    \n", + "example": 497, + "start_line": 7666, + "end_line": 7670, + "section": "Links" + }, + { + "markdown": "[link](foo\\(and\\(bar\\))\n", + "html": "

    link

    \n", + "example": 498, + "start_line": 7673, + "end_line": 7677, + "section": "Links" + }, + { + "markdown": "[link]()\n", + "html": "

    link

    \n", + "example": 499, + "start_line": 7680, + "end_line": 7684, + "section": "Links" + }, + { + "markdown": "[link](foo\\)\\:)\n", + "html": "

    link

    \n", + "example": 500, + "start_line": 7690, + "end_line": 7694, + "section": "Links" + }, + { + "markdown": "[link](#fragment)\n\n[link](https://example.com#fragment)\n\n[link](https://example.com?foo=3#frag)\n", + "html": "

    link

    \n

    link

    \n

    link

    \n", + "example": 501, + "start_line": 7699, + "end_line": 7709, + "section": "Links" + }, + { + "markdown": "[link](foo\\bar)\n", + "html": "

    link

    \n", + "example": 502, + "start_line": 7715, + "end_line": 7719, + "section": "Links" + }, + { + "markdown": "[link](foo%20bä)\n", + "html": "

    link

    \n", + "example": 503, + "start_line": 7731, + "end_line": 7735, + "section": "Links" + }, + { + "markdown": "[link](\"title\")\n", + "html": "

    link

    \n", + "example": 504, + "start_line": 7742, + "end_line": 7746, + "section": "Links" + }, + { + "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n", + "html": "

    link\nlink\nlink

    \n", + "example": 505, + "start_line": 7751, + "end_line": 7759, + "section": "Links" + }, + { + "markdown": "[link](/url \"title \\\""\")\n", + "html": "

    link

    \n", + "example": 506, + "start_line": 7765, + "end_line": 7769, + "section": "Links" + }, + { + "markdown": "[link](/url \"title\")\n", + "html": "

    link

    \n", + "example": 507, + "start_line": 7776, + "end_line": 7780, + "section": "Links" + }, + { + "markdown": "[link](/url \"title \"and\" title\")\n", + "html": "

    [link](/url "title "and" title")

    \n", + "example": 508, + "start_line": 7785, + "end_line": 7789, + "section": "Links" + }, + { + "markdown": "[link](/url 'title \"and\" title')\n", + "html": "

    link

    \n", + "example": 509, + "start_line": 7794, + "end_line": 7798, + "section": "Links" + }, + { + "markdown": "[link]( /uri\n \"title\" )\n", + "html": "

    link

    \n", + "example": 510, + "start_line": 7819, + "end_line": 7824, + "section": "Links" + }, + { + "markdown": "[link] (/uri)\n", + "html": "

    [link] (/uri)

    \n", + "example": 511, + "start_line": 7830, + "end_line": 7834, + "section": "Links" + }, + { + "markdown": "[link [foo [bar]]](/uri)\n", + "html": "

    link [foo [bar]]

    \n", + "example": 512, + "start_line": 7840, + "end_line": 7844, + "section": "Links" + }, + { + "markdown": "[link] bar](/uri)\n", + "html": "

    [link] bar](/uri)

    \n", + "example": 513, + "start_line": 7847, + "end_line": 7851, + "section": "Links" + }, + { + "markdown": "[link [bar](/uri)\n", + "html": "

    [link bar

    \n", + "example": 514, + "start_line": 7854, + "end_line": 7858, + "section": "Links" + }, + { + "markdown": "[link \\[bar](/uri)\n", + "html": "

    link [bar

    \n", + "example": 515, + "start_line": 7861, + "end_line": 7865, + "section": "Links" + }, + { + "markdown": "[link *foo **bar** `#`*](/uri)\n", + "html": "

    link foo bar #

    \n", + "example": 516, + "start_line": 7870, + "end_line": 7874, + "section": "Links" + }, + { + "markdown": "[![moon](moon.jpg)](/uri)\n", + "html": "

    \"moon\"

    \n", + "example": 517, + "start_line": 7877, + "end_line": 7881, + "section": "Links" + }, + { + "markdown": "[foo [bar](/uri)](/uri)\n", + "html": "

    [foo bar](/uri)

    \n", + "example": 518, + "start_line": 7886, + "end_line": 7890, + "section": "Links" + }, + { + "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n", + "html": "

    [foo [bar baz](/uri)](/uri)

    \n", + "example": 519, + "start_line": 7893, + "end_line": 7897, + "section": "Links" + }, + { + "markdown": "![[[foo](uri1)](uri2)](uri3)\n", + "html": "

    \"[foo](uri2)\"

    \n", + "example": 520, + "start_line": 7900, + "end_line": 7904, + "section": "Links" + }, + { + "markdown": "*[foo*](/uri)\n", + "html": "

    *foo*

    \n", + "example": 521, + "start_line": 7910, + "end_line": 7914, + "section": "Links" + }, + { + "markdown": "[foo *bar](baz*)\n", + "html": "

    foo *bar

    \n", + "example": 522, + "start_line": 7917, + "end_line": 7921, + "section": "Links" + }, + { + "markdown": "*foo [bar* baz]\n", + "html": "

    foo [bar baz]

    \n", + "example": 523, + "start_line": 7927, + "end_line": 7931, + "section": "Links" + }, + { + "markdown": "[foo \n", + "html": "

    [foo

    \n", + "example": 524, + "start_line": 7937, + "end_line": 7941, + "section": "Links" + }, + { + "markdown": "[foo`](/uri)`\n", + "html": "

    [foo](/uri)

    \n", + "example": 525, + "start_line": 7944, + "end_line": 7948, + "section": "Links" + }, + { + "markdown": "[foo\n", + "html": "

    [foohttps://example.com/?search=](uri)

    \n", + "example": 526, + "start_line": 7951, + "end_line": 7955, + "section": "Links" + }, + { + "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 527, + "start_line": 7989, + "end_line": 7995, + "section": "Links" + }, + { + "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n", + "html": "

    link [foo [bar]]

    \n", + "example": 528, + "start_line": 8004, + "end_line": 8010, + "section": "Links" + }, + { + "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n", + "html": "

    link [bar

    \n", + "example": 529, + "start_line": 8013, + "end_line": 8019, + "section": "Links" + }, + { + "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n", + "html": "

    link foo bar #

    \n", + "example": 530, + "start_line": 8024, + "end_line": 8030, + "section": "Links" + }, + { + "markdown": "[![moon](moon.jpg)][ref]\n\n[ref]: /uri\n", + "html": "

    \"moon\"

    \n", + "example": 531, + "start_line": 8033, + "end_line": 8039, + "section": "Links" + }, + { + "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n", + "html": "

    [foo bar]ref

    \n", + "example": 532, + "start_line": 8044, + "end_line": 8050, + "section": "Links" + }, + { + "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n", + "html": "

    [foo bar baz]ref

    \n", + "example": 533, + "start_line": 8053, + "end_line": 8059, + "section": "Links" + }, + { + "markdown": "*[foo*][ref]\n\n[ref]: /uri\n", + "html": "

    *foo*

    \n", + "example": 534, + "start_line": 8068, + "end_line": 8074, + "section": "Links" + }, + { + "markdown": "[foo *bar][ref]*\n\n[ref]: /uri\n", + "html": "

    foo *bar*

    \n", + "example": 535, + "start_line": 8077, + "end_line": 8083, + "section": "Links" + }, + { + "markdown": "[foo \n\n[ref]: /uri\n", + "html": "

    [foo

    \n", + "example": 536, + "start_line": 8089, + "end_line": 8095, + "section": "Links" + }, + { + "markdown": "[foo`][ref]`\n\n[ref]: /uri\n", + "html": "

    [foo][ref]

    \n", + "example": 537, + "start_line": 8098, + "end_line": 8104, + "section": "Links" + }, + { + "markdown": "[foo\n\n[ref]: /uri\n", + "html": "

    [foohttps://example.com/?search=][ref]

    \n", + "example": 538, + "start_line": 8107, + "end_line": 8113, + "section": "Links" + }, + { + "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 539, + "start_line": 8118, + "end_line": 8124, + "section": "Links" + }, + { + "markdown": "[ẞ]\n\n[SS]: /url\n", + "html": "

    \n", + "example": 540, + "start_line": 8129, + "end_line": 8135, + "section": "Links" + }, + { + "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n", + "html": "

    Baz

    \n", + "example": 541, + "start_line": 8141, + "end_line": 8148, + "section": "Links" + }, + { + "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n", + "html": "

    [foo] bar

    \n", + "example": 542, + "start_line": 8154, + "end_line": 8160, + "section": "Links" + }, + { + "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n", + "html": "

    [foo]\nbar

    \n", + "example": 543, + "start_line": 8163, + "end_line": 8171, + "section": "Links" + }, + { + "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n", + "html": "

    bar

    \n", + "example": 544, + "start_line": 8204, + "end_line": 8212, + "section": "Links" + }, + { + "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n", + "html": "

    [bar][foo!]

    \n", + "example": 545, + "start_line": 8219, + "end_line": 8225, + "section": "Links" + }, + { + "markdown": "[foo][ref[]\n\n[ref[]: /uri\n", + "html": "

    [foo][ref[]

    \n

    [ref[]: /uri

    \n", + "example": 546, + "start_line": 8231, + "end_line": 8238, + "section": "Links" + }, + { + "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n", + "html": "

    [foo][ref[bar]]

    \n

    [ref[bar]]: /uri

    \n", + "example": 547, + "start_line": 8241, + "end_line": 8248, + "section": "Links" + }, + { + "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n", + "html": "

    [[[foo]]]

    \n

    [[[foo]]]: /url

    \n", + "example": 548, + "start_line": 8251, + "end_line": 8258, + "section": "Links" + }, + { + "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n", + "html": "

    foo

    \n", + "example": 549, + "start_line": 8261, + "end_line": 8267, + "section": "Links" + }, + { + "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n", + "html": "

    bar\\

    \n", + "example": 550, + "start_line": 8272, + "end_line": 8278, + "section": "Links" + }, + { + "markdown": "[]\n\n[]: /uri\n", + "html": "

    []

    \n

    []: /uri

    \n", + "example": 551, + "start_line": 8284, + "end_line": 8291, + "section": "Links" + }, + { + "markdown": "[\n ]\n\n[\n ]: /uri\n", + "html": "

    [\n]

    \n

    [\n]: /uri

    \n", + "example": 552, + "start_line": 8294, + "end_line": 8305, + "section": "Links" + }, + { + "markdown": "[foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 553, + "start_line": 8317, + "end_line": 8323, + "section": "Links" + }, + { + "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    foo bar

    \n", + "example": 554, + "start_line": 8326, + "end_line": 8332, + "section": "Links" + }, + { + "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    Foo

    \n", + "example": 555, + "start_line": 8337, + "end_line": 8343, + "section": "Links" + }, + { + "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n", + "html": "

    foo\n[]

    \n", + "example": 556, + "start_line": 8350, + "end_line": 8358, + "section": "Links" + }, + { + "markdown": "[foo]\n\n[foo]: /url \"title\"\n", + "html": "

    foo

    \n", + "example": 557, + "start_line": 8370, + "end_line": 8376, + "section": "Links" + }, + { + "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    foo bar

    \n", + "example": 558, + "start_line": 8379, + "end_line": 8385, + "section": "Links" + }, + { + "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    [foo bar]

    \n", + "example": 559, + "start_line": 8388, + "end_line": 8394, + "section": "Links" + }, + { + "markdown": "[[bar [foo]\n\n[foo]: /url\n", + "html": "

    [[bar foo

    \n", + "example": 560, + "start_line": 8397, + "end_line": 8403, + "section": "Links" + }, + { + "markdown": "[Foo]\n\n[foo]: /url \"title\"\n", + "html": "

    Foo

    \n", + "example": 561, + "start_line": 8408, + "end_line": 8414, + "section": "Links" + }, + { + "markdown": "[foo] bar\n\n[foo]: /url\n", + "html": "

    foo bar

    \n", + "example": 562, + "start_line": 8419, + "end_line": 8425, + "section": "Links" + }, + { + "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n", + "html": "

    [foo]

    \n", + "example": 563, + "start_line": 8431, + "end_line": 8437, + "section": "Links" + }, + { + "markdown": "[foo*]: /url\n\n*[foo*]\n", + "html": "

    *foo*

    \n", + "example": 564, + "start_line": 8443, + "end_line": 8449, + "section": "Links" + }, + { + "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n", + "html": "

    foo

    \n", + "example": 565, + "start_line": 8455, + "end_line": 8462, + "section": "Links" + }, + { + "markdown": "[foo][]\n\n[foo]: /url1\n", + "html": "

    foo

    \n", + "example": 566, + "start_line": 8464, + "end_line": 8470, + "section": "Links" + }, + { + "markdown": "[foo]()\n\n[foo]: /url1\n", + "html": "

    foo

    \n", + "example": 567, + "start_line": 8474, + "end_line": 8480, + "section": "Links" + }, + { + "markdown": "[foo](not a link)\n\n[foo]: /url1\n", + "html": "

    foo(not a link)

    \n", + "example": 568, + "start_line": 8482, + "end_line": 8488, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url\n", + "html": "

    [foo]bar

    \n", + "example": 569, + "start_line": 8493, + "end_line": 8499, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n", + "html": "

    foobaz

    \n", + "example": 570, + "start_line": 8505, + "end_line": 8512, + "section": "Links" + }, + { + "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n", + "html": "

    [foo]bar

    \n", + "example": 571, + "start_line": 8518, + "end_line": 8525, + "section": "Links" + }, + { + "markdown": "![foo](/url \"title\")\n", + "html": "

    \"foo\"

    \n", + "example": 572, + "start_line": 8541, + "end_line": 8545, + "section": "Images" + }, + { + "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "html": "

    \"foo

    \n", + "example": 573, + "start_line": 8548, + "end_line": 8554, + "section": "Images" + }, + { + "markdown": "![foo ![bar](/url)](/url2)\n", + "html": "

    \"foo

    \n", + "example": 574, + "start_line": 8557, + "end_line": 8561, + "section": "Images" + }, + { + "markdown": "![foo [bar](/url)](/url2)\n", + "html": "

    \"foo

    \n", + "example": 575, + "start_line": 8564, + "end_line": 8568, + "section": "Images" + }, + { + "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n", + "html": "

    \"foo

    \n", + "example": 576, + "start_line": 8578, + "end_line": 8584, + "section": "Images" + }, + { + "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n", + "html": "

    \"foo

    \n", + "example": 577, + "start_line": 8587, + "end_line": 8593, + "section": "Images" + }, + { + "markdown": "![foo](train.jpg)\n", + "html": "

    \"foo\"

    \n", + "example": 578, + "start_line": 8596, + "end_line": 8600, + "section": "Images" + }, + { + "markdown": "My ![foo bar](/path/to/train.jpg \"title\" )\n", + "html": "

    My \"foo

    \n", + "example": 579, + "start_line": 8603, + "end_line": 8607, + "section": "Images" + }, + { + "markdown": "![foo]()\n", + "html": "

    \"foo\"

    \n", + "example": 580, + "start_line": 8610, + "end_line": 8614, + "section": "Images" + }, + { + "markdown": "![](/url)\n", + "html": "

    \"\"

    \n", + "example": 581, + "start_line": 8617, + "end_line": 8621, + "section": "Images" + }, + { + "markdown": "![foo][bar]\n\n[bar]: /url\n", + "html": "

    \"foo\"

    \n", + "example": 582, + "start_line": 8626, + "end_line": 8632, + "section": "Images" + }, + { + "markdown": "![foo][bar]\n\n[BAR]: /url\n", + "html": "

    \"foo\"

    \n", + "example": 583, + "start_line": 8635, + "end_line": 8641, + "section": "Images" + }, + { + "markdown": "![foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    \"foo\"

    \n", + "example": 584, + "start_line": 8646, + "end_line": 8652, + "section": "Images" + }, + { + "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    \"foo

    \n", + "example": 585, + "start_line": 8655, + "end_line": 8661, + "section": "Images" + }, + { + "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n", + "html": "

    \"Foo\"

    \n", + "example": 586, + "start_line": 8666, + "end_line": 8672, + "section": "Images" + }, + { + "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n", + "html": "

    \"foo\"\n[]

    \n", + "example": 587, + "start_line": 8678, + "end_line": 8686, + "section": "Images" + }, + { + "markdown": "![foo]\n\n[foo]: /url \"title\"\n", + "html": "

    \"foo\"

    \n", + "example": 588, + "start_line": 8691, + "end_line": 8697, + "section": "Images" + }, + { + "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n", + "html": "

    \"foo

    \n", + "example": 589, + "start_line": 8700, + "end_line": 8706, + "section": "Images" + }, + { + "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n", + "html": "

    ![[foo]]

    \n

    [[foo]]: /url "title"

    \n", + "example": 590, + "start_line": 8711, + "end_line": 8718, + "section": "Images" + }, + { + "markdown": "![Foo]\n\n[foo]: /url \"title\"\n", + "html": "

    \"Foo\"

    \n", + "example": 591, + "start_line": 8723, + "end_line": 8729, + "section": "Images" + }, + { + "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n", + "html": "

    ![foo]

    \n", + "example": 592, + "start_line": 8735, + "end_line": 8741, + "section": "Images" + }, + { + "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n", + "html": "

    !foo

    \n", + "example": 593, + "start_line": 8747, + "end_line": 8753, + "section": "Images" + }, + { + "markdown": "\n", + "html": "

    http://foo.bar.baz

    \n", + "example": 594, + "start_line": 8780, + "end_line": 8784, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    https://foo.bar.baz/test?q=hello&id=22&boolean

    \n", + "example": 595, + "start_line": 8787, + "end_line": 8791, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    irc://foo.bar:2233/baz

    \n", + "example": 596, + "start_line": 8794, + "end_line": 8798, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    MAILTO:FOO@BAR.BAZ

    \n", + "example": 597, + "start_line": 8803, + "end_line": 8807, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    a+b+c:d

    \n", + "example": 598, + "start_line": 8815, + "end_line": 8819, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    made-up-scheme://foo,bar

    \n", + "example": 599, + "start_line": 8822, + "end_line": 8826, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    https://../

    \n", + "example": 600, + "start_line": 8829, + "end_line": 8833, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    localhost:5001/foo

    \n", + "example": 601, + "start_line": 8836, + "end_line": 8840, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <https://foo.bar/baz bim>

    \n", + "example": 602, + "start_line": 8845, + "end_line": 8849, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    https://example.com/\\[\\

    \n", + "example": 603, + "start_line": 8854, + "end_line": 8858, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    foo@bar.example.com

    \n", + "example": 604, + "start_line": 8876, + "end_line": 8880, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    foo+special@Bar.baz-bar0.com

    \n", + "example": 605, + "start_line": 8883, + "end_line": 8887, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <foo+@bar.example.com>

    \n", + "example": 606, + "start_line": 8892, + "end_line": 8896, + "section": "Autolinks" + }, + { + "markdown": "<>\n", + "html": "

    <>

    \n", + "example": 607, + "start_line": 8901, + "end_line": 8905, + "section": "Autolinks" + }, + { + "markdown": "< https://foo.bar >\n", + "html": "

    < https://foo.bar >

    \n", + "example": 608, + "start_line": 8908, + "end_line": 8912, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <m:abc>

    \n", + "example": 609, + "start_line": 8915, + "end_line": 8919, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    <foo.bar.baz>

    \n", + "example": 610, + "start_line": 8922, + "end_line": 8926, + "section": "Autolinks" + }, + { + "markdown": "https://example.com\n", + "html": "

    https://example.com

    \n", + "example": 611, + "start_line": 8929, + "end_line": 8933, + "section": "Autolinks" + }, + { + "markdown": "foo@bar.example.com\n", + "html": "

    foo@bar.example.com

    \n", + "example": 612, + "start_line": 8936, + "end_line": 8940, + "section": "Autolinks" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 613, + "start_line": 9016, + "end_line": 9020, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 614, + "start_line": 9025, + "end_line": 9029, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 615, + "start_line": 9034, + "end_line": 9040, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 616, + "start_line": 9045, + "end_line": 9051, + "section": "Raw HTML" + }, + { + "markdown": "Foo \n", + "html": "

    Foo

    \n", + "example": 617, + "start_line": 9056, + "end_line": 9060, + "section": "Raw HTML" + }, + { + "markdown": "<33> <__>\n", + "html": "

    <33> <__>

    \n", + "example": 618, + "start_line": 9065, + "end_line": 9069, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    <a h*#ref="hi">

    \n", + "example": 619, + "start_line": 9074, + "end_line": 9078, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    <a href="hi'> <a href=hi'>

    \n", + "example": 620, + "start_line": 9083, + "end_line": 9087, + "section": "Raw HTML" + }, + { + "markdown": "< a><\nfoo>\n\n", + "html": "

    < a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />

    \n", + "example": 621, + "start_line": 9092, + "end_line": 9102, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    <a href='bar'title=title>

    \n", + "example": 622, + "start_line": 9107, + "end_line": 9111, + "section": "Raw HTML" + }, + { + "markdown": "
    \n", + "html": "

    \n", + "example": 623, + "start_line": 9116, + "end_line": 9120, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    </a href="foo">

    \n", + "example": 624, + "start_line": 9125, + "end_line": 9129, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 625, + "start_line": 9134, + "end_line": 9140, + "section": "Raw HTML" + }, + { + "markdown": "foo foo -->\n\nfoo foo -->\n", + "html": "

    foo foo -->

    \n

    foo foo -->

    \n", + "example": 626, + "start_line": 9142, + "end_line": 9149, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 627, + "start_line": 9154, + "end_line": 9158, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 628, + "start_line": 9163, + "end_line": 9167, + "section": "Raw HTML" + }, + { + "markdown": "foo &<]]>\n", + "html": "

    foo &<]]>

    \n", + "example": 629, + "start_line": 9172, + "end_line": 9176, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 630, + "start_line": 9182, + "end_line": 9186, + "section": "Raw HTML" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 631, + "start_line": 9191, + "end_line": 9195, + "section": "Raw HTML" + }, + { + "markdown": "\n", + "html": "

    <a href=""">

    \n", + "example": 632, + "start_line": 9198, + "end_line": 9202, + "section": "Raw HTML" + }, + { + "markdown": "foo \nbaz\n", + "html": "

    foo
    \nbaz

    \n", + "example": 633, + "start_line": 9212, + "end_line": 9218, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\nbaz\n", + "html": "

    foo
    \nbaz

    \n", + "example": 634, + "start_line": 9224, + "end_line": 9230, + "section": "Hard line breaks" + }, + { + "markdown": "foo \nbaz\n", + "html": "

    foo
    \nbaz

    \n", + "example": 635, + "start_line": 9235, + "end_line": 9241, + "section": "Hard line breaks" + }, + { + "markdown": "foo \n bar\n", + "html": "

    foo
    \nbar

    \n", + "example": 636, + "start_line": 9246, + "end_line": 9252, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\n bar\n", + "html": "

    foo
    \nbar

    \n", + "example": 637, + "start_line": 9255, + "end_line": 9261, + "section": "Hard line breaks" + }, + { + "markdown": "*foo \nbar*\n", + "html": "

    foo
    \nbar

    \n", + "example": 638, + "start_line": 9267, + "end_line": 9273, + "section": "Hard line breaks" + }, + { + "markdown": "*foo\\\nbar*\n", + "html": "

    foo
    \nbar

    \n", + "example": 639, + "start_line": 9276, + "end_line": 9282, + "section": "Hard line breaks" + }, + { + "markdown": "`code \nspan`\n", + "html": "

    code span

    \n", + "example": 640, + "start_line": 9287, + "end_line": 9292, + "section": "Hard line breaks" + }, + { + "markdown": "`code\\\nspan`\n", + "html": "

    code\\ span

    \n", + "example": 641, + "start_line": 9295, + "end_line": 9300, + "section": "Hard line breaks" + }, + { + "markdown": "
    \n", + "html": "

    \n", + "example": 642, + "start_line": 9305, + "end_line": 9311, + "section": "Hard line breaks" + }, + { + "markdown": "\n", + "html": "

    \n", + "example": 643, + "start_line": 9314, + "end_line": 9320, + "section": "Hard line breaks" + }, + { + "markdown": "foo\\\n", + "html": "

    foo\\

    \n", + "example": 644, + "start_line": 9327, + "end_line": 9331, + "section": "Hard line breaks" + }, + { + "markdown": "foo \n", + "html": "

    foo

    \n", + "example": 645, + "start_line": 9334, + "end_line": 9338, + "section": "Hard line breaks" + }, + { + "markdown": "### foo\\\n", + "html": "

    foo\\

    \n", + "example": 646, + "start_line": 9341, + "end_line": 9345, + "section": "Hard line breaks" + }, + { + "markdown": "### foo \n", + "html": "

    foo

    \n", + "example": 647, + "start_line": 9348, + "end_line": 9352, + "section": "Hard line breaks" + }, + { + "markdown": "foo\nbaz\n", + "html": "

    foo\nbaz

    \n", + "example": 648, + "start_line": 9363, + "end_line": 9369, + "section": "Soft line breaks" + }, + { + "markdown": "foo \n baz\n", + "html": "

    foo\nbaz

    \n", + "example": 649, + "start_line": 9375, + "end_line": 9381, + "section": "Soft line breaks" + }, + { + "markdown": "hello $.;'there\n", + "html": "

    hello $.;'there

    \n", + "example": 650, + "start_line": 9395, + "end_line": 9399, + "section": "Textual content" + }, + { + "markdown": "Foo χρῆν\n", + "html": "

    Foo χρῆν

    \n", + "example": 651, + "start_line": 9402, + "end_line": 9406, + "section": "Textual content" + }, + { + "markdown": "Multiple spaces\n", + "html": "

    Multiple spaces

    \n", + "example": 652, + "start_line": 9411, + "end_line": 9415, + "section": "Textual content" + } +] \ No newline at end of file diff --git a/src/ReverseMarkdown/Config.cs b/src/ReverseMarkdown/Config.cs index 72527719..10bef88f 100644 --- a/src/ReverseMarkdown/Config.cs +++ b/src/ReverseMarkdown/Config.cs @@ -11,6 +11,21 @@ public class Config public bool SlackFlavored { get; set; } = false; + /// + /// Enable CommonMark compatible emphasis handling (avoid intraword emphasis by inserting spaces). + /// + public bool CommonMark { get; set; } = false; + + /// + /// When CommonMark is enabled, insert spaces to avoid intraword emphasis. + /// + public bool CommonMarkIntrawordEmphasisSpacing { get; set; } = false; + + /// + /// When CommonMark is enabled, emit HTML for inline tags (em/strong/a/img) to avoid delimiter edge cases. + /// + public bool CommonMarkUseHtmlInlineTags { get; set; } = true; + public bool SuppressDivNewlines { get; set; } = false; public bool RemoveComments { get; set; } = false; diff --git a/src/ReverseMarkdown/Converter.cs b/src/ReverseMarkdown/Converter.cs index 48a256ed..a5e7de88 100644 --- a/src/ReverseMarkdown/Converter.cs +++ b/src/ReverseMarkdown/Converter.cs @@ -95,6 +95,33 @@ public Converter(Config config, params Assembly[]? additionalAssemblies) public virtual string Convert(string html) { using var _ = EnsureContext(); + + if (Config.CommonMark && LooksLikeCommonMarkHtmlBlock(html)) { + return html; + } + + if (Config.CommonMark) { + var trimmed = html.TrimStart('\uFEFF', ' ', '\t', '\r', '\n'); + if (trimmed.StartsWith("", StringComparison.OrdinalIgnoreCase) && + paragraphTrimmed.EndsWith("

    ", StringComparison.OrdinalIgnoreCase)) { + var inner = paragraphTrimmed.Substring(3, paragraphTrimmed.Length - 7); + if (inner.TrimStart().StartsWith("", StringComparison.Ordinal))) { + WriteRawHtmlAnchor(writer, node, EncodeAnchorText(node.InnerText)); + return; + } + + + + if (isCommonMark && (name.Contains('[') || name.Contains(']') || name.Contains('\n'))) { + writer.Write(node.OuterHtml); + return; + } + + var hrefAttribute = node.Attributes["href"]; + var hasHrefAttribute = hrefAttribute != null; + var href = node.GetAttributeValue("href", string.Empty).Trim(); + if (!isCommonMark) { + href = href.Replace(_escapeValues); + } + else { + href = href.Replace("\\", "\\\\"); + href = href.Replace("*", "\\*").Replace("_", "\\_"); + if (href.Contains('\n') || href.Contains('\r')) { + writer.Write(node.OuterHtml); + return; + } + var openCount = href.Count(c => c == '('); + var closeCount = href.Count(c => c == ')'); + if (closeCount > openCount) { + href = href.Replace(")", "\\)"); + } + if (href.Contains(' ') || href.Contains('(') || href.Contains(')')) { + href = $"<{href.Replace("<", "%3C").Replace(">", "%3E")}>"; + } + } + + if (isCommonMark && !hasHrefAttribute) { + writer.Write(node.OuterHtml); + return; + } + + if (isCommonMark && + node.OuterHtml.IndexOf("href=", StringComparison.OrdinalIgnoreCase) < 0) { + writer.Write(node.OuterHtml); + return; + } + + if (isCommonMark && hrefAttribute != null) { + if (hrefAttribute.Value.Contains("&", StringComparison.Ordinal) || + hrefAttribute.Value.Contains("\\", StringComparison.Ordinal)) { + writer.Write(node.OuterHtml); + return; + } + + var outerHtml = node.OuterHtml; + if (outerHtml.Contains("href=\"&", StringComparison.OrdinalIgnoreCase) || + outerHtml.Contains("href='&", StringComparison.OrdinalIgnoreCase) || + outerHtml.Contains("href=\"\\", StringComparison.OrdinalIgnoreCase) || + outerHtml.Contains("href='\\", StringComparison.OrdinalIgnoreCase)) { + writer.Write(outerHtml); + return; + } - var href = node.GetAttributeValue("href", string.Empty).Trim().Replace(_escapeValues); + var text = node.InnerText; + if (text.Contains("\\", StringComparison.Ordinal) || + text.Contains("[", StringComparison.Ordinal) || + text.Contains("]", StringComparison.Ordinal) || + text.Contains("<", StringComparison.Ordinal) || + text.Contains("*", StringComparison.Ordinal) || + text.Contains("_", StringComparison.Ordinal)) { + writer.Write(node.OuterHtml); + return; + } + } + + if (isCommonMark && string.IsNullOrEmpty(name)) { + if (!hasHrefAttribute) { + writer.Write(node.OuterHtml); + } + else if (string.IsNullOrEmpty(href)) { + writer.Write("[]()"); + } + else { + writer.Write("[]("); + writer.Write(href); + writer.Write(")"); + } + + return; + } + + if (isCommonMark && hrefAttribute != null && + hrefAttribute.Value.Contains("&", StringComparison.Ordinal)) { + writer.Write(node.OuterHtml); + return; + } + + if (isCommonMark && href.Contains('`')) { + writer.Write(node.OuterHtml); + return; + } var scheme = StringUtils.GetScheme(href); var isRemoveLinkWhenSameName = ( @@ -35,10 +145,10 @@ public override void Convert(TextWriter writer, HtmlNode node) ) ); - if (href.StartsWith("#") //anchor link + if ((!Converter.Config.CommonMark && href.StartsWith("#")) //anchor link || !Converter.Config.IsSchemeWhitelisted(scheme) //Not allowed scheme || isRemoveLinkWhenSameName - || string.IsNullOrEmpty(href) //We would otherwise print empty () here... + || (string.IsNullOrEmpty(href) && !Converter.Config.CommonMark) //We would otherwise print empty () here... ) { writer.Write(name); return; @@ -61,7 +171,13 @@ public override void Convert(TextWriter writer, HtmlNode node) var linkText = hasSingleChildImgNode ? name : StringUtils.EscapeLinkText(name); if (string.IsNullOrEmpty(linkText)) { - writer.Write(href); + if (Converter.Config.CommonMark && string.IsNullOrEmpty(href)) { + writer.Write("[]()"); + } + else { + writer.Write(href); + } + return; } @@ -83,5 +199,26 @@ public override void Convert(TextWriter writer, HtmlNode node) writer.Write(")"); } } + + private static void WriteRawHtmlAnchor(TextWriter writer, HtmlNode node, string text) + { + writer.Write("'); + writer.Write(text); + writer.Write("
    "); + } + + private static string EncodeAnchorText(string text) + { + return text.Replace("\\", "\").Replace("<", "<").Replace(">", ">"); + } } } diff --git a/src/ReverseMarkdown/Converters/Br.cs b/src/ReverseMarkdown/Converters/Br.cs index 258b56c1..1f06bc98 100644 --- a/src/ReverseMarkdown/Converters/Br.cs +++ b/src/ReverseMarkdown/Converters/Br.cs @@ -12,10 +12,15 @@ public Br(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { if (node.ParentNode.Name is "strong" or "b" or "em" or "i") { - return; + if (!Converter.Config.CommonMark) { + return; + } } - if (Converter.Config.GithubFlavored) { + if (Converter.Config.CommonMark) { + writer.WriteLine("\\"); + } + else if (Converter.Config.GithubFlavored) { writer.WriteLine(); } else { diff --git a/src/ReverseMarkdown/Converters/Code.cs b/src/ReverseMarkdown/Converters/Code.cs index 5255474c..d18b995a 100644 --- a/src/ReverseMarkdown/Converters/Code.cs +++ b/src/ReverseMarkdown/Converters/Code.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using HtmlAgilityPack; @@ -11,6 +12,29 @@ public Code(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + if (Converter.Config.CommonMark) { + var content = node.InnerHtml; + var fence = CreateCommonMarkCodeFence(content); + writer.Write(fence); + var needsBacktickPadding = content.Length > 0 && (content[0] == '`' || content[^1] == '`'); + var needsWhitespacePadding = content.Length > 0 && + (char.IsWhiteSpace(content[0]) || char.IsWhiteSpace(content[^1])); + var needsPadding = needsBacktickPadding || needsWhitespacePadding; + + if (needsPadding) { + writer.Write(' '); + } + + DecodeHtml(writer, content); + + if (needsPadding) { + writer.Write(' '); + } + + writer.Write(fence); + return; + } + // Depending on the content "surrounding" the element, // leading/trailing whitespace is significant. For example, the // following HTML renders as expected in a browser (meaning there is @@ -53,5 +77,25 @@ public override void Convert(TextWriter writer, HtmlNode node) DecodeHtml(writer, node.InnerText); writer.Write('`'); } + + private static string CreateCommonMarkCodeFence(string content) + { + var maxRun = 0; + var currentRun = 0; + foreach (var c in content) { + if (c == '`') { + currentRun++; + if (currentRun > maxRun) { + maxRun = currentRun; + } + } + else { + currentRun = 0; + } + } + + var fenceLength = Math.Max(1, maxRun + 1); + return new string('`', fenceLength); + } } } diff --git a/src/ReverseMarkdown/Converters/ConverterBase.cs b/src/ReverseMarkdown/Converters/ConverterBase.cs index 125df1ff..f7268bdd 100644 --- a/src/ReverseMarkdown/Converters/ConverterBase.cs +++ b/src/ReverseMarkdown/Converters/ConverterBase.cs @@ -53,6 +53,7 @@ protected static void DecodeHtml(TextWriter writer, string html) protected string IndentationFor(HtmlNode node, bool zeroIndex = false) { var length = Context.AncestorsCount("ol") + Context.AncestorsCount("ul"); + var indentSize = Converter.Config.CommonMark ? 2 : 4; // li not required to have a parent ol/ul if (length == 0) { @@ -63,15 +64,21 @@ protected string IndentationFor(HtmlNode node, bool zeroIndex = false) length -= 1; } - return new string(' ', length * 4); + return new string(' ', length * indentSize); } - public static void TreatEmphasizeContentWhitespaceGuard(TextWriter writer, string content, string emphasis, string nextSiblingSpaceSuffix = "") + public static void TreatEmphasizeContentWhitespaceGuard( + TextWriter writer, + string content, + string emphasis, + string nextSiblingSpaceSuffix = "", + bool preserveLineEndings = false + ) { WriteLeadingSpace(writer, content); writer.Write(emphasis); - writer.Write(content.Chomp()); + writer.Write(preserveLineEndings ? content.Trim(' ') : content.Chomp()); writer.Write(emphasis); WriteTrailingSpace(writer, content, nextSiblingSpaceSuffix); diff --git a/src/ReverseMarkdown/Converters/Div.cs b/src/ReverseMarkdown/Converters/Div.cs index 1c6ca05a..1570e504 100644 --- a/src/ReverseMarkdown/Converters/Div.cs +++ b/src/ReverseMarkdown/Converters/Div.cs @@ -19,6 +19,11 @@ public Div(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + if (Converter.Config.CommonMark) { + writer.Write(node.OuterHtml); + return; + } + while (node.ChildNodes.Count == 1 && node.FirstChild.Name == "div") { node = node.FirstChild; } diff --git a/src/ReverseMarkdown/Converters/Em.cs b/src/ReverseMarkdown/Converters/Em.cs index 885611a3..97aab024 100644 --- a/src/ReverseMarkdown/Converters/Em.cs +++ b/src/ReverseMarkdown/Converters/Em.cs @@ -12,24 +12,134 @@ public Em(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + var isCommonMark = Converter.Config.CommonMark; + if (isCommonMark && Converter.Config.CommonMarkUseHtmlInlineTags) { + var innerHtml = node.InnerHtml; + if (innerHtml.Contains('[') || innerHtml.Contains(']')) { + writer.Write('<'); + writer.Write(node.Name); + foreach (var attribute in node.Attributes) { + writer.Write(' '); + writer.Write(attribute.Name); + writer.Write("=\""); + writer.Write(attribute.Value); + writer.Write('"'); + } + + writer.Write('>'); + writer.Write(innerHtml.Replace("[", "[").Replace("]", "]")); + writer.Write("'); + } + else { + writer.Write(node.OuterHtml); + } + + return; + } + var content = TreatChildrenAsString(node); - if (string.IsNullOrWhiteSpace(content) || AlreadyItalic()) { + if (string.IsNullOrWhiteSpace(content) || (!isCommonMark && AlreadyItalic())) { writer.Write(content); return; } + var commonMarkPrefix = string.Empty; + var commonMarkSuffix = string.Empty; + if (isCommonMark && Converter.Config.CommonMarkIntrawordEmphasisSpacing) { + var contentFirst = FirstNonWhitespaceChar(content); + var contentLast = LastNonWhitespaceChar(content); + var contentHasLeadingWhitespace = content.Length > 0 && char.IsWhiteSpace(content[0]); + var contentHasTrailingWhitespace = content.Length > 0 && char.IsWhiteSpace(content[content.Length - 1]); + + if (IsWordChar(contentFirst) && IsWordChar(contentLast)) { + var hasPrevWord = !contentHasLeadingWhitespace && + IsAdjacentWordChar(node.PreviousSibling, checkEnd: true); + var hasNextWord = !contentHasTrailingWhitespace && + IsAdjacentWordChar(node.NextSibling, checkEnd: false); + if (hasPrevWord && hasNextWord) { + commonMarkPrefix = " "; + commonMarkSuffix = " "; + } + } + } + var spaceSuffix = node.NextSibling?.Name is "i" or "em" ? " " : string.Empty; - var emphasis = Converter.Config.SlackFlavored ? "_" : "*"; - TreatEmphasizeContentWhitespaceGuard(writer, content, emphasis, spaceSuffix); + var emphasis = Converter.Config.SlackFlavored + ? "_" + : isCommonMark && Context.AncestorsAny("i") + ? "_" + : "*"; + var suffix = commonMarkSuffix.Length > 0 || spaceSuffix.Length > 0 ? " " : string.Empty; + if (commonMarkPrefix.Length > 0) { + writer.Write(commonMarkPrefix); + } + + TreatEmphasizeContentWhitespaceGuard( + writer, + content, + emphasis, + suffix, + preserveLineEndings: isCommonMark + ); } private bool AlreadyItalic() { return Context.AncestorsAny("i") || Context.AncestorsAny("em"); } + + private static bool IsWordChar(char? value) + { + return value.HasValue && char.IsLetterOrDigit(value.Value); + } + + private static char? FirstNonWhitespaceChar(string content) + { + foreach (var c in content) { + if (!char.IsWhiteSpace(c)) { + return c; + } + } + + return null; + } + + private static char? LastNonWhitespaceChar(string content) + { + for (var i = content.Length - 1; i >= 0; i--) { + var c = content[i]; + if (!char.IsWhiteSpace(c)) { + return c; + } + } + + return null; + } + + private static bool IsAdjacentWordChar(HtmlNode? sibling, bool checkEnd) + { + if (sibling == null) { + return false; + } + + var text = sibling.InnerText; + if (string.IsNullOrEmpty(text)) { + return false; + } + + var index = checkEnd ? text.Length - 1 : 0; + var c = text[index]; + if (char.IsWhiteSpace(c)) { + return false; + } + + return char.IsLetterOrDigit(c); + } } } diff --git a/src/ReverseMarkdown/Converters/H.cs b/src/ReverseMarkdown/Converters/H.cs index fe9fea56..2030ee4b 100644 --- a/src/ReverseMarkdown/Converters/H.cs +++ b/src/ReverseMarkdown/Converters/H.cs @@ -24,11 +24,38 @@ public override void Convert(TextWriter writer, HtmlNode node) var level = node.Name[1] - '0'; // 'h1' -> 1, 'h2' -> 2, etc. + var content = TreatChildrenAsString(node); + if (Converter.Config.CommonMark) { + content = content.ReplaceLineEndings(" "); + content = EscapeTrailingHashes(content); + } + writer.WriteLine(); writer.Write(new string('#', level)); writer.Write(' '); - TreatChildren(writer, node); + writer.Write(content); writer.WriteLine(); } + + private static string EscapeTrailingHashes(string content) + { + if (string.IsNullOrEmpty(content)) { + return content; + } + + var index = content.Length - 1; + while (index >= 0 && content[index] == '#') { + index--; + } + + if (index == content.Length - 1) { + return content; + } + + var hashCount = content.Length - 1 - index; + var escapedHashes = new string('#', hashCount); + escapedHashes = escapedHashes.Replace("#", "\\#"); + return content[..(index + 1)] + escapedHashes; + } } } diff --git a/src/ReverseMarkdown/Converters/Img.cs b/src/ReverseMarkdown/Converters/Img.cs index 55ecf1b8..9269b76d 100644 --- a/src/ReverseMarkdown/Converters/Img.cs +++ b/src/ReverseMarkdown/Converters/Img.cs @@ -18,6 +18,7 @@ public override void Convert(TextWriter writer, HtmlNode node) throw new SlackUnsupportedTagException(node.Name); } + var altAttribute = node.Attributes["alt"]; var alt = node.GetAttributeValue("alt", string.Empty); var src = node.GetAttributeValue("src", string.Empty); @@ -58,6 +59,11 @@ public override void Convert(TextWriter writer, HtmlNode node) } } + if (Converter.Config.CommonMark && altAttribute == null) { + writer.Write(node.OuterHtml); + return; + } + writer.Write("!["); writer.Write(StringUtils.EscapeLinkText(alt)); writer.Write("]("); diff --git a/src/ReverseMarkdown/Converters/Li.cs b/src/ReverseMarkdown/Converters/Li.cs index ebb9ea15..47b44db0 100644 --- a/src/ReverseMarkdown/Converters/Li.cs +++ b/src/ReverseMarkdown/Converters/Li.cs @@ -22,11 +22,13 @@ public override void Convert(TextWriter writer, HtmlNode node) } } - writer.Write(IndentationFor(node, true)); + var baseIndentation = IndentationFor(node, true); + writer.Write(baseIndentation); if (node.ParentNode is { Name: "ol" }) { // index are zero based hence add one - var index = node.ParentNode.SelectNodes("./li").IndexOf(node) + 1; + var start = node.ParentNode.GetAttributeValue("start", 1); + var index = node.ParentNode.SelectNodes("./li").IndexOf(node) + start; writer.Write(index); writer.Write(". "); } @@ -35,9 +37,44 @@ public override void Convert(TextWriter writer, HtmlNode node) writer.Write(' '); } - var content = ContentFor(node).Trim(); - writer.Write(content); - writer.WriteLine(); + var content = ContentFor(node); + + if (!Converter.Config.CommonMark) { + content = content.Trim(); + writer.Write(content); + writer.WriteLine(); + return; + } + + var markerLength = node.ParentNode is { Name: "ol" } + ? ($"{node.ParentNode.GetAttributeValue("start", 1) + node.ParentNode.SelectNodes("./li").IndexOf(node)}. ").Length + : 2; + + var indentation = baseIndentation + new string(' ', markerLength); + var lines = content.ReplaceLineEndings("\n").Split('\n'); + + if (lines.Length == 0) { + writer.WriteLine(); + return; + } + + writer.WriteLine(lines[0].TrimEnd()); + + for (var i = 1; i < lines.Length; i++) { + var line = lines[i]; + if (line.Length == 0) { + writer.WriteLine(); + continue; + } + + if (line[0] == ' ' || line[0] == '\t') { + writer.WriteLine(line); + continue; + } + + writer.Write(indentation); + writer.WriteLine(line); + } } private string ContentFor(HtmlNode node) diff --git a/src/ReverseMarkdown/Converters/Ol.cs b/src/ReverseMarkdown/Converters/Ol.cs index c42e209b..0640c2f6 100644 --- a/src/ReverseMarkdown/Converters/Ol.cs +++ b/src/ReverseMarkdown/Converters/Ol.cs @@ -1,5 +1,6 @@ using System.IO; using HtmlAgilityPack; +using ReverseMarkdown.Helpers; namespace ReverseMarkdown.Converters { @@ -12,6 +13,11 @@ public Ol(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + if (Converter.Config.CommonMark) { + writer.Write(node.OuterHtml.CompactHtmlForCommonMarkBlock()); + return; + } + // Lists inside tables are not supported as markdown, so leave as HTML if (Context.AncestorsAny("table")) { writer.Write(node.OuterHtml); @@ -23,7 +29,26 @@ public override void Convert(TextWriter writer, HtmlNode node) if (block) writer.WriteLine(); TreatChildren(writer, node); - if (block) writer.WriteLine(); + if (block) { + writer.WriteLine(); + if (Converter.Config.CommonMark && NextElementIsList(node)) { + writer.WriteLine(); + } + } + } + + private static bool NextElementIsList(HtmlNode node) + { + var sibling = node.NextSibling; + while (sibling != null) { + if (sibling.NodeType == HtmlNodeType.Element) { + return sibling.Name is "ul" or "ol"; + } + + sibling = sibling.NextSibling; + } + + return false; } } } diff --git a/src/ReverseMarkdown/Converters/P.cs b/src/ReverseMarkdown/Converters/P.cs index 7c5f6dcc..c035c95a 100644 --- a/src/ReverseMarkdown/Converters/P.cs +++ b/src/ReverseMarkdown/Converters/P.cs @@ -25,16 +25,26 @@ public override void Convert(TextWriter writer, HtmlNode node) } } - private void TreatIndentation(TextWriter writer, HtmlNode node) - { - // If p follows a list item, add newline and indent it - var parentIsList = node.ParentNode.Name is "li" or "ol" or "ul"; - if (parentIsList && node.ParentNode.FirstChild != node) { - var length = Context.AncestorsCount("ol") + Context.AncestorsCount("ul"); - writer.WriteLine(); - writer.Write(new string(' ', length * 4)); - return; - } + private void TreatIndentation(TextWriter writer, HtmlNode node) + { + // If p follows a list item, add newline and indent it + var parentIsList = node.ParentNode.Name is "li" or "ol" or "ul"; + if (Converter.Config.CommonMark && parentIsList) { + if (node.ParentNode.FirstChild != node) { + writer.WriteLine(); + writer.WriteLine(); + } + + return; + } + + if (parentIsList && node.ParentNode.FirstChild != node) { + var length = Context.AncestorsCount("ol") + Context.AncestorsCount("ul"); + var indentSize = Converter.Config.CommonMark ? 2 : 4; + writer.WriteLine(); + writer.Write(new string(' ', length * indentSize)); + return; + } // If p is at the start of a table cell, no leading newline if (!Td.FirstNodeWithinCell(node)) { diff --git a/src/ReverseMarkdown/Converters/Pre.cs b/src/ReverseMarkdown/Converters/Pre.cs index aca9688b..2189904f 100644 --- a/src/ReverseMarkdown/Converters/Pre.cs +++ b/src/ReverseMarkdown/Converters/Pre.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text.RegularExpressions; using HtmlAgilityPack; using ReverseMarkdown.Helpers; @@ -13,42 +14,51 @@ public Pre(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { - var isFencedCodeBlock = Converter.Config.GithubFlavored; + var isFencedCodeBlock = Converter.Config.GithubFlavored || Converter.Config.CommonMark; - // check if indentation need to be added if it is under an ordered or unordered list - var indentation = IndentationFor(node); + var indentation = Converter.Config.CommonMark + ? string.Empty + : IndentationFor(node); + var contentIndentation = indentation; // 4 space indent for code if it is not fenced code block if (!isFencedCodeBlock) { indentation += " "; + contentIndentation = indentation; } writer.WriteLine(); writer.WriteLine(); + // content: + var content = DecodeHtml(node.InnerText); + if (isFencedCodeBlock) { + var fence = Converter.Config.CommonMark + ? CreateCommonMarkFence(content) + : "```"; var lang = GetLanguage(node); writer.Write(indentation); - writer.Write("```"); + writer.Write(fence); writer.Write(lang); writer.WriteLine(); } - - // content: - var content = DecodeHtml(node.InnerText); foreach (var line in content.ReadLines()) { - writer.Write(indentation); + writer.Write(contentIndentation); writer.WriteLine(line); } if (string.IsNullOrEmpty(content)) { - if (!isFencedCodeBlock) writer.Write(indentation); + if (!isFencedCodeBlock) writer.Write(contentIndentation); writer.WriteLine(); } if (isFencedCodeBlock) { + var fence = Converter.Config.CommonMark + ? CreateCommonMarkFence(content) + : "```"; writer.Write(indentation); - writer.Write("```"); + writer.Write(fence); } writer.WriteLine(); @@ -59,6 +69,10 @@ public override void Convert(TextWriter writer, HtmlNode node) { var language = GetLanguageFromHighlightClassAttribute(node); + if (!Converter.Config.CommonMark && !string.IsNullOrEmpty(language)) { + language = language.TrimEnd(';'); + } + return !string.IsNullOrEmpty(language) ? language : Converter.Config.DefaultCodeBlockLanguage; @@ -92,7 +106,7 @@ private static string GetLanguageFromHighlightClassAttribute(HtmlNode node) /// Extracts class attribute syntax using: highlight-json, highlight-source-json, language-json, brush: language /// Returns the Language in Match.Groups[2] /// - [GeneratedRegex(@"(highlight-source-|language-|highlight-|brush:\s)([a-zA-Z0-9]+)")] + [GeneratedRegex(@"(highlight-source-|language-|highlight-|brush:\s)([^\s]+)")] private static partial Regex ClassRegex(); /// @@ -105,10 +119,31 @@ private static Match ClassMatch(HtmlNode node) { var val = node.GetAttributeValue("class", string.Empty); if (!string.IsNullOrEmpty(val)) { + val = System.Net.WebUtility.HtmlDecode(val); return ClassRegex().Match(val); } return Match.Empty; } + + private static string CreateCommonMarkFence(string content) + { + var maxRun = 0; + var currentRun = 0; + foreach (var c in content) { + if (c == '`') { + currentRun++; + if (currentRun > maxRun) { + maxRun = currentRun; + } + } + else { + currentRun = 0; + } + } + + var fenceLength = Math.Max(3, maxRun + 1); + return new string('`', fenceLength); + } } } diff --git a/src/ReverseMarkdown/Converters/S.cs b/src/ReverseMarkdown/Converters/S.cs index f2cf0038..e4103013 100644 --- a/src/ReverseMarkdown/Converters/S.cs +++ b/src/ReverseMarkdown/Converters/S.cs @@ -13,6 +13,11 @@ public S(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + if (Converter.Config.CommonMark) { + writer.Write(node.OuterHtml); + return; + } + var content = TreatChildrenAsString(node); if (string.IsNullOrEmpty(content) || AlreadyStrikethrough()) { diff --git a/src/ReverseMarkdown/Converters/Strong.cs b/src/ReverseMarkdown/Converters/Strong.cs index 2bf3392b..7a877632 100644 --- a/src/ReverseMarkdown/Converters/Strong.cs +++ b/src/ReverseMarkdown/Converters/Strong.cs @@ -12,24 +12,113 @@ public Strong(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + var isCommonMark = Converter.Config.CommonMark; + if (isCommonMark && Converter.Config.CommonMarkUseHtmlInlineTags) { + writer.Write(node.OuterHtml); + return; + } + var content = TreatChildrenAsString(node); - if (string.IsNullOrEmpty(content) || AlreadyBold()) { + if (string.IsNullOrEmpty(content) || (!isCommonMark && AlreadyBold())) { writer.Write(content); return; } + var commonMarkPrefix = string.Empty; + var commonMarkSuffix = string.Empty; + if (isCommonMark && Converter.Config.CommonMarkIntrawordEmphasisSpacing) { + var contentFirst = FirstNonWhitespaceChar(content); + var contentLast = LastNonWhitespaceChar(content); + var contentHasLeadingWhitespace = content.Length > 0 && char.IsWhiteSpace(content[0]); + var contentHasTrailingWhitespace = content.Length > 0 && char.IsWhiteSpace(content[content.Length - 1]); + + if (IsWordChar(contentFirst) && IsWordChar(contentLast)) { + var hasPrevWord = !contentHasLeadingWhitespace && + IsAdjacentWordChar(node.PreviousSibling, checkEnd: true); + var hasNextWord = !contentHasTrailingWhitespace && + IsAdjacentWordChar(node.NextSibling, checkEnd: false); + if (hasPrevWord && hasNextWord) { + commonMarkPrefix = " "; + commonMarkSuffix = " "; + } + } + } + var spaceSuffix = node.NextSibling?.Name is "strong" or "b" ? " " : ""; - var emphasis = Converter.Config.SlackFlavored ? "*" : "**"; - TreatEmphasizeContentWhitespaceGuard(writer, content, emphasis, spaceSuffix); + var emphasis = Converter.Config.SlackFlavored + ? "*" + : isCommonMark && Context.AncestorsAny("strong") + ? "__" + : "**"; + var suffix = commonMarkSuffix.Length > 0 || spaceSuffix.Length > 0 ? " " : ""; + if (commonMarkPrefix.Length > 0) { + writer.Write(commonMarkPrefix); + } + + TreatEmphasizeContentWhitespaceGuard( + writer, + content, + emphasis, + suffix, + preserveLineEndings: isCommonMark + ); } private bool AlreadyBold() { return Context.AncestorsAny("strong") || Context.AncestorsAny("b"); } + + private static bool IsWordChar(char? value) + { + return value.HasValue && char.IsLetterOrDigit(value.Value); + } + + private static char? FirstNonWhitespaceChar(string content) + { + foreach (var c in content) { + if (!char.IsWhiteSpace(c)) { + return c; + } + } + + return null; + } + + private static char? LastNonWhitespaceChar(string content) + { + for (var i = content.Length - 1; i >= 0; i--) { + var c = content[i]; + if (!char.IsWhiteSpace(c)) { + return c; + } + } + + return null; + } + + private static bool IsAdjacentWordChar(HtmlNode? sibling, bool checkEnd) + { + if (sibling == null) { + return false; + } + + var text = sibling.InnerText; + if (string.IsNullOrEmpty(text)) { + return false; + } + + var index = checkEnd ? text.Length - 1 : 0; + var c = text[index]; + if (char.IsWhiteSpace(c)) { + return false; + } + + return char.IsLetterOrDigit(c); + } } } diff --git a/src/ReverseMarkdown/Converters/Table.cs b/src/ReverseMarkdown/Converters/Table.cs index 22ce0e28..f382873c 100644 --- a/src/ReverseMarkdown/Converters/Table.cs +++ b/src/ReverseMarkdown/Converters/Table.cs @@ -15,6 +15,11 @@ public Table(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { + if (Converter.Config.CommonMark) { + writer.Write(node.OuterHtml); + return; + } + if (Converter.Config.SlackFlavored) { throw new SlackUnsupportedTagException(node.Name); } diff --git a/src/ReverseMarkdown/Converters/Text.cs b/src/ReverseMarkdown/Converters/Text.cs index 25cb983d..d5c82e5d 100644 --- a/src/ReverseMarkdown/Converters/Text.cs +++ b/src/ReverseMarkdown/Converters/Text.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Text.RegularExpressions; using HtmlAgilityPack; @@ -52,18 +53,60 @@ public Text(Converter converter) : base(converter) public override void Convert(TextWriter writer, HtmlNode node) { - if (node.InnerText is " " or " " && node.ParentNode.Name is not ("ol" or "ul")) { - writer.Write(' '); + var isCommonMark = Converter.Config.CommonMark; + var innerText = node.InnerText; + if (innerText is " " or " " || innerText == "\u00A0") { + if (node.ParentNode.Name is not ("ol" or "ul")) { + if (isCommonMark && innerText != " ") { + writer.Write(" "); + } + else { + writer.Write(' '); + } + + return; + } } - else { - TreatText(writer, node); + + if (isCommonMark) { + if (innerText == "!" && node.NextSibling?.Name == "a") { + writer.Write("\\!"); + return; + } + + if (innerText == "*" && node.NextSibling?.Name == "img") { + writer.Write("\\*"); + return; + } } + + TreatText(writer, node); } private void TreatText(TextWriter writer, HtmlNode node) { - var text = node.InnerText; + var isCommonMark = Converter.Config.CommonMark; + var rawText = isCommonMark + ? node.OuterHtml + : node.InnerText; + if (isCommonMark && + (rawText.Contains("