Compare commits

..

1 Commits

Author SHA1 Message Date
Alexandre Mutel
62907f9314 Example API doc generation 2022-06-29 07:00:28 +02:00
390 changed files with 35777 additions and 34723 deletions

View File

@@ -20,13 +20,10 @@ jobs:
submodules: true
fetch-depth: 0
- name: Install .NET 6.0, 7.0, and 8.0
- name: Install .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
dotnet-version: '6.0.x'
- name: Build, Test, Pack, Publish
shell: bash

3
.gitignore vendored
View File

@@ -242,3 +242,6 @@ _Pvt_Extensions
# Remove artifacts produced by dotnet-releaser
artifacts-dotnet-releaser/
# Remove .lunet temp folder
.lunet/build

View File

@@ -1,36 +0,0 @@
# How to Contribute
Thanks for your interest in contributing to `Markdig`! Here are a few general guidelines on contributing and
reporting bugs that we ask you to review. Following these guidelines helps to communicate that you respect the time of
the contributors managing and developing this open source project.
## Reporting Issues
Before reporting a new issue, please ensure that the issue was not already reported or fixed by searching through our
[issues list](https://github.com/xoofx/markdig/issues).
When creating a new issue, please be sure to include a **title and clear description**, as much relevant information as
possible, and, if possible, a test case.
## Sending Pull Requests
Before sending a new pull request, take a look at existing pull requests and issues to see if the proposed change or fix
has been discussed in the past, or if the change was already implemented but not yet released.
We expect new pull requests to include tests for any affected behavior, and, as we follow semantic versioning, we may
reserve breaking changes until the next major version release.
## Other Ways to Contribute
We welcome anyone that wants to contribute to `Markdig` to triage and reply to open issues to help troubleshoot
and fix existing bugs. Here is what you can do:
- Help ensure that existing issues follows the recommendations from the _[Reporting Issues](#reporting-issues)_ section,
providing feedback to the issue's author on what might be missing.
instructions and code samples.
- Review existing pull requests, and testing patches against real existing applications that use `Markdig`.
- Write a test, or add a missing test case to an existing test.
Thanks again for your interest on contributing to `Markdig`!
:heart:

View File

@@ -12,7 +12,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- **Very fast parser and html renderer** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
- Checkout [Markdown Editor v2 for Visual Studio 2022](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2) powered by Markdig!
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
- Converter to **HTML**
- Passing more than **600+ tests** from the latest [CommonMark specs (0.30)](http://spec.commonmark.org/)
- Includes all the core elements of CommonMark:
@@ -101,10 +101,6 @@ var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
You can have a look at the [MarkdownExtensions](https://github.com/lunet-io/markdig/blob/master/src/Markdig/MarkdownExtensions.cs) that describes all actionable extensions (by modifying the MarkdownPipeline)
## Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. For detailed contributing guidelines, please see [contributing.md](contributing.md).
## Build
In order to build Markdig, you need to install [.NET 6.0](https://dotnet.microsoft.com/en-us/download)

162
site/.lunet/css/main.css Normal file
View File

@@ -0,0 +1,162 @@
/* main */
:root {
--base-html-font-size: 62.5%;
--base-font-size: 1.65rem;
--container-xl-max-width: 160rem;
--site-logo-size: 45px;
--site-logo-url: url("/img/markdig.png");
}
/* make tocbot working with halfmoon */
html, body, .page-wrapper, .content-wrapper {
position: static;
}
.page-wrapper {
overflow: visible;
}
/* disable any box around active headers with tocbot */
h1:focus, h1:active,
h2:focus, h2:active,
h3:focus, h3:active,
h4:focus, h4:active,
h5:focus, h5:active,
h6:focus, h6:active
{
outline: none;
}
.nav-item.active {
border-bottom-width: 2px;
border-bottom-style: solid;
border-bottom-color: var(--lm-navbar-link-active-text-color);
}
.main-row {
min-height: 92vh;
}
.nav-item.active .nav-link {
color: var(--lm-navbar-link-active-text-color);
background-color: var(--lm-navbar-link-active-bg-color);
}
.site-logo {
background: transparent var(--site-logo-url) no-repeat scroll 0px 0px / var(--site-logo-size) var(--site-logo-size);
display: block;
width: var(--site-logo-size);
height: var(--site-logo-size);
}
.in-this-article-nav {
position: fixed;
margin-right: 2rem;
z-index: 20;
padding: 0.5rem;
border-radius: 0.4rem;
}
.in-this-article-nav .title {
font-weight: bold;
font-size: 1em;
padding-bottom: 1em;
}
.content .is-active-link::before {
background-color: var(--primary-color);
}
.heading-anchor-hidden {
display: none !important;
}
@media (min-width: 1600px) {
html {
font-size: var(--base-html-font-size);
}
}
@media (min-width: 1920px) {
html {
font-size: var(--base-html-font-size);
}
}
table.api-dotnet-inherit, table.api-dotnet-implements, table.api-dotnet-derived {
margin-bottom: 1em;
}
.api-dotnet-inherit td, .api-dotnet-implements td, .api-dotnet-derived td {
vertical-align: top;
}
.api-dotnet-inherit div:not(:first-child):before {
content: " ᐅ ";
white-space: pre;
}
/*.api-dotnet-inherit td, .api-dotnet-inherit tr, .api-dotnet-implements td, .api-dotnet-implements tr, .api-dotnet-derived tr {
display: flex;
}
*/
.api-dotnet-inherit div, .api-dotnet-implements div
{
display: inline-block;
}
.api-dotnet-inherit td:not(:first-child), .api-dotnet-implements td:not(:first-child), .api-dotnet-derived td:not(:first-child) {
padding-left: 1em;
}
.api-dotnet-implements div:not(:first-child):before {
content: ", ";
white-space: pre;
}
.api-dotnet-parameter-list {
display: flex;
}
.api-dotnet-parameter-list dd {
margin-left: 1em;
}
dl.api-dotnet-parameter-list {
margin-bottom: 0px;
}
table.api-dotnet-members-table {
width: 100%;
}
table.api-dotnet-members-table p {
margin-top: 0px;
margin-bottom: 0px;
}
.api-dotnet-members-table tr td:first-child {
width: 33%;
}
div.api-dotnet {
width: 100%;
}
.api-dotnet-members-table tr:not(:first-child) {
border-top: 1px solid #ddd;
}
.api-dotnet-members-table td {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
@media (prefers-color-scheme: dark) {
.api-dotnet-members-table tr:not(:first-child) {
border-top: 1px solid #777;
}
}

701
site/.lunet/css/prism.css Normal file
View File

@@ -0,0 +1,701 @@
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism-coy&languages=markup+clike+c+csharp+cpp+fsharp+markdown&plugins=line-highlight+line-numbers+toolbar+show-language */
/**
* prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics);
* @author Tim Shedor
*/
@media (prefers-color-scheme: light) {
code[class*="language-"],
pre[class*="language-"] {
color: #393A34;
background: none;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre-wrap;
word-spacing: normal;
word-break: break-word;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
position: relative;
margin: .5em 0;
overflow: visible;
padding: 0;
}
pre[class*="language-"]>code {
position: relative;
border-left: 4px solid #358ccb;
box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;
background-color: #fdfdfd;
background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);
background-size: 3em 3em;
background-origin: content-box;
background-attachment: local;
}
code[class*="language"] {
max-height: inherit;
height: inherit;
padding: 0 1em;
display: block;
overflow: auto;
}
/* Margin bottom to accommodate shadow */
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background-color: #fdfdfd;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
position: relative;
padding: .2em;
border-radius: 0.3em;
color: #c92c2c;
border: 1px solid #dddddd;
display: inline;
white-space: normal;
}
pre[class*="language-"]:before,
pre[class*="language-"]:after {
content: '';
z-index: -2;
display: block;
position: absolute;
bottom: 0.75em;
left: 0.18em;
width: 40%;
height: 20%;
max-height: 13em;
}
:not(pre) > code[class*="language-"]:after,
pre[class*="language-"]:after {
right: 0.75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #008000; font-style: italic;
}
.token.char,
.token.string {
color: #A31515;
}
.token.punctuation {
color: #393A34; /* no highlight */
}
.token.deleted {
color: #c92c2c;
}
.token.function-name,
.token.function {
color: #393A34;
}
.token.tag,
.token.selector {
color: #800000;
}
.token.attr-name,
.token.regex,
.token.entity {
color: #ff0000;
}
.token.builtin,
.token.url,
.token.symbol,
.token.number,
.token.boolean,
.token.variable,
.token.constant,
.token.inserted {
color: #36acaa;
}
.token.operator,
.token.variable {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword,
.token.class-name {
color: #0000ff;
}
.token.directive.tag .tag {
background: #ffff00;
color: #393A34;
}
.token.class-name,
.token.property {
color: #2B91AF;
}
.token.important {
color: #e90;
}
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: rgba(255, 255, 255, 0.5);
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.namespace {
opacity: .7;
}
@media screen and (max-width: 767px) {
pre[class*="language-"]:before,
pre[class*="language-"]:after {
bottom: 14px;
box-shadow: none;
}
}
/* Plugin styles */
.token.tab:not(:empty):before,
.token.cr:before,
.token.lf:before {
color: #e0d7d1;
}
/* Plugin styles: Line Numbers */
pre[class*="language-"].line-numbers.line-numbers {
padding-left: 0;
}
pre[class*="language-"].line-numbers.line-numbers code {
padding-left: 3.8em;
}
pre[class*="language-"].line-numbers.line-numbers .line-numbers-rows {
left: 0;
}
/* Plugin styles: Line Highlight */
pre[class*="language-"][data-line] {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
pre[data-line] code {
position: relative;
padding-left: 4em;
}
pre .line-highlight {
margin-top: 0;
}
pre[data-line] {
position: relative;
padding: 1em 0 1em 3em;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em; /* Same as .prisms padding-top */
background: hsla(24, 20%, 50%,.08);
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
pointer-events: none;
line-height: inherit;
white-space: pre;
}
.line-highlight:before,
.line-highlight[data-end]:after {
content: attr(data-start);
position: absolute;
top: .4em;
left: .6em;
min-width: 1em;
padding: 0 .5em;
background-color: hsla(24, 20%, 50%,.4);
color: hsl(24, 20%, 95%);
font: bold 65%/1.5 sans-serif;
text-align: center;
vertical-align: .3em;
border-radius: 999px;
text-shadow: none;
box-shadow: 0 1px white;
}
.line-highlight[data-end]:after {
content: attr(data-end);
top: auto;
bottom: .4em;
}
.line-numbers .line-highlight:before,
.line-numbers .line-highlight:after {
content: none;
}
pre[class*="language-"].line-numbers {
position: relative;
padding-left: 3.8em;
counter-reset: linenumber;
}
pre[class*="language-"].line-numbers > code {
position: relative;
white-space: inherit;
}
.line-numbers .line-numbers-rows {
position: absolute;
pointer-events: none;
top: 0;
font-size: 100%;
left: -3.8em;
width: 3em; /* works for line-numbers below 1000 lines */
letter-spacing: -1px;
border-right: 1px solid #a5a5a5;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.line-numbers-rows > span {
pointer-events: none;
display: block;
counter-increment: linenumber;
}
.line-numbers-rows > span:before {
content: counter(linenumber);
color: #2B91AF;
display: block;
padding-right: 0.8em;
text-align: right;
}
div.code-toolbar {
position: relative;
}
div.code-toolbar > .toolbar {
position: absolute;
top: .3em;
right: .2em;
transition: opacity 0.3s ease-in-out;
opacity: 0;
}
div.code-toolbar:hover > .toolbar {
opacity: 1;
}
div.code-toolbar > .toolbar .toolbar-item {
display: inline-block;
}
div.code-toolbar > .toolbar a {
cursor: pointer;
}
div.code-toolbar > .toolbar button {
background: none;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none; /* for button */
-moz-user-select: none;
-ms-user-select: none;
}
div.code-toolbar > .toolbar a,
div.code-toolbar > .toolbar button,
div.code-toolbar > .toolbar span {
color: #bbb;
font-size: .8em;
padding: 0 .5em;
background: #f5f2f0;
background: rgba(224, 224, 224, 0.2);
box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
border-radius: .5em;
}
div.code-toolbar > .toolbar a:hover,
div.code-toolbar > .toolbar a:focus,
div.code-toolbar > .toolbar button:hover,
div.code-toolbar > .toolbar button:focus,
div.code-toolbar > .toolbar span:hover,
div.code-toolbar > .toolbar span:focus {
color: inherit;
text-decoration: none;
}
}
@media (prefers-color-scheme: dark) {
/* https://github.com/PrismJS/prism-themes/blob/master/themes/prism-vsc-dark-plus.css */
pre[class*="language-"],
code[class*="language-"] {
color: #d4d4d4;
font-size: 13px;
text-shadow: none;
font-family: Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace;
direction: ltr;
text-align: left;
white-space: pre-wrap;
word-spacing: normal;
word-break: break-word;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"] {
position: relative;
margin: .5em 0;
overflow: visible;
background: #1e1e1e;
}
pre[class*="language-"]>code {
border-left: 4px solid #358ccb;
}
code[class*="language"] {
max-height: inherit;
height: inherit;
padding: 0.5em 1em;
display: block;
overflow: auto;
}
pre[class*="language-"]::selection,
code[class*="language-"]::selection,
pre[class*="language-"] *::selection,
code[class*="language-"] *::selection {
text-shadow: none;
background: #75a7ca;
}
@media print {
pre[class*="language-"],
code[class*="language-"] {
text-shadow: none;
}
}
:not(pre) > code[class*="language-"] {
padding: .1em .3em;
border-radius: .3em;
color: #db4c69;
background: #f9f2f4;
}
/*********************************************************
* Tokens
*/
.namespace {
opacity: .7;
}
.token.doctype .token.doctype-tag {
color: #569CD6;
}
.token.doctype .token.name {
color: #9cdcfe;
}
.token.comment,
.token.prolog {
color: #6a9955;
}
.token.punctuation,
.language-html .language-css .token.punctuation,
.language-html .language-javascript .token.punctuation {
color: #d4d4d4;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.inserted,
.token.unit {
color: #b5cea8;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.deleted {
color: #ce9178;
}
.language-css .token.string.url {
text-decoration: underline;
}
.token.operator,
.token.entity {
color: #d4d4d4;
}
.token.operator.arrow {
color: #569CD6;
}
.token.atrule {
color: #ce9178;
}
.token.atrule .token.rule {
color: #c586c0;
}
.token.atrule .token.url {
color: #9cdcfe;
}
.token.atrule .token.url .token.function {
color: #dcdcaa;
}
.token.atrule .token.url .token.punctuation {
color: #d4d4d4;
}
.token.keyword {
color: #569CD6;
}
.token.keyword.module,
.token.keyword.control-flow {
color: #c586c0;
}
.token.function,
.token.function .token.maybe-class-name {
color: #dcdcaa;
}
.token.regex {
color: #d16969;
}
.token.important {
color: #569cd6;
}
.token.italic {
font-style: italic;
}
.token.constant {
color: #9cdcfe;
}
.token.class-name,
.token.maybe-class-name {
color: #4ec9b0;
}
.token.console {
color: #9cdcfe;
}
.token.parameter {
color: #9cdcfe;
}
.token.interpolation {
color: #9cdcfe;
}
.token.punctuation.interpolation-punctuation {
color: #569cd6;
}
.token.boolean {
color: #569cd6;
}
.token.property,
.token.variable,
.token.imports .token.maybe-class-name,
.token.exports .token.maybe-class-name {
color: #9cdcfe;
}
.token.selector {
color: #d7ba7d;
}
.token.escape {
color: #d7ba7d;
}
.token.tag {
color: #569cd6;
}
.token.tag .token.punctuation {
color: #808080;
}
.token.cdata {
color: #808080;
}
.token.attr-name {
color: #9cdcfe;
}
.token.attr-value,
.token.attr-value .token.punctuation {
color: #ce9178;
}
.token.attr-value .token.punctuation.attr-equals {
color: #d4d4d4;
}
.token.entity {
color: #569cd6;
}
.token.namespace {
color: #4ec9b0;
}
/*********************************************************
* Language Specific
*/
pre[class*="language-javascript"],
code[class*="language-javascript"],
pre[class*="language-jsx"],
code[class*="language-jsx"],
pre[class*="language-typescript"],
code[class*="language-typescript"],
pre[class*="language-tsx"],
code[class*="language-tsx"] {
color: #9cdcfe;
}
pre[class*="language-css"],
code[class*="language-css"] {
color: #ce9178;
}
pre[class*="language-html"],
code[class*="language-html"] {
color: #d4d4d4;
}
.language-regex .token.anchor {
color: #dcdcaa;
}
.language-html .token.punctuation {
color: #808080;
}
/*********************************************************
* Line highlighting
*/
pre[data-line] {
position: relative;
}
pre[class*="language-"] > code[class*="language-"] {
position: relative;
z-index: 1;
}
.line-highlight {
position: absolute;
left: 0;
right: 0;
padding: inherit 0;
margin-top: 1em;
background: #f7ebc6;
box-shadow: inset 5px 0 0 #f7d87c;
z-index: 0;
pointer-events: none;
line-height: inherit;
white-space: pre;
}
}

24
site/.lunet/js/xoofx.js Normal file
View File

@@ -0,0 +1,24 @@
anchors.add();
var jstoc = document.getElementsByClassName("js-toc");
if (jstoc.length > 0)
{
tocbot.init({
// Which headings to grab inside of the contentSelector element.
headingSelector: 'h2, h3, h4, h5',
collapseDepth: 3,
orderedList: true,
hasInnerContainers: true,
});
}
(function () {
const InitLunetTheme = function (e) {
if (halfmoon.darkModeOn != e.matches) {
halfmoon.toggleDarkMode();
}
}
let colorSchemeQueryList = window.matchMedia('(prefers-color-scheme: dark)')
InitLunetTheme(colorSchemeQueryList);
colorSchemeQueryList.addListener(InitLunetTheme);
})();

View File

@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en" itemscope itemtype="http://schema.org/WebPage" class="my-html-setup">
<head>
{{~ Head ~}}
</head>
<body class="with-custom-webkit-scrollbars with-custom-css-scrollbars">
<div class="page-wrapper with-transitions">
<div class="sticky-alerts"></div>
<div class="content-wrapper bg-light-lm bg-dark-dm">
<div class="container-xl bg-white-lm bg-dark-light-dm">
<div class="row">
<div class="col-xl-12">
<nav class="navbar">
<a class="site-logo navbar-brand" href="/"></a>
<div id="navbarSupportedContent" class="collapse navbar-collapse justify-content-between">
{{~ site.menu.home.render {
depth: 1,
kind: "nav",
list_class: "w-100"
}
~}}
</div>
</nav>
</div>
</div>
<div class="row main-row">
{{~ $main_content_col_size = 8 ~}}
{{~ if page.nomenu; $main_content_col_size = $main_content_col_size + 2; end }}
{{~ if page.notoc; $main_content_col_size = $main_content_col_size + 2; end }}
{{~ if !page.nomenu ~}}
<div class="col-xl-2 ">
<div class="content">
Menu
</div>
</div>
{{~ end ~}}
<div class="col-xl-{{ $main_content_col_size }} ">
<div class="content js-toc-content">
{{ content }}
</div>
</div>
{{~ if !page.notoc ~}}
<div class="col-xl-2 ">
<div class="content">
<div class="in-this-article-nav">
<div class="title">In this article</div>
<nav class="js-toc toc"></nav>
</div>
</div>
</div>
{{~ end ~}}
</div>
<div class="row">
<div class="col-xl-12">
<nav class="navbar navbar-fixed-bottom justify-content-center">
<footer>Copyright &copy; 2009 - {{ date.now.year }}, Alexandre Mutel - Blog content licensed under the Creative Commons <a href="http://creativecommons.org/licenses/by/2.5/">CC BY 2.5</a> | Site powered by <a href="https://github.com/lunet-io/lunet">lunet</a></footer>
</nav>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
---
layout: _default
---
<h1 class="title">{{ page.title_html ?? page.title }}</h1>
{{ content }}

70
site/config.scriban Normal file
View File

@@ -0,0 +1,70 @@
# Site Settings
author = "Alexandre Mutel"
title = "markdig"
description = "markdig website"
html.head.title = do; ret page.title + " | " + site.title; end
basepath = ""
baseurl = baseurl ?? "https://markdig.net"
# Github repository
github_user = "xoofx"
github_repo_url = "https://github.com/lunet-io/markdig/"
with cards.twitter
enable = true
card = "summary_large_image"
user = "markdig"
image = "/images/twitter-banner.png"
end
# Resources bundle
with bundle
fontawesome = resource "npm:font-awesome" "4.7.0"
halfmoon = resource "npm:halfmoon" "1.1.0"
tocbot = resource "npm:tocbot" "4.12.1"
anchorjs = resource "npm:anchor-js" "4.2.2"
prismjs = resource "npm:prismjs" "1.20.0"
# scss.includes.add fontawesome.path + "/scss"
# css files
css halfmoon "/css/halfmoon-variables.min.css"
css tocbot "/dist/tocbot.css"
css fontawesome "/css/font-awesome.min.css"
css "/css/prism.css"
css "/css/main.css"
# js files
js anchorjs "/anchor.min.js"
js halfmoon "/js/halfmoon.min.js"
js tocbot "/dist/tocbot.min.js"
js prismjs "/prism.js"
js prismjs "/components/prism-shell-session.min.js"
js prismjs "/components/prism-clike.min.js"
js prismjs "/components/prism-c.min.js"
js prismjs "/components/prism-cpp.min.js"
js prismjs "/components/prism-csharp.min.js"
js "/js/prism-stark.js"
js "/js/xoofx.js"
# copy font files
content fontawesome "/fonts/fontawesome-webfont.*" "/fonts/"
# concatenate css/js files
concat = true
minify = true
end
with attributes
# match "/blog/**/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*.md" {
# url: "/:section/:year/:month/:day/:title:output_ext"
#}
end
with api.dotnet
title = "Markdig .NET API Reference"
projects = ["../src/Markdig/Markdig.csproj"]
properties = {TargetFramework: "netstandard2.0"}
end

BIN
site/img/markdig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

3
site/menu.yml Normal file
View File

@@ -0,0 +1,3 @@
home:
- {path: readme.md}
- {path: api/readme.md, title: "Markdig API"}

10
site/readme.md Normal file
View File

@@ -0,0 +1,10 @@
---
layout: simple
title: Home
title_html: Hi and Welcome!
---
Hello World!
Test: <xref:system.object>

8
site/system.md Normal file
View File

@@ -0,0 +1,8 @@
---
uid: system.object
layout: simple
title: Home
title_html: Hi and Welcome!
---
Hello World from system.object!

View File

@@ -1,38 +1,40 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Testamina.Markdig.Benchmarks;
public static class CommonMarkLib
namespace Testamina.Markdig.Benchmarks
{
public static string ToHtml(string text)
public static class CommonMarkLib
{
unsafe
public static string ToHtml(string text)
{
var textAsArray = Encoding.UTF8.GetBytes(text);
fixed (void* ptext = textAsArray)
unsafe
{
var ptr = (byte*)cmark_markdown_to_html(new IntPtr(ptext), text.Length);
int length = 0;
while (ptr[length] != 0)
var textAsArray = Encoding.UTF8.GetBytes(text);
fixed (void* ptext = textAsArray)
{
length++;
var ptr = (byte*)cmark_markdown_to_html(new IntPtr(ptext), text.Length);
int length = 0;
while (ptr[length] != 0)
{
length++;
}
var buffer = new byte[length];
Marshal.Copy(new IntPtr(ptr), buffer, 0, length);
var result = Encoding.UTF8.GetString(buffer);
Marshal.FreeHGlobal(new IntPtr(ptr));
return result;
}
var buffer = new byte[length];
Marshal.Copy(new IntPtr(ptr), buffer, 0, length);
var result = Encoding.UTF8.GetString(buffer);
Marshal.FreeHGlobal(new IntPtr(ptr));
return result;
}
}
}
// char *cmark_markdown_to_html(const char *text, size_t len, int options);
[DllImport("cmark", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr cmark_markdown_to_html(IntPtr charBuffer, int len, int options = 0);
// char *cmark_markdown_to_html(const char *text, size_t len, int options);
[DllImport("cmark", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr cmark_markdown_to_html(IntPtr charBuffer, int len, int options = 0);
}
}

View File

@@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Remove="spec.md" />
@@ -19,12 +18,12 @@
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.11" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.11" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.1" />
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
<PackageReference Include="Markdown" Version="2.2.1" />
<PackageReference Include="MarkdownSharp" Version="2.0.5" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="3.1.456101" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="2.0.226801" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />

View File

@@ -2,75 +2,76 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using Markdig;
namespace Testamina.Markdig.Benchmarks;
//[BenchmarkTask(platform: BenchmarkPlatform.X64, jitVersion: BenchmarkJitVersion.RyuJit, processCount: 1, warmupIterationCount: 2)]
public class Program
namespace Testamina.Markdig.Benchmarks
{
private string text;
public Program()
//[BenchmarkTask(platform: BenchmarkPlatform.X64, jitVersion: BenchmarkJitVersion.RyuJit, processCount: 1, warmupIterationCount: 2)]
public class Program
{
//text = File.ReadAllText("progit.md");
text = File.ReadAllText("spec.md");
}
private string text;
//[Benchmark(Description = "TestMarkdig", OperationsPerInvoke = 4096)]
[Benchmark(Description = "markdig")]
public void TestMarkdig()
{
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
Markdown.ToHtml(text);
//File.WriteAllText("spec.html", writer.ToString());
}
public Program()
{
//text = File.ReadAllText("progit.md");
text = File.ReadAllText("spec.md");
}
[Benchmark(Description = "cmark")]
public void TestCommonMarkCpp()
{
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
CommonMarkLib.ToHtml(text);
//File.WriteAllText("spec.html", writer.ToString());
}
//[Benchmark(Description = "TestMarkdig", OperationsPerInvoke = 4096)]
[Benchmark(Description = "markdig")]
public void TestMarkdig()
{
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
Markdown.ToHtml(text);
//File.WriteAllText("spec.html", writer.ToString());
}
[Benchmark(Description = "CommonMark.NET")]
public void TestCommonMarkNet()
{
////var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
// var reader = new StringReader(text);
//CommonMark.CommonMarkConverter.Parse(reader);
//CommonMark.CommonMarkConverter.Parse(reader);
//reader.Dispose();
//var writer = new StringWriter();
CommonMark.CommonMarkConverter.Convert(text);
//writer.Flush();
//writer.ToString();
}
[Benchmark(Description = "cmark")]
public void TestCommonMarkCpp()
{
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
CommonMarkLib.ToHtml(text);
//File.WriteAllText("spec.html", writer.ToString());
}
[Benchmark(Description = "MarkdownSharp")]
public void TestMarkdownSharp()
{
new MarkdownSharp.Markdown().Transform(text);
}
[Benchmark(Description = "CommonMark.NET")]
public void TestCommonMarkNet()
{
////var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
// var reader = new StringReader(text);
//CommonMark.CommonMarkConverter.Parse(reader);
//CommonMark.CommonMarkConverter.Parse(reader);
//reader.Dispose();
//var writer = new StringWriter();
CommonMark.CommonMarkConverter.Convert(text);
//writer.Flush();
//writer.ToString();
}
static void Main(string[] args)
{
var config = ManualConfig.Create(DefaultConfig.Instance);
//var gcDiagnoser = new MemoryDiagnoser();
//config.Add(new Job { Mode = Mode.SingleRun, LaunchCount = 2, WarmupCount = 2, IterationTime = 1024, TargetCount = 10 });
//config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
//config.Add(gcDiagnoser);
[Benchmark(Description = "MarkdownSharp")]
public void TestMarkdownSharp()
{
new MarkdownSharp.Markdown().Transform(text);
}
//var config = DefaultConfig.Instance;
BenchmarkRunner.Run<Program>(config);
//BenchmarkRunner.Run<TestDictionary>(config);
//BenchmarkRunner.Run<TestMatchPerf>();
//BenchmarkRunner.Run<TestStringPerf>();
static void Main(string[] args)
{
var config = ManualConfig.Create(DefaultConfig.Instance);
//var gcDiagnoser = new MemoryDiagnoser();
//config.Add(new Job { Mode = Mode.SingleRun, LaunchCount = 2, WarmupCount = 2, IterationTime = 1024, TargetCount = 10 });
//config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
//config.Add(gcDiagnoser);
//var config = DefaultConfig.Instance;
BenchmarkRunner.Run<Program>(config);
//BenchmarkRunner.Run<TestDictionary>(config);
//BenchmarkRunner.Run<TestMatchPerf>();
//BenchmarkRunner.Run<TestStringPerf>();
}
}
}

View File

@@ -2,79 +2,81 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Testamina.Markdig.Benchmarks;
public class TextRegexHelper
namespace Testamina.Markdig.Benchmarks
{
private readonly Dictionary<string, string> replacers;
private readonly Regex regex;
public TextRegexHelper(Dictionary<string, string> replacers)
public class TextRegexHelper
{
this.replacers = replacers;
var builder = new StringBuilder();
private readonly Dictionary<string, string> replacers;
private readonly Regex regex;
// (?<1>:value:?)|(?<1>:noo:?)
foreach (var replace in replacers)
public TextRegexHelper(Dictionary<string, string> replacers)
{
var matchStr = Regex.Escape(replace.Key);
if (builder.Length > 0)
this.replacers = replacers;
var builder = new StringBuilder();
// (?<1>:value:?)|(?<1>:noo:?)
foreach (var replace in replacers)
{
builder.Append('|');
var matchStr = Regex.Escape(replace.Key);
if (builder.Length > 0)
{
builder.Append('|');
}
builder.Append("(?<1>").Append(matchStr).Append("?)");
}
builder.Append("(?<1>").Append(matchStr).Append("?)");
regex = new Regex(builder.ToString());
}
regex = new Regex(builder.ToString());
}
public bool TryMatch(string text, int offset, out string matchText, out string replaceText)
{
replaceText = null;
matchText = null;
var result = regex.Match(text, offset);
if (!result.Success)
public bool TryMatch(string text, int offset, out string matchText, out string replaceText)
{
return false;
replaceText = null;
matchText = null;
var result = regex.Match(text, offset);
if (!result.Success)
{
return false;
}
matchText = result.Groups[1].Value;
replaceText = replacers[matchText];
return true;
}
matchText = result.Groups[1].Value;
replaceText = replacers[matchText];
return true;
}
}
/*
public class TestMatchPerf
{
private readonly TextMatchHelper matcher;
public TestMatchPerf()
/*
public class TestMatchPerf
{
var replacers = new Dictionary<string, string>();
for (int i = 0; i < 1000; i++)
private readonly TextMatchHelper matcher;
public TestMatchPerf()
{
replacers.Add($":z{i}:", i.ToString());
var replacers = new Dictionary<string, string>();
for (int i = 0; i < 1000; i++)
{
replacers.Add($":z{i}:", i.ToString());
}
replacers.Add(":abc:", "yes");
matcher = new TextMatchHelper(new HashSet<string>(replacers.Keys));
}
replacers.Add(":abc:", "yes");
matcher = new TextMatchHelper(new HashSet<string>(replacers.Keys));
}
[Benchmark]
public void TestMatch()
{
for (int i = 0; i < 1000; i++)
[Benchmark]
public void TestMatch()
{
string matchText;
//var text = ":z150: this is a long string";
var text = ":z1:";
matcher.TryMatch(text, 0, text.Length, out matchText);
for (int i = 0; i < 1000; i++)
{
string matchText;
//var text = ":z150: this is a long string";
var text = ":z1:";
matcher.TryMatch(text, 0, text.Length, out matchText);
}
}
}
}
*/
*/
}

View File

@@ -1,57 +1,58 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Markdig.Renderers;
namespace Testamina.Markdig.Benchmarks;
public class TestStringPerf
namespace Testamina.Markdig.Benchmarks
{
private string text;
public TestStringPerf()
public class TestStringPerf
{
text = new string('a', 1000);
}
private string text;
[Benchmark]
public void TestIndexOfAny()
{
var writer = new HtmlRenderer(new StringWriter());
for (int i = 0; i < 100; i++)
public TestStringPerf()
{
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
text = new string('a', 1000);
}
[Benchmark]
public void TestIndexOfAny()
{
var writer = new HtmlRenderer(new StringWriter());
for (int i = 0; i < 100; i++)
{
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
writer.WriteEscape(text, 0, text.Length);
}
}
//[Benchmark]
//public void TestCustomIndexOfAny()
//{
// var writer = new HtmlRenderer(new StringWriter());
// for (int i = 0; i < 100; i++)
// {
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// }
//}
}
//[Benchmark]
//public void TestCustomIndexOfAny()
//{
// var writer = new HtmlRenderer(new StringWriter());
// for (int i = 0; i < 100; i++)
// {
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// writer.WriteEscapeOptimized(text, 0, text.Length);
// }
//}
}

View File

@@ -1,5 +0,0 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
global using Assert = NUnit.Framework.Legacy.ClassicAssert;

View File

@@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<StartupObject>Markdig.Tests.Program</StartupObject>
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net6.0\SpecFileGen.dll</SpecExecutable>
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\net6.0\SpecFileGen.timestamp</SpecTimestamp>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="4.0.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
</ItemGroup>
<ItemGroup>
@@ -21,10 +20,6 @@
<ProjectReference Include="..\SpecFileGen\SpecFileGen.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
<ItemGroup>
<ItemSpecExecutable Include="$(SpecExecutable)" />
<InputSpecFiles Include="Specs\*.md" />

View File

@@ -1,192 +1,192 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Markdig.Extensions.AutoLinks;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests;
public class MiscTests
namespace Markdig.Tests
{
[Test]
public void LinkWithInvalidNonAsciiDomainNameIsIgnored()
public class MiscTests
{
// Url from https://github.com/lunet-io/markdig/issues/438
_ = Markdown.ToHtml("[minulém díle](http://V%20minulém%20díle%20jsme%20nainstalovali%20SQL%20Server,%20který%20je%20nutný%20pro%20běh%20Configuration%20Manageru.%20Dnes%20nás%20čeká%20instalace%20WSUS,%20což%20je%20produkt,%20jež%20je%20možné%20používat%20i%20jako%20samostatnou%20funkci%20ve%20Windows%20Serveru,%20který%20se%20stará%20o%20stažení%20a%20instalaci%20aktualizací%20z%20Microsoft%20Update%20na%20klientské%20počítače.%20Stejně%20jako%20v%20předchozích%20dílech,%20tak%20i%20v%20tomto%20si%20ukážeme%20obě%20varianty%20instalace%20%20a%20to%20jak%20instalaci%20z%20PowerShellu,%20tak%20instalaci%20pomocí%20GUI.) ");
// Valid IDN
TestParser.TestSpec("[foo](http://ünicode.com)", "<p><a href=\"http://xn--nicode-2ya.com\">foo</a></p>");
TestParser.TestSpec("[foo](http://ünicode.ünicode.com)", "<p><a href=\"http://xn--nicode-2ya.xn--nicode-2ya.com\">foo</a></p>");
// Invalid IDN
TestParser.TestSpec("[foo](http://ünicode..com)", "<p><a href=\"http://%C3%BCnicode..com\">foo</a></p>");
}
[TestCase("link [foo [bar]]")] // https://spec.commonmark.org/0.29/#example-508
[TestCase("link [foo][bar]")]
[TestCase("link [][foo][bar][]")]
[TestCase("link [][foo][bar][[]]")]
[TestCase("link [foo] [bar]")]
[TestCase("link [[foo] [] [bar] [[abc]def]]")]
[TestCase("[]")]
[TestCase("[ ]")]
[TestCase("[bar][]")]
[TestCase("[bar][ foo]")]
[TestCase("[bar][foo ][]")]
[TestCase("[bar][fo[ ]o ][][]")]
[TestCase("[a]b[c[d[e]f]g]h")]
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o")]
[TestCase("a[b[c[d]e]f[g]h]i[] [][foo][bar][] foo [j]k[l[m]n]o")]
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o[][]")]
public void LinkTextMayContainBalancedBrackets(string linkText)
{
string markdown = $"[{linkText}](/uri)";
string expected = $@"<p><a href=""/uri"">{linkText}</a></p>";
TestParser.TestSpec(markdown, expected);
// Make the link text unbalanced
foreach (var bracketIndex in linkText
.Select((c, i) => new Tuple<char, int>(c, i))
.Where(t => t.Item1 == '[' || t.Item1 == ']')
.Select(t => t.Item2))
[Test]
public void LinkWithInvalidNonAsciiDomainNameIsIgnored()
{
string brokenLinkText = linkText.Remove(bracketIndex, 1);
// Url from https://github.com/lunet-io/markdig/issues/438
_ = Markdown.ToHtml("[minulém díle](http://V%20minulém%20díle%20jsme%20nainstalovali%20SQL%20Server,%20který%20je%20nutný%20pro%20běh%20Configuration%20Manageru.%20Dnes%20nás%20čeká%20instalace%20WSUS,%20což%20je%20produkt,%20jež%20je%20možné%20používat%20i%20jako%20samostatnou%20funkci%20ve%20Windows%20Serveru,%20který%20se%20stará%20o%20stažení%20a%20instalaci%20aktualizací%20z%20Microsoft%20Update%20na%20klientské%20počítače.%20Stejně%20jako%20v%20předchozích%20dílech,%20tak%20i%20v%20tomto%20si%20ukážeme%20obě%20varianty%20instalace%20%20a%20to%20jak%20instalaci%20z%20PowerShellu,%20tak%20instalaci%20pomocí%20GUI.) ");
markdown = $"[{brokenLinkText}](/uri)";
expected = $@"<p><a href=""/uri"">{brokenLinkText}</a></p>";
// Valid IDN
TestParser.TestSpec("[foo](http://ünicode.com)", "<p><a href=\"http://xn--nicode-2ya.com\">foo</a></p>");
TestParser.TestSpec("[foo](http://ünicode.ünicode.com)", "<p><a href=\"http://xn--nicode-2ya.xn--nicode-2ya.com\">foo</a></p>");
string actual = Markdown.ToHtml(markdown);
Assert.AreNotEqual(expected, actual);
// Invalid IDN
TestParser.TestSpec("[foo](http://ünicode..com)", "<p><a href=\"http://%C3%BCnicode..com\">foo</a></p>");
}
}
[Theory]
[TestCase('[', 9 * 1024, true, false)]
[TestCase('[', 11 * 1024, true, true)]
[TestCase('[', 100, false, false)]
[TestCase('[', 150, false, true)]
[TestCase('>', 100, true, false)]
[TestCase('>', 150, true, true)]
public void GuardsAgainstHighlyNestedNodes(char c, int count, bool parseOnly, bool shouldThrow)
{
var markdown = new string(c, count);
TestDelegate test = parseOnly ? () => Markdown.Parse(markdown) : () => Markdown.ToHtml(markdown);
if (shouldThrow)
[TestCase("link [foo [bar]]")] // https://spec.commonmark.org/0.29/#example-508
[TestCase("link [foo][bar]")]
[TestCase("link [][foo][bar][]")]
[TestCase("link [][foo][bar][[]]")]
[TestCase("link [foo] [bar]")]
[TestCase("link [[foo] [] [bar] [[abc]def]]")]
[TestCase("[]")]
[TestCase("[ ]")]
[TestCase("[bar][]")]
[TestCase("[bar][ foo]")]
[TestCase("[bar][foo ][]")]
[TestCase("[bar][fo[ ]o ][][]")]
[TestCase("[a]b[c[d[e]f]g]h")]
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o")]
[TestCase("a[b[c[d]e]f[g]h]i[] [][foo][bar][] foo [j]k[l[m]n]o")]
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o[][]")]
public void LinkTextMayContainBalancedBrackets(string linkText)
{
Exception e = Assert.Throws<ArgumentException>(test);
Assert.True(e.Message.Contains("depth limit"));
string markdown = $"[{linkText}](/uri)";
string expected = $@"<p><a href=""/uri"">{linkText}</a></p>";
TestParser.TestSpec(markdown, expected);
// Make the link text unbalanced
foreach (var bracketIndex in linkText
.Select((c, i) => new Tuple<char, int>(c, i))
.Where(t => t.Item1 == '[' || t.Item1 == ']')
.Select(t => t.Item2))
{
string brokenLinkText = linkText.Remove(bracketIndex, 1);
markdown = $"[{brokenLinkText}](/uri)";
expected = $@"<p><a href=""/uri"">{brokenLinkText}</a></p>";
string actual = Markdown.ToHtml(markdown);
Assert.AreNotEqual(expected, actual);
}
}
else
[Theory]
[TestCase('[', 9 * 1024, true, false)]
[TestCase('[', 11 * 1024, true, true)]
[TestCase('[', 100, false, false)]
[TestCase('[', 150, false, true)]
[TestCase('>', 100, true, false)]
[TestCase('>', 150, true, true)]
public void GuardsAgainstHighlyNestedNodes(char c, int count, bool parseOnly, bool shouldThrow)
{
test();
var markdown = new string(c, count);
TestDelegate test = parseOnly ? () => Markdown.Parse(markdown) : () => Markdown.ToHtml(markdown);
if (shouldThrow)
{
Exception e = Assert.Throws<ArgumentException>(test);
Assert.True(e.Message.Contains("depth limit"));
}
else
{
test();
}
}
}
[Test]
public void IsIssue356Corrected()
{
string input = @"https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97";
string expected = @"<p><a href=""https://foo.bar/path/%5C#m4mv5W0GYKZpGvfA.97"">https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97</a></p>";
TestParser.TestSpec($"<{input}>", expected);
TestParser.TestSpec(input, expected, "autolinks|advanced");
}
[Test]
public void IsIssue365Corrected()
{
// The scheme must be escaped too...
string input = "![image](\"onclick=\"alert&amp;#40;'click'&amp;#41;\"://)";
string expected = "<p><img src=\"%22onclick=%22alert&amp;#40;%27click%27&amp;#41;%22://\" alt=\"image\" /></p>";
TestParser.TestSpec(input, expected);
}
[Test]
public void TestAltTextIsCorrectlyEscaped()
{
TestParser.TestSpec(
@"![This is image alt text with quotation ' and double quotation ""hello"" world](girl.png)",
@"<p><img src=""girl.png"" alt=""This is image alt text with quotation ' and double quotation &quot;hello&quot; world"" /></p>");
}
[Test]
public void TestChangelogPRLinksMatchDescription()
{
string solutionFolder = Path.GetFullPath(Path.Combine(TestParser.TestsDirectory, "../.."));
string changelogPath = Path.Combine(solutionFolder, "changelog.md");
string changelog = File.ReadAllText(changelogPath);
var matches = Regex.Matches(changelog, @"\(\[\(PR #(\d+)\)\]\(.*?pull\/(\d+)\)\)");
Assert.Greater(matches.Count, 0);
foreach (Match match in matches)
[Test]
public void IsIssue356Corrected()
{
Assert.True(int.TryParse(match.Groups[1].Value, out int textNr));
Assert.True(int.TryParse(match.Groups[2].Value, out int linkNr));
Assert.AreEqual(textNr, linkNr);
string input = @"https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97";
string expected = @"<p><a href=""https://foo.bar/path/%5C#m4mv5W0GYKZpGvfA.97"">https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97</a></p>";
TestParser.TestSpec($"<{input}>", expected);
TestParser.TestSpec(input, expected, "autolinks|advanced");
}
}
[Test]
public void TestFixHang()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "hang.md"));
_ = Markdown.ToHtml(input);
}
[Test]
public void IsIssue365Corrected()
{
// The scheme must be escaped too...
string input = "![image](\"onclick=\"alert&amp;#40;'click'&amp;#41;\"://)";
string expected = "<p><img src=\"%22onclick=%22alert&amp;#40;%27click%27&amp;#41;%22://\" alt=\"image\" /></p>";
[Test]
public void TestInvalidHtmlEntity()
{
var input = "9&ddr;&*&ddr;&de<64><65>__";
TestParser.TestSpec(input, "<p>9&amp;ddr;&amp;*&amp;ddr;&amp;de<64><65>__</p>");
}
TestParser.TestSpec(input, expected);
}
[Test]
public void TestInvalidCharacterHandling()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "ArgumentOutOfRangeException.md"));
_ = Markdown.ToHtml(input);
}
[Test]
public void TestAltTextIsCorrectlyEscaped()
{
TestParser.TestSpec(
@"![This is image alt text with quotation ' and double quotation ""hello"" world](girl.png)",
@"<p><img src=""girl.png"" alt=""This is image alt text with quotation ' and double quotation &quot;hello&quot; world"" /></p>");
}
[Test]
public void TestInvalidCodeEscape()
{
var input = "```**Header** ";
_ = Markdown.ToHtml(input);
}
[Test]
public void TestChangelogPRLinksMatchDescription()
{
string solutionFolder = Path.GetFullPath(Path.Combine(TestParser.TestsDirectory, "../.."));
string changelogPath = Path.Combine(solutionFolder, "changelog.md");
string changelog = File.ReadAllText(changelogPath);
var matches = Regex.Matches(changelog, @"\(\[\(PR #(\d+)\)\]\(.*?pull\/(\d+)\)\)");
Assert.Greater(matches.Count, 0);
foreach (Match match in matches)
{
Assert.True(int.TryParse(match.Groups[1].Value, out int textNr));
Assert.True(int.TryParse(match.Groups[2].Value, out int linkNr));
Assert.AreEqual(textNr, linkNr);
}
}
[Test]
public void TestEmphasisAndHtmlEntity()
{
var markdownText = "*Unlimited-Fun&#174;*&#174;";
TestParser.TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
}
[Test]
public void TestFixHang()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "hang.md"));
_ = Markdown.ToHtml(input);
}
[Test]
public void TestThematicInsideCodeBlockInsideList()
{
var input = @"1. In the :
[Test]
public void TestInvalidHtmlEntity()
{
var input = "9&ddr;&*&ddr;&de<64><65>__";
TestParser.TestSpec(input, "<p>9&amp;ddr;&amp;*&amp;ddr;&amp;de<64><65>__</p>");
}
[Test]
public void TestInvalidCharacterHandling()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "ArgumentOutOfRangeException.md"));
_ = Markdown.ToHtml(input);
}
[Test]
public void TestInvalidCodeEscape()
{
var input = "```**Header** ";
_ = Markdown.ToHtml(input);
}
[Test]
public void TestEmphasisAndHtmlEntity()
{
var markdownText = "*Unlimited-Fun&#174;*&#174;";
TestParser.TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
}
[Test]
public void TestThematicInsideCodeBlockInsideList()
{
var input = @"1. In the :
```
Id DisplayName Description
-- ----------- -----------
62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ...
```";
TestParser.TestSpec(input, @"<ol>
TestParser.TestSpec(input, @"<ol>
<li><p>In the :</p>
<pre><code>Id DisplayName Description
-- ----------- -----------
62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ...
</code></pre></li>
</ol>");
}
}
[Test]
public void VisualizeMathExpressions()
{
string math = @"Math expressions
[Test]
public void VisualizeMathExpressions()
{
string math = @"Math expressions
$\frac{n!}{k!(n-k)!} = \binom{n}{k}$
@@ -199,122 +199,98 @@ $$
<div class=""math"">
\begin{align}
\sqrt{37} & = \sqrt{\frac{73^2-1}{12^2}} \\
& = \sqrt{\frac{73^2}{12^2}\cdot\frac{73^2-1}{73^2}} \\
& = \sqrt{\frac{73^2}{12^2}\cdot\frac{73^2-1}{73^2}} \\
& = \sqrt{\frac{73^2}{12^2}}\sqrt{\frac{73^2-1}{73^2}} \\
& = \frac{73}{12}\sqrt{1 - \frac{1}{73^2}} \\
& = \frac{73}{12}\sqrt{1 - \frac{1}{73^2}} \\
& \approx \frac{73}{12}\left(1 - \frac{1}{2\cdot73^2}\right)
\end{align}
</div>
";
//Console.WriteLine("Math Expressions:\n");
var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build()
var html = Markdown.ToHtml(math, pl);
//Console.WriteLine(html);
}
//Console.WriteLine("Math Expressions:\n");
var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build()
var html = Markdown.ToHtml(math, pl);
//Console.WriteLine(html);
}
[Test]
public void InlineMathExpression()
{
string math = @"Math expressions
[Test]
public void InlineMathExpression()
{
string math = @"Math expressions
$\frac{n!}{k!(n-k)!} = \binom{n}{k}$
";
var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build()
var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build()
var html = Markdown.ToHtml(math, pl);
var html = Markdown.ToHtml(math, pl);
var test1 = html.Contains("<p><span class=\"math\">\\(");
var test2 = html.Contains("\\)</span></p>");
if (!test1 || !test2)
{
Console.WriteLine(html);
var test1 = html.Contains("<p><span class=\"math\">\\(");
var test2 = html.Contains("\\)</span></p>");
if (!test1 || !test2)
{
Console.WriteLine(html);
}
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
[Test]
public void BlockMathExpression()
{
string math = @"Math expressions
[Test]
public void BlockMathExpression()
{
string math = @"Math expressions
$$
\frac{n!}{k!(n-k)!} = \binom{n}{k}
$$
";
var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build()
var pl = new MarkdownPipelineBuilder().UseMathematics().Build(); // UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build()
var html = Markdown.ToHtml(math, pl);
var test1 = html.Contains("<div class=\"math\">\n\\[");
var test2 = html.Contains("\\]</div>");
if (!test1 || !test2)
{
Console.WriteLine(html);
var html = Markdown.ToHtml(math, pl);
var test1 = html.Contains("<div class=\"math\">\n\\[");
var test2 = html.Contains("\\]</div>");
if (!test1 || !test2)
{
Console.WriteLine(html);
}
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
[Test]
public void CanDisableParsingHeadings()
{
var noHeadingsPipeline = new MarkdownPipelineBuilder().DisableHeadings().Build();
[Test]
public void CanDisableParsingHeadings()
{
var noHeadingsPipeline = new MarkdownPipelineBuilder().DisableHeadings().Build();
TestParser.TestSpec("Foo\n===", "<h1>Foo</h1>");
TestParser.TestSpec("Foo\n===", "<p>Foo\n===</p>", noHeadingsPipeline);
TestParser.TestSpec("Foo\n===", "<h1>Foo</h1>");
TestParser.TestSpec("Foo\n===", "<p>Foo\n===</p>", noHeadingsPipeline);
TestParser.TestSpec("# Heading 1", "<h1>Heading 1</h1>");
TestParser.TestSpec("# Heading 1", "<p># Heading 1</p>", noHeadingsPipeline);
TestParser.TestSpec("# Heading 1", "<h1>Heading 1</h1>");
TestParser.TestSpec("# Heading 1", "<p># Heading 1</p>", noHeadingsPipeline);
// Does not also disable link reference definitions
TestParser.TestSpec("[Foo]\n\n[Foo]: bar", "<p><a href=\"bar\">Foo</a></p>");
TestParser.TestSpec("[Foo]\n\n[Foo]: bar", "<p><a href=\"bar\">Foo</a></p>", noHeadingsPipeline);
}
// Does not also disable link reference definitions
TestParser.TestSpec("[Foo]\n\n[Foo]: bar", "<p><a href=\"bar\">Foo</a></p>");
TestParser.TestSpec("[Foo]\n\n[Foo]: bar", "<p><a href=\"bar\">Foo</a></p>", noHeadingsPipeline);
}
[Test]
public void CanOpenAutoLinksInNewWindow()
{
var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
var newWindowPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { OpenInNewWindow = true }).Build();
[Test]
public void CanOpenAutoLinksInNewWindow()
{
var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
var newWindowPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { OpenInNewWindow = true }).Build();
TestParser.TestSpec("www.foo.bar", "<p><a href=\"http://www.foo.bar\">www.foo.bar</a></p>", pipeline);
TestParser.TestSpec("www.foo.bar", "<p><a href=\"http://www.foo.bar\" target=\"_blank\">www.foo.bar</a></p>", newWindowPipeline);
}
TestParser.TestSpec("www.foo.bar", "<p><a href=\"http://www.foo.bar\">www.foo.bar</a></p>", pipeline);
TestParser.TestSpec("www.foo.bar", "<p><a href=\"http://www.foo.bar\" target=\"_blank\">www.foo.bar</a></p>", newWindowPipeline);
}
[Test]
public void CanUseHttpsPrefixForWWWAutoLinks()
{
var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
var httpsPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { UseHttpsForWWWLinks = true }).Build();
[Test]
public void CanUseHttpsPrefixForWWWAutoLinks()
{
var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
var httpsPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { UseHttpsForWWWLinks = true }).Build();
TestParser.TestSpec("www.foo.bar", "<p><a href=\"http://www.foo.bar\">www.foo.bar</a></p>", pipeline);
TestParser.TestSpec("www.foo.bar", "<p><a href=\"https://www.foo.bar\">www.foo.bar</a></p>", httpsPipeline);
}
[Test]
public void RootInlineHasCorrectSourceSpan()
{
var pipeline = new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build();
pipeline.TrackTrivia = true;
var document = Markdown.Parse("0123456789\n", pipeline);
var expectedSourceSpan = new SourceSpan(0, 10);
Assert.That(((LeafBlock)document.LastChild).Inline.Span == expectedSourceSpan);
}
[Test]
public void RootInlineInTableCellHasCorrectSourceSpan()
{
var pipeline = new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseAdvancedExtensions().Build();
pipeline.TrackTrivia = true;
var document = Markdown.Parse("| a | b |\n| --- | --- |\n| <span id=\"dest\"></span><span id=\"DEST\"></span>*dest*<br/> | \\[in\\] The address of the result of the operation.<br/> |", pipeline);
var paragraph = (ParagraphBlock)((TableCell)((TableRow)((Table)document.LastChild).LastChild).First()).LastChild;
Assert.That(paragraph.Inline.Span.Start == paragraph.Inline.FirstChild.Span.Start);
Assert.That(paragraph.Inline.Span.End == paragraph.Inline.LastChild.Span.End);
TestParser.TestSpec("www.foo.bar", "<p><a href=\"http://www.foo.bar\">www.foo.bar</a></p>", pipeline);
TestParser.TestSpec("www.foo.bar", "<p><a href=\"https://www.foo.bar\">www.foo.bar</a></p>", httpsPipeline);
}
}
}

View File

@@ -2,15 +2,18 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
namespace Markdig.Tests;
using System;
class Program
namespace Markdig.Tests
{
public static void Main(string[] args)
class Program
{
Console.WriteLine("Run NUnit tests runner with this");
// Uncomment the following line to debug a specific tests more easily
//var tests = new Specs.CommonMarkV_0_29.TestLeafBlocksSetextHeadings();
//tests.LeafBlocksSetextHeadings_Example063();
public static void Main(string[] args)
{
Console.WriteLine("Run NUnit tests runner with this");
// Uncomment the following line to debug a specific tests more easily
//var tests = new Specs.CommonMarkV_0_29.TestLeafBlocksSetextHeadings();
//tests.LeafBlocksSetextHeadings_Example063();
}
}
}

View File

@@ -1,53 +1,55 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestAtxHeading
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("# h")]
[TestCase("# h ")]
[TestCase("# h\n#h")]
[TestCase("# h\n #h")]
[TestCase("# h\n # h")]
[TestCase("# h\n # h ")]
[TestCase(" # h \n # h ")]
public void Test(string value)
[TestFixture]
public class TestAtxHeading
{
RoundTrip(value);
}
[TestCase("# h")]
[TestCase("# h ")]
[TestCase("# h\n#h")]
[TestCase("# h\n #h")]
[TestCase("# h\n # h")]
[TestCase("# h\n # h ")]
[TestCase(" # h \n # h ")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("\n# h\n\np")]
[TestCase("\n# h\n\np\n")]
[TestCase("\n# h\n\np\n\n")]
[TestCase("\n\n# h\n\np\n\n")]
[TestCase("\n\n# h\np\n\n")]
[TestCase("\n\n# h\np\n\n")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase("\n# h\n\np")]
[TestCase("\n# h\n\np\n")]
[TestCase("\n# h\n\np\n\n")]
[TestCase("\n\n# h\n\np\n\n")]
[TestCase("\n\n# h\np\n\n")]
[TestCase("\n\n# h\np\n\n")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase("\n# h")]
[TestCase("\n# h\n")]
[TestCase("\n# h\r")]
[TestCase("\n# h\r\n")]
[TestCase("\n# h")]
[TestCase("\n# h\n")]
[TestCase("\n# h\r")]
[TestCase("\n# h\r\n")]
[TestCase("\r# h")]
[TestCase("\r# h\n")]
[TestCase("\r# h\r")]
[TestCase("\r# h\r\n")]
[TestCase("\r# h")]
[TestCase("\r# h\n")]
[TestCase("\r# h\r")]
[TestCase("\r# h\r\n")]
[TestCase("\r\n# h")]
[TestCase("\r\n# h\n")]
[TestCase("\r\n# h\r")]
[TestCase("\r\n# h\r\n")]
[TestCase("\r\n# h")]
[TestCase("\r\n# h\n")]
[TestCase("\r\n# h\r")]
[TestCase("\r\n# h\r\n")]
[TestCase("# h\n\n ")]
[TestCase("# h\n\n ")]
[TestCase("# h\n\n ")]
public void TestNewline(string value)
{
RoundTrip(value);
[TestCase("# h\n\n ")]
[TestCase("# h\n\n ")]
[TestCase("# h\n\n ")]
public void TestNewline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,105 +1,107 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestFencedCodeBlock
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("```\nc\n```")]
[TestCase("```\nc\n```\n")]
[TestCase("\n```\nc\n```")]
[TestCase("\n\n```\nc\n```")]
[TestCase("```\nc\n```\n")]
[TestCase("```\nc\n```\n\n")]
[TestCase("\n```\nc\n```\n")]
[TestCase("\n```\nc\n```\n\n")]
[TestCase("\n\n```\nc\n```\n")]
[TestCase("\n\n```\nc\n```\n\n")]
[TestCase(" ```\nc\n````")]
[TestCase("```\nc\n````")]
[TestCase("p\n\n```\nc\n```")]
[TestCase("```\n c\n```")]
[TestCase("```\nc \n```")]
[TestCase("```\n c \n```")]
[TestCase(" ``` \n c \n ``` ")]
[TestCase("\t```\t\n\tc\t\n\t```\t")]
[TestCase("\v```\v\n\vc\v\n\v```\v")]
[TestCase("\f```\f\n\fc\f\n\f```\f")]
public void Test(string value)
[TestFixture]
public class TestFencedCodeBlock
{
RoundTrip(value);
}
[TestCase("```\nc\n```")]
[TestCase("```\nc\n```\n")]
[TestCase("\n```\nc\n```")]
[TestCase("\n\n```\nc\n```")]
[TestCase("```\nc\n```\n")]
[TestCase("```\nc\n```\n\n")]
[TestCase("\n```\nc\n```\n")]
[TestCase("\n```\nc\n```\n\n")]
[TestCase("\n\n```\nc\n```\n")]
[TestCase("\n\n```\nc\n```\n\n")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~ ")]
public void TestTilde(string value)
{
RoundTrip(value);
}
[TestCase(" ```\nc\n````")]
[TestCase("```\nc\n````")]
[TestCase("p\n\n```\nc\n```")]
[TestCase("```\n c \n```")]
[TestCase("```\n c \r```")]
[TestCase("```\n c \r\n```")]
[TestCase("```\r c \n```")]
[TestCase("```\r c \r```")]
[TestCase("```\r c \r\n```")]
[TestCase("```\r\n c \n```")]
[TestCase("```\r\n c \r```")]
[TestCase("```\r\n c \r\n```")]
[TestCase("```\n c\n```")]
[TestCase("```\nc \n```")]
[TestCase("```\n c \n```")]
[TestCase("```\n c \n```\n")]
[TestCase("```\n c \r```\n")]
[TestCase("```\n c \r\n```\n")]
[TestCase("```\r c \n```\n")]
[TestCase("```\r c \r```\n")]
[TestCase("```\r c \r\n```\n")]
[TestCase("```\r\n c \n```\n")]
[TestCase("```\r\n c \r```\n")]
[TestCase("```\r\n c \r\n```\n")]
[TestCase(" ``` \n c \n ``` ")]
[TestCase("\t```\t\n\tc\t\n\t```\t")]
[TestCase("\v```\v\n\vc\v\n\v```\v")]
[TestCase("\f```\f\n\fc\f\n\f```\f")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("```\n c \n```\r")]
[TestCase("```\n c \r```\r")]
[TestCase("```\n c \r\n```\r")]
[TestCase("```\r c \n```\r")]
[TestCase("```\r c \r```\r")]
[TestCase("```\r c \r\n```\r")]
[TestCase("```\r\n c \n```\r")]
[TestCase("```\r\n c \r```\r")]
[TestCase("```\r\n c \r\n```\r")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~ ")]
public void TestTilde(string value)
{
RoundTrip(value);
}
[TestCase("```\n c \n```\r\n")]
[TestCase("```\n c \r```\r\n")]
[TestCase("```\n c \r\n```\r\n")]
[TestCase("```\r c \n```\r\n")]
[TestCase("```\r c \r```\r\n")]
[TestCase("```\r c \r\n```\r\n")]
[TestCase("```\r\n c \n```\r\n")]
[TestCase("```\r\n c \r```\r\n")]
[TestCase("```\r\n c \r\n```\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase("```\n c \n```")]
[TestCase("```\n c \r```")]
[TestCase("```\n c \r\n```")]
[TestCase("```\r c \n```")]
[TestCase("```\r c \r```")]
[TestCase("```\r c \r\n```")]
[TestCase("```\r\n c \n```")]
[TestCase("```\r\n c \r```")]
[TestCase("```\r\n c \r\n```")]
[TestCase("```i a\n```")]
[TestCase("```i a a2\n```")]
[TestCase("```i a a2 a3\n```")]
[TestCase("```i a a2 a3 a4\n```")]
[TestCase("```\n c \n```\n")]
[TestCase("```\n c \r```\n")]
[TestCase("```\n c \r\n```\n")]
[TestCase("```\r c \n```\n")]
[TestCase("```\r c \r```\n")]
[TestCase("```\r c \r\n```\n")]
[TestCase("```\r\n c \n```\n")]
[TestCase("```\r\n c \r```\n")]
[TestCase("```\r\n c \r\n```\n")]
[TestCase("```i\ta\n```")]
[TestCase("```i\ta a2\n```")]
[TestCase("```i\ta a2 a3\n```")]
[TestCase("```i\ta a2 a3 a4\n```")]
[TestCase("```\n c \n```\r")]
[TestCase("```\n c \r```\r")]
[TestCase("```\n c \r\n```\r")]
[TestCase("```\r c \n```\r")]
[TestCase("```\r c \r```\r")]
[TestCase("```\r c \r\n```\r")]
[TestCase("```\r\n c \n```\r")]
[TestCase("```\r\n c \r```\r")]
[TestCase("```\r\n c \r\n```\r")]
[TestCase("```i\ta \n```")]
[TestCase("```i\ta a2 \n```")]
[TestCase("```i\ta a2 a3 \n```")]
[TestCase("```i\ta a2 a3 a4 \n```")]
public void TestInfoArguments(string value)
{
RoundTrip(value);
[TestCase("```\n c \n```\r\n")]
[TestCase("```\n c \r```\r\n")]
[TestCase("```\n c \r\n```\r\n")]
[TestCase("```\r c \n```\r\n")]
[TestCase("```\r c \r```\r\n")]
[TestCase("```\r c \r\n```\r\n")]
[TestCase("```\r\n c \n```\r\n")]
[TestCase("```\r\n c \r```\r\n")]
[TestCase("```\r\n c \r\n```\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase("```i a\n```")]
[TestCase("```i a a2\n```")]
[TestCase("```i a a2 a3\n```")]
[TestCase("```i a a2 a3 a4\n```")]
[TestCase("```i\ta\n```")]
[TestCase("```i\ta a2\n```")]
[TestCase("```i\ta a2 a3\n```")]
[TestCase("```i\ta a2 a3 a4\n```")]
[TestCase("```i\ta \n```")]
[TestCase("```i\ta a2 \n```")]
[TestCase("```i\ta a2 a3 \n```")]
[TestCase("```i\ta a2 a3 a4 \n```")]
public void TestInfoArguments(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,18 +1,20 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestHtmlBlock
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("<br>")]
[TestCase("<br>\n")]
[TestCase("<br>\n\n")]
[TestCase("<div></div>\n\n# h")]
[TestCase("p\n\n<div></div>\n")]
[TestCase("<div></div>\n\n# h")]
public void Test(string value)
[TestFixture]
public class TestHtmlBlock
{
RoundTrip(value);
[TestCase("<br>")]
[TestCase("<br>\n")]
[TestCase("<br>\n\n")]
[TestCase("<div></div>\n\n# h")]
[TestCase("p\n\n<div></div>\n")]
[TestCase("<div></div>\n\n# h")]
public void Test(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,84 +1,86 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestIndentedCodeBlock
namespace Markdig.Tests.RoundtripSpecs
{
// A codeblock is indented with 4 spaces. After the 4th space, whitespace is interpreted as content.
// l = line
[TestCase(" l")]
[TestCase(" l")]
[TestCase("\tl")]
[TestCase("\t\tl")]
[TestCase("\tl1\n l1")]
[TestCase("\n l")]
[TestCase("\n\n l")]
[TestCase("\n l\n")]
[TestCase("\n l\n\n")]
[TestCase("\n\n l\n")]
[TestCase("\n\n l\n\n")]
[TestCase(" l\n l")]
[TestCase(" l\n l\n l")]
// two newlines are needed for indented codeblock start after paragraph
[TestCase("p\n\n l")]
[TestCase("p\n\n l\n")]
[TestCase("p\n\n l\n\n")]
[TestCase("p\n\n l\n l")]
[TestCase("p\n\n l\n l")]
[TestCase(" l\n\np\n\n l")]
[TestCase(" l l\n\np\n\n l l")]
public void Test(string value)
[TestFixture]
public class TestIndentedCodeBlock
{
RoundTrip(value);
}
// A codeblock is indented with 4 spaces. After the 4th space, whitespace is interpreted as content.
// l = line
[TestCase(" l")]
[TestCase(" l")]
[TestCase("\tl")]
[TestCase("\t\tl")]
[TestCase("\tl1\n l1")]
[TestCase(" l\n")]
[TestCase(" l\r")]
[TestCase(" l\r\n")]
[TestCase("\n l")]
[TestCase("\n\n l")]
[TestCase("\n l\n")]
[TestCase("\n l\n\n")]
[TestCase("\n\n l\n")]
[TestCase("\n\n l\n\n")]
[TestCase(" l\n l")]
[TestCase(" l\n l\n")]
[TestCase(" l\n l\r")]
[TestCase(" l\n l\r\n")]
[TestCase(" l\n l")]
[TestCase(" l\n l\n l")]
[TestCase(" l\r l")]
[TestCase(" l\r l\n")]
[TestCase(" l\r l\r")]
[TestCase(" l\r l\r\n")]
[TestCase(" l\r\n l")]
[TestCase(" l\r\n l\n")]
[TestCase(" l\r\n l\r")]
[TestCase(" l\r\n l\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
// two newlines are needed for indented codeblock start after paragraph
[TestCase("p\n\n l")]
[TestCase("p\n\n l\n")]
[TestCase("p\n\n l\n\n")]
[TestCase(" l\n\n l\n")]
[TestCase(" l\n\n\n l\n")]
public void TestNewlinesInBetweenResultInOneCodeBlock(string value)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
MarkdownPipeline pipeline = pipelineBuilder.Build();
var markdownDocument = Markdown.Parse(value, pipeline);
[TestCase("p\n\n l\n l")]
[TestCase("p\n\n l\n l")]
Assert.AreEqual(1, markdownDocument.Count);
}
[TestCase(" l\n\np\n\n l")]
[TestCase(" l l\n\np\n\n l l")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase(" l\n\np")]
[TestCase(" l\n\n\np")]
[TestCase(" l\n\n\n\np")]
public void TestParagraph(string value)
{
RoundTrip(value);
[TestCase(" l\n")]
[TestCase(" l\r")]
[TestCase(" l\r\n")]
[TestCase(" l\n l")]
[TestCase(" l\n l\n")]
[TestCase(" l\n l\r")]
[TestCase(" l\n l\r\n")]
[TestCase(" l\r l")]
[TestCase(" l\r l\n")]
[TestCase(" l\r l\r")]
[TestCase(" l\r l\r\n")]
[TestCase(" l\r\n l")]
[TestCase(" l\r\n l\n")]
[TestCase(" l\r\n l\r")]
[TestCase(" l\r\n l\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase(" l\n\n l\n")]
[TestCase(" l\n\n\n l\n")]
public void TestNewlinesInBetweenResultInOneCodeBlock(string value)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
MarkdownPipeline pipeline = pipelineBuilder.Build();
var markdownDocument = Markdown.Parse(value, pipeline);
Assert.AreEqual(1, markdownDocument.Count);
}
[TestCase(" l\n\np")]
[TestCase(" l\n\n\np")]
[TestCase(" l\n\n\n\np")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,212 +1,214 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestLinkReferenceDefinition
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase(@"[a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@"[a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@"[a]: /r ")]
[TestCase(@" [a]: /r ")]
[TestCase(@" [a]: /r ")]
[TestCase(@" [a]: /r ")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l"" ")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase("[a]:\t/r")]
[TestCase("[a]:\t/r\t")]
[TestCase("[a]:\t/r\t\"l\"")]
[TestCase("[a]:\t/r\t\"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r\t")]
[TestCase("[a]: \t/r\t\"l\"")]
[TestCase("[a]: \t/r\t\"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r\t")]
[TestCase("[a]:\t /r\t\"l\"")]
[TestCase("[a]:\t /r\t\"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r\t")]
[TestCase("[a]: \t /r\t\"l\"")]
[TestCase("[a]: \t /r\t\"l\"\t")]
[TestCase("[a]:\t/r \t")]
[TestCase("[a]:\t/r \t\"l\"")]
[TestCase("[a]:\t/r \t\"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r \t")]
[TestCase("[a]: \t/r \t\"l\"")]
[TestCase("[a]: \t/r \t\"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r \t")]
[TestCase("[a]:\t /r \t\"l\"")]
[TestCase("[a]:\t /r \t\"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r \t")]
[TestCase("[a]: \t /r \t\"l\"")]
[TestCase("[a]: \t /r \t\"l\"\t")]
[TestCase("[a]:\t/r\t ")]
[TestCase("[a]:\t/r\t \"l\"")]
[TestCase("[a]:\t/r\t \"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r\t ")]
[TestCase("[a]: \t/r\t \"l\"")]
[TestCase("[a]: \t/r\t \"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r\t ")]
[TestCase("[a]:\t /r\t \"l\"")]
[TestCase("[a]:\t /r\t \"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r\t ")]
[TestCase("[a]: \t /r\t \"l\"")]
[TestCase("[a]: \t /r\t \"l\"\t")]
[TestCase("[a]:\t/r \t ")]
[TestCase("[a]:\t/r \t \"l\"")]
[TestCase("[a]:\t/r \t \"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r \t ")]
[TestCase("[a]: \t/r \t \"l\"")]
[TestCase("[a]: \t/r \t \"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r \t ")]
[TestCase("[a]:\t /r \t \"l\"")]
[TestCase("[a]:\t /r \t \"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r \t ")]
[TestCase("[a]: \t /r \t \"l\"")]
[TestCase("[a]: \t /r \t \"l\"\t")]
public void Test(string value)
[TestFixture]
public class TestLinkReferenceDefinition
{
RoundTrip(value);
}
[TestCase(@"[a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase("[a]: /r\n[b]: /r\n")]
[TestCase("[a]: /r\n[b]: /r\n[c] /r\n")]
public void TestMultiple(string value)
{
RoundTrip(value);
}
[TestCase(@"[a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase("[a]:\f/r\f\"l\"")]
[TestCase("[a]:\v/r\v\"l\"")]
public void TestUncommonWhitespace(string value)
{
RoundTrip(value);
}
[TestCase(@"[a]: /r ")]
[TestCase(@" [a]: /r ")]
[TestCase(@" [a]: /r ")]
[TestCase(@" [a]: /r ")]
[TestCase("[a]:\n/r\n\"t\"")]
[TestCase("[a]:\n/r\r\"t\"")]
[TestCase("[a]:\n/r\r\n\"t\"")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l"" ")]
[TestCase(@"[a]: /r ""l""")]
[TestCase(@"[a]: /r ""l"" ")]
[TestCase("[a]:\r/r\n\"t\"")]
[TestCase("[a]:\r/r\r\"t\"")]
[TestCase("[a]:\r/r\r\n\"t\"")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase("[a]:\r\n/r\n\"t\"")]
[TestCase("[a]:\r\n/r\r\"t\"")]
[TestCase("[a]:\r\n/r\r\n\"t\"")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase("[a]:\n/r\n\"t\nt\"")]
[TestCase("[a]:\n/r\n\"t\rt\"")]
[TestCase("[a]:\n/r\n\"t\r\nt\"")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase(@" [a]: /r ""l""")]
[TestCase(@" [a]: /r ""l"" ")]
[TestCase("[a]:\r\n /r\t \n \t \"t\r\nt\" ")]
[TestCase("[a]:\n/r\n\n[a],")]
[TestCase("[a]: /r\n[b]: /r\n\n[a],")]
public void TestNewlines(string value)
{
RoundTrip(value);
}
[TestCase("[a]:\t/r")]
[TestCase("[a]:\t/r\t")]
[TestCase("[a]:\t/r\t\"l\"")]
[TestCase("[a]:\t/r\t\"l\"\t")]
[TestCase("[ a]: /r")]
[TestCase("[a ]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[ a]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[a ]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[a a]: /r")]
[TestCase("[a\va]: /r")]
[TestCase("[a\fa]: /r")]
[TestCase("[a\ta]: /r")]
[TestCase("[\va]: /r")]
[TestCase("[\fa]: /r")]
[TestCase("[\ta]: /r")]
[TestCase(@"[\]]: /r")]
public void TestLabel(string value)
{
RoundTrip(value);
}
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r\t")]
[TestCase("[a]: \t/r\t\"l\"")]
[TestCase("[a]: \t/r\t\"l\"\t")]
[TestCase("[a]: /r ()")]
[TestCase("[a]: /r (t)")]
[TestCase("[a]: /r ( t)")]
[TestCase("[a]: /r (t )")]
[TestCase("[a]: /r ( t )")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r\t")]
[TestCase("[a]:\t /r\t\"l\"")]
[TestCase("[a]:\t /r\t\"l\"\t")]
[TestCase("[a]: /r ''")]
[TestCase("[a]: /r 't'")]
[TestCase("[a]: /r ' t'")]
[TestCase("[a]: /r 't '")]
[TestCase("[a]: /r ' t '")]
public void Test_Title(string value)
{
RoundTrip(value);
}
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r\t")]
[TestCase("[a]: \t /r\t\"l\"")]
[TestCase("[a]: \t /r\t\"l\"\t")]
[TestCase("[a]: /r\n===\n[a]")]
public void TestSetextHeader(string value)
{
RoundTrip(value);
[TestCase("[a]:\t/r \t")]
[TestCase("[a]:\t/r \t\"l\"")]
[TestCase("[a]:\t/r \t\"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r \t")]
[TestCase("[a]: \t/r \t\"l\"")]
[TestCase("[a]: \t/r \t\"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r \t")]
[TestCase("[a]:\t /r \t\"l\"")]
[TestCase("[a]:\t /r \t\"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r \t")]
[TestCase("[a]: \t /r \t\"l\"")]
[TestCase("[a]: \t /r \t\"l\"\t")]
[TestCase("[a]:\t/r\t ")]
[TestCase("[a]:\t/r\t \"l\"")]
[TestCase("[a]:\t/r\t \"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r\t ")]
[TestCase("[a]: \t/r\t \"l\"")]
[TestCase("[a]: \t/r\t \"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r\t ")]
[TestCase("[a]:\t /r\t \"l\"")]
[TestCase("[a]:\t /r\t \"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r\t ")]
[TestCase("[a]: \t /r\t \"l\"")]
[TestCase("[a]: \t /r\t \"l\"\t")]
[TestCase("[a]:\t/r \t ")]
[TestCase("[a]:\t/r \t \"l\"")]
[TestCase("[a]:\t/r \t \"l\"\t")]
[TestCase("[a]: \t/r")]
[TestCase("[a]: \t/r \t ")]
[TestCase("[a]: \t/r \t \"l\"")]
[TestCase("[a]: \t/r \t \"l\"\t")]
[TestCase("[a]:\t /r")]
[TestCase("[a]:\t /r \t ")]
[TestCase("[a]:\t /r \t \"l\"")]
[TestCase("[a]:\t /r \t \"l\"\t")]
[TestCase("[a]: \t /r")]
[TestCase("[a]: \t /r \t ")]
[TestCase("[a]: \t /r \t \"l\"")]
[TestCase("[a]: \t /r \t \"l\"\t")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("[a]: /r\n[b]: /r\n")]
[TestCase("[a]: /r\n[b]: /r\n[c] /r\n")]
public void TestMultiple(string value)
{
RoundTrip(value);
}
[TestCase("[a]:\f/r\f\"l\"")]
[TestCase("[a]:\v/r\v\"l\"")]
public void TestUncommonWhitespace(string value)
{
RoundTrip(value);
}
[TestCase("[a]:\n/r\n\"t\"")]
[TestCase("[a]:\n/r\r\"t\"")]
[TestCase("[a]:\n/r\r\n\"t\"")]
[TestCase("[a]:\r/r\n\"t\"")]
[TestCase("[a]:\r/r\r\"t\"")]
[TestCase("[a]:\r/r\r\n\"t\"")]
[TestCase("[a]:\r\n/r\n\"t\"")]
[TestCase("[a]:\r\n/r\r\"t\"")]
[TestCase("[a]:\r\n/r\r\n\"t\"")]
[TestCase("[a]:\n/r\n\"t\nt\"")]
[TestCase("[a]:\n/r\n\"t\rt\"")]
[TestCase("[a]:\n/r\n\"t\r\nt\"")]
[TestCase("[a]:\r\n /r\t \n \t \"t\r\nt\" ")]
[TestCase("[a]:\n/r\n\n[a],")]
[TestCase("[a]: /r\n[b]: /r\n\n[a],")]
public void TestNewlines(string value)
{
RoundTrip(value);
}
[TestCase("[ a]: /r")]
[TestCase("[a ]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[ a]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[a ]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[ a ]: /r")]
[TestCase("[a a]: /r")]
[TestCase("[a\va]: /r")]
[TestCase("[a\fa]: /r")]
[TestCase("[a\ta]: /r")]
[TestCase("[\va]: /r")]
[TestCase("[\fa]: /r")]
[TestCase("[\ta]: /r")]
[TestCase(@"[\]]: /r")]
public void TestLabel(string value)
{
RoundTrip(value);
}
[TestCase("[a]: /r ()")]
[TestCase("[a]: /r (t)")]
[TestCase("[a]: /r ( t)")]
[TestCase("[a]: /r (t )")]
[TestCase("[a]: /r ( t )")]
[TestCase("[a]: /r ''")]
[TestCase("[a]: /r 't'")]
[TestCase("[a]: /r ' t'")]
[TestCase("[a]: /r 't '")]
[TestCase("[a]: /r ' t '")]
public void Test_Title(string value)
{
RoundTrip(value);
}
[TestCase("[a]: /r\n===\n[a]")]
public void TestSetextHeader(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,21 +1,23 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestNoBlocksFoundBlock
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("\r")]
[TestCase("\n")]
[TestCase("\r\n")]
[TestCase("\t")]
[TestCase("\v")]
[TestCase("\f")]
[TestCase(" ")]
[TestCase(" ")]
[TestCase(" ")]
public void Test(string value)
[TestFixture]
public class TestNoBlocksFoundBlock
{
RoundTrip(value);
[TestCase("\r")]
[TestCase("\n")]
[TestCase("\r\n")]
[TestCase("\t")]
[TestCase("\v")]
[TestCase("\f")]
[TestCase(" ")]
[TestCase(" ")]
[TestCase(" ")]
public void Test(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,189 +1,191 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestOrderedList
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("1. i")]
[TestCase("1. i")]
[TestCase("1. i ")]
[TestCase("1. i ")]
[TestCase("1. i ")]
[TestCase(" 1. i")]
[TestCase(" 1. i")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i")]
[TestCase(" 1. i")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i")]
[TestCase(" 1. i")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase("1. i\n")]
[TestCase("1. i\n")]
[TestCase("1. i \n")]
[TestCase("1. i \n")]
[TestCase("1. i \n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase("1. i\n2. j")]
[TestCase("1. i\n2. j")]
[TestCase("1. i \n2. j")]
[TestCase("1. i \n2. j")]
[TestCase("1. i \n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase("1. i\n2. j\n")]
[TestCase("1. i\n2. j\n")]
[TestCase("1. i \n2. j\n")]
[TestCase("1. i \n2. j\n")]
[TestCase("1. i \n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase("1. i\n2. j\n3. k")]
[TestCase("1. i\n2. j\n3. k\n")]
public void Test(string value)
[TestFixture]
public class TestOrderedList
{
RoundTrip(value);
}
[TestCase("1. i")]
[TestCase("1. i")]
[TestCase("1. i ")]
[TestCase("1. i ")]
[TestCase("1. i ")]
[TestCase("10. i")]
[TestCase("11. i")]
[TestCase("10. i\n12. i")]
[TestCase("2. i\n3. i")]
public void Test_MoreThenOneStart(string value)
{
RoundTrip(value);
}
[TestCase(" 1. i")]
[TestCase(" 1. i")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i")]
[TestCase(" 1. i")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i")]
[TestCase(" 1. i")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase(" 1. i ")]
[TestCase("1. i\n")]
[TestCase("1. i\n")]
[TestCase("1. i \n")]
[TestCase("1. i \n")]
[TestCase("1. i \n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i\n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase(" 1. i \n")]
[TestCase("1. i\n2. j")]
[TestCase("1. i\n2. j")]
[TestCase("1. i \n2. j")]
[TestCase("1. i \n2. j")]
[TestCase("1. i \n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i\n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase(" 1. i \n2. j")]
[TestCase("1. i\n2. j\n")]
[TestCase("1. i\n2. j\n")]
[TestCase("1. i \n2. j\n")]
[TestCase("1. i \n2. j\n")]
[TestCase("1. i \n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i\n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase(" 1. i \n2. j\n")]
[TestCase("1. i\n2. j\n3. k")]
[TestCase("1. i\n2. j\n3. k\n")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("10. i")]
[TestCase("11. i")]
[TestCase("10. i\n12. i")]
[TestCase("2. i\n3. i")]
public void Test_MoreThenOneStart(string value)
{
RoundTrip(value);
}
[TestCase("\n1. i")]
[TestCase("\r1. i")]
[TestCase("\r\n1. i")]
[TestCase("\n1. i")]
[TestCase("\r1. i")]
[TestCase("\r\n1. i")]
[TestCase("\n1. i\n")]
[TestCase("\r1. i\n")]
[TestCase("\r\n1. i\n")]
[TestCase("\n1. i\n")]
[TestCase("\r1. i\n")]
[TestCase("\r\n1. i\n")]
[TestCase("\n1. i\r")]
[TestCase("\r1. i\r")]
[TestCase("\r\n1. i\r")]
[TestCase("\n1. i\r")]
[TestCase("\r1. i\r")]
[TestCase("\r\n1. i\r")]
[TestCase("\n1. i\r\n")]
[TestCase("\r1. i\r\n")]
[TestCase("\r\n1. i\r\n")]
[TestCase("\n1. i\r\n")]
[TestCase("\r1. i\r\n")]
[TestCase("\r\n1. i\r\n")]
[TestCase("1. i\n2. i")]
[TestCase("\n1. i\n2. i")]
[TestCase("\r1. i\n2. i")]
[TestCase("\r\n1. i\n2. i")]
[TestCase("1. i\n2. i")]
[TestCase("\n1. i\n2. i")]
[TestCase("\r1. i\n2. i")]
[TestCase("\r\n1. i\n2. i")]
[TestCase("1. i\r2. i")]
[TestCase("\n1. i\r2. i")]
[TestCase("\r1. i\r2. i")]
[TestCase("\r\n1. i\r2. i")]
[TestCase("1. i\r2. i")]
[TestCase("\n1. i\r2. i")]
[TestCase("\r1. i\r2. i")]
[TestCase("\r\n1. i\r2. i")]
[TestCase("1. i\r\n2. i")]
[TestCase("\n1. i\r\n2. i")]
[TestCase("\r1. i\r\n2. i")]
[TestCase("\r\n1. i\r\n2. i")]
[TestCase("1. i\r\n2. i")]
[TestCase("\n1. i\r\n2. i")]
[TestCase("\r1. i\r\n2. i")]
[TestCase("\r\n1. i\r\n2. i")]
[TestCase("1. i\n2. i\n")]
[TestCase("\n1. i\n2. i\n")]
[TestCase("\r1. i\n2. i\n")]
[TestCase("\r\n1. i\n2. i\n")]
[TestCase("1. i\n2. i\n")]
[TestCase("\n1. i\n2. i\n")]
[TestCase("\r1. i\n2. i\n")]
[TestCase("\r\n1. i\n2. i\n")]
[TestCase("1. i\r2. i\r")]
[TestCase("\n1. i\r2. i\r")]
[TestCase("\r1. i\r2. i\r")]
[TestCase("\r\n1. i\r2. i\r")]
[TestCase("1. i\r2. i\r")]
[TestCase("\n1. i\r2. i\r")]
[TestCase("\r1. i\r2. i\r")]
[TestCase("\r\n1. i\r2. i\r")]
[TestCase("1. i\r\n2. i\r\n")]
[TestCase("\n1. i\r\n2. i\r\n")]
[TestCase("\r1. i\r\n2. i\r\n")]
[TestCase("\r\n1. i\r\n2. i\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase("1. i\r\n2. i\r\n")]
[TestCase("\n1. i\r\n2. i\r\n")]
[TestCase("\r1. i\r\n2. i\r\n")]
[TestCase("\r\n1. i\r\n2. i\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase("1. i\n 1. i")]
[TestCase("1. i\n 1. i\n")]
[TestCase("1. i\n 1. i\n 2. i")]
[TestCase("1. i\n 2. i\n 3. i")]
[TestCase("1. i\n 1. i")]
[TestCase("1. i\n 1. i\n")]
[TestCase("1. i\n 1. i\n 2. i")]
[TestCase("1. i\n 2. i\n 3. i")]
[TestCase("1. i\n\t1. i")]
[TestCase("1. i\n\t1. i\n2. i")]
public void TestMultipleLevels(string value)
{
RoundTrip(value);
}
[TestCase("1. i\n\t1. i")]
[TestCase("1. i\n\t1. i\n2. i")]
public void TestMultipleLevels(string value)
{
RoundTrip(value);
}
[TestCase("1. c")]
[TestCase("1. c")]
public void Test_IndentedCodeBlock(string value)
{
RoundTrip(value);
[TestCase("1. c")]
[TestCase("1. c")]
public void Test_IndentedCodeBlock(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,245 +1,247 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestParagraph
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("p")]
[TestCase(" p")]
[TestCase("p ")]
[TestCase(" p ")]
[TestCase("p\np")]
[TestCase(" p\np")]
[TestCase("p \np")]
[TestCase(" p \np")]
[TestCase("p\n p")]
[TestCase(" p\n p")]
[TestCase("p \n p")]
[TestCase(" p \n p")]
[TestCase("p\np ")]
[TestCase(" p\np ")]
[TestCase("p \np ")]
[TestCase(" p \np ")]
[TestCase("p\n\n p ")]
[TestCase(" p\n\n p ")]
[TestCase("p \n\n p ")]
[TestCase(" p \n\n p ")]
[TestCase("p\n\np")]
[TestCase(" p\n\np")]
[TestCase("p \n\np")]
[TestCase(" p \n\np")]
[TestCase("p\n\n p")]
[TestCase(" p\n\n p")]
[TestCase("p \n\n p")]
[TestCase(" p \n\n p")]
[TestCase("p\n\np ")]
[TestCase(" p\n\np ")]
[TestCase("p \n\np ")]
[TestCase(" p \n\np ")]
[TestCase("p\n\n p ")]
[TestCase(" p\n\n p ")]
[TestCase("p \n\n p ")]
[TestCase(" p \n\n p ")]
[TestCase("\np")]
[TestCase("\n p")]
[TestCase("\np ")]
[TestCase("\n p ")]
[TestCase("\np\np")]
[TestCase("\n p\np")]
[TestCase("\np \np")]
[TestCase("\n p \np")]
[TestCase("\np\n p")]
[TestCase("\n p\n p")]
[TestCase("\np \n p")]
[TestCase("\n p \n p")]
[TestCase("\np\np ")]
[TestCase("\n p\np ")]
[TestCase("\np \np ")]
[TestCase("\n p \np ")]
[TestCase("\np\n\n p ")]
[TestCase("\n p\n\n p ")]
[TestCase("\np \n\n p ")]
[TestCase("\n p \n\n p ")]
[TestCase("\np\n\np")]
[TestCase("\n p\n\np")]
[TestCase("\np \n\np")]
[TestCase("\n p \n\np")]
[TestCase("\np\n\n p")]
[TestCase("\n p\n\n p")]
[TestCase("\np \n\n p")]
[TestCase("\n p \n\n p")]
[TestCase("\np\n\np ")]
[TestCase("\n p\n\np ")]
[TestCase("\np \n\np ")]
[TestCase("\n p \n\np ")]
[TestCase("\np\n\n p ")]
[TestCase("\n p\n\n p ")]
[TestCase("\np \n\n p ")]
[TestCase("\n p \n\n p ")]
[TestCase("p p")]
[TestCase("p\tp")]
[TestCase("p \tp")]
[TestCase("p \t p")]
[TestCase("p \tp")]
// special cases
[TestCase(" p \n\n\n\n p \n\n")]
[TestCase("\n\np")]
[TestCase("p\n")]
[TestCase("p\n\n")]
[TestCase("p\np\n p")]
[TestCase("p\np\n p\n p")]
public void Test(string value)
[TestFixture]
public class TestParagraph
{
RoundTrip(value);
}
[TestCase("p")]
[TestCase(" p")]
[TestCase("p ")]
[TestCase(" p ")]
[TestCase("p\np")]
[TestCase(" p\np")]
[TestCase("p \np")]
[TestCase(" p \np")]
[TestCase("p\n p")]
[TestCase(" p\n p")]
[TestCase("p \n p")]
[TestCase(" p \n p")]
[TestCase("p\np ")]
[TestCase(" p\np ")]
[TestCase("p \np ")]
[TestCase(" p \np ")]
[TestCase("p\n\n p ")]
[TestCase(" p\n\n p ")]
[TestCase("p \n\n p ")]
[TestCase(" p \n\n p ")]
[TestCase("p\n\np")]
[TestCase(" p\n\np")]
[TestCase("p \n\np")]
[TestCase(" p \n\np")]
[TestCase("p\n\n p")]
[TestCase(" p\n\n p")]
[TestCase("p \n\n p")]
[TestCase(" p \n\n p")]
[TestCase("p\n\np ")]
[TestCase(" p\n\np ")]
[TestCase("p \n\np ")]
[TestCase(" p \n\np ")]
[TestCase("p\n\n p ")]
[TestCase(" p\n\n p ")]
[TestCase("p \n\n p ")]
[TestCase(" p \n\n p ")]
[TestCase("\np")]
[TestCase("\n p")]
[TestCase("\np ")]
[TestCase("\n p ")]
[TestCase("\np\np")]
[TestCase("\n p\np")]
[TestCase("\np \np")]
[TestCase("\n p \np")]
[TestCase("\np\n p")]
[TestCase("\n p\n p")]
[TestCase("\np \n p")]
[TestCase("\n p \n p")]
[TestCase("\np\np ")]
[TestCase("\n p\np ")]
[TestCase("\np \np ")]
[TestCase("\n p \np ")]
[TestCase("\np\n\n p ")]
[TestCase("\n p\n\n p ")]
[TestCase("\np \n\n p ")]
[TestCase("\n p \n\n p ")]
[TestCase("\np\n\np")]
[TestCase("\n p\n\np")]
[TestCase("\np \n\np")]
[TestCase("\n p \n\np")]
[TestCase("\np\n\n p")]
[TestCase("\n p\n\n p")]
[TestCase("\np \n\n p")]
[TestCase("\n p \n\n p")]
[TestCase("\np\n\np ")]
[TestCase("\n p\n\np ")]
[TestCase("\np \n\np ")]
[TestCase("\n p \n\np ")]
[TestCase("\np\n\n p ")]
[TestCase("\n p\n\n p ")]
[TestCase("\np \n\n p ")]
[TestCase("\n p \n\n p ")]
[TestCase("p p")]
[TestCase("p\tp")]
[TestCase("p \tp")]
[TestCase("p \t p")]
[TestCase("p \tp")]
// special cases
[TestCase(" p \n\n\n\n p \n\n")]
[TestCase("\n\np")]
[TestCase("p\n")]
[TestCase("p\n\n")]
[TestCase("p\np\n p")]
[TestCase("p\np\n p\n p")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("\n")]
[TestCase("\r\n")]
[TestCase("\r")]
[TestCase("\n")]
[TestCase("\r\n")]
[TestCase("\r")]
[TestCase("p\n")]
[TestCase("p\r")]
[TestCase("p\r\n")]
[TestCase("p\n")]
[TestCase("p\r")]
[TestCase("p\r\n")]
[TestCase("p\np")]
[TestCase("p\rp")]
[TestCase("p\r\np")]
[TestCase("p\np")]
[TestCase("p\rp")]
[TestCase("p\r\np")]
[TestCase("p\np\n")]
[TestCase("p\rp\n")]
[TestCase("p\r\np\n")]
[TestCase("p\np\n")]
[TestCase("p\rp\n")]
[TestCase("p\r\np\n")]
[TestCase("p\np\r")]
[TestCase("p\rp\r")]
[TestCase("p\r\np\r")]
[TestCase("p\np\r")]
[TestCase("p\rp\r")]
[TestCase("p\r\np\r")]
[TestCase("p\np\r\n")]
[TestCase("p\rp\r\n")]
[TestCase("p\r\np\r\n")]
[TestCase("p\np\r\n")]
[TestCase("p\rp\r\n")]
[TestCase("p\r\np\r\n")]
[TestCase("\np\n")]
[TestCase("\np\r")]
[TestCase("\np\r\n")]
[TestCase("\np\n")]
[TestCase("\np\r")]
[TestCase("\np\r\n")]
[TestCase("\np\np")]
[TestCase("\np\rp")]
[TestCase("\np\r\np")]
[TestCase("\np\np")]
[TestCase("\np\rp")]
[TestCase("\np\r\np")]
[TestCase("\np\np\n")]
[TestCase("\np\rp\n")]
[TestCase("\np\r\np\n")]
[TestCase("\np\np\n")]
[TestCase("\np\rp\n")]
[TestCase("\np\r\np\n")]
[TestCase("\np\np\r")]
[TestCase("\np\rp\r")]
[TestCase("\np\r\np\r")]
[TestCase("\np\np\r")]
[TestCase("\np\rp\r")]
[TestCase("\np\r\np\r")]
[TestCase("\np\np\r\n")]
[TestCase("\np\rp\r\n")]
[TestCase("\np\r\np\r\n")]
[TestCase("\np\np\r\n")]
[TestCase("\np\rp\r\n")]
[TestCase("\np\r\np\r\n")]
[TestCase("\rp\n")]
[TestCase("\rp\r")]
[TestCase("\rp\r\n")]
[TestCase("\rp\n")]
[TestCase("\rp\r")]
[TestCase("\rp\r\n")]
[TestCase("\rp\np")]
[TestCase("\rp\rp")]
[TestCase("\rp\r\np")]
[TestCase("\rp\np")]
[TestCase("\rp\rp")]
[TestCase("\rp\r\np")]
[TestCase("\rp\np\n")]
[TestCase("\rp\rp\n")]
[TestCase("\rp\r\np\n")]
[TestCase("\rp\np\n")]
[TestCase("\rp\rp\n")]
[TestCase("\rp\r\np\n")]
[TestCase("\rp\np\r")]
[TestCase("\rp\rp\r")]
[TestCase("\rp\r\np\r")]
[TestCase("\rp\np\r")]
[TestCase("\rp\rp\r")]
[TestCase("\rp\r\np\r")]
[TestCase("\rp\np\r\n")]
[TestCase("\rp\rp\r\n")]
[TestCase("\rp\r\np\r\n")]
[TestCase("\rp\np\r\n")]
[TestCase("\rp\rp\r\n")]
[TestCase("\rp\r\np\r\n")]
[TestCase("\r\np\n")]
[TestCase("\r\np\r")]
[TestCase("\r\np\r\n")]
[TestCase("\r\np\n")]
[TestCase("\r\np\r")]
[TestCase("\r\np\r\n")]
[TestCase("\r\np\np")]
[TestCase("\r\np\rp")]
[TestCase("\r\np\r\np")]
[TestCase("\r\np\np")]
[TestCase("\r\np\rp")]
[TestCase("\r\np\r\np")]
[TestCase("\r\np\np\n")]
[TestCase("\r\np\rp\n")]
[TestCase("\r\np\r\np\n")]
[TestCase("\r\np\np\n")]
[TestCase("\r\np\rp\n")]
[TestCase("\r\np\r\np\n")]
[TestCase("\r\np\np\r")]
[TestCase("\r\np\rp\r")]
[TestCase("\r\np\r\np\r")]
[TestCase("\r\np\np\r")]
[TestCase("\r\np\rp\r")]
[TestCase("\r\np\r\np\r")]
[TestCase("\r\np\np\r\n")]
[TestCase("\r\np\rp\r\n")]
[TestCase("\r\np\r\np\r\n")]
[TestCase("\r\np\np\r\n")]
[TestCase("\r\np\rp\r\n")]
[TestCase("\r\np\r\np\r\n")]
[TestCase("p\n")]
[TestCase("p\n\n")]
[TestCase("p\n\n\n")]
[TestCase("p\n\n\n\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase("p\n")]
[TestCase("p\n\n")]
[TestCase("p\n\n\n")]
[TestCase("p\n\n\n\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase(" \n")]
[TestCase(" \r")]
[TestCase(" \r\n")]
[TestCase(" \n")]
[TestCase(" \r")]
[TestCase(" \r\n")]
[TestCase(" \np")]
[TestCase(" \rp")]
[TestCase(" \r\np")]
[TestCase(" \np")]
[TestCase(" \rp")]
[TestCase(" \r\np")]
[TestCase(" \np")]
[TestCase(" \rp")]
[TestCase(" \r\np")]
[TestCase(" \np")]
[TestCase(" \rp")]
[TestCase(" \r\np")]
[TestCase(" \np")]
[TestCase(" \rp")]
[TestCase(" \r\np")]
[TestCase(" \np")]
[TestCase(" \rp")]
[TestCase(" \r\np")]
[TestCase(" \n ")]
[TestCase(" \r ")]
[TestCase(" \r\n ")]
[TestCase(" \n ")]
[TestCase(" \r ")]
[TestCase(" \r\n ")]
[TestCase(" \np ")]
[TestCase(" \rp ")]
[TestCase(" \r\np ")]
[TestCase(" \np ")]
[TestCase(" \rp ")]
[TestCase(" \r\np ")]
[TestCase(" \np ")]
[TestCase(" \rp ")]
[TestCase(" \r\np ")]
[TestCase(" \np ")]
[TestCase(" \rp ")]
[TestCase(" \r\np ")]
[TestCase(" \np ")]
[TestCase(" \rp ")]
[TestCase(" \r\np ")]
public void Test_WhitespaceWithNewline(string value)
{
RoundTrip(value);
[TestCase(" \np ")]
[TestCase(" \rp ")]
[TestCase(" \r\np ")]
public void Test_WhitespaceWithNewline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,284 +1,286 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestQuoteBlock
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase(">q")]
[TestCase(" >q")]
[TestCase(" >q")]
[TestCase(" >q")]
[TestCase("> q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase("> q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(">q\n>q")]
[TestCase(">q\n >q")]
[TestCase(">q\n >q")]
[TestCase(">q\n >q")]
[TestCase(">q\n> q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(">q\n> q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(" >q\n>q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n>q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase("> q\n>q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase("> q\n>q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(">q\n>q\n>q")]
[TestCase(">q\n>\n>q")]
[TestCase(">q\np\n>q")]
[TestCase(">q\n>\n>\n>q")]
[TestCase(">q\n>\n>\n>\n>q")]
[TestCase(">q\n>\n>q\n>\n>q")]
[TestCase("p\n\n> **q**\n>p\n")]
[TestCase("> q\np\n> q")] // lazy
[TestCase("> q\n> q\np")] // lazy
[TestCase(">>q")]
[TestCase(" > > q")]
[TestCase("> **q**\n>p\n")]
[TestCase("> **q**")]
public void Test(string value)
[TestFixture]
public class TestQuoteBlock
{
RoundTrip(value);
}
[TestCase(">q")]
[TestCase(" >q")]
[TestCase(" >q")]
[TestCase(" >q")]
[TestCase("> q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase("> q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase("> q")] // 5
[TestCase("> q")] // 6
[TestCase(" > q")] //5
[TestCase(" > q")] //6
[TestCase(" > \tq")]
[TestCase("> q\n> q")] // 5, 5
[TestCase("> q\n> q")] // 5, 6
[TestCase("> q\n> q")] // 6, 5
[TestCase("> q\n> q")] // 6, 6
[TestCase("> q\n\n> 5")] // 5, 5
public void TestIndentedCodeBlock(string value)
{
RoundTrip(value);
}
[TestCase(">q\n>q")]
[TestCase(">q\n >q")]
[TestCase(">q\n >q")]
[TestCase(">q\n >q")]
[TestCase(">q\n> q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(">q\n> q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase(">q\n > q")]
[TestCase("\n> q")]
[TestCase("\n> q\n")]
[TestCase("\n> q\n\n")]
[TestCase("> q\n\np")]
[TestCase("p\n\n> q\n\n# h")]
[TestCase(" >q\n>q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
//https://github.com/lunet-io/markdig/issues/480
//[TestCase(">\np")]
//[TestCase(">**b**\n>\n>p\n>\np\n")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase(" >q\n>q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n >q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n> q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase(" >q\n > q")]
[TestCase("> q\n\n# h\n")]
public void TestAtxHeader(string value)
{
RoundTrip(value);
}
[TestCase("> q\n>q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase(">- i")]
[TestCase("> - i")]
[TestCase(">- i\n>- i")]
[TestCase(">- >p")]
[TestCase("> - >p")]
[TestCase(">- i1\n>- i2\n")]
[TestCase("> **p** p\n>- i1\n>- i2\n")]
public void TestUnorderedList(string value)
{
RoundTrip(value);
}
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase("> *q*\n>p\n")]
[TestCase("> *q*")]
public void TestEmphasis(string value)
{
RoundTrip(value);
}
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase("> **q**\n>p\n")]
[TestCase("> **q**")]
public void TestStrongEmphasis(string value)
{
RoundTrip(value);
}
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(">p\n")]
[TestCase(">p\r")]
[TestCase(">p\r\n")]
[TestCase("> q\n>q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n >q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n> q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase("> q\n > q")]
[TestCase(">p\n>p")]
[TestCase(">p\r>p")]
[TestCase(">p\r\n>p")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(">p\n>p\n")]
[TestCase(">p\r>p\n")]
[TestCase(">p\r\n>p\n")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(">p\n>p\r")]
[TestCase(">p\r>p\r")]
[TestCase(">p\r\n>p\r")]
[TestCase(" > q\n>q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n >q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n> q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(" > q\n > q")]
[TestCase(">p\n>p\r\n")]
[TestCase(">p\r>p\r\n")]
[TestCase(">p\r\n>p\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase(">q\n>q\n>q")]
[TestCase(">q\n>\n>q")]
[TestCase(">q\np\n>q")]
[TestCase(">q\n>\n>\n>q")]
[TestCase(">q\n>\n>\n>\n>q")]
[TestCase(">q\n>\n>q\n>\n>q")]
[TestCase("p\n\n> **q**\n>p\n")]
[TestCase(">\n>q")]
[TestCase(">\n>\n>q")]
[TestCase(">q\n>\n>q")]
[TestCase(">q\n>\n>\n>q")]
[TestCase(">q\n> \n>q")]
[TestCase(">q\n> \n>q")]
[TestCase(">q\n> \n>q")]
[TestCase(">q\n>\t\n>q")]
[TestCase(">q\n>\v\n>q")]
[TestCase(">q\n>\f\n>q")]
public void TestEmptyLines(string value)
{
RoundTrip(value);
[TestCase("> q\np\n> q")] // lazy
[TestCase("> q\n> q\np")] // lazy
[TestCase(">>q")]
[TestCase(" > > q")]
[TestCase("> **q**\n>p\n")]
[TestCase("> **q**")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("> q")] // 5
[TestCase("> q")] // 6
[TestCase(" > q")] //5
[TestCase(" > q")] //6
[TestCase(" > \tq")]
[TestCase("> q\n> q")] // 5, 5
[TestCase("> q\n> q")] // 5, 6
[TestCase("> q\n> q")] // 6, 5
[TestCase("> q\n> q")] // 6, 6
[TestCase("> q\n\n> 5")] // 5, 5
public void TestIndentedCodeBlock(string value)
{
RoundTrip(value);
}
[TestCase("\n> q")]
[TestCase("\n> q\n")]
[TestCase("\n> q\n\n")]
[TestCase("> q\n\np")]
[TestCase("p\n\n> q\n\n# h")]
//https://github.com/lunet-io/markdig/issues/480
//[TestCase(">\np")]
//[TestCase(">**b**\n>\n>p\n>\np\n")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase("> q\n\n# h\n")]
public void TestAtxHeader(string value)
{
RoundTrip(value);
}
[TestCase(">- i")]
[TestCase("> - i")]
[TestCase(">- i\n>- i")]
[TestCase(">- >p")]
[TestCase("> - >p")]
[TestCase(">- i1\n>- i2\n")]
[TestCase("> **p** p\n>- i1\n>- i2\n")]
public void TestUnorderedList(string value)
{
RoundTrip(value);
}
[TestCase("> *q*\n>p\n")]
[TestCase("> *q*")]
public void TestEmphasis(string value)
{
RoundTrip(value);
}
[TestCase("> **q**\n>p\n")]
[TestCase("> **q**")]
public void TestStrongEmphasis(string value)
{
RoundTrip(value);
}
[TestCase(">p\n")]
[TestCase(">p\r")]
[TestCase(">p\r\n")]
[TestCase(">p\n>p")]
[TestCase(">p\r>p")]
[TestCase(">p\r\n>p")]
[TestCase(">p\n>p\n")]
[TestCase(">p\r>p\n")]
[TestCase(">p\r\n>p\n")]
[TestCase(">p\n>p\r")]
[TestCase(">p\r>p\r")]
[TestCase(">p\r\n>p\r")]
[TestCase(">p\n>p\r\n")]
[TestCase(">p\r>p\r\n")]
[TestCase(">p\r\n>p\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase(">\n>q")]
[TestCase(">\n>\n>q")]
[TestCase(">q\n>\n>q")]
[TestCase(">q\n>\n>\n>q")]
[TestCase(">q\n> \n>q")]
[TestCase(">q\n> \n>q")]
[TestCase(">q\n> \n>q")]
[TestCase(">q\n>\t\n>q")]
[TestCase(">q\n>\v\n>q")]
[TestCase(">q\n>\f\n>q")]
public void TestEmptyLines(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,51 +1,53 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestSetextHeading
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("h1\n===")] //3
[TestCase("h1\n ===")] //3
[TestCase("h1\n ===")] //3
[TestCase("h1\n ===")] //3
[TestCase("h1\n=== ")] //3
[TestCase("h1 \n===")] //3
[TestCase("h1\\\n===")] //3
[TestCase("h1\n === ")] //3
[TestCase("h1\nh1 l2\n===")] //3
[TestCase("h1\n====")] // 4
[TestCase("h1\n ====")] // 4
[TestCase("h1\n==== ")] // 4
[TestCase("h1\n ==== ")] // 4
[TestCase("h1\n===\nh1\n===")] //3
[TestCase("\\>h1\n===")] //3
public void Test(string value)
[TestFixture]
public class TestSetextHeading
{
RoundTrip(value);
}
[TestCase("h1\n===")] //3
[TestCase("h1\n ===")] //3
[TestCase("h1\n ===")] //3
[TestCase("h1\n ===")] //3
[TestCase("h1\n=== ")] //3
[TestCase("h1 \n===")] //3
[TestCase("h1\\\n===")] //3
[TestCase("h1\n === ")] //3
[TestCase("h1\nh1 l2\n===")] //3
[TestCase("h1\n====")] // 4
[TestCase("h1\n ====")] // 4
[TestCase("h1\n==== ")] // 4
[TestCase("h1\n ==== ")] // 4
[TestCase("h1\n===\nh1\n===")] //3
[TestCase("\\>h1\n===")] //3
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("h1\r===")]
[TestCase("h1\n===")]
[TestCase("h1\r\n===")]
[TestCase("h1\r===")]
[TestCase("h1\n===")]
[TestCase("h1\r\n===")]
[TestCase("h1\r===\r")]
[TestCase("h1\n===\r")]
[TestCase("h1\r\n===\r")]
[TestCase("h1\r===\r")]
[TestCase("h1\n===\r")]
[TestCase("h1\r\n===\r")]
[TestCase("h1\r===\n")]
[TestCase("h1\n===\n")]
[TestCase("h1\r\n===\n")]
[TestCase("h1\r===\n")]
[TestCase("h1\n===\n")]
[TestCase("h1\r\n===\n")]
[TestCase("h1\r===\r\n")]
[TestCase("h1\n===\r\n")]
[TestCase("h1\r\n===\r\n")]
[TestCase("h1\r===\r\n")]
[TestCase("h1\n===\r\n")]
[TestCase("h1\r\n===\r\n")]
[TestCase("h1\n===\n\n\nh2---\n\n")]
[TestCase("h1\r===\r\r\rh2---\r\r")]
[TestCase("h1\r\n===\r\n\r\n\r\nh2---\r\n\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
[TestCase("h1\n===\n\n\nh2---\n\n")]
[TestCase("h1\r===\r\r\rh2---\r\r")]
[TestCase("h1\r\n===\r\n\r\n\r\nh2---\r\n\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,51 +1,53 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestThematicBreak
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("---")]
[TestCase(" ---")]
[TestCase(" ---")]
[TestCase(" ---")]
[TestCase("--- ")]
[TestCase(" --- ")]
[TestCase(" --- ")]
[TestCase(" --- ")]
[TestCase("- - -")]
[TestCase(" - - -")]
[TestCase(" - - - ")]
[TestCase("-- -")]
[TestCase("---\n")]
[TestCase("---\n\n")]
[TestCase("---\np")]
[TestCase("---\n\np")]
[TestCase("---\n# h")]
[TestCase("p\n\n---")]
// Note: "p\n---" is parsed as setext heading
public void Test(string value)
[TestFixture]
public class TestThematicBreak
{
RoundTrip(value);
}
[TestCase("---")]
[TestCase(" ---")]
[TestCase(" ---")]
[TestCase(" ---")]
[TestCase("--- ")]
[TestCase(" --- ")]
[TestCase(" --- ")]
[TestCase(" --- ")]
[TestCase("- - -")]
[TestCase(" - - -")]
[TestCase(" - - - ")]
[TestCase("-- -")]
[TestCase("---\n")]
[TestCase("---\n\n")]
[TestCase("---\np")]
[TestCase("---\n\np")]
[TestCase("---\n# h")]
[TestCase("p\n\n---")]
// Note: "p\n---" is parsed as setext heading
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("\n---")]
[TestCase("\r---")]
[TestCase("\r\n---")]
[TestCase("\n---")]
[TestCase("\r---")]
[TestCase("\r\n---")]
[TestCase("\n---\n")]
[TestCase("\r---\n")]
[TestCase("\r\n---\n")]
[TestCase("\n---\n")]
[TestCase("\r---\n")]
[TestCase("\r\n---\n")]
[TestCase("\n---\r")]
[TestCase("\r---\r")]
[TestCase("\r\n---\r")]
[TestCase("\n---\r")]
[TestCase("\r---\r")]
[TestCase("\r\n---\r")]
[TestCase("\n---\r\n")]
[TestCase("\r---\r\n")]
[TestCase("\r\n---\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
[TestCase("\n---\r\n")]
[TestCase("\r---\r\n")]
[TestCase("\r\n---\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,181 +1,183 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestUnorderedList
namespace Markdig.Tests.RoundtripSpecs
{
// i = item
[TestCase("- i1")]
[TestCase("- i1 ")]
[TestCase("- i1\n")]
[TestCase("- i1\n\n")]
[TestCase("- i1\n- i2")]
[TestCase("- i1\n - i2")]
[TestCase("- i1\n - i1.1\n - i1.2")]
[TestCase("- i1 \n- i2 \n")]
[TestCase("- i1 \n- i2 \n")]
[TestCase(" - i1")]
[TestCase(" - i1")]
[TestCase(" - i1")]
[TestCase("- i1\n\n- i1")]
[TestCase("- i1\n\n\n- i1")]
[TestCase("- i1\n - i1.1\n - i1.1.1\n")]
[TestCase("-\ti1")]
[TestCase("-\ti1\n-\ti2")]
[TestCase("-\ti1\n- i2\n-\ti3")]
public void Test(string value)
[TestFixture]
public class TestUnorderedList
{
RoundTrip(value);
}
// i = item
[TestCase("- i1")]
[TestCase("- i1 ")]
[TestCase("- i1\n")]
[TestCase("- i1\n\n")]
[TestCase("- i1\n- i2")]
[TestCase("- i1\n - i2")]
[TestCase("- i1\n - i1.1\n - i1.2")]
[TestCase("- i1 \n- i2 \n")]
[TestCase("- i1 \n- i2 \n")]
[TestCase(" - i1")]
[TestCase(" - i1")]
[TestCase(" - i1")]
[TestCase("- i1\n\n- i1")]
[TestCase("- i1\n\n\n- i1")]
[TestCase("- i1\n - i1.1\n - i1.1.1\n")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q1\n - > q2")]
public void TestBlockQuote(string value)
{
RoundTrip(value);
}
[TestCase("-\ti1")]
[TestCase("-\ti1\n-\ti2")]
[TestCase("-\ti1\n- i2\n-\ti3")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("- i1\n\np\n")] // TODO: listblock should render newline, apparently last paragraph of last listitem dont have newline
[TestCase("- i1\n\n\np\n")]
[TestCase("- i1\n\np")]
[TestCase("- i1\n\np\n")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase("- > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q")]
[TestCase(" - > q1\n - > q2")]
public void TestBlockQuote(string value)
{
RoundTrip(value);
}
[TestCase("- i1\n\n---\n")]
[TestCase("- i1\n\n\n---\n")]
public void TestThematicBreak(string value)
{
RoundTrip(value);
}
[TestCase("- i1\n\np\n")] // TODO: listblock should render newline, apparently last paragraph of last listitem dont have newline
[TestCase("- i1\n\n\np\n")]
[TestCase("- i1\n\np")]
[TestCase("- i1\n\np\n")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase("- c")] // 5
[TestCase("- c\n c")] // 5, 6
[TestCase(" - c\n c")] // 5, 6
[TestCase(" - c\n c")] // 5, 7
[TestCase("- c\n c")] // 6, 6
[TestCase(" - c\n c")] // 6, 6
[TestCase(" - c\n c")] // 6, 7
public void TestIndentedCodeBlock(string value)
{
RoundTrip(value);
}
[TestCase("- i1\n\n---\n")]
[TestCase("- i1\n\n\n---\n")]
public void TestThematicBreak(string value)
{
RoundTrip(value);
}
[TestCase("- ```a```")]
[TestCase("- ```\n a\n```")]
[TestCase("- i1\n - i1.1\n ```\n c\n ```")]
[TestCase("- i1\n - i1.1\n ```\nc\n```")]
[TestCase("- i1\n - i1.1\n ```\nc\n```\n")]
public void TestFencedCodeBlock(string value)
{
RoundTrip(value);
}
[TestCase("- c")] // 5
[TestCase("- c\n c")] // 5, 6
[TestCase(" - c\n c")] // 5, 6
[TestCase(" - c\n c")] // 5, 7
[TestCase("- c\n c")] // 6, 6
[TestCase(" - c\n c")] // 6, 6
[TestCase(" - c\n c")] // 6, 7
public void TestIndentedCodeBlock(string value)
{
RoundTrip(value);
}
[TestCase("\n- i")]
[TestCase("\r- i")]
[TestCase("\r\n- i")]
[TestCase("- ```a```")]
[TestCase("- ```\n a\n```")]
[TestCase("- i1\n - i1.1\n ```\n c\n ```")]
[TestCase("- i1\n - i1.1\n ```\nc\n```")]
[TestCase("- i1\n - i1.1\n ```\nc\n```\n")]
public void TestFencedCodeBlock(string value)
{
RoundTrip(value);
}
[TestCase("\n- i\n")]
[TestCase("\r- i\n")]
[TestCase("\r\n- i\n")]
[TestCase("\n- i")]
[TestCase("\r- i")]
[TestCase("\r\n- i")]
[TestCase("\n- i\r")]
[TestCase("\r- i\r")]
[TestCase("\r\n- i\r")]
[TestCase("\n- i\n")]
[TestCase("\r- i\n")]
[TestCase("\r\n- i\n")]
[TestCase("\n- i\r\n")]
[TestCase("\r- i\r\n")]
[TestCase("\r\n- i\r\n")]
[TestCase("\n- i\r")]
[TestCase("\r- i\r")]
[TestCase("\r\n- i\r")]
[TestCase("- i\n- j")]
[TestCase("- i\r- j")]
[TestCase("- i\r\n- j")]
[TestCase("\n- i\r\n")]
[TestCase("\r- i\r\n")]
[TestCase("\r\n- i\r\n")]
[TestCase("\n- i\n- j")]
[TestCase("\n- i\r- j")]
[TestCase("\n- i\r\n- j")]
[TestCase("- i\n- j")]
[TestCase("- i\r- j")]
[TestCase("- i\r\n- j")]
[TestCase("\r- i\n- j")]
[TestCase("\r- i\r- j")]
[TestCase("\r- i\r\n- j")]
[TestCase("\n- i\n- j")]
[TestCase("\n- i\r- j")]
[TestCase("\n- i\r\n- j")]
[TestCase("\r\n- i\n- j")]
[TestCase("\r\n- i\r- j")]
[TestCase("\r\n- i\r\n- j")]
[TestCase("\r- i\n- j")]
[TestCase("\r- i\r- j")]
[TestCase("\r- i\r\n- j")]
[TestCase("- i\n- j\n")]
[TestCase("- i\r- j\n")]
[TestCase("- i\r\n- j\n")]
[TestCase("\r\n- i\n- j")]
[TestCase("\r\n- i\r- j")]
[TestCase("\r\n- i\r\n- j")]
[TestCase("\n- i\n- j\n")]
[TestCase("\n- i\r- j\n")]
[TestCase("\n- i\r\n- j\n")]
[TestCase("- i\n- j\n")]
[TestCase("- i\r- j\n")]
[TestCase("- i\r\n- j\n")]
[TestCase("\r- i\n- j\n")]
[TestCase("\r- i\r- j\n")]
[TestCase("\r- i\r\n- j\n")]
[TestCase("\n- i\n- j\n")]
[TestCase("\n- i\r- j\n")]
[TestCase("\n- i\r\n- j\n")]
[TestCase("\r\n- i\n- j\n")]
[TestCase("\r\n- i\r- j\n")]
[TestCase("\r\n- i\r\n- j\n")]
[TestCase("\r- i\n- j\n")]
[TestCase("\r- i\r- j\n")]
[TestCase("\r- i\r\n- j\n")]
[TestCase("- i\n- j\r")]
[TestCase("- i\r- j\r")]
[TestCase("- i\r\n- j\r")]
[TestCase("\r\n- i\n- j\n")]
[TestCase("\r\n- i\r- j\n")]
[TestCase("\r\n- i\r\n- j\n")]
[TestCase("\n- i\n- j\r")]
[TestCase("\n- i\r- j\r")]
[TestCase("\n- i\r\n- j\r")]
[TestCase("- i\n- j\r")]
[TestCase("- i\r- j\r")]
[TestCase("- i\r\n- j\r")]
[TestCase("\r- i\n- j\r")]
[TestCase("\r- i\r- j\r")]
[TestCase("\r- i\r\n- j\r")]
[TestCase("\n- i\n- j\r")]
[TestCase("\n- i\r- j\r")]
[TestCase("\n- i\r\n- j\r")]
[TestCase("\r\n- i\n- j\r")]
[TestCase("\r\n- i\r- j\r")]
[TestCase("\r\n- i\r\n- j\r")]
[TestCase("\r- i\n- j\r")]
[TestCase("\r- i\r- j\r")]
[TestCase("\r- i\r\n- j\r")]
[TestCase("- i\n- j\r\n")]
[TestCase("- i\r- j\r\n")]
[TestCase("- i\r\n- j\r\n")]
[TestCase("\r\n- i\n- j\r")]
[TestCase("\r\n- i\r- j\r")]
[TestCase("\r\n- i\r\n- j\r")]
[TestCase("\n- i\n- j\r\n")]
[TestCase("\n- i\r- j\r\n")]
[TestCase("\n- i\r\n- j\r\n")]
[TestCase("- i\n- j\r\n")]
[TestCase("- i\r- j\r\n")]
[TestCase("- i\r\n- j\r\n")]
[TestCase("\r- i\n- j\r\n")]
[TestCase("\r- i\r- j\r\n")]
[TestCase("\r- i\r\n- j\r\n")]
[TestCase("\n- i\n- j\r\n")]
[TestCase("\n- i\r- j\r\n")]
[TestCase("\n- i\r\n- j\r\n")]
[TestCase("\r\n- i\n- j\r\n")]
[TestCase("\r\n- i\r- j\r\n")]
[TestCase("\r\n- i\r\n- j\r\n")]
[TestCase("\r- i\n- j\r\n")]
[TestCase("\r- i\r- j\r\n")]
[TestCase("\r- i\r\n- j\r\n")]
[TestCase("- i\n")]
[TestCase("- i\n\n")]
[TestCase("- i\n\n\n")]
[TestCase("- i\n\n\n\n")]
public void TestNewline(string value)
{
RoundTrip(value);
[TestCase("\r\n- i\n- j\r\n")]
[TestCase("\r\n- i\r- j\r\n")]
[TestCase("\r\n- i\r\n- j\r\n")]
[TestCase("- i\n")]
[TestCase("- i\n\n")]
[TestCase("- i\n\n\n")]
[TestCase("- i\n\n\n\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -1,15 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestYamlFrontMatterBlock
namespace Markdig.Tests.RoundtripSpecs
{
[TestCase("---\nkey1: value1\nkey2: value2\n---\n\nContent\n")]
[TestCase("No front matter")]
[TestCase("Looks like front matter but actually is not\n---\nkey1: value1\nkey2: value2\n---")]
public void FrontMatterBlockIsPreserved(string value)
[TestFixture]
public class TestYamlFrontMatterBlock
{
RoundTrip(value);
[TestCase("---\nkey1: value1\nkey2: value2\n---\n\nContent\n")]
[TestCase("No front matter")]
[TestCase("Looks like front matter but actually is not\n---\nkey1: value1\nkey2: value2\n---")]
public void FrontMatterBlockIsPreserved(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -17,8 +17,7 @@ namespace Markdig.Tests.Specs.GridTables
//
// ## Grid Table
//
// A grid table allows having multiple lines per cells and allows spanning cells over multiple columns.
// The following shows a simple grid table:
// A grid table allows to have multiple lines per cells and allows to span cells over multiple columns. The following shows a simple grid table
//
// ```
// +---------+---------+
@@ -39,20 +38,17 @@ namespace Markdig.Tests.Specs.GridTables
// ```
//
// **Rule #1**
// The first line of a grid table must be a **row separator**.
// It must start with the column separator character `+` used to separate columns in a row separator.
// The first line of a grid table must a **row separator**. It must start with the column separator character `+` used to separate columns in a row separator. Each column separator is:
// - starting by optional spaces
// - followed by an optional `:` to specify left align, followed by optional spaces
// - followed by a sequence of at least one `-` character, followed by optional spaces
// - followed by an optional `:` to specify right align (or center align if left align is also defined)
// - ending by optional spaces
//
// Each column separator:
// - starts with optional spaces
// - followed by an optional `:` to specify left align, followed by optional spaces
// - followed by a sequence of one or more `-` characters, followed by optional spaces
// - followed by an optional `:` to specify right align (or center align if left align is also defined)
// - ends with optional spaces
// The first row separator must be followed by a *regular row*. A regular row must start with the character `|` that is starting at the same position than the column separator `+` of the first row separator.
//
// The first row separator must be followed by a *regular row*.
// A regular row must start with a `|` character starting at the same position as the column separator `+` of the first row separator.
//
// The following is a valid row separator:
// The following is a valid row separator
[Test]
public void ExtensionsGridTable_Example001()
{
@@ -78,7 +74,7 @@ namespace Markdig.Tests.Specs.GridTables
TestParser.TestSpec("+---------+---------+\n| This is | a table |", "<table>\n<col style=\"width:50%\" />\n<col style=\"width:50%\" />\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced", context: "Example 1\nSection Extensions / Grid Table\n");
}
// The following is not a valid row separator:
// The following is not a valid row separator
[Test]
public void ExtensionsGridTable_Example002()
{
@@ -97,8 +93,7 @@ namespace Markdig.Tests.Specs.GridTables
}
// **Rule #2**
// A regular row can continue a previous regular row when the column separators `|` are positioned at the same position as those of the previous line.
// If they are positioned at the same location, the column may span over multiple columns:
// A regular row can continue a previous regular row when column separator `|` are positioned at the same position than the previous line. If they are positioned at the same location, the column may span over multiple columns:
[Test]
public void ExtensionsGridTable_Example003()
{
@@ -192,13 +187,15 @@ namespace Markdig.Tests.Specs.GridTables
TestParser.TestSpec("+---------+---------+\n| This is | a table with a longer text in the second column", "<table>\n<col style=\"width:50%\" />\n<col style=\"width:50%\" />\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table with a longer text in the second column</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced", context: "Example 5\nSection Extensions / Grid Table\n");
}
// The respective widths of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
// The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
//
// - `----` → 4 characters
// - `--------` → 8 characters
// - `----` → 4 characters
// Total size is : 16
//
// The total size is 16 characters, so the widths would be 4/16 = 25%, 8/16 = 50%, and 4/16 = 25%.
// - `----` -> 4
// - `--------` -> 8
// - `----` -> 4
//
// So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
[Test]
public void ExtensionsGridTable_Example006()
{
@@ -299,7 +296,7 @@ namespace Markdig.Tests.Specs.GridTables
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+---+---+ B +\n| D | E | B |\n+ D +---+---+\n| D | CCCCC |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<tbody>\n<tr>\n<td colspan=\"2\">AAAAA</td>\n<td rowspan=\"2\">B\nB\nB</td>\n</tr>\n<tr>\n<td rowspan=\"2\">D\nD\nD</td>\n<td>E</td>\n</tr>\n<tr>\n<td colspan=\"2\">CCCCC</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced", context: "Example 8\nSection Extensions / Grid Table\n");
}
// A grid table may have cells with both `colspan` and `rowspan`:
// A grid table may have cells with both colspan and rowspan:
[Test]
public void ExtensionsGridTable_Example009()
{

View File

@@ -4,8 +4,7 @@ This section describes the different extensions supported:
## Grid Table
A grid table allows having multiple lines per cells and allows spanning cells over multiple columns.
The following shows a simple grid table:
A grid table allows to have multiple lines per cells and allows to span cells over multiple columns. The following shows a simple grid table
```
+---------+---------+
@@ -26,20 +25,17 @@ The following shows a simple grid table:
```
**Rule #1**
The first line of a grid table must be a **row separator**.
It must start with the column separator character `+` used to separate columns in a row separator.
The first line of a grid table must a **row separator**. It must start with the column separator character `+` used to separate columns in a row separator. Each column separator is:
- starting by optional spaces
- followed by an optional `:` to specify left align, followed by optional spaces
- followed by a sequence of at least one `-` character, followed by optional spaces
- followed by an optional `:` to specify right align (or center align if left align is also defined)
- ending by optional spaces
Each column separator:
- starts with optional spaces
- followed by an optional `:` to specify left align, followed by optional spaces
- followed by a sequence of one or more `-` characters, followed by optional spaces
- followed by an optional `:` to specify right align (or center align if left align is also defined)
- ends with optional spaces
The first row separator must be followed by a *regular row*. A regular row must start with the character `|` that is starting at the same position than the column separator `+` of the first row separator.
The first row separator must be followed by a *regular row*.
A regular row must start with a `|` character starting at the same position as the column separator `+` of the first row separator.
The following is a valid row separator:
The following is a valid row separator
```````````````````````````````` example
+---------+---------+
@@ -57,8 +53,8 @@ The following is a valid row separator:
</table>
````````````````````````````````
The following is not a valid row separator:
The following is not a valid row separator
```````````````````````````````` example
|-----xxx----+---------+
| This is | not a table
@@ -68,8 +64,7 @@ The following is not a valid row separator:
````````````````````````````````
**Rule #2**
A regular row can continue a previous regular row when the column separators `|` are positioned at the same position as those of the previous line.
If they are positioned at the same location, the column may span over multiple columns:
A regular row can continue a previous regular row when column separator `|` are positioned at the same position than the previous line. If they are positioned at the same location, the column may span over multiple columns:
```````````````````````````````` example
+---------+---------+---------+
@@ -139,13 +134,15 @@ The last column separator `|` may be omitted:
</table>
````````````````````````````````
The respective widths of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
- `----` → 4 characters
- `--------` → 8 characters
- `----` → 4 characters
Total size is : 16
The total size is 16 characters, so the widths would be 4/16 = 25%, 8/16 = 50%, and 4/16 = 25%.
- `----` -> 4
- `--------` -> 8
- `----` -> 4
So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
```````````````````````````````` example
+----+--------+----+
@@ -168,6 +165,7 @@ The total size is 16 characters, so the widths would be 4/16 = 25%, 8/16 = 50%,
Alignment might be specified on the first row using the character `:`:
```````````````````````````````` example
+-----+:---:+-----+
| A | B | C |
@@ -222,7 +220,7 @@ D</td>
</table>
````````````````````````````````
A grid table may have cells with both `colspan` and `rowspan`:
A grid table may have cells with both colspan and rowspan:
```````````````````````````````` example
+---+---+---+
@@ -278,6 +276,7 @@ A grid table may not have irregularly shaped cells:
An empty `+` on a line should result in a simple empty list output:
```````````````````````````````` example
+
.

View File

@@ -1,90 +0,0 @@
using System.Globalization;
using Markdig.Helpers;
namespace Markdig.Tests;
public class TestCharHelper
{
// An ASCII punctuation character is
// !, ", #, $, %, &, ', (, ), *, +, ,, -, ., / (U+00212F),
// :, ;, <, =, >, ?, @ (U+003A0040),
// [, \, ], ^, _, ` (U+005B0060),
// {, |, }, or ~ (U+007B007E).
private static readonly HashSet<char> s_asciiPunctuation = new()
{
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
':', ';', '<', '=', '>', '?', '@',
'[', '\\', ']', '^', '_', '`',
'{', '|', '}', '~'
};
// A Unicode punctuation character is an ASCII punctuation character or anything in the general Unicode categories
// Pc, Pd, Pe, Pf, Pi, Po, or Ps.
private static readonly HashSet<UnicodeCategory> s_punctuationCategories = new()
{
UnicodeCategory.ConnectorPunctuation,
UnicodeCategory.DashPunctuation,
UnicodeCategory.ClosePunctuation,
UnicodeCategory.FinalQuotePunctuation,
UnicodeCategory.InitialQuotePunctuation,
UnicodeCategory.OtherPunctuation,
UnicodeCategory.OpenPunctuation
};
private static bool ExpectedIsPunctuation(char c)
{
return c <= 127
? s_asciiPunctuation.Contains(c)
: s_punctuationCategories.Contains(CharUnicodeInfo.GetUnicodeCategory(c));
}
private static bool ExpectedIsWhitespace(char c)
{
// A Unicode whitespace character is any code point in the Unicode Zs general category,
// or a tab (U+0009), line feed (U+000A), form feed (U+000C), or carriage return (U+000D).
return c == '\t' || c == '\n' || c == '\u000C' || c == '\r' ||
CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator;
}
[Test]
public void IsWhitespace()
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
char c = (char)i;
Assert.AreEqual(ExpectedIsWhitespace(c), CharHelper.IsWhitespace(c));
}
}
[Test]
public void CheckUnicodeCategory()
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
char c = (char)i;
bool expectedSpace = c == 0 || ExpectedIsWhitespace(c);
bool expectedPunctuation = c == 0 || ExpectedIsPunctuation(c);
CharHelper.CheckUnicodeCategory(c, out bool spaceActual, out bool punctuationActual);
Assert.AreEqual(expectedSpace, spaceActual);
Assert.AreEqual(expectedPunctuation, punctuationActual);
}
}
[Test]
public void IsSpaceOrPunctuation()
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
char c = (char)i;
bool expected = c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuation(c);
Assert.AreEqual(expected, CharHelper.IsSpaceOrPunctuation(c));
}
}
}

View File

@@ -1,39 +1,42 @@
namespace Markdig.Tests;
using NUnit.Framework;
[TestFixture]
public class TestConfigureNewLine
namespace Markdig.Tests
{
[Test]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>\n<em>2</em></p>\n")]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>\n<em>2</em></p>\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>\r\n<em>2</em></p>\r\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>\r\n<em>2</em></p>\r\n")]
[TestCase(/* newLineForWriting: */ "!!!" , /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>!!!<em>2</em></p>!!!")]
[TestCase(/* newLineForWriting: */ "!!!" , /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>!!!<em>2</em></p>!!!")]
public void TestHtmlOutputWhenConfiguringNewLine(string newLineForWriting, string markdownText, string expected)
[TestFixture]
public class TestConfigureNewLine
{
var pipeline = new MarkdownPipelineBuilder()
.ConfigureNewLine(newLineForWriting)
.Build();
[Test]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>\n<em>2</em></p>\n")]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>\n<em>2</em></p>\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>\r\n<em>2</em></p>\r\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>\r\n<em>2</em></p>\r\n")]
[TestCase(/* newLineForWriting: */ "!!!" , /* markdownText: */ "*1*\n*2*\n", /* expected: */ "<p><em>1</em>!!!<em>2</em></p>!!!")]
[TestCase(/* newLineForWriting: */ "!!!" , /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "<p><em>1</em>!!!<em>2</em></p>!!!")]
public void TestHtmlOutputWhenConfiguringNewLine(string newLineForWriting, string markdownText, string expected)
{
var pipeline = new MarkdownPipelineBuilder()
.ConfigureNewLine(newLineForWriting)
.Build();
var actual = Markdown.ToHtml(markdownText, pipeline);
Assert.AreEqual(expected, actual);
}
var actual = Markdown.ToHtml(markdownText, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "1\n2\n")]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "1\n2\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "1\r\n2\r\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "1\r\n2\r\n")]
[TestCase(/* newLineForWriting: */ "!!!", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "1!!!2!!!")]
[TestCase(/* newLineForWriting: */ "!!!", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "1!!!2!!!")]
public void TestPlainOutputWhenConfiguringNewLine(string newLineForWriting, string markdownText, string expected)
{
var pipeline = new MarkdownPipelineBuilder()
.ConfigureNewLine(newLineForWriting)
.Build();
[Test]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "1\n2\n")]
[TestCase(/* newLineForWriting: */ "\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "1\n2\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "1\r\n2\r\n")]
[TestCase(/* newLineForWriting: */ "\r\n", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "1\r\n2\r\n")]
[TestCase(/* newLineForWriting: */ "!!!", /* markdownText: */ "*1*\n*2*\n", /* expected: */ "1!!!2!!!")]
[TestCase(/* newLineForWriting: */ "!!!", /* markdownText: */ "*1*\r\n*2*\r\n", /* expected: */ "1!!!2!!!")]
public void TestPlainOutputWhenConfiguringNewLine(string newLineForWriting, string markdownText, string expected)
{
var pipeline = new MarkdownPipelineBuilder()
.ConfigureNewLine(newLineForWriting)
.Build();
var actual = Markdown.ToPlainText(markdownText, pipeline);
Assert.AreEqual(expected, actual);
var actual = Markdown.ToPlainText(markdownText, pipeline);
Assert.AreEqual(expected, actual);
}
}
}

View File

@@ -1,186 +1,189 @@
using System;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestContainerBlocks
namespace Markdig.Tests
{
private class MockContainerBlock : ContainerBlock
public class TestContainerBlocks
{
public MockContainerBlock()
: base(null)
private class MockContainerBlock : ContainerBlock
{
public MockContainerBlock()
: base(null)
{
}
}
[Test]
public void CanBeCleared()
{
ContainerBlock container = new MockContainerBlock();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
var paragraph = new ParagraphBlock();
Assert.Null(paragraph.Parent);
container.Add(paragraph);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container, paragraph.Parent);
Assert.AreSame(paragraph, container.LastChild);
container.Clear();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
Assert.Null(paragraph.Parent);
}
[Test]
public void CanBeInsertedInto()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container, one.Parent);
var two = new ParagraphBlock();
container.Insert(1, two);
Assert.AreEqual(2, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container[1], two);
Assert.AreSame(container, two.Parent);
var three = new ParagraphBlock();
container.Insert(0, three);
Assert.AreEqual(3, container.Count);
Assert.AreSame(container[0], three);
Assert.AreSame(container[1], one);
Assert.AreSame(container[2], two);
Assert.AreSame(container, three.Parent);
Assert.Throws<ArgumentNullException>(() => container.Insert(0, null));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(4, new ParagraphBlock()));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(-1, new ParagraphBlock()));
Assert.Throws<ArgumentException>(() => container.Insert(0, one)); // one already has a parent
}
[Test]
public void CanBeSet()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container, one.Parent);
var two = new ParagraphBlock();
container[0] = two;
Assert.AreSame(container, two.Parent);
Assert.Null(one.Parent);
Assert.Throws<ArgumentException>(() => container[0] = two); // two already has a parent
}
[Test]
public void Contains()
{
var container = new MockContainerBlock();
var block = new ParagraphBlock();
Assert.False(container.Contains(block));
container.Add(block);
Assert.True(container.Contains(block));
container.Add(new ParagraphBlock());
Assert.True(container.Contains(block));
container.Insert(0, new ParagraphBlock());
Assert.True(container.Contains(block));
}
[Test]
public void Remove()
{
var container = new MockContainerBlock();
var block = new ParagraphBlock();
Assert.False(container.Remove(block));
Assert.AreEqual(0, container.Count);
Assert.Throws<ArgumentOutOfRangeException>(() => container.RemoveAt(0));
Assert.AreEqual(0, container.Count);
container.Add(block);
Assert.AreEqual(1, container.Count);
Assert.True(container.Remove(block));
Assert.AreEqual(0, container.Count);
Assert.False(container.Remove(block));
Assert.AreEqual(0, container.Count);
container.Add(block);
Assert.AreEqual(1, container.Count);
container.RemoveAt(0);
Assert.AreEqual(0, container.Count);
Assert.Throws<ArgumentOutOfRangeException>(() => container.RemoveAt(0));
Assert.AreEqual(0, container.Count);
container.Add(new ParagraphBlock { Column = 1 });
container.Add(new ParagraphBlock { Column = 2 });
container.Add(new ParagraphBlock { Column = 3 });
container.Add(new ParagraphBlock { Column = 4 });
Assert.AreEqual(4, container.Count);
container.RemoveAt(2);
Assert.AreEqual(3, container.Count);
Assert.AreEqual(4, container[2].Column);
Assert.True(container.Remove(container[1]));
Assert.AreEqual(2, container.Count);
Assert.AreEqual(1, container[0].Column);
Assert.AreEqual(4, container[1].Column);
Assert.Throws<IndexOutOfRangeException>(() => _ = container[2]);
}
[Test]
public void CopyTo()
{
var container = new MockContainerBlock();
var destination = new Block[4];
container.CopyTo(destination, 0);
container.CopyTo(destination, 1);
container.CopyTo(destination, -1);
container.CopyTo(destination, 5);
Assert.Null(destination[0]);
container.Add(new ParagraphBlock());
container.CopyTo(destination, 0);
Assert.NotNull(destination[0]);
Assert.Null(destination[1]);
Assert.Null(destination[2]);
Assert.Null(destination[3]);
container.CopyTo(destination, 2);
Assert.NotNull(destination[0]);
Assert.Null(destination[1]);
Assert.NotNull(destination[2]);
Assert.Null(destination[3]);
Array.Clear(destination);
container.Add(new ParagraphBlock());
container.CopyTo(destination, 1);
Assert.Null(destination[0]);
Assert.NotNull(destination[1]);
Assert.NotNull(destination[2]);
Assert.Null(destination[3]);
Assert.Throws<IndexOutOfRangeException>(() => container.CopyTo(destination, 3));
}
}
[Test]
public void CanBeCleared()
{
ContainerBlock container = new MockContainerBlock();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
var paragraph = new ParagraphBlock();
Assert.Null(paragraph.Parent);
container.Add(paragraph);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container, paragraph.Parent);
Assert.AreSame(paragraph, container.LastChild);
container.Clear();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
Assert.Null(paragraph.Parent);
}
[Test]
public void CanBeInsertedInto()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container, one.Parent);
var two = new ParagraphBlock();
container.Insert(1, two);
Assert.AreEqual(2, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container[1], two);
Assert.AreSame(container, two.Parent);
var three = new ParagraphBlock();
container.Insert(0, three);
Assert.AreEqual(3, container.Count);
Assert.AreSame(container[0], three);
Assert.AreSame(container[1], one);
Assert.AreSame(container[2], two);
Assert.AreSame(container, three.Parent);
Assert.Throws<ArgumentNullException>(() => container.Insert(0, null));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(4, new ParagraphBlock()));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(-1, new ParagraphBlock()));
Assert.Throws<ArgumentException>(() => container.Insert(0, one)); // one already has a parent
}
[Test]
public void CanBeSet()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container, one.Parent);
var two = new ParagraphBlock();
container[0] = two;
Assert.AreSame(container, two.Parent);
Assert.Null(one.Parent);
Assert.Throws<ArgumentException>(() => container[0] = two); // two already has a parent
}
[Test]
public void Contains()
{
var container = new MockContainerBlock();
var block = new ParagraphBlock();
Assert.False(container.Contains(block));
container.Add(block);
Assert.True(container.Contains(block));
container.Add(new ParagraphBlock());
Assert.True(container.Contains(block));
container.Insert(0, new ParagraphBlock());
Assert.True(container.Contains(block));
}
[Test]
public void Remove()
{
var container = new MockContainerBlock();
var block = new ParagraphBlock();
Assert.False(container.Remove(block));
Assert.AreEqual(0, container.Count);
Assert.Throws<ArgumentOutOfRangeException>(() => container.RemoveAt(0));
Assert.AreEqual(0, container.Count);
container.Add(block);
Assert.AreEqual(1, container.Count);
Assert.True(container.Remove(block));
Assert.AreEqual(0, container.Count);
Assert.False(container.Remove(block));
Assert.AreEqual(0, container.Count);
container.Add(block);
Assert.AreEqual(1, container.Count);
container.RemoveAt(0);
Assert.AreEqual(0, container.Count);
Assert.Throws<ArgumentOutOfRangeException>(() => container.RemoveAt(0));
Assert.AreEqual(0, container.Count);
container.Add(new ParagraphBlock { Column = 1 });
container.Add(new ParagraphBlock { Column = 2 });
container.Add(new ParagraphBlock { Column = 3 });
container.Add(new ParagraphBlock { Column = 4 });
Assert.AreEqual(4, container.Count);
container.RemoveAt(2);
Assert.AreEqual(3, container.Count);
Assert.AreEqual(4, container[2].Column);
Assert.True(container.Remove(container[1]));
Assert.AreEqual(2, container.Count);
Assert.AreEqual(1, container[0].Column);
Assert.AreEqual(4, container[1].Column);
Assert.Throws<IndexOutOfRangeException>(() => _ = container[2]);
}
[Test]
public void CopyTo()
{
var container = new MockContainerBlock();
var destination = new Block[4];
container.CopyTo(destination, 0);
container.CopyTo(destination, 1);
container.CopyTo(destination, -1);
container.CopyTo(destination, 5);
Assert.Null(destination[0]);
container.Add(new ParagraphBlock());
container.CopyTo(destination, 0);
Assert.NotNull(destination[0]);
Assert.Null(destination[1]);
Assert.Null(destination[2]);
Assert.Null(destination[3]);
container.CopyTo(destination, 2);
Assert.NotNull(destination[0]);
Assert.Null(destination[1]);
Assert.NotNull(destination[2]);
Assert.Null(destination[3]);
Array.Clear(destination);
container.Add(new ParagraphBlock());
container.CopyTo(destination, 1);
Assert.Null(destination[0]);
Assert.NotNull(destination[1]);
Assert.NotNull(destination[2]);
Assert.Null(destination[3]);
Assert.Throws<IndexOutOfRangeException>(() => container.CopyTo(destination, 3));
}
}

View File

@@ -1,38 +1,41 @@
using System;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestContainerInlines
namespace Markdig.Tests
{
private class MockLeafBlock : LeafBlock
public class TestContainerInlines
{
public MockLeafBlock()
: base(null)
private class MockLeafBlock : LeafBlock
{
public MockLeafBlock()
: base(null)
{
}
}
[Test]
public void CanBeAddedToLeafBlock()
{
var leafBlock1 = new MockLeafBlock();
var one = new ContainerInline();
Assert.Null(one.ParentBlock);
leafBlock1.Inline = one;
Assert.AreSame(leafBlock1, one.ParentBlock);
var two = new ContainerInline();
Assert.Null(two.ParentBlock);
leafBlock1.Inline = two;
Assert.AreSame(leafBlock1, two.ParentBlock);
Assert.Null(one.ParentBlock);
var leafBlock2 = new MockLeafBlock();
Assert.Throws<ArgumentException>(() => leafBlock2.Inline = two);
}
}
[Test]
public void CanBeAddedToLeafBlock()
{
var leafBlock1 = new MockLeafBlock();
var one = new ContainerInline();
Assert.Null(one.ParentBlock);
leafBlock1.Inline = one;
Assert.AreSame(leafBlock1, one.ParentBlock);
var two = new ContainerInline();
Assert.Null(two.ParentBlock);
leafBlock1.Inline = two;
Assert.AreSame(leafBlock1, two.ParentBlock);
Assert.Null(one.ParentBlock);
var leafBlock2 = new MockLeafBlock();
Assert.Throws<ArgumentException>(() => leafBlock2.Inline = two);
}
}

View File

@@ -1,124 +1,128 @@
using System;
using System.Collections.Generic;
using Markdig.Extensions.Emoji;
using NUnit.Framework;
namespace Markdig.Tests;
[TestFixture]
public class TestCustomEmojis
namespace Markdig.Tests
{
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomEmoji(string input, string expected)
[TestFixture]
public class TestCustomEmojis
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomEmoji(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
emojiToUnicode[":smiley:"] = "♥";
emojiToUnicode[":smiley:"] = "♥";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomSmiley(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomSmiley(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":)", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":)", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
emojiToUnicode[":smiley:"] = "♥";
emojiToUnicode[":smiley:"] = "♥";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomSmiley(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomSmiley(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
public void TestCustomEmojiValidation()
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
[Test]
public void TestCustomEmojiValidation()
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(null, smileyToEmoji));
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(emojiToUnicode, null));
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(null, smileyToEmoji));
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(emojiToUnicode, null));
emojiToUnicode.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
emojiToUnicode.Clear();
emojiToUnicode.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
emojiToUnicode.Clear();
smileyToEmoji.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
smileyToEmoji.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
smileyToEmoji.Add("foo", "something-that-does-not-exist-in-emojiToUnicode");
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
smileyToEmoji.Add("foo", "something-that-does-not-exist-in-emojiToUnicode");
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
emojiToUnicode.Add("a", "aaa");
emojiToUnicode.Add("b", "bbb");
emojiToUnicode.Add("c", "ccc");
smileyToEmoji.Add("a", "c"); // "a" already exists in emojiToUnicode
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
emojiToUnicode.Add("a", "aaa");
emojiToUnicode.Add("b", "bbb");
emojiToUnicode.Add("c", "ccc");
smileyToEmoji.Add("a", "c"); // "a" already exists in emojiToUnicode
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
}
}
}

View File

@@ -1,145 +1,150 @@
using NUnit.Framework;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Linq;
using System.Collections.Generic;
namespace Markdig.Tests;
[TestFixture]
public class TestDescendantsOrder
namespace Markdig.Tests
{
public static void TestSchemas(MarkdownDocument[] specsSyntaxTrees)
[TestFixture]
public class TestDescendantsOrder
{
foreach (var syntaxTree in specsSyntaxTrees)
public static void TestSchemas(MarkdownDocument[] specsSyntaxTrees)
{
AssertIEnumerablesAreEqual(
Descendants_Legacy(syntaxTree),
syntaxTree.Descendants());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
syntaxTree.Descendants<ParagraphBlock>());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
(syntaxTree as ContainerBlock).Descendants<ParagraphBlock>());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<LiteralInline>(),
syntaxTree.Descendants<LiteralInline>());
foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
{
Assert.AreSame(Array.Empty<ListBlock>(), literalInline.Descendants<ListBlock>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), literalInline.Descendants<ParagraphBlock>());
Assert.AreSame(Array.Empty<ContainerInline>(), literalInline.Descendants<ContainerInline>());
}
foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
foreach (var syntaxTree in specsSyntaxTrees)
{
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());
Descendants_Legacy(syntaxTree),
syntaxTree.Descendants());
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
(containerInline as MarkdownObject).Descendants<LiteralInline>());
syntaxTree.Descendants().OfType<ParagraphBlock>(),
syntaxTree.Descendants<ParagraphBlock>());
if (containerInline.FirstChild is null)
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
(syntaxTree as ContainerBlock).Descendants<ParagraphBlock>());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<LiteralInline>(),
syntaxTree.Descendants<LiteralInline>());
foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
{
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerInline as MarkdownObject).Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper.Empty<ListBlock>(), literalInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper.Empty<ParagraphBlock>(), literalInline.Descendants<ParagraphBlock>());
Assert.AreSame(ArrayHelper.Empty<ContainerInline>(), literalInline.Descendants<ContainerInline>());
}
Assert.AreSame(Array.Empty<ListBlock>(), containerInline.Descendants<ListBlock>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), containerInline.Descendants<ParagraphBlock>());
}
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
{
AssertIEnumerablesAreEqual(
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
paragraphBlock.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), paragraphBlock.Descendants<ParagraphBlock>());
}
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
{
AssertIEnumerablesAreEqual(
containerBlock.Descendants<LiteralInline>(),
(containerBlock as MarkdownObject).Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
containerBlock.Descendants<ParagraphBlock>(),
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());
if (containerBlock.Count == 0)
foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
{
Assert.AreSame(Array.Empty<LiteralInline>(), containerBlock.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerBlock as Block).Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerBlock as MarkdownObject).Descendants<LiteralInline>());
}
}
}
}
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
{
var firstList = new List<T>(first);
var secondList = new List<T>(second);
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
(containerInline as MarkdownObject).Descendants<LiteralInline>());
Assert.AreEqual(firstList.Count, secondList.Count);
for (int i = 0; i < firstList.Count; i++)
{
Assert.AreSame(firstList[i], secondList[i]);
}
}
private static IEnumerable<MarkdownObject> Descendants_Legacy(MarkdownObject markdownObject)
{
// TODO: implement a recursiveless method
var block = markdownObject as ContainerBlock;
if (block != null)
{
foreach (var subBlock in block)
{
yield return subBlock;
foreach (var sub in Descendants_Legacy(subBlock))
{
yield return sub;
}
// Visit leaf block that have inlines
var leafBlock = subBlock as LeafBlock;
if (leafBlock?.Inline != null)
{
foreach (var subInline in Descendants_Legacy(leafBlock.Inline))
if (containerInline.FirstChild is null)
{
yield return subInline;
Assert.AreSame(ArrayHelper.Empty<LiteralInline>(), containerInline.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper.Empty<LiteralInline>(), containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(ArrayHelper.Empty<LiteralInline>(), (containerInline as MarkdownObject).Descendants<LiteralInline>());
}
Assert.AreSame(ArrayHelper.Empty<ListBlock>(), containerInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper.Empty<ParagraphBlock>(), containerInline.Descendants<ParagraphBlock>());
}
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
{
AssertIEnumerablesAreEqual(
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
paragraphBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper.Empty<ParagraphBlock>(), paragraphBlock.Descendants<ParagraphBlock>());
}
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
{
AssertIEnumerablesAreEqual(
containerBlock.Descendants<LiteralInline>(),
(containerBlock as MarkdownObject).Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
containerBlock.Descendants<ParagraphBlock>(),
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());
if (containerBlock.Count == 0)
{
Assert.AreSame(ArrayHelper.Empty<LiteralInline>(), containerBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper.Empty<LiteralInline>(), (containerBlock as Block).Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper.Empty<LiteralInline>(), (containerBlock as MarkdownObject).Descendants<LiteralInline>());
}
}
}
}
else
{
var inline = markdownObject as ContainerInline;
if (inline != null)
{
var child = inline.FirstChild;
while (child != null)
{
var next = child.NextSibling;
yield return child;
foreach (var sub in Descendants_Legacy(child))
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
{
var firstList = new List<T>(first);
var secondList = new List<T>(second);
Assert.AreEqual(firstList.Count, secondList.Count);
for (int i = 0; i < firstList.Count; i++)
{
Assert.AreSame(firstList[i], secondList[i]);
}
}
private static IEnumerable<MarkdownObject> Descendants_Legacy(MarkdownObject markdownObject)
{
// TODO: implement a recursiveless method
var block = markdownObject as ContainerBlock;
if (block != null)
{
foreach (var subBlock in block)
{
yield return subBlock;
foreach (var sub in Descendants_Legacy(subBlock))
{
yield return sub;
}
child = next;
// Visit leaf block that have inlines
var leafBlock = subBlock as LeafBlock;
if (leafBlock?.Inline != null)
{
foreach (var subInline in Descendants_Legacy(leafBlock.Inline))
{
yield return subInline;
}
}
}
}
else
{
var inline = markdownObject as ContainerInline;
if (inline != null)
{
var child = inline.FirstChild;
while (child != null)
{
var next = child.NextSibling;
yield return child;
foreach (var sub in Descendants_Legacy(child))
{
yield return sub;
}
child = next;
}
}
}
}

View File

@@ -1,166 +1,169 @@
using System.Diagnostics;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Markdig.Tests;
[TestFixture]
public class TestEmphasisExtended
namespace Markdig.Tests
{
class EmphasisTestExtension : IMarkdownExtension
[TestFixture]
public class TestEmphasisExtended
{
public void Setup(MarkdownPipelineBuilder pipeline)
class EmphasisTestExtension : IMarkdownExtension
{
var emphasisParser = pipeline.InlineParsers.Find<EmphasisInlineParser>();
Debug.Assert(emphasisParser != null);
foreach (var emphasis in EmphasisTestDescriptors)
public void Setup(MarkdownPipelineBuilder pipeline)
{
emphasisParser.EmphasisDescriptors.Add(
new EmphasisDescriptor(emphasis.Character, emphasis.Minimum, emphasis.Maximum, true));
var emphasisParser = pipeline.InlineParsers.Find<EmphasisInlineParser>();
Debug.Assert(emphasisParser != null);
foreach (var emphasis in EmphasisTestDescriptors)
{
emphasisParser.EmphasisDescriptors.Add(
new EmphasisDescriptor(emphasis.Character, emphasis.Minimum, emphasis.Maximum, true));
}
emphasisParser.TryCreateEmphasisInlineList.Add((delimiterChar, delimiterCount) =>
{
return delimiterChar == '*' || delimiterChar == '_'
? null
: new CustomEmphasisInline() { DelimiterChar = delimiterChar, DelimiterCount = delimiterCount };
});
}
emphasisParser.TryCreateEmphasisInlineList.Add((delimiterChar, delimiterCount) =>
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
return delimiterChar is '*' or '_'
? null
: new CustomEmphasisInline() { DelimiterChar = delimiterChar, DelimiterCount = delimiterCount };
});
}
renderer.ObjectRenderers.Insert(0, new EmphasisRenderer());
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
renderer.ObjectRenderers.Insert(0, new EmphasisRenderer());
}
class EmphasisRenderer : HtmlObjectRenderer<CustomEmphasisInline>
{
protected override void Write(HtmlRenderer renderer, CustomEmphasisInline obj)
class EmphasisRenderer : HtmlObjectRenderer<CustomEmphasisInline>
{
var tag = EmphasisTestDescriptors.First(test => test.Character == obj.DelimiterChar).Tags[obj.DelimiterCount];
protected override void Write(HtmlRenderer renderer, CustomEmphasisInline obj)
{
var tag = EmphasisTestDescriptors.First(test => test.Character == obj.DelimiterChar).Tags[obj.DelimiterCount];
renderer.Write(tag.OpeningTag);
renderer.WriteChildren(obj);
renderer.Write(tag.ClosingTag);
renderer.Write(tag.OpeningTag);
renderer.WriteChildren(obj);
renderer.Write(tag.ClosingTag);
}
}
}
}
class Tag
{
class Tag
{
#pragma warning disable CS0649
public int Level;
public int Level;
#pragma warning restore CS0649
public string RawTag;
public string OpeningTag;
public string ClosingTag;
public string RawTag;
public string OpeningTag;
public string ClosingTag;
public Tag(string tag)
{
RawTag = tag;
OpeningTag = "<" + tag + ">";
ClosingTag = "</" + tag + ">";
}
public static implicit operator Tag(string tag)
=> new Tag(tag);
}
class EmphasisTestDescriptor
{
public char Character;
public int Minimum;
public int Maximum;
public Dictionary<int, Tag> Tags = new Dictionary<int, Tag>();
private EmphasisTestDescriptor(char character, int min, int max)
{
Character = character;
Minimum = min;
Maximum = max;
}
public EmphasisTestDescriptor(char character, int min, int max, params Tag[] tags)
: this(character, min, max)
{
Debug.Assert(tags.Length == max - min + 1);
foreach (var tag in tags)
public Tag(string tag)
{
Tags.Add(min++, tag);
RawTag = tag;
OpeningTag = "<" + tag + ">";
ClosingTag = "</" + tag + ">";
}
public static implicit operator Tag(string tag)
=> new Tag(tag);
}
public EmphasisTestDescriptor(char character, int min, int max, string tag)
: this(character, min, max, new Tag(tag)) { }
}
class CustomEmphasisInline : EmphasisInline { }
static readonly EmphasisTestDescriptor[] EmphasisTestDescriptors = new[]
{
// Min Max
new EmphasisTestDescriptor('"', 1, 1, "quotation"),
new EmphasisTestDescriptor(',', 1, 2, "comma", "extra-comma"),
new EmphasisTestDescriptor('!', 2, 3, "warning", "error"),
new EmphasisTestDescriptor('=', 1, 3, "equal", "really-equal", "congruent"),
new EmphasisTestDescriptor('1', 1, 1, "one-only"),
new EmphasisTestDescriptor('2', 2, 2, "two-only"),
new EmphasisTestDescriptor('3', 3, 3, "three-only"),
};
class EmphasisTestDescriptor
{
public char Character;
public int Minimum;
public int Maximum;
public Dictionary<int, Tag> Tags = new Dictionary<int, Tag>();
static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder().Use<EmphasisTestExtension>().Build();
private EmphasisTestDescriptor(char character, int min, int max)
{
Character = character;
Minimum = min;
Maximum = max;
}
public EmphasisTestDescriptor(char character, int min, int max, params Tag[] tags)
: this(character, min, max)
{
Debug.Assert(tags.Length == max - min + 1);
foreach (var tag in tags)
{
Tags.Add(min++, tag);
}
}
public EmphasisTestDescriptor(char character, int min, int max, string tag)
: this(character, min, max, new Tag(tag)) { }
}
class CustomEmphasisInline : EmphasisInline { }
static readonly EmphasisTestDescriptor[] EmphasisTestDescriptors = new[]
{
// Min Max
new EmphasisTestDescriptor('"', 1, 1, "quotation"),
new EmphasisTestDescriptor(',', 1, 2, "comma", "extra-comma"),
new EmphasisTestDescriptor('!', 2, 3, "warning", "error"),
new EmphasisTestDescriptor('=', 1, 3, "equal", "really-equal", "congruent"),
new EmphasisTestDescriptor('1', 1, 1, "one-only"),
new EmphasisTestDescriptor('2', 2, 2, "two-only"),
new EmphasisTestDescriptor('3', 3, 3, "three-only"),
};
[Test]
[TestCase("*foo**", "<em>foo</em>*")]
[TestCase("**foo*", "*<em>foo</em>")]
[TestCase("***foo***", "<em><strong>foo</strong></em>")]
[TestCase("**_foo_**", "<strong><em>foo</em></strong>")]
[TestCase("_**foo**_", "<em><strong>foo</strong></em>")]
[TestCase("\"foo\"", "<quotation>foo</quotation>")]
[TestCase("\"\"foo\"\"", "<quotation><quotation>foo</quotation></quotation>")]
[TestCase("\"foo\"\"", "<quotation>foo</quotation>&quot;")]
[TestCase("\"\"foo\"", "&quot;<quotation>foo</quotation>")]
[TestCase(", foo", ", foo")]
[TestCase(", foo,", ", foo,")]
[TestCase(",some, foo,", "<comma>some</comma> foo,")]
[TestCase(",,foo,,", "<extra-comma>foo</extra-comma>")]
[TestCase(",foo,,", "<comma>foo</comma>,")]
[TestCase(",,,foo,,,", "<comma><extra-comma>foo</extra-comma></comma>")]
[TestCase("*foo*&_foo_", "<em>foo</em>&amp;<em>foo</em>")]
[TestCase("!1!", "!1!")]
[TestCase("!!2!!", "<warning>2</warning>")]
[TestCase("!!!3!!!", "<error>3</error>")]
[TestCase("!!!34!!!!", "<error>34</error>!")]
[TestCase("!!!!43!!!", "!<error>43</error>")]
[TestCase("!!!!44!!!!", "!<error>44!</error>")] // This is a new case - should the second ! be before or after </error>?
[TestCase("!!!!!5!!!!!", "<warning><error>5</error></warning>")]
[TestCase("!!!!!!6!!!!!!", "<error><error>6</error></error>")]
[TestCase("!! !mixed!!!", "!! !mixed!!!")] // can't open the delimiter because of the whitespace
[TestCase("=", "=")]
[TestCase("==", "==")]
[TestCase("====", "====")]
[TestCase("=a", "=a")]
[TestCase("=a=", "<equal>a</equal>")]
[TestCase("==a=", "=<equal>a</equal>")]
[TestCase("==a==", "<really-equal>a</really-equal>")]
[TestCase("==a===", "<really-equal>a</really-equal>=")]
[TestCase("===a===", "<congruent>a</congruent>")]
[TestCase("====a====", "<equal><congruent>a</congruent></equal>")]
[TestCase("=====a=====", "<really-equal><congruent>a</congruent></really-equal>")]
[TestCase("1", "1")]
[TestCase("1 1", "1 1")]
[TestCase("1Foo1", "<one-only>Foo</one-only>")]
[TestCase("1121", "1<one-only>2</one-only>")]
[TestCase("22322", "<two-only>3</two-only>")]
[TestCase("2232", "2232")]
[TestCase("333", "333")]
[TestCase("3334333", "<three-only>4</three-only>")]
[TestCase("33334333", "3<three-only>4</three-only>")]
[TestCase("33343333", "<three-only>4</three-only>3")]
[TestCase("122122", "<one-only>22</one-only>22")]
[TestCase("221221", "<two-only>1</two-only>1")]
[TestCase("122foo221", "<one-only><two-only>foo</two-only></one-only>")]
[TestCase("122foo122", "<one-only>22foo</one-only>22")]
[TestCase("!!!!!Attention:!! \"==1+1== 2\",but ===333 and 222===, mod 111!!!",
"<error><warning>Attention:</warning> <quotation><really-equal><one-only>+</one-only></really-equal> 2</quotation><comma>but <congruent>333 and 222</congruent></comma> mod 111</error>")]
public void TestEmphasis(string markdown, string expectedHtml)
{
TestParser.TestSpec(markdown, "<p>" + expectedHtml + "</p>", Pipeline);
static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder().Use<EmphasisTestExtension>().Build();
[Test]
[TestCase("*foo**", "<em>foo</em>*")]
[TestCase("**foo*", "*<em>foo</em>")]
[TestCase("***foo***", "<em><strong>foo</strong></em>")]
[TestCase("**_foo_**", "<strong><em>foo</em></strong>")]
[TestCase("_**foo**_", "<em><strong>foo</strong></em>")]
[TestCase("\"foo\"", "<quotation>foo</quotation>")]
[TestCase("\"\"foo\"\"", "<quotation><quotation>foo</quotation></quotation>")]
[TestCase("\"foo\"\"", "<quotation>foo</quotation>&quot;")]
[TestCase("\"\"foo\"", "&quot;<quotation>foo</quotation>")]
[TestCase(", foo", ", foo")]
[TestCase(", foo,", ", foo,")]
[TestCase(",some, foo,", "<comma>some</comma> foo,")]
[TestCase(",,foo,,", "<extra-comma>foo</extra-comma>")]
[TestCase(",foo,,", "<comma>foo</comma>,")]
[TestCase(",,,foo,,,", "<comma><extra-comma>foo</extra-comma></comma>")]
[TestCase("*foo*&_foo_", "<em>foo</em>&amp;<em>foo</em>")]
[TestCase("!1!", "!1!")]
[TestCase("!!2!!", "<warning>2</warning>")]
[TestCase("!!!3!!!", "<error>3</error>")]
[TestCase("!!!34!!!!", "<error>34</error>!")]
[TestCase("!!!!43!!!", "!<error>43</error>")]
[TestCase("!!!!44!!!!", "!<error>44!</error>")] // This is a new case - should the second ! be before or after </error>?
[TestCase("!!!!!5!!!!!", "<warning><error>5</error></warning>")]
[TestCase("!!!!!!6!!!!!!", "<error><error>6</error></error>")]
[TestCase("!! !mixed!!!", "!! !mixed!!!")] // can't open the delimiter because of the whitespace
[TestCase("=", "=")]
[TestCase("==", "==")]
[TestCase("====", "====")]
[TestCase("=a", "=a")]
[TestCase("=a=", "<equal>a</equal>")]
[TestCase("==a=", "=<equal>a</equal>")]
[TestCase("==a==", "<really-equal>a</really-equal>")]
[TestCase("==a===", "<really-equal>a</really-equal>=")]
[TestCase("===a===", "<congruent>a</congruent>")]
[TestCase("====a====", "<equal><congruent>a</congruent></equal>")]
[TestCase("=====a=====", "<really-equal><congruent>a</congruent></really-equal>")]
[TestCase("1", "1")]
[TestCase("1 1", "1 1")]
[TestCase("1Foo1", "<one-only>Foo</one-only>")]
[TestCase("1121", "1<one-only>2</one-only>")]
[TestCase("22322", "<two-only>3</two-only>")]
[TestCase("2232", "2232")]
[TestCase("333", "333")]
[TestCase("3334333", "<three-only>4</three-only>")]
[TestCase("33334333", "3<three-only>4</three-only>")]
[TestCase("33343333", "<three-only>4</three-only>3")]
[TestCase("122122", "<one-only>22</one-only>22")]
[TestCase("221221", "<two-only>1</two-only>1")]
[TestCase("122foo221", "<one-only><two-only>foo</two-only></one-only>")]
[TestCase("122foo122", "<one-only>22foo</one-only>22")]
[TestCase("!!!!!Attention:!! \"==1+1== 2\",but ===333 and 222===, mod 111!!!",
"<error><warning>Attention:</warning> <quotation><really-equal><one-only>+</one-only></really-equal> 2</quotation><comma>but <congruent>333 and 222</congruent></comma> mod 111</error>")]
public void TestEmphasis(string markdown, string expectedHtml)
{
TestParser.TestSpec(markdown, "<p>" + expectedHtml + "</p>", Pipeline);
}
}
}

View File

@@ -1,43 +1,45 @@
using NUnit.Framework;
using Markdig.Extensions.EmphasisExtras;
namespace Markdig.Tests;
[TestFixture]
public class TestEmphasisExtraOptions
namespace Markdig.Tests
{
[Test]
public void OnlyStrikethrough_Single()
[TestFixture]
public class TestEmphasisExtraOptions
{
TestParser.TestSpec("~foo~", "<p>~foo~</p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough).Build());
}
[Test]
public void OnlyStrikethrough_Single()
{
TestParser.TestSpec("~foo~", "<p>~foo~</p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough).Build());
}
[Test]
public void OnlyStrikethrough_Double()
{
TestParser.TestSpec("~~foo~~", "<p><del>foo</del></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough).Build());
}
[Test]
public void OnlyStrikethrough_Double()
{
TestParser.TestSpec("~~foo~~", "<p><del>foo</del></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough).Build());
}
[Test]
public void OnlySubscript_Single()
{
TestParser.TestSpec("~foo~", "<p><sub>foo</sub></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build());
}
[Test]
public void OnlySubscript_Single()
{
TestParser.TestSpec("~foo~", "<p><sub>foo</sub></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build());
}
[Test]
public void OnlySubscript_Double()
{
TestParser.TestSpec("~~foo~~", "<p><sub><sub>foo</sub></sub></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build());
}
[Test]
public void OnlySubscript_Double()
{
TestParser.TestSpec("~~foo~~", "<p><sub><sub>foo</sub></sub></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Subscript).Build());
}
[Test]
public void SubscriptAndStrikethrough_Single()
{
TestParser.TestSpec("~foo~", "<p><sub>foo</sub></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough | EmphasisExtraOptions.Subscript).Build());
}
[Test]
public void SubscriptAndStrikethrough_Single()
{
TestParser.TestSpec("~foo~", "<p><sub>foo</sub></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough | EmphasisExtraOptions.Subscript).Build());
}
[Test]
public void SubscriptAndStrikethrough_Double()
{
TestParser.TestSpec("~~foo~~", "<p><del>foo</del></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough | EmphasisExtraOptions.Subscript).Build());
[Test]
public void SubscriptAndStrikethrough_Double()
{
TestParser.TestSpec("~~foo~~", "<p><del>foo</del></p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough | EmphasisExtraOptions.Subscript).Build());
}
}
}

View File

@@ -1,37 +1,23 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using NUnit.Framework;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Tests;
[TestFixture]
public partial class TestEmphasisPlus
namespace Markdig.Tests
{
[Test]
public void StrongNormal()
[TestFixture]
public partial class TestEmphasisPlus
{
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
}
[Test]
public void StrongNormal()
{
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
}
[Test]
public void NormalStrongNormal()
{
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
}
[Test]
public void OpenEmphasisHasConvenientContentStringSlice()
{
var pipeline = new MarkdownPipelineBuilder().Build();
var document = Markdown.Parse("test*test", pipeline);
var emphasisDelimiterLiteral = (LiteralInline)((ParagraphBlock)document.LastChild).Inline.ElementAt(1);
Assert.That(emphasisDelimiterLiteral.Content.Text == "test*test");
Assert.That(emphasisDelimiterLiteral.Content.Start == 4);
Assert.That(emphasisDelimiterLiteral.Content.End == 4);
[Test]
public void NormalStrongNormal()
{
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
}
}
}

View File

@@ -1,45 +1,48 @@
namespace Markdig.Tests;
using NUnit.Framework;
[TestFixture]
public class TestExceptionNotThrown
namespace Markdig.Tests
{
[Test]
public void DoesNotThrowIndexOutOfRangeException1()
[TestFixture]
public class TestExceptionNotThrown
{
Assert.DoesNotThrow(() =>
[Test]
public void DoesNotThrowIndexOutOfRangeException1()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+-\n|\n+", pipeline);
});
}
Assert.DoesNotThrow(() =>
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+-\n|\n+", pipeline);
});
}
[Test]
public void DoesNotThrowIndexOutOfRangeException2()
{
Assert.DoesNotThrow(() =>
[Test]
public void DoesNotThrowIndexOutOfRangeException2()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+--\n|\n+0", pipeline);
});
}
Assert.DoesNotThrow(() =>
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+--\n|\n+0", pipeline);
});
}
[Test]
public void DoesNotThrowIndexOutOfRangeException3()
{
Assert.DoesNotThrow(() =>
[Test]
public void DoesNotThrowIndexOutOfRangeException3()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+-\n|\n+\n0", pipeline);
});
}
Assert.DoesNotThrow(() =>
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+-\n|\n+\n0", pipeline);
});
}
[Test]
public void DoesNotThrowIndexOutOfRangeException4()
{
Assert.DoesNotThrow(() =>
[Test]
public void DoesNotThrowIndexOutOfRangeException4()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+-\n|\n+0", pipeline);
});
Assert.DoesNotThrow(() =>
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
Markdown.ToHtml("+-\n|\n+0", pipeline);
});
}
}
}

View File

@@ -1,186 +1,189 @@
using System.Text;
using Markdig.Helpers;
using NUnit.Framework;
using System;
using System.Text;
using System.Threading.Tasks;
namespace Markdig.Tests;
[TestFixture]
public class TestFastStringWriter
namespace Markdig.Tests
{
private const string NewLineReplacement = "~~NEW_LINE~~";
private FastStringWriter _writer = new();
[SetUp]
public void Setup()
[TestFixture]
public class TestFastStringWriter
{
_writer = new FastStringWriter
private const string NewLineReplacement = "~~NEW_LINE~~";
private FastStringWriter _writer = new();
[SetUp]
public void Setup()
{
NewLine = NewLineReplacement
};
}
_writer = new FastStringWriter
{
NewLine = NewLineReplacement
};
}
public void AssertToString(string value)
{
value = value.Replace("\n", NewLineReplacement);
Assert.AreEqual(value, _writer.ToString());
Assert.AreEqual(value, _writer.ToString());
}
public void AssertToString(string value)
{
value = value.Replace("\n", NewLineReplacement);
Assert.AreEqual(value, _writer.ToString());
Assert.AreEqual(value, _writer.ToString());
}
[Test]
public async Task NewLine()
{
Assert.AreEqual("\n", new FastStringWriter().NewLine);
[Test]
public async Task NewLine()
{
Assert.AreEqual("\n", new FastStringWriter().NewLine);
_writer.NewLine = "\r";
Assert.AreEqual("\r", _writer.NewLine);
_writer.NewLine = "\r";
Assert.AreEqual("\r", _writer.NewLine);
_writer.NewLine = "foo";
Assert.AreEqual("foo", _writer.NewLine);
_writer.NewLine = "foo";
Assert.AreEqual("foo", _writer.NewLine);
_writer.WriteLine();
await _writer.WriteLineAsync();
_writer.WriteLine("bar");
Assert.AreEqual("foofoobarfoo", _writer.ToString());
}
_writer.WriteLine();
await _writer.WriteLineAsync();
_writer.WriteLine("bar");
Assert.AreEqual("foofoobarfoo", _writer.ToString());
}
[Test]
public async Task FlushCloseDispose()
{
_writer.Write('a');
// Nops
_writer.Close();
_writer.Dispose();
await _writer.DisposeAsync();
_writer.Flush();
await _writer.FlushAsync();
_writer.Write('b');
AssertToString("ab");
}
[Test]
public async Task Write_Char()
{
_writer.Write('a');
AssertToString("a");
_writer.Write('b');
AssertToString("ab");
_writer.Write('\0');
_writer.Write('\r');
_writer.Write('\u1234');
AssertToString("ab\0\r\u1234");
_writer.Reset();
AssertToString("");
_writer.Write('a');
_writer.WriteLine('b');
_writer.Write('c');
_writer.Write('d');
_writer.WriteLine('e');
AssertToString("ab\ncde\n");
await _writer.WriteAsync('f');
await _writer.WriteLineAsync('g');
AssertToString("ab\ncde\nfg\n");
_writer.Reset();
for (int i = 0; i < 2050; i++)
[Test]
public async Task FlushCloseDispose()
{
_writer.Write('a');
AssertToString(new string('a', i + 1));
// Nops
_writer.Close();
_writer.Dispose();
await _writer.DisposeAsync();
_writer.Flush();
await _writer.FlushAsync();
_writer.Write('b');
AssertToString("ab");
}
[Test]
public async Task Write_Char()
{
_writer.Write('a');
AssertToString("a");
_writer.Write('b');
AssertToString("ab");
_writer.Write('\0');
_writer.Write('\r');
_writer.Write('\u1234');
AssertToString("ab\0\r\u1234");
_writer.Reset();
AssertToString("");
_writer.Write('a');
_writer.WriteLine('b');
_writer.Write('c');
_writer.Write('d');
_writer.WriteLine('e');
AssertToString("ab\ncde\n");
await _writer.WriteAsync('f');
await _writer.WriteLineAsync('g');
AssertToString("ab\ncde\nfg\n");
_writer.Reset();
for (int i = 0; i < 2050; i++)
{
_writer.Write('a');
AssertToString(new string('a', i + 1));
}
}
[Test]
public async Task Write_String()
{
_writer.Write("foo");
AssertToString("foo");
_writer.WriteLine("bar");
AssertToString("foobar\n");
await _writer.WriteAsync("baz");
await _writer.WriteLineAsync("foo");
AssertToString("foobar\nbazfoo\n");
_writer.Write(new string('a', 1050));
AssertToString("foobar\nbazfoo\n" + new string('a', 1050));
}
[Test]
public async Task Write_Span()
{
_writer.Write("foo".AsSpan());
AssertToString("foo");
_writer.WriteLine("bar".AsSpan());
AssertToString("foobar\n");
await _writer.WriteAsync("baz".AsMemory());
await _writer.WriteLineAsync("foo".AsMemory());
AssertToString("foobar\nbazfoo\n");
_writer.Write(new string('a', 1050).AsSpan());
AssertToString("foobar\nbazfoo\n" + new string('a', 1050));
}
[Test]
public async Task Write_CharArray()
{
_writer.Write("foo".ToCharArray());
AssertToString("foo");
_writer.WriteLine("bar".ToCharArray());
AssertToString("foobar\n");
await _writer.WriteAsync("baz".ToCharArray());
await _writer.WriteLineAsync("foo".ToCharArray());
AssertToString("foobar\nbazfoo\n");
_writer.Write(new string('a', 1050).ToCharArray());
AssertToString("foobar\nbazfoo\n" + new string('a', 1050));
}
[Test]
public async Task Write_CharArrayWithIndexes()
{
_writer.Write("foo".ToCharArray(), 1, 1);
AssertToString("o");
_writer.WriteLine("bar".ToCharArray(), 0, 2);
AssertToString("oba\n");
await _writer.WriteAsync("baz".ToCharArray(), 0, 1);
await _writer.WriteLineAsync("foo".ToCharArray(), 0, 3);
AssertToString("oba\nbfoo\n");
_writer.Write(new string('a', 1050).ToCharArray(), 10, 1035);
AssertToString("oba\nbfoo\n" + new string('a', 1035));
}
[Test]
public async Task Write_StringBuilder()
{
_writer.Write(new StringBuilder("foo"));
AssertToString("foo");
_writer.WriteLine(new StringBuilder("bar"));
AssertToString("foobar\n");
await _writer.WriteAsync(new StringBuilder("baz"));
await _writer.WriteLineAsync(new StringBuilder("foo"));
AssertToString("foobar\nbazfoo\n");
var sb = new StringBuilder("foo");
sb.Append('a', 1050);
_writer.Write(sb);
AssertToString("foobar\nbazfoo\nfoo" + new string('a', 1050));
}
}
[Test]
public async Task Write_String()
{
_writer.Write("foo");
AssertToString("foo");
_writer.WriteLine("bar");
AssertToString("foobar\n");
await _writer.WriteAsync("baz");
await _writer.WriteLineAsync("foo");
AssertToString("foobar\nbazfoo\n");
_writer.Write(new string('a', 1050));
AssertToString("foobar\nbazfoo\n" + new string('a', 1050));
}
[Test]
public async Task Write_Span()
{
_writer.Write("foo".AsSpan());
AssertToString("foo");
_writer.WriteLine("bar".AsSpan());
AssertToString("foobar\n");
await _writer.WriteAsync("baz".AsMemory());
await _writer.WriteLineAsync("foo".AsMemory());
AssertToString("foobar\nbazfoo\n");
_writer.Write(new string('a', 1050).AsSpan());
AssertToString("foobar\nbazfoo\n" + new string('a', 1050));
}
[Test]
public async Task Write_CharArray()
{
_writer.Write("foo".ToCharArray());
AssertToString("foo");
_writer.WriteLine("bar".ToCharArray());
AssertToString("foobar\n");
await _writer.WriteAsync("baz".ToCharArray());
await _writer.WriteLineAsync("foo".ToCharArray());
AssertToString("foobar\nbazfoo\n");
_writer.Write(new string('a', 1050).ToCharArray());
AssertToString("foobar\nbazfoo\n" + new string('a', 1050));
}
[Test]
public async Task Write_CharArrayWithIndexes()
{
_writer.Write("foo".ToCharArray(), 1, 1);
AssertToString("o");
_writer.WriteLine("bar".ToCharArray(), 0, 2);
AssertToString("oba\n");
await _writer.WriteAsync("baz".ToCharArray(), 0, 1);
await _writer.WriteLineAsync("foo".ToCharArray(), 0, 3);
AssertToString("oba\nbfoo\n");
_writer.Write(new string('a', 1050).ToCharArray(), 10, 1035);
AssertToString("oba\nbfoo\n" + new string('a', 1035));
}
[Test]
public async Task Write_StringBuilder()
{
_writer.Write(new StringBuilder("foo"));
AssertToString("foo");
_writer.WriteLine(new StringBuilder("bar"));
AssertToString("foobar\n");
await _writer.WriteAsync(new StringBuilder("baz"));
await _writer.WriteLineAsync(new StringBuilder("foo"));
AssertToString("foobar\nbazfoo\n");
var sb = new StringBuilder("foo");
sb.Append('a', 1050);
_writer.Write(sb);
AssertToString("foobar\nbazfoo\nfoo" + new string('a', 1050));
}
}

View File

@@ -1,43 +1,46 @@
using System.Linq;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestFencedCodeBlocks
namespace Markdig.Tests
{
[Test]
[TestCase("c#", "c#", "")]
[TestCase("C#", "C#", "")]
[TestCase(" c#", "c#", "")]
[TestCase(" c# ", "c#", "")]
[TestCase(" \tc# ", "c#", "")]
[TestCase("\t c# \t", "c#", "")]
[TestCase(" c# ", "c#", "")]
[TestCase(" c# foo", "c#", "foo")]
[TestCase(" c# \t fOo \t", "c#", "fOo")]
[TestCase("in\\%fo arg\\%ument", "in%fo", "arg%ument")]
[TestCase("info&#9; arg&acute;ument", "info\t", "arg\u00B4ument")]
public void TestInfoAndArguments(string infoString, string expectedInfo, string expectedArguments)
public class TestFencedCodeBlocks
{
Test('`');
Test('~');
void Test(char fencedChar)
[Test]
[TestCase("c#", "c#", "")]
[TestCase("C#", "C#", "")]
[TestCase(" c#", "c#", "")]
[TestCase(" c# ", "c#", "")]
[TestCase(" \tc# ", "c#", "")]
[TestCase("\t c# \t", "c#", "")]
[TestCase(" c# ", "c#", "")]
[TestCase(" c# foo", "c#", "foo")]
[TestCase(" c# \t fOo \t", "c#", "fOo")]
[TestCase("in\\%fo arg\\%ument", "in%fo", "arg%ument")]
[TestCase("info&#9; arg&acute;ument", "info\t", "arg\u00B4ument")]
public void TestInfoAndArguments(string infoString, string expectedInfo, string expectedArguments)
{
const string Contents = "Foo\nBar\n";
Test('`');
Test('~');
var fence = new string(fencedChar, 3);
string markdownText = $"{fence}{infoString}\n{Contents}\n{fence}\n";
void Test(char fencedChar)
{
const string Contents = "Foo\nBar\n";
MarkdownDocument document = Markdown.Parse(markdownText);
string fence = new string(fencedChar, 3);
string markdownText = $"{fence}{infoString}\n{Contents}\n{fence}\n";
FencedCodeBlock codeBlock = document.Descendants<FencedCodeBlock>().Single();
MarkdownDocument document = Markdown.Parse(markdownText);
Assert.AreEqual(fencedChar, codeBlock.FencedChar);
Assert.AreEqual(3, codeBlock.OpeningFencedCharCount);
Assert.AreEqual(3, codeBlock.ClosingFencedCharCount);
Assert.AreEqual(expectedInfo, codeBlock.Info);
Assert.AreEqual(expectedArguments, codeBlock.Arguments);
Assert.AreEqual(Contents, codeBlock.Lines.ToString());
FencedCodeBlock codeBlock = document.Descendants<FencedCodeBlock>().Single();
Assert.AreEqual(fencedChar, codeBlock.FencedChar);
Assert.AreEqual(3, codeBlock.OpeningFencedCharCount);
Assert.AreEqual(3, codeBlock.ClosingFencedCharCount);
Assert.AreEqual(expectedInfo, codeBlock.Info);
Assert.AreEqual(expectedArguments, codeBlock.Arguments);
Assert.AreEqual(Contents, codeBlock.Lines.ToString());
}
}
}
}

View File

@@ -3,91 +3,94 @@
// See the license.txt file in the project root for more information.
using Markdig.Renderers.Html;
using NUnit.Framework;
using System.Collections.Generic;
namespace Markdig.Tests;
[TestFixture()]
public class TestHtmlAttributes
namespace Markdig.Tests
{
[Test]
public void TestAddClass()
[TestFixture()]
public class TestHtmlAttributes
{
var attributes = new HtmlAttributes();
attributes.AddClass("test");
Assert.NotNull(attributes.Classes);
Assert.AreEqual(new List<string>() { "test" }, attributes.Classes);
[Test]
public void TestAddClass()
{
var attributes = new HtmlAttributes();
attributes.AddClass("test");
Assert.NotNull(attributes.Classes);
Assert.AreEqual(new List<string>() { "test" }, attributes.Classes);
attributes.AddClass("test");
Assert.AreEqual(1, attributes.Classes.Count);
attributes.AddClass("test");
Assert.AreEqual(1, attributes.Classes.Count);
attributes.AddClass("test1");
Assert.AreEqual(new List<string>() { "test", "test1" }, attributes.Classes);
}
attributes.AddClass("test1");
Assert.AreEqual(new List<string>() { "test", "test1" }, attributes.Classes);
}
[Test]
public void TestAddProperty()
{
var attributes = new HtmlAttributes();
attributes.AddProperty("key1", "1");
Assert.NotNull(attributes.Properties);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
[Test]
public void TestAddProperty()
{
var attributes = new HtmlAttributes();
attributes.AddProperty("key1", "1");
Assert.NotNull(attributes.Properties);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
attributes.AddPropertyIfNotExist("key1", "1");
Assert.NotNull(attributes.Properties);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
attributes.AddPropertyIfNotExist("key1", "1");
Assert.NotNull(attributes.Properties);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
attributes.AddPropertyIfNotExist("key2", "2");
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, attributes.Properties);
}
attributes.AddPropertyIfNotExist("key2", "2");
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, attributes.Properties);
}
[Test]
public void TestCopyTo()
{
var from = new HtmlAttributes();
from.AddClass("test");
from.AddProperty("key1", "1");
[Test]
public void TestCopyTo()
{
var from = new HtmlAttributes();
from.AddClass("test");
from.AddProperty("key1", "1");
var to = new HtmlAttributes();
from.CopyTo(to);
var to = new HtmlAttributes();
from.CopyTo(to);
Assert.True(ReferenceEquals(from.Classes, to.Classes));
Assert.True(ReferenceEquals(from.Properties, to.Properties));
Assert.True(ReferenceEquals(from.Classes, to.Classes));
Assert.True(ReferenceEquals(from.Properties, to.Properties));
// From: Classes From: Properties To: Classes To: Properties
// test1: null null null null
from = new HtmlAttributes();
to = new HtmlAttributes();
from.CopyTo(to, false, false);
Assert.Null(to.Classes);
Assert.Null(to.Properties);
// From: Classes From: Properties To: Classes To: Properties
// test1: null null null null
from = new HtmlAttributes();
to = new HtmlAttributes();
from.CopyTo(to, false, false);
Assert.Null(to.Classes);
Assert.Null(to.Properties);
// test2: ["test"] ["key1", "1"] null null
from = new HtmlAttributes();
to = new HtmlAttributes();
from.AddClass("test");
from.AddProperty("key1", "1");
from.CopyTo(to, false, false);
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1")}, to.Properties);
// test2: ["test"] ["key1", "1"] null null
from = new HtmlAttributes();
to = new HtmlAttributes();
from.AddClass("test");
from.AddProperty("key1", "1");
from.CopyTo(to, false, false);
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1")}, to.Properties);
// test3: null null ["test"] ["key1", "1"]
from = new HtmlAttributes();
to = new HtmlAttributes();
to.AddClass("test");
to.AddProperty("key1", "1");
from.CopyTo(to, false, false);
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, to.Properties);
// test3: null null ["test"] ["key1", "1"]
from = new HtmlAttributes();
to = new HtmlAttributes();
to.AddClass("test");
to.AddProperty("key1", "1");
from.CopyTo(to, false, false);
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, to.Properties);
// test4: ["test1"] ["key2", "2"] ["test"] ["key1", "1"]
from = new HtmlAttributes();
to = new HtmlAttributes();
from.AddClass("test1");
from.AddProperty("key2", "2");
to.AddClass("test");
to.AddProperty("key1", "1");
from.CopyTo(to, false, false);
Assert.AreEqual(new List<string>() { "test", "test1" }, to.Classes);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, to.Properties);
// test4: ["test1"] ["key2", "2"] ["test"] ["key1", "1"]
from = new HtmlAttributes();
to = new HtmlAttributes();
from.AddClass("test1");
from.AddProperty("key2", "2");
to.AddClass("test");
to.AddProperty("key1", "1");
from.CopyTo(to, false, false);
Assert.AreEqual(new List<string>() { "test", "test1" }, to.Classes);
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, to.Properties);
}
}
}

View File

@@ -1,28 +1,30 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using NUnit.Framework;
using Markdig.Helpers;
namespace Markdig.Tests;
[TestFixture]
public class TestHtmlHelper
namespace Markdig.Tests
{
[Test]
public void TestParseHtmlTagSimple()
[TestFixture]
public class TestHtmlHelper
{
var inputTag = "<a>";
var text = new StringSlice(inputTag);
Assert.True(HtmlHelper.TryParseHtmlTag(ref text, out string outputTag));
Assert.AreEqual(inputTag, outputTag);
}
[Test]
public void TestParseHtmlTagSimple()
{
var inputTag = "<a>";
var text = new StringSlice(inputTag);
Assert.True(HtmlHelper.TryParseHtmlTag(ref text, out string outputTag));
Assert.AreEqual(inputTag, outputTag);
}
[Test]
public void TestParseHtmlTagSimpleWithAttribute()
{
var inputTag = "<a href='http://google.com'>";
var text = new StringSlice(inputTag);
Assert.True(HtmlHelper.TryParseHtmlTag(ref text, out string outputTag));
Assert.AreEqual(inputTag, outputTag);
[Test]
public void TestParseHtmlTagSimpleWithAttribute()
{
var inputTag = "<a href='http://google.com'>";
var text = new StringSlice(inputTag);
Assert.True(HtmlHelper.TryParseHtmlTag(ref text, out string outputTag));
Assert.AreEqual(inputTag, outputTag);
}
}
}

View File

@@ -1,23 +1,25 @@
using NUnit.Framework;
using System.Text.RegularExpressions;
namespace Markdig.Tests;
[TestFixture]
public class TestImageAltText
namespace Markdig.Tests
{
[Test]
[TestCase("![](image.jpg)", "")]
[TestCase("![foo](image.jpg)", "foo")]
[TestCase("![][1]\n\n[1]: image.jpg", "")]
[TestCase("![bar][1]\n\n[1]: image.jpg", "bar")]
[TestCase("![](image.jpg 'title')", "")]
[TestCase("![foo](image.jpg 'title')", "foo")]
[TestCase("![][1]\n\n[1]: image.jpg 'title'", "")]
[TestCase("![bar][1]\n\n[1]: image.jpg 'title'", "bar")]
public void TestImageHtmlAltText(string markdown, string expectedAltText)
[TestFixture]
public class TestImageAltText
{
string html = Markdown.ToHtml(markdown);
string actualAltText = Regex.Match(html, "alt=\"(.*?)\"").Groups[1].Value;
Assert.AreEqual(expectedAltText, actualAltText);
[Test]
[TestCase("![](image.jpg)", "")]
[TestCase("![foo](image.jpg)", "foo")]
[TestCase("![][1]\n\n[1]: image.jpg", "")]
[TestCase("![bar][1]\n\n[1]: image.jpg", "bar")]
[TestCase("![](image.jpg 'title')", "")]
[TestCase("![foo](image.jpg 'title')", "foo")]
[TestCase("![][1]\n\n[1]: image.jpg 'title'", "")]
[TestCase("![bar][1]\n\n[1]: image.jpg 'title'", "bar")]
public void TestImageHtmlAltText(string markdown, string expectedAltText)
{
string html = Markdown.ToHtml(markdown);
string actualAltText = Regex.Match(html, "alt=\"(.*?)\"").Groups[1].Value;
Assert.AreEqual(expectedAltText, actualAltText);
}
}
}

View File

@@ -1,65 +1,67 @@
using Markdig.Helpers;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestLazySubstring
namespace Markdig.Tests
{
[Theory]
[TestCase("")]
[TestCase("a")]
[TestCase("foo")]
public void LazySubstring_ReturnsCorrectSubstring(string text)
public class TestLazySubstring
{
var substring = new LazySubstring(text);
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
[Theory]
[TestCase("")]
[TestCase("a")]
[TestCase("foo")]
public void LazySubstring_ReturnsCorrectSubstring(string text)
{
var substring = new LazySubstring(text);
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
Assert.AreSame(substring.ToString(), substring.ToString());
Assert.AreEqual(text, substring.ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
Assert.AreSame(substring.ToString(), substring.ToString());
Assert.AreEqual(text, substring.ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
}
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(text, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(text.Length, substring.Length);
}
[Theory]
[TestCase("", 0, 0)]
[TestCase("a", 0, 0)]
[TestCase("a", 1, 0)]
[TestCase("a", 0, 1)]
[TestCase("foo", 1, 0)]
[TestCase("foo", 1, 1)]
[TestCase("foo", 1, 2)]
[TestCase("foo", 0, 3)]
public void LazySubstring_ReturnsCorrectSubstring(string text, int start, int length)
{
var substring = new LazySubstring(text, start, length);
Assert.AreEqual(start, substring.Offset);
Assert.AreEqual(length, substring.Length);
[Theory]
[TestCase("", 0, 0)]
[TestCase("a", 0, 0)]
[TestCase("a", 1, 0)]
[TestCase("a", 0, 1)]
[TestCase("foo", 1, 0)]
[TestCase("foo", 1, 1)]
[TestCase("foo", 1, 2)]
[TestCase("foo", 0, 3)]
public void LazySubstring_ReturnsCorrectSubstring(string text, int start, int length)
{
var substring = new LazySubstring(text, start, length);
Assert.AreEqual(start, substring.Offset);
Assert.AreEqual(length, substring.Length);
string expectedSubstring = text.Substring(start, length);
string expectedSubstring = text.Substring(start, length);
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(start, substring.Offset);
Assert.AreEqual(length, substring.Length);
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(start, substring.Offset);
Assert.AreEqual(length, substring.Length);
Assert.AreSame(substring.ToString(), substring.ToString());
Assert.AreEqual(expectedSubstring, substring.ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(length, substring.Length);
Assert.AreSame(substring.ToString(), substring.ToString());
Assert.AreEqual(expectedSubstring, substring.ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(length, substring.Length);
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(length, substring.Length);
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(expectedSubstring, substring.AsSpan().ToString());
Assert.AreEqual(0, substring.Offset);
Assert.AreEqual(length, substring.Length);
}
}
}

View File

@@ -2,124 +2,126 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using NUnit.Framework;
using Markdig.Helpers;
namespace Markdig.Tests;
/// <summary>
/// Test for <see cref="LineReader"/>.
/// </summary>
[TestFixture]
public class TestLineReader
namespace Markdig.Tests
{
[Test]
public void TestEmpty()
/// <summary>
/// Test for <see cref="LineReader"/>.
/// </summary>
[TestFixture]
public class TestLineReader
{
var lineReader = new LineReader("");
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestEmpty()
{
var lineReader = new LineReader("");
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyLf()
{
var lineReader = new LineReader("\n\n\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyLf()
{
var lineReader = new LineReader("\n\n\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyCr()
{
var lineReader = new LineReader("\r\r\r");
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyCr()
{
var lineReader = new LineReader("\r\r\r");
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyCrLf()
{
var lineReader = new LineReader("\r\n\r\n\r\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyCrLf()
{
var lineReader = new LineReader("\r\n\r\n\r\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestNoEndOfLine()
{
var lineReader = new LineReader("123");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestNoEndOfLine()
{
var lineReader = new LineReader("123");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLf()
{
var lineReader = new LineReader("123\n");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLf()
{
var lineReader = new LineReader("123\n");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLf2()
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\n'
var lineReader = new LineReader("123\n456");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLf2()
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\n'
var lineReader = new LineReader("123\n456");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCr()
{
var lineReader = new LineReader("123\r");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCr()
{
var lineReader = new LineReader("123\r");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCr2()
{
var lineReader = new LineReader("123\r456");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCr2()
{
var lineReader = new LineReader("123\r456");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCrLf()
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\r'
// and we check that we don't get a new line for `\n`
var lineReader = new LineReader("123\r\n");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCrLf()
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\r'
// and we check that we don't get a new line for `\n`
var lineReader = new LineReader("123\r\n");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCrLf2()
{
var lineReader = new LineReader("123\r\n456");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
[Test]
public void TestCrLf2()
{
var lineReader = new LineReader("123\r\n456");
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
}
}

View File

@@ -2,372 +2,374 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using NUnit.Framework;
using Markdig.Helpers;
using Markdig.Syntax;
namespace Markdig.Tests;
[TestFixture]
public class TestLinkHelper
namespace Markdig.Tests
{
[Test]
public void TestUrlSimple()
[TestFixture]
public class TestLinkHelper
{
var text = new StringSlice("toto tutu");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("toto", link);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlSimple()
{
var text = new StringSlice("toto tutu");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("toto", link);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlUrl()
{
var text = new StringSlice("http://google.com)");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual(')', text.CurrentChar);
}
[Test]
public void TestUrlUrl()
{
var text = new StringSlice("http://google.com)");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual(')', text.CurrentChar);
}
[Test]
[TestCase("http://google.com.")]
[TestCase("http://google.com. ")]
public void TestUrlTrailingFullStop(string uri)
{
var text = new StringSlice(uri);
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _, true));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}
[Test]
[TestCase("http://google.com.")]
[TestCase("http://google.com. ")]
public void TestUrlTrailingFullStop(string uri)
{
var text = new StringSlice(uri);
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _, true));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}
[Test]
public void TestUrlNestedParenthesis()
{
var text = new StringSlice("(toto)tutu(tata) nooo");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("(toto)tutu(tata)", link);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlNestedParenthesis()
{
var text = new StringSlice("(toto)tutu(tata) nooo");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("(toto)tutu(tata)", link);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlAlternate()
{
var text = new StringSlice("<toto_tata_tutu> nooo");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("toto_tata_tutu", link);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlAlternate()
{
var text = new StringSlice("<toto_tata_tutu> nooo");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("toto_tata_tutu", link);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlAlternateInvalid()
{
var text = new StringSlice("<toto_tata_tutu");
Assert.False(LinkHelper.TryParseUrl(ref text, out string link, out _));
}
[Test]
public void TestUrlAlternateInvalid()
{
var text = new StringSlice("<toto_tata_tutu");
Assert.False(LinkHelper.TryParseUrl(ref text, out string link, out _));
}
[Test]
public void TestTitleSimple()
{
var text = new StringSlice(@"'tata\tutu\''");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual(@"tata\tutu'", title);
}
[Test]
public void TestTitleSimple()
{
var text = new StringSlice(@"'tata\tutu\''");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual(@"tata\tutu'", title);
}
[Test]
public void TestTitleSimpleAlternate()
{
var text = new StringSlice(@"""tata\tutu\"""" ");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual(@"tata\tutu""", title);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestTitleSimpleAlternate()
{
var text = new StringSlice(@"""tata\tutu\"""" ");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual(@"tata\tutu""", title);
Assert.AreEqual(' ', text.CurrentChar);
}
[Test]
public void TestUrlAndTitle()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("this is a title", title);
Assert.AreEqual(new SourceSpan(1, 17), linkSpan);
Assert.AreEqual(new SourceSpan(19, 35), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitle()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("this is a title", title);
Assert.AreEqual(new SourceSpan(1, 17), linkSpan);
Assert.AreEqual(new SourceSpan(19, 35), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitleEmpty()
{
// 01234
var text = new StringSlice(@"(<>)A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(1, 2), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitleEmpty()
{
// 01234
var text = new StringSlice(@"(<>)A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(1, 2), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitleEmpty2()
{
// 012345
var text = new StringSlice(@"( <> )A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(2, 3), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitleEmpty2()
{
// 012345
var text = new StringSlice(@"( <> )A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(2, 3), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlEmptyWithTitleWithMultipleSpaces()
{
// 0 1 2
// 0123456789012345678901234567
var text = new StringSlice(@"( <> 'toto' )A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(4, 5), linkSpan);
Assert.AreEqual(new SourceSpan(12, 17), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlEmptyWithTitleWithMultipleSpaces()
{
// 0 1 2
// 0123456789012345678901234567
var text = new StringSlice(@"( <> 'toto' )A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(4, 5), linkSpan);
Assert.AreEqual(new SourceSpan(12, 17), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlEmpty()
{
var text = new StringSlice(@"()A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(SourceSpan.Empty, linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlEmpty()
{
var text = new StringSlice(@"()A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(SourceSpan.Empty, linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestMultipleLines()
{
// 0 1 2 3
// 01 2345678901234567890 1234567890123456789
var text = new StringSlice("(\n<http://google.com>\n 'toto' )A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(2, 20), linkSpan);
Assert.AreEqual(new SourceSpan(26, 31), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestMultipleLines()
{
// 0 1 2 3
// 01 2345678901234567890 1234567890123456789
var text = new StringSlice("(\n<http://google.com>\n 'toto' )A");
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(2, 20), linkSpan);
Assert.AreEqual(new SourceSpan(26, 31), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestLabelSimple()
{
// 01234
var text = new StringSlice("[foo]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual("foo", label);
}
[Test]
public void TestLabelSimple()
{
// 01234
var text = new StringSlice("[foo]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual("foo", label);
}
[Test]
public void TestLabelEscape()
{
// 012345678
var text = new StringSlice(@"[fo\[\]o]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 7), labelSpan);
Assert.AreEqual(@"fo[]o", label);
}
[Test]
public void TestLabelEscape()
{
// 012345678
var text = new StringSlice(@"[fo\[\]o]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 7), labelSpan);
Assert.AreEqual(@"fo[]o", label);
}
[Test]
public void TestLabelEscape2()
{
// 0123
var text = new StringSlice(@"[\]]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 2), labelSpan);
Assert.AreEqual(@"]", label);
}
[Test]
public void TestLabelEscape2()
{
// 0123
var text = new StringSlice(@"[\]]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 2), labelSpan);
Assert.AreEqual(@"]", label);
}
[Test]
public void TestLabelInvalids()
{
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"a"), out string label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"["), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[\x]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[[]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ ]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ \t \n ]"), out label));
}
[Test]
public void TestLabelInvalids()
{
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"a"), out string label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"["), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[\x]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[[]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ ]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[ \t \n ]"), out label));
}
[Test]
public void TestLabelWhitespaceCollapsedAndTrim()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[ fo o z ]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(6, 17), labelSpan);
Assert.AreEqual(@"fo o z", label);
}
[Test]
public void TestLabelWhitespaceCollapsedAndTrim()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[ fo o z ]");
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(6, 17), labelSpan);
Assert.AreEqual(@"fo o z", label);
}
[Test]
public void TestlLinkReferenceDefinitionSimple()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[foo]: /toto 'title'");
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out string label, out string url, out string title, out SourceSpan labelSpan, out SourceSpan urlSpan, out SourceSpan titleSpan));
Assert.AreEqual(@"foo", label);
Assert.AreEqual(@"/toto", url);
Assert.AreEqual(@"title", title);
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual(new SourceSpan(7, 11), urlSpan);
Assert.AreEqual(new SourceSpan(13, 19), titleSpan);
[Test]
public void TestlLinkReferenceDefinitionSimple()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[foo]: /toto 'title'");
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out string label, out string url, out string title, out SourceSpan labelSpan, out SourceSpan urlSpan, out SourceSpan titleSpan));
Assert.AreEqual(@"foo", label);
Assert.AreEqual(@"/toto", url);
Assert.AreEqual(@"title", title);
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual(new SourceSpan(7, 11), urlSpan);
Assert.AreEqual(new SourceSpan(13, 19), titleSpan);
}
}
[Test]
public void TestAutoLinkUrlSimple()
{
var text = new StringSlice(@"<http://google.com>");
Assert.True(LinkHelper.TryParseAutolink(ref text, out string url, out bool isEmail));
Assert.False(isEmail);
Assert.AreEqual("http://google.com", url);
}
[Test]
public void TestAutoLinkUrlSimple()
{
var text = new StringSlice(@"<http://google.com>");
Assert.True(LinkHelper.TryParseAutolink(ref text, out string url, out bool isEmail));
Assert.False(isEmail);
Assert.AreEqual("http://google.com", url);
}
[Test]
public void TestAutoLinkEmailSimple()
{
var text = new StringSlice(@"<user@host.com>");
Assert.True(LinkHelper.TryParseAutolink(ref text, out string email, out bool isEmail));
Assert.True(isEmail);
Assert.AreEqual("user@host.com", email);
}
[Test]
public void TestAutoLinkEmailSimple()
{
var text = new StringSlice(@"<user@host.com>");
Assert.True(LinkHelper.TryParseAutolink(ref text, out string email, out bool isEmail));
Assert.True(isEmail);
Assert.AreEqual("user@host.com", email);
}
[Test]
public void TestAutolinkInvalid()
{
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@""), out string text, out bool isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
}
[Test]
public void TestAutolinkInvalid()
{
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@""), out string text, out bool isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
}
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
[TestCase("* Dogs*?--in *my* house?", "dogs-in-my-house")] // Not Pandoc equivalent: dogs--in...
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "applications")]
[TestCase("33", "")]
public void TestUrilizeNonAscii_Pandoc(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
[TestCase("* Dogs*?--in *my* house?", "dogs-in-my-house")] // Not Pandoc equivalent: dogs--in...
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "applications")]
[TestCase("33", "")]
public void TestUrilizeNonAscii_Pandoc(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
[TestCase("* Dogs*?--in *my* house?", "-dogs--in-my-house")]
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "3-applications")]
[TestCase("33", "33")]
public void TestUrilizeGfm(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.UrilizeAsGfm(input));
}
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
[TestCase("* Dogs*?--in *my* house?", "-dogs--in-my-house")]
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "3-applications")]
[TestCase("33", "33")]
public void TestUrilizeGfm(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.UrilizeAsGfm(input));
}
[TestCase("abc", "abc")]
[TestCase("a-c", "a-c")]
[TestCase("a c", "a-c")]
[TestCase("a_c", "a_c")]
[TestCase("a.c", "a.c")]
[TestCase("a,c", "ac")]
[TestCase("a--", "a")] // Not Pandoc-equivalent: a--
[TestCase("a__", "a")] // Not Pandoc-equivalent: a__
[TestCase("a..", "a")] // Not Pandoc-equivalent: a..
[TestCase("a??", "a")]
[TestCase("a ", "a")]
[TestCase("a--d", "a-d")]
[TestCase("a__d", "a_d")]
[TestCase("a??d", "ad")]
[TestCase("a d", "a-d")]
[TestCase("a..d", "a.d")]
[TestCase("-bc", "bc")]
[TestCase("_bc", "bc")]
[TestCase(" bc", "bc")]
[TestCase("?bc", "bc")]
[TestCase(".bc", "bc")]
[TestCase("a-.-", "a")] // Not Pandoc equivalent: a-.-
public void TestUrilizeOnlyAscii_Simple(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("abc", "abc")]
[TestCase("a-c", "a-c")]
[TestCase("a c", "a-c")]
[TestCase("a_c", "a_c")]
[TestCase("a.c", "a.c")]
[TestCase("a,c", "ac")]
[TestCase("a--", "a")] // Not Pandoc-equivalent: a--
[TestCase("a__", "a")] // Not Pandoc-equivalent: a__
[TestCase("a..", "a")] // Not Pandoc-equivalent: a..
[TestCase("a??", "a")]
[TestCase("a ", "a")]
[TestCase("a--d", "a-d")]
[TestCase("a__d", "a_d")]
[TestCase("a??d", "ad")]
[TestCase("a d", "a-d")]
[TestCase("a..d", "a.d")]
[TestCase("-bc", "bc")]
[TestCase("_bc", "bc")]
[TestCase(" bc", "bc")]
[TestCase("?bc", "bc")]
[TestCase(".bc", "bc")]
[TestCase("a-.-", "a")] // Not Pandoc equivalent: a-.-
public void TestUrilizeOnlyAscii_Simple(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("bær", "br")]
[TestCase("bør", "br")]
[TestCase("bΘr", "br")]
[TestCase("四五", "")]
public void TestUrilizeOnlyAscii_NonAscii(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("bær", "br")]
[TestCase("bør", "br")]
[TestCase("bΘr", "br")]
[TestCase("四五", "")]
public void TestUrilizeOnlyAscii_NonAscii(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("bár", "bar")]
[TestCase("àrrivé", "arrive")]
public void TestUrilizeOnlyAscii_Normalization(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("bár", "bar")]
[TestCase("àrrivé", "arrive")]
public void TestUrilizeOnlyAscii_Normalization(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("123", "")]
[TestCase("1,-b", "b")]
[TestCase("b1,-", "b1")] // Not Pandoc equivalent: b1-
[TestCase("ab3", "ab3")]
[TestCase("ab3de", "ab3de")]
public void TestUrilizeOnlyAscii_Numeric(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("123", "")]
[TestCase("1,-b", "b")]
[TestCase("b1,-", "b1")] // Not Pandoc equivalent: b1-
[TestCase("ab3", "ab3")]
[TestCase("ab3de", "ab3de")]
public void TestUrilizeOnlyAscii_Numeric(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("一二三四五", "一二三四五")]
[TestCase("一,-b", "一-b")]
public void TestUrilizeNonAscii_NonAsciiNumeric(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("一二三四五", "一二三四五")]
[TestCase("一,-b", "一-b")]
public void TestUrilizeNonAscii_NonAsciiNumeric(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("bær", "bær")]
[TestCase("æ5el", "æ5el")]
[TestCase("-æ5el", "æ5el")]
[TestCase("-frø-", "frø")]
[TestCase("-fr-ø", "fr-ø")]
public void TestUrilizeNonAscii_Simple(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("bær", "bær")]
[TestCase("æ5el", "æ5el")]
[TestCase("-æ5el", "æ5el")]
[TestCase("-frø-", "frø")]
[TestCase("-fr-ø", "fr-ø")]
public void TestUrilizeNonAscii_Simple(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
// Just to be sure, test for characters expressly forbidden in URI fragments:
[TestCase("b#r", "br")]
[TestCase("b%r", "br")] // Invalid except as an escape character
[TestCase("b^r", "br")]
[TestCase("b[r", "br")]
[TestCase("b]r", "br")]
[TestCase("b{r", "br")]
[TestCase("b}r", "br")]
[TestCase("b<r", "br")]
[TestCase("b>r", "br")]
[TestCase(@"b\r", "br")]
[TestCase(@"b""r", "br")]
[TestCase(@"Requirement 😀", "requirement")]
public void TestUrilizeNonAscii_NonValidCharactersForFragments(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
// Just to be sure, test for characters expressly forbidden in URI fragments:
[TestCase("b#r", "br")]
[TestCase("b%r", "br")] // Invalid except as an escape character
[TestCase("b^r", "br")]
[TestCase("b[r", "br")]
[TestCase("b]r", "br")]
[TestCase("b{r", "br")]
[TestCase("b}r", "br")]
[TestCase("b<r", "br")]
[TestCase("b>r", "br")]
[TestCase(@"b\r", "br")]
[TestCase(@"b""r", "br")]
[TestCase(@"Requirement 😀", "requirement")]
public void TestUrilizeNonAscii_NonValidCharactersForFragments(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[Test]
public void TestUnicodeInDomainNameOfLinkReferenceDefinition()
{
TestParser.TestSpec("[Foo]\n\n[Foo]: http://ünicode.com", "<p><a href=\"http://xn--nicode-2ya.com\">Foo</a></p>");
[Test]
public void TestUnicodeInDomainNameOfLinkReferenceDefinition()
{
TestParser.TestSpec("[Foo]\n\n[Foo]: http://ünicode.com", "<p><a href=\"http://xn--nicode-2ya.com\">Foo</a></p>");
}
}
}

View File

@@ -1,40 +1,44 @@
using System;
using System.IO;
using Markdig.Parsers;
using Markdig.Renderers;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestLinkRewriter
namespace Markdig.Tests
{
[Test]
public void ReplacesRelativeLinks()
public class TestLinkRewriter
{
TestSpec(s => "abc" + s, "Link: [hello](/relative.jpg)", "abc/relative.jpg");
TestSpec(s => s + "xyz", "Link: [hello](relative.jpg)", "relative.jpgxyz");
TestSpec(null, "Link: [hello](relative.jpg)", "relative.jpg");
TestSpec(null, "Link: [hello](/relative.jpg)", "/relative.jpg");
}
[Test]
public void ReplacesRelativeLinks()
{
TestSpec(s => "abc" + s, "Link: [hello](/relative.jpg)", "abc/relative.jpg");
TestSpec(s => s + "xyz", "Link: [hello](relative.jpg)", "relative.jpgxyz");
TestSpec(null, "Link: [hello](relative.jpg)", "relative.jpg");
TestSpec(null, "Link: [hello](/relative.jpg)", "/relative.jpg");
}
[Test]
public void ReplacesRelativeImageSources()
{
TestSpec(s => "abc" + s, "Image: ![alt text](/image.jpg)", "abc/image.jpg");
TestSpec(s => "abc" + s, "Image: ![alt text](image.jpg \"title\")", "abcimage.jpg");
TestSpec(null, "Image: ![alt text](/image.jpg)", "/image.jpg");
}
[Test]
public void ReplacesRelativeImageSources()
{
TestSpec(s => "abc" + s, "Image: ![alt text](/image.jpg)", "abc/image.jpg");
TestSpec(s => "abc" + s, "Image: ![alt text](image.jpg \"title\")", "abcimage.jpg");
TestSpec(null, "Image: ![alt text](/image.jpg)", "/image.jpg");
}
public static void TestSpec(Func<string,string> linkRewriter, string markdown, string expectedLink)
{
var pipeline = new MarkdownPipelineBuilder().Build();
public static void TestSpec(Func<string,string> linkRewriter, string markdown, string expectedLink)
{
var pipeline = new MarkdownPipelineBuilder().Build();
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
renderer.LinkRewriter = linkRewriter;
pipeline.Setup(renderer);
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
renderer.LinkRewriter = linkRewriter;
pipeline.Setup(renderer);
var document = MarkdownParser.Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
var document = MarkdownParser.Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
}
}
}

View File

@@ -1,214 +1,217 @@
using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests;
public class TestMarkdigCoreApi
namespace Markdig.Tests
{
[Test]
public void TestToHtml()
public class TestMarkdigCoreApi
{
for (int i = 0; i < 5; i++)
[Test]
public void TestToHtml()
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/");
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
}
[Test]
public void TestToHtmlWithPipeline()
{
var pipeline = new MarkdownPipelineBuilder()
.Build();
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/");
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
}
pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
[Test]
public void TestToHtmlWithPipeline()
{
string html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
}
var pipeline = new MarkdownPipelineBuilder()
.Build();
[Test]
public void TestToHtmlWithWriter()
{
var writer = new StringWriter();
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
}
writer = new StringWriter();
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestDocumentToHtmlWithWriter()
{
var writer = new StringWriter();
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse("This is a text with some *emphasis*");
document.ToHtml(writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
writer = new StringWriter();
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse("This is a text with a https://link.tld/", pipeline);
document.ToHtml(writer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestConvert()
{
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
for (int i = 0; i < 5; i++)
{
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
writer = new StringWriter();
renderer = new HtmlRenderer(writer);
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestParse()
{
const string markdown = "This is a text with some *emphasis*";
var pipeline = new MarkdownPipelineBuilder()
.UsePreciseSourceLocation()
.Build();
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
Assert.AreEqual(1, document.LineCount);
Assert.AreEqual(markdown.Length, document.Span.Length);
Assert.AreEqual(1, document.LineStartIndexes.Count);
Assert.AreEqual(0, document.LineStartIndexes[0]);
Assert.AreEqual(1, document.Count);
ParagraphBlock paragraph = document[0] as ParagraphBlock;
Assert.NotNull(paragraph);
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
Assert.NotNull(literal);
Assert.AreEqual("This is a text with some ", literal.ToString());
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
Assert.NotNull(emphasis);
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
Assert.NotNull(emphasisLiteral);
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
Assert.Null(emphasisLiteral.NextSibling);
Assert.Null(emphasis.NextSibling);
}
}
[Test]
public void TestNormalize()
{
for (int i = 0; i < 5; i++)
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestNormalizeWithWriter()
{
for (int i = 0; i < 5; i++)
[Test]
public void TestToHtmlWithWriter()
{
var writer = new StringWriter();
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
}
}
for (int i = 0; i < 5; i++)
{
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
[Test]
public void TestToPlainText()
{
for (int i = 0; i < 5; i++)
{
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
Assert.AreEqual("Hello, world!\n", plainText);
}
}
writer = new StringWriter();
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
[Test]
public void TestToPlainTextWithWriter()
{
for (int i = 0; i < 5; i++)
for (int i = 0; i < 5; i++)
{
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestDocumentToHtmlWithWriter()
{
var writer = new StringWriter();
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
string plainText = writer.ToString();
Assert.AreEqual("Hello, world!\n", plainText);
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse("This is a text with some *emphasis*");
document.ToHtml(writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
writer = new StringWriter();
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse("This is a text with a https://link.tld/", pipeline);
document.ToHtml(writer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestConvert()
{
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
for (int i = 0; i < 5; i++)
{
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
writer = new StringWriter();
renderer = new HtmlRenderer(writer);
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
writer.GetStringBuilder().Length = 0;
}
}
[Test]
public void TestParse()
{
const string markdown = "This is a text with some *emphasis*";
var pipeline = new MarkdownPipelineBuilder()
.UsePreciseSourceLocation()
.Build();
for (int i = 0; i < 5; i++)
{
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
Assert.AreEqual(1, document.LineCount);
Assert.AreEqual(markdown.Length, document.Span.Length);
Assert.AreEqual(1, document.LineStartIndexes.Count);
Assert.AreEqual(0, document.LineStartIndexes[0]);
Assert.AreEqual(1, document.Count);
ParagraphBlock paragraph = document[0] as ParagraphBlock;
Assert.NotNull(paragraph);
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
Assert.NotNull(literal);
Assert.AreEqual("This is a text with some ", literal.ToString());
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
Assert.NotNull(emphasis);
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
Assert.NotNull(emphasisLiteral);
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
Assert.Null(emphasisLiteral.NextSibling);
Assert.Null(emphasis.NextSibling);
}
}
[Test]
public void TestNormalize()
{
for (int i = 0; i < 5; i++)
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestNormalizeWithWriter()
{
for (int i = 0; i < 5; i++)
{
var writer = new StringWriter();
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestToPlainText()
{
for (int i = 0; i < 5; i++)
{
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
Assert.AreEqual("Hello, world!\n", plainText);
}
}
[Test]
public void TestToPlainTextWithWriter()
{
for (int i = 0; i < 5; i++)
{
var writer = new StringWriter();
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
string plainText = writer.ToString();
Assert.AreEqual("Hello, world!\n", plainText);
}
}
}
}

View File

@@ -1,112 +1,104 @@
using Markdig.Extensions.MediaLinks;
using NUnit.Framework;
using System;
using System.Text.RegularExpressions;
using Markdig.Extensions.MediaLinks;
namespace Markdig.Tests;
[TestFixture]
public class TestMediaLinks
namespace Markdig.Tests
{
private MarkdownPipeline GetPipeline(MediaOptions options = null)
[TestFixture]
public class TestMediaLinks
{
return new MarkdownPipelineBuilder()
.UseMediaLinks(options)
.Build();
}
private MarkdownPipeline GetPipelineWithBootstrap(MediaOptions options = null)
{
return new MarkdownPipelineBuilder()
.UseBootstrap()
.UseMediaLinks(options)
.Build();
}
[Test]
[TestCase("![static mp4](https://sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" class=\"yandex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n")]
[TestCase("![vimeo](https://vimeo.com/8607834)", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" class=\"vimeo\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](//ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
public void TestBuiltInHosts(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipeline());
Assert.AreEqual(html, expected);
}
[TestCase("![static video relative path](./video.mp4)",
"<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"./video.mp4\"></source></video></p>\n")]
[TestCase("![static audio relative path](./audio.mp3)",
"<p><audio width=\"500\" controls=\"\"><source type=\"audio/mpeg\" src=\"./audio.mp3\"></source></audio></p>\n")]
public void TestBuiltInHostsWithRelativePaths(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipeline());
Assert.AreEqual(html, expected);
}
private class TestHostProvider : IHostProvider
{
public string Class { get; } = "regex";
public bool AllowFullScreen { get; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
private MarkdownPipeline GetPipeline(MediaOptions options = null)
{
iframeUrl = null;
var uri = isSchemaRelative ? "//" + mediaUri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Scheme, UriFormat.UriEscaped) : mediaUri.ToString();
if (!matcher.IsMatch(uri))
return false;
iframeUrl = matcher.Replace(uri, replacement);
return true;
return new MarkdownPipelineBuilder()
.UseMediaLinks(options)
.Build();
}
private Regex matcher;
private string replacement;
public TestHostProvider(string provider, string replace)
private MarkdownPipeline GetPipelineWithBootstrap(MediaOptions options = null)
{
matcher = new Regex(provider);
replacement = replace;
return new MarkdownPipelineBuilder()
.UseBootstrap()
.UseMediaLinks(options)
.Build();
}
}
[Test]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](//sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^//sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4?token=aaabbb\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1?token=aaabbb")]
public void TestCustomHostProvider(string markdown, string expected, string provider, string replace)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
[Test]
[TestCase("![static mp4](https://sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" class=\"yandex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n")]
[TestCase("![vimeo](https://vimeo.com/8607834)", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" class=\"vimeo\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](//ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
public void TestBuiltInHosts(string markdown, string expected)
{
Hosts =
string html = Markdown.ToHtml(markdown, GetPipeline());
Assert.AreEqual(html, expected);
}
private class TestHostProvider : IHostProvider
{
public string Class { get; } = "regex";
public bool AllowFullScreen { get; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
{
new TestHostProvider(provider, replace),
iframeUrl = null;
var uri = isSchemaRelative ? "//" + mediaUri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Scheme, UriFormat.UriEscaped) : mediaUri.ToString();
if (!matcher.IsMatch(uri))
return false;
iframeUrl = matcher.Replace(uri, replacement);
return true;
}
}));
Assert.AreEqual(html, expected);
}
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video class=\"k\" width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "k")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"k youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "k")]
public void TestCustomClass(string markdown, string expected, string klass)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
private Regex matcher;
private string replacement;
public TestHostProvider(string provider, string replace)
{
matcher = new Regex(provider);
replacement = replace;
}
}
[Test]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](//sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^//sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4?token=aaabbb\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1?token=aaabbb")]
public void TestCustomHostProvider(string markdown, string expected, string provider, string replace)
{
Class = klass,
}));
Assert.AreEqual(html, expected);
}
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
{
Hosts =
{
new TestHostProvider(provider, replace),
}
}));
Assert.AreEqual(html, expected);
}
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video class=\"img-fluid\" width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"img-fluid youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
public void TestWithBootstrap(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipelineWithBootstrap());
Assert.AreEqual(html, expected);
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video class=\"k\" width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "k")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"k youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "k")]
public void TestCustomClass(string markdown, string expected, string klass)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
{
Class = klass,
}));
Assert.AreEqual(html, expected);
}
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video class=\"img-fluid\" width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"img-fluid youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
public void TestWithBootstrap(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipelineWithBootstrap());
Assert.AreEqual(html, expected);
}
}
}

View File

@@ -1,28 +1,31 @@
using System.Linq;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests;
[TestFixture]
public class TestNewLine
namespace Markdig.Tests
{
[TestCase("a \nb", "<p>a<br />\nb</p>\n")]
[TestCase("a\\\nb", "<p>a<br />\nb</p>\n")]
[TestCase("a `b\nc`", "<p>a <code>b c</code></p>\n")]
[TestCase("# Text A\nText B\n\n## Text C", "<h1>Text A</h1>\n<p>Text B</p>\n<h2>Text C</h2>\n")]
public void Test(string value, string expectedHtml)
[TestFixture]
public class TestNewLine
{
Assert.AreEqual(expectedHtml, Markdown.ToHtml(value));
Assert.AreEqual(expectedHtml, Markdown.ToHtml(value.Replace("\n", "\r\n")));
}
[TestCase("a \nb", "<p>a<br />\nb</p>\n")]
[TestCase("a\\\nb", "<p>a<br />\nb</p>\n")]
[TestCase("a `b\nc`", "<p>a <code>b c</code></p>\n")]
[TestCase("# Text A\nText B\n\n## Text C", "<h1>Text A</h1>\n<p>Text B</p>\n<h2>Text C</h2>\n")]
public void Test(string value, string expectedHtml)
{
Assert.AreEqual(expectedHtml, Markdown.ToHtml(value));
Assert.AreEqual(expectedHtml, Markdown.ToHtml(value.Replace("\n", "\r\n")));
}
[Test()]
public void TestEscapeLineBreak()
{
var input = "test\\\r\ntest1\r\n";
var doc = Markdown.Parse(input);
var inlines = doc.Descendants<LineBreakInline>().ToList();
Assert.AreEqual(1, inlines.Count, "Invalid number of LineBreakInline");
Assert.True(inlines[0].IsBackslash);
[Test()]
public void TestEscapeLineBreak()
{
var input = "test\\\r\ntest1\r\n";
var doc = Markdown.Parse(input);
var inlines = doc.Descendants<LineBreakInline>().ToList();
Assert.AreEqual(1, inlines.Count, "Invalid number of LineBreakInline");
Assert.True(inlines[0].IsBackslash);
}
}
}

View File

@@ -2,177 +2,180 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Renderers.Normalize;
using System;
using NUnit.Framework;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.IO;
using Markdig.Renderers.Normalize;
using Markdig.Helpers;
namespace Markdig.Tests;
[TestFixture]
public class TestNormalize
namespace Markdig.Tests
{
[Test]
public void SyntaxCodeBlock()
[TestFixture]
public class TestNormalize
{
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
[Test]
public void SyntaxCodeBlock()
{
FencedChar = '`',
OpeningFencedCharCount = 4,
ClosingFencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
FencedChar = '`',
OpeningFencedCharCount = 4,
ClosingFencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
AssertSyntax(" public void HelloWorld()\n {\n }", new CodeBlock(null)
{
Lines = new StringLineGroup(4)
AssertSyntax(" public void HelloWorld()\n {\n }", new CodeBlock(null)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
}
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
}
[Test]
public void SyntaxHeadline()
{
AssertSyntax("## Headline", new HeadingBlock(null)
[Test]
public void SyntaxHeadline()
{
HeaderChar = '#',
Level = 2,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
AssertSyntax("## Headline", new HeadingBlock(null)
{
HeaderChar = '#',
Level = 2,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
[Test]
public void SyntaxParagraph()
{
AssertSyntax("This is a normal paragraph", new ParagraphBlock()
[Test]
public void SyntaxParagraph()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a normal paragraph")),
});
AssertSyntax("This is a normal paragraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a normal paragraph")),
});
AssertSyntax("This is a\nnormal\nparagraph", new ParagraphBlock()
AssertSyntax("This is a\nnormal\nparagraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("normal"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("paragraph")),
});
}
[Test]
public void CodeBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("normal"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("paragraph")),
});
}
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }");
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }\n\ntext after two newlines");
AssertNormalizeNoTrim("````\npublic void HelloWorld();\n{\n}\n````\n\ntext after two newlines");
AssertNormalizeNoTrim("````csharp\npublic void HelloWorld();\n{\n}\n````");
AssertNormalizeNoTrim("````csharp hideNewKeyword=true\npublic void HelloWorld();\n{\n}\n````");
}
[Test]
public void CodeBlock()
{
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }");
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }\n\ntext after two newlines");
AssertNormalizeNoTrim("````\npublic void HelloWorld();\n{\n}\n````\n\ntext after two newlines");
AssertNormalizeNoTrim("````csharp\npublic void HelloWorld();\n{\n}\n````");
AssertNormalizeNoTrim("````csharp hideNewKeyword=true\npublic void HelloWorld();\n{\n}\n````");
}
[Test]
public void Heading()
{
AssertNormalizeNoTrim("# Heading");
AssertNormalizeNoTrim("## Heading");
AssertNormalizeNoTrim("### Heading");
AssertNormalizeNoTrim("#### Heading");
AssertNormalizeNoTrim("##### Heading");
AssertNormalizeNoTrim("###### Heading");
AssertNormalizeNoTrim("###### Heading\n\ntext after two newlines");
AssertNormalizeNoTrim("# Heading\nAnd Text1\n\nAndText2", options: new NormalizeOptions() { EmptyLineAfterHeading = false });
[Test]
public void Heading()
{
AssertNormalizeNoTrim("# Heading");
AssertNormalizeNoTrim("## Heading");
AssertNormalizeNoTrim("### Heading");
AssertNormalizeNoTrim("#### Heading");
AssertNormalizeNoTrim("##### Heading");
AssertNormalizeNoTrim("###### Heading");
AssertNormalizeNoTrim("###### Heading\n\ntext after two newlines");
AssertNormalizeNoTrim("# Heading\nAnd Text1\n\nAndText2", options: new NormalizeOptions() { EmptyLineAfterHeading = false });
AssertNormalizeNoTrim("Heading\n=======\n\ntext after two newlines", "# Heading\n\ntext after two newlines");
}
AssertNormalizeNoTrim("Heading\n=======\n\ntext after two newlines", "# Heading\n\ntext after two newlines");
}
[Test]
public void Backslash()
{
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void Backslash()
{
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void HtmlBlock()
{
/*AssertNormalizeNoTrim(@"<div id=""foo"" class=""bar
baz"">
[Test]
public void HtmlBlock()
{
/*AssertNormalizeNoTrim(@"<div id=""foo"" class=""bar
baz"">
</ div >");*/ // TODO: Bug: Throws Exception during emit
}
}
[Test]
public void Paragraph()
{
AssertNormalizeNoTrim("This is a plain paragraph");
AssertNormalizeNoTrim(@"This
[Test]
public void Paragraph()
{
AssertNormalizeNoTrim("This is a plain paragraph");
AssertNormalizeNoTrim(@"This
is
a
plain
paragraph");
}
}
[Test]
public void ParagraphMulti()
{
AssertNormalizeNoTrim(@"line1
[Test]
public void ParagraphMulti()
{
AssertNormalizeNoTrim(@"line1
line2
line3");
}
}
[Test]
public void ListUnordered()
{
AssertNormalizeNoTrim(@"- a
[Test]
public void ListUnordered()
{
AssertNormalizeNoTrim(@"- a
- b
- c");
}
}
[Test]
public void ListUnorderedLoose()
{
AssertNormalizeNoTrim(@"- a
[Test]
public void ListUnorderedLoose()
{
AssertNormalizeNoTrim(@"- a
- b
- c");
}
}
[Test]
public void ListOrderedLooseAndCodeBlock()
{
AssertNormalizeNoTrim(@"1. ```
[Test]
public void ListOrderedLooseAndCodeBlock()
{
AssertNormalizeNoTrim(@"1. ```
foo
```
bar");
}
}
[Test, Ignore("Not sure this is the correct normalize for this one. Need to check the specs")]
public void ListUnorderedLooseTop()
{
AssertNormalizeNoTrim(@"* foo
[Test, Ignore("Not sure this is the correct normalize for this one. Need to check the specs")]
public void ListUnorderedLooseTop()
{
AssertNormalizeNoTrim(@"* foo
* bar
baz", options: new NormalizeOptions() { ListItemCharacter = '*' });
}
}
[Test]
public void ListUnorderedLooseMultiParagraph()
{
AssertNormalizeNoTrim(
[Test]
public void ListUnorderedLooseMultiParagraph()
{
AssertNormalizeNoTrim(
@"- a
And another paragraph a
@@ -182,22 +185,22 @@ line3");
And another paragraph b
- c");
}
}
[Test]
public void ListOrdered()
{
AssertNormalizeNoTrim(@"1. a
[Test]
public void ListOrdered()
{
AssertNormalizeNoTrim(@"1. a
2. b
3. c");
}
}
[Test]
public void ListOrderedAndIntended()
{
AssertNormalizeNoTrim(@"1. a
[Test]
public void ListOrderedAndIntended()
{
AssertNormalizeNoTrim(@"1. a
2. b
- foo
- bar
@@ -215,169 +218,169 @@ line3");
- Bar
11. c
12. c");
}
}
[Test]
public void HeaderAndParagraph()
{
AssertNormalizeNoTrim(@"# heading
[Test]
public void HeaderAndParagraph()
{
AssertNormalizeNoTrim(@"# heading
paragraph
paragraph2 without newlines");
}
}
[Test]
public void QuoteBlock()
{
AssertNormalizeNoTrim(@"> test1
[Test]
public void QuoteBlock()
{
AssertNormalizeNoTrim(@"> test1
>
> test2");
AssertNormalizeNoTrim(@"> test1
AssertNormalizeNoTrim(@"> test1
This is a continuation
> test2",
@"> test1
@"> test1
> This is a continuation
> test2"
);
AssertNormalizeNoTrim(@"> test1
AssertNormalizeNoTrim(@"> test1
> -foobar
asdf
> test2
> -foobar sen.");
}
}
[Test]
public void ThematicBreak()
{
AssertNormalizeNoTrim("***\n");
[Test]
public void ThematicBreak()
{
AssertNormalizeNoTrim("***\n");
AssertNormalizeNoTrim("* * *\n", "***\n");
}
AssertNormalizeNoTrim("* * *\n", "***\n");
}
[Test]
public void AutolinkInline()
{
AssertNormalizeNoTrim("This has a <auto.link.com>");
}
[Test]
public void AutolinkInline()
{
AssertNormalizeNoTrim("This has a <auto.link.com>");
}
[Test]
public void CodeInline()
{
AssertNormalizeNoTrim("This has a ` ` in it");
AssertNormalizeNoTrim("This has a `HelloWorld()` in it");
AssertNormalizeNoTrim(@"This has a ``Hello`World()`` in it");
AssertNormalizeNoTrim(@"This has a ``` Hello`World() ``` in it", @"This has a ``Hello`World()`` in it");
AssertNormalizeNoTrim(@"This has a ``Hello`World()` `` in it");
AssertNormalizeNoTrim(@"This has a ```` ``Hello```World()` ```` in it");
AssertNormalizeNoTrim(@"This has a `` `Hello`World()`` in it");
AssertNormalizeNoTrim(@"This has a ``` ``Hello`World()` ``` in it");
}
[Test]
public void CodeInline()
{
AssertNormalizeNoTrim("This has a ` ` in it");
AssertNormalizeNoTrim("This has a `HelloWorld()` in it");
AssertNormalizeNoTrim(@"This has a ``Hello`World()`` in it");
AssertNormalizeNoTrim(@"This has a ``` Hello`World() ``` in it", @"This has a ``Hello`World()`` in it");
AssertNormalizeNoTrim(@"This has a ``Hello`World()` `` in it");
AssertNormalizeNoTrim(@"This has a ```` ``Hello```World()` ```` in it");
AssertNormalizeNoTrim(@"This has a `` `Hello`World()`` in it");
AssertNormalizeNoTrim(@"This has a ``` ``Hello`World()` ``` in it");
}
[Test]
public void EmphasisInline()
{
AssertNormalizeNoTrim("This is a plain **paragraph**");
AssertNormalizeNoTrim("This is a plain *paragraph*");
AssertNormalizeNoTrim("This is a plain _paragraph_");
AssertNormalizeNoTrim("This is a plain __paragraph__");
AssertNormalizeNoTrim("This is a pl*ai*n **paragraph**");
}
[Test]
public void EmphasisInline()
{
AssertNormalizeNoTrim("This is a plain **paragraph**");
AssertNormalizeNoTrim("This is a plain *paragraph*");
AssertNormalizeNoTrim("This is a plain _paragraph_");
AssertNormalizeNoTrim("This is a plain __paragraph__");
AssertNormalizeNoTrim("This is a pl*ai*n **paragraph**");
}
[Test]
public void LineBreakInline()
{
AssertNormalizeNoTrim("normal\nline break");
AssertNormalizeNoTrim("hard \nline break");
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void LineBreakInline()
{
AssertNormalizeNoTrim("normal\nline break");
AssertNormalizeNoTrim("hard \nline break");
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void LinkInline()
{
AssertNormalizeNoTrim("This is a [link](http://company.com)");
AssertNormalizeNoTrim("This is an ![image](http://company.com)");
[Test]
public void LinkInline()
{
AssertNormalizeNoTrim("This is a [link](http://company.com)");
AssertNormalizeNoTrim("This is an ![image](http://company.com)");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy Company"")");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy \"" Company"")");
}
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy Company"")");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy \"" Company"")");
}
[Test]
public void LinkReferenceDefinition()
{
// Full link
AssertNormalizeNoTrim("This is a [link][MyLink]\n\n[MyLink]: http://company.com");
[Test]
public void LinkReferenceDefinition()
{
// Full link
AssertNormalizeNoTrim("This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("[MyLink]: http://company.com\nThis is a [link][MyLink]",
"This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("[MyLink]: http://company.com\nThis is a [link][MyLink]",
"This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("This is a [link][MyLink] a normal link [link](http://google.com) and another def link [link2][MyLink2]\n\n[MyLink]: http://company.com\n[MyLink2]: http://company2.com");
AssertNormalizeNoTrim("This is a [link][MyLink] a normal link [link](http://google.com) and another def link [link2][MyLink2]\n\n[MyLink]: http://company.com\n[MyLink2]: http://company2.com");
// Collapsed link
AssertNormalizeNoTrim("This is a [link][]\n\n[link]: http://company.com");
// Collapsed link
AssertNormalizeNoTrim("This is a [link][]\n\n[link]: http://company.com");
// Shortcut link
AssertNormalizeNoTrim("This is a [link]\n\n[link]: http://company.com");
}
// Shortcut link
AssertNormalizeNoTrim("This is a [link]\n\n[link]: http://company.com");
}
[Test]
public void EscapeInline()
{
AssertNormalizeNoTrim("This is an escape \\* with another \\[");
}
[Test]
public void EscapeInline()
{
AssertNormalizeNoTrim("This is an escape \\* with another \\[");
}
[Test]
public void HtmlEntityInline()
{
AssertNormalizeNoTrim("This is a &auml; blank");
}
[Test]
public void HtmlEntityInline()
{
AssertNormalizeNoTrim("This is a &auml; blank");
}
[Test]
public void HtmlInline()
{
AssertNormalizeNoTrim("foo <hr/> bar");
AssertNormalizeNoTrim(@"foo <hr foo=""bar""/> bar");
}
[Test]
public void HtmlInline()
{
AssertNormalizeNoTrim("foo <hr/> bar");
AssertNormalizeNoTrim(@"foo <hr foo=""bar""/> bar");
}
[Test]
public void SpaceBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void SpaceBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void SpaceBetweenNodesEvenForHeadlines()
{
AssertNormalizeNoTrim("# Hello World\n## Chapter 1\nFoobar is a better bar.",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceBetweenNodesEvenForHeadlines()
{
AssertNormalizeNoTrim("# Hello World\n## Chapter 1\nFoobar is a better bar.",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceRemoveAtStartAndEnd()
{
AssertNormalizeNoTrim("\n\n# Hello World\n## Chapter 1\nFoobar is a better bar.\n\n",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceRemoveAtStartAndEnd()
{
AssertNormalizeNoTrim("\n\n# Hello World\n## Chapter 1\nFoobar is a better bar.\n\n",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceShortenBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\n\n\n\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void SpaceShortenBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\n\n\n\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void BiggerSample()
{
var input = @"# Heading 1
[Test]
public void BiggerSample()
{
var input = @"# Heading 1
This is a paragraph
@@ -397,99 +400,100 @@ This is a code block
line 2 of indented
This is a last line";
AssertNormalizeNoTrim(input);
}
[Test]
public void TaskLists()
{
AssertNormalizeNoTrim("- [X] This is done");
AssertNormalizeNoTrim("- [x] This is done",
"- [X] This is done");
AssertNormalizeNoTrim("- [ ] This is not done");
// ignore
AssertNormalizeNoTrim("[x] This is not a task list");
AssertNormalizeNoTrim("[ ] This is not a task list");
}
[Test]
public void JiraLinks()
{
AssertNormalizeNoTrim("FOO-1234");
AssertNormalizeNoTrim("AB-1");
AssertNormalizeNoTrim("**Hello World AB-1**");
}
[Test]
public void AutoLinks()
{
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from [http://example.com/foo](http://example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from [www.example.com/foo](http://www.example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from ftp://example.com", "Hello from [ftp://example.com](ftp://example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from [hello@example.com](mailto:hello@example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from http://example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from http://www.example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from mailto:hello@example.com", new NormalizeOptions() { ExpandAutoLinks = false, });
}
private static void AssertSyntax(string expected, MarkdownObject syntax)
{
var writer = new StringWriter();
var normalizer = new NormalizeRenderer(writer);
var document = new MarkdownDocument();
if (syntax is Block)
{
document.Add(syntax as Block);
AssertNormalizeNoTrim(input);
}
else
[Test]
public void TaskLists()
{
throw new InvalidOperationException();
AssertNormalizeNoTrim("- [X] This is done");
AssertNormalizeNoTrim("- [x] This is done",
"- [X] This is done");
AssertNormalizeNoTrim("- [ ] This is not done");
// ignore
AssertNormalizeNoTrim("[x] This is not a task list");
AssertNormalizeNoTrim("[ ] This is not a task list");
}
normalizer.Render(document);
var actual = writer.ToString();
Assert.AreEqual(expected, actual);
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null, string context = null)
{
foreach (var pipeline in TestParser.GetPipeline(extensions))
[Test]
public void JiraLinks()
{
AssertNormalize(inputText, expectedOutputText, trim: false, pipeline: pipeline.Value, context: context);
AssertNormalizeNoTrim("FOO-1234");
AssertNormalizeNoTrim("AB-1");
AssertNormalizeNoTrim("**Hello World AB-1**");
}
}
public static void AssertNormalizeNoTrim(string input, string expected = null, NormalizeOptions options = null)
=> AssertNormalize(input, expected, false, options);
public static void AssertNormalize(string input, string expected = null, bool trim = true, NormalizeOptions options = null, MarkdownPipeline pipeline = null, string context = null)
{
expected = expected ?? input;
input = NormText(input, trim);
expected = NormText(expected, trim);
pipeline = pipeline ?? new MarkdownPipelineBuilder()
.UseAutoLinks()
.UseJiraLinks(new Extensions.JiraLinks.JiraLinkOptions("https://jira.example.com"))
.UseTaskLists()
.Build();
var result = Markdown.Normalize(input, options, pipeline: pipeline);
result = NormText(result, trim);
TestParser.PrintAssertExpected(input, result, expected, context);
}
private static string NormText(string text, bool trim)
{
if (trim)
[Test]
public void AutoLinks()
{
text = text.Trim();
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from [http://example.com/foo](http://example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from [www.example.com/foo](http://www.example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from ftp://example.com", "Hello from [ftp://example.com](ftp://example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from [hello@example.com](mailto:hello@example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from http://example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from http://www.example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from mailto:hello@example.com", new NormalizeOptions() { ExpandAutoLinks = false, });
}
private static void AssertSyntax(string expected, MarkdownObject syntax)
{
var writer = new StringWriter();
var normalizer = new NormalizeRenderer(writer);
var document = new MarkdownDocument();
if (syntax is Block)
{
document.Add(syntax as Block);
}
else
{
throw new InvalidOperationException();
}
normalizer.Render(document);
var actual = writer.ToString();
Assert.AreEqual(expected, actual);
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null, string context = null)
{
foreach (var pipeline in TestParser.GetPipeline(extensions))
{
AssertNormalize(inputText, expectedOutputText, trim: false, pipeline: pipeline.Value, context: context);
}
}
public static void AssertNormalizeNoTrim(string input, string expected = null, NormalizeOptions options = null)
=> AssertNormalize(input, expected, false, options);
public static void AssertNormalize(string input, string expected = null, bool trim = true, NormalizeOptions options = null, MarkdownPipeline pipeline = null, string context = null)
{
expected = expected ?? input;
input = NormText(input, trim);
expected = NormText(expected, trim);
pipeline = pipeline ?? new MarkdownPipelineBuilder()
.UseAutoLinks()
.UseJiraLinks(new Extensions.JiraLinks.JiraLinkOptions("https://jira.example.com"))
.UseTaskLists()
.Build();
var result = Markdown.Normalize(input, options, pipeline: pipeline);
result = NormText(result, trim);
TestParser.PrintAssertExpected(input, result, expected, context);
}
private static string NormText(string text, bool trim)
{
if (trim)
{
text = text.Trim();
}
return text.Replace("\r\n", "\n").Replace('\r', '\n');
}
return text.Replace("\r\n", "\n").Replace('\r', '\n');
}
}

View File

@@ -1,41 +1,43 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using NUnit.Framework;
namespace Markdig.Tests;
[TestFixture]
public class TestOrderedList
namespace Markdig.Tests
{
[Test]
public void TestReplace()
[TestFixture]
public class TestOrderedList
{
var list = new OrderedList<ITest>
[Test]
public void TestReplace()
{
new A(),
new B(),
new C(),
};
var list = new OrderedList<ITest>
{
new A(),
new B(),
new C(),
};
// Replacing B with D. Order should now be A, D, B.
var result = list.Replace<B>(new D());
Assert.That(result, Is.True);
Assert.That(list.Count, Is.EqualTo(3));
Assert.That(list[0], Is.InstanceOf<A>());
Assert.That(list[1], Is.InstanceOf<D>());
Assert.That(list[2], Is.InstanceOf<C>());
// Replacing B with D. Order should now be A, D, B.
var result = list.Replace<B>(new D());
Assert.That(result, Is.True);
Assert.That(list.Count, Is.EqualTo(3));
Assert.That(list[0], Is.InstanceOf<A>());
Assert.That(list[1], Is.InstanceOf<D>());
Assert.That(list[2], Is.InstanceOf<C>());
// Replacing B again should fail, as it's no longer in the list.
Assert.That(list.Replace<B>(new D()), Is.False);
// Replacing B again should fail, as it's no longer in the list.
Assert.That(list.Replace<B>(new D()), Is.False);
}
#region Test fixtures
private interface ITest { }
private class A : ITest { }
private class B : ITest { }
private class C : ITest { }
private class D : ITest { }
#endregion
}
#region Test fixtures
private interface ITest { }
private class A : ITest { }
private class B : ITest { }
private class C : ITest { }
private class D : ITest { }
#endregion
}

View File

@@ -1,196 +1,191 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestParser
namespace Markdig.Tests
{
[Test]
public void EnsureSpecsAreUpToDate()
public class TestParser
{
// In CI, SpecFileGen is guaranteed to run
if (IsContinuousIntegration)
return;
var specsFilePaths = Directory.GetDirectories(TestsDirectory)
.Where(dir => dir.EndsWith("Specs"))
.SelectMany(dir => Directory.GetFiles(dir)
.Where(file => file.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
.Where(file => file.IndexOf("readme", StringComparison.OrdinalIgnoreCase) == -1))
.ToArray();
var specsMarkdown = new string[specsFilePaths.Length];
var specsSyntaxTrees = new MarkdownDocument[specsFilePaths.Length];
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < specsFilePaths.Length; i++)
[Test]
public void EnsureSpecsAreUpToDate()
{
string markdown = specsMarkdown[i] = File.ReadAllText(specsFilePaths[i]);
specsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
// In CI, SpecFileGen is guaranteed to run
if (IsContinuousIntegration)
return;
foreach (var specFilePath in specsFilePaths)
{
string testFilePath = Path.ChangeExtension(specFilePath, ".generated.cs");
var specsFilePaths = Directory.GetDirectories(TestsDirectory)
.Where(dir => dir.EndsWith("Specs"))
.SelectMany(dir => Directory.GetFiles(dir)
.Where(file => file.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
.Where(file => file.IndexOf("readme", StringComparison.OrdinalIgnoreCase) == -1))
.ToArray();
Assert.True(File.Exists(testFilePath),
"A new specification file has been added. Add the spec to the list in SpecFileGen and regenerate the tests.");
var specsMarkdown = new string[specsFilePaths.Length];
var specsSyntaxTrees = new MarkdownDocument[specsFilePaths.Length];
DateTime specTime = File.GetLastWriteTimeUtc(specFilePath);
DateTime testTime = File.GetLastWriteTimeUtc(testFilePath);
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
// If file creation times aren't preserved by git, add some leeway
// If specs have come from git, assume that they were regenerated since CI would fail otherwise
testTime = testTime.AddMinutes(3);
// This might not catch a changed spec every time, but should at least sometimes. Otherwise CI will catch it
// This could also trigger, if a user has modified the spec file but reverted the change - can't think of a good workaround
Assert.Less(specTime, testTime,
$"{Path.GetFileName(specFilePath)} has been modified. Run SpecFileGen to regenerate the tests. " +
"If you have modified a specification file, but reverted all changes, ignore this error or revert the 'changed' timestamp metadata on the file.");
}
TestDescendantsOrder.TestSchemas(specsSyntaxTrees);
}
[Test]
public void ParseEmptyDocumentWithTrackTriviaEnabled()
{
var document = Markdown.Parse("", trackTrivia: true);
using var sw = new StringWriter();
new RoundtripRenderer(sw).Render(document);
Assert.AreEqual("", sw.ToString());
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null, bool plainText = false, string context = null)
{
context ??= string.Empty;
if (!string.IsNullOrEmpty(context))
{
context += "\n";
}
foreach (var pipeline in GetPipeline(extensions))
{
TestSpec(inputText, expectedOutputText, pipeline.Value, plainText, context: context + $"Pipeline configured with extensions: {pipeline.Key}");
}
}
public static void TestSpec(string inputText, string expectedOutputText, MarkdownPipeline pipeline, bool plainText = false, string context = null)
{
// Uncomment this line to get more debug information for process inlines.
//pipeline.DebugLog = Console.Out;
var result = plainText ? Markdown.ToPlainText(inputText, pipeline) : Markdown.ToHtml(inputText, pipeline);
result = Compact(result);
expectedOutputText = Compact(expectedOutputText);
PrintAssertExpected(inputText, result, expectedOutputText, context);
}
public static void PrintAssertExpected(string source, string result, string expected, string context = null)
{
if (expected != result)
{
if (context != null)
for (int i = 0; i < specsFilePaths.Length; i++)
{
Console.WriteLine(context);
string markdown = specsMarkdown[i] = File.ReadAllText(specsFilePaths[i]);
specsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
Console.WriteLine("```````````````````Source");
Console.WriteLine(DisplaySpaceAndTabs(source));
Console.WriteLine("```````````````````Result");
Console.WriteLine(DisplaySpaceAndTabs(result));
Console.WriteLine("```````````````````Expected");
Console.WriteLine(DisplaySpaceAndTabs(expected));
Console.WriteLine("```````````````````");
Console.WriteLine();
TextAssert.AreEqual(expected, result);
}
}
public static IEnumerable<KeyValuePair<string, MarkdownPipeline>> GetPipeline(string extensionsGroupText)
{
// For the standard case, we make sure that both the CommmonMark core and Extra/Advanced are CommonMark compliant!
if (string.IsNullOrEmpty(extensionsGroupText))
{
yield return new KeyValuePair<string, MarkdownPipeline>("default", new MarkdownPipelineBuilder().Build());
//yield return new KeyValuePair<string, MarkdownPipeline>("default-trivia", new MarkdownPipelineBuilder().EnableTrackTrivia().Build());
yield return new KeyValuePair<string, MarkdownPipeline>("advanced", new MarkdownPipelineBuilder() // Use similar to advanced extension without auto-identifier
.UseAbbreviations()
//.UseAutoIdentifiers()
.UseCitations()
.UseCustomContainers()
.UseDefinitionLists()
.UseEmphasisExtras()
.UseFigures()
.UseFooters()
.UseFootnotes()
.UseGridTables()
.UseMathematics()
.UseMediaLinks()
.UsePipeTables()
.UseListExtras()
.UseGenericAttributes().Build());
yield break;
}
var extensionGroups = extensionsGroupText.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var extensionsText in extensionGroups)
{
var builder = new MarkdownPipelineBuilder();
builder.DebugLog = Console.Out;
if (extensionsText == "jiralinks")
foreach (var specFilePath in specsFilePaths)
{
builder.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"));
string testFilePath = Path.ChangeExtension(specFilePath, ".generated.cs");
Assert.True(File.Exists(testFilePath),
"A new specification file has been added. Add the spec to the list in SpecFileGen and regenerate the tests.");
DateTime specTime = File.GetLastWriteTimeUtc(specFilePath);
DateTime testTime = File.GetLastWriteTimeUtc(testFilePath);
// If file creation times aren't preserved by git, add some leeway
// If specs have come from git, assume that they were regenerated since CI would fail otherwise
testTime = testTime.AddMinutes(3);
// This might not catch a changed spec every time, but should at least sometimes. Otherwise CI will catch it
// This could also trigger, if a user has modified the spec file but reverted the change - can't think of a good workaround
Assert.Less(specTime, testTime,
$"{Path.GetFileName(specFilePath)} has been modified. Run SpecFileGen to regenerate the tests. " +
"If you have modified a specification file, but reverted all changes, ignore this error or revert the 'changed' timestamp metadata on the file.");
}
else
{
builder = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
}
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, builder.Build());
TestDescendantsOrder.TestSchemas(specsSyntaxTrees);
}
}
public static string DisplaySpaceAndTabs(string text)
{
// Output special characters to check correctly the results
return text.Replace('\t', '→').Replace(' ', '·');
}
private static string Compact(string html)
{
// Normalize the output to make it compatible with CommonMark specs
html = html.Replace("\r\n", "\n").Replace(@"\r", @"\n").Trim();
html = Regex.Replace(html, @"\s+</li>", "</li>");
html = Regex.Replace(html, @"<li>\s+", "<li>");
html = html.Normalize(NormalizationForm.FormKD);
return html;
}
public static readonly bool IsContinuousIntegration = Environment.GetEnvironmentVariable("CI") != null;
public static readonly string TestsDirectory = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(TestParser).Assembly.Location), "../../.."));
static TestParser()
{
const string RunningInsideVisualStudioPath = "\\src\\.vs\\markdig\\";
int index = TestsDirectory.IndexOf(RunningInsideVisualStudioPath);
if (index != -1)
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null, bool plainText = false, string context = null)
{
TestsDirectory = TestsDirectory.Substring(0, index) + "\\src\\Markdig.Tests";
context ??= string.Empty;
if (!string.IsNullOrEmpty(context))
{
context += "\n";
}
foreach (var pipeline in GetPipeline(extensions))
{
TestSpec(inputText, expectedOutputText, pipeline.Value, plainText, context: context + $"Pipeline configured with extensions: {pipeline.Key}");
}
}
public static void TestSpec(string inputText, string expectedOutputText, MarkdownPipeline pipeline, bool plainText = false, string context = null)
{
// Uncomment this line to get more debug information for process inlines.
//pipeline.DebugLog = Console.Out;
var result = plainText ? Markdown.ToPlainText(inputText, pipeline) : Markdown.ToHtml(inputText, pipeline);
result = Compact(result);
expectedOutputText = Compact(expectedOutputText);
PrintAssertExpected(inputText, result, expectedOutputText, context);
}
public static void PrintAssertExpected(string source, string result, string expected, string context = null)
{
if (expected != result)
{
if (context != null)
{
Console.WriteLine(context);
}
Console.WriteLine("```````````````````Source");
Console.WriteLine(DisplaySpaceAndTabs(source));
Console.WriteLine("```````````````````Result");
Console.WriteLine(DisplaySpaceAndTabs(result));
Console.WriteLine("```````````````````Expected");
Console.WriteLine(DisplaySpaceAndTabs(expected));
Console.WriteLine("```````````````````");
Console.WriteLine();
TextAssert.AreEqual(expected, result);
}
}
public static IEnumerable<KeyValuePair<string, MarkdownPipeline>> GetPipeline(string extensionsGroupText)
{
// For the standard case, we make sure that both the CommmonMark core and Extra/Advanced are CommonMark compliant!
if (string.IsNullOrEmpty(extensionsGroupText))
{
yield return new KeyValuePair<string, MarkdownPipeline>("default", new MarkdownPipelineBuilder().Build());
//yield return new KeyValuePair<string, MarkdownPipeline>("default-trivia", new MarkdownPipelineBuilder().EnableTrackTrivia().Build());
yield return new KeyValuePair<string, MarkdownPipeline>("advanced", new MarkdownPipelineBuilder() // Use similar to advanced extension without auto-identifier
.UseAbbreviations()
//.UseAutoIdentifiers()
.UseCitations()
.UseCustomContainers()
.UseDefinitionLists()
.UseEmphasisExtras()
.UseFigures()
.UseFooters()
.UseFootnotes()
.UseGridTables()
.UseMathematics()
.UseMediaLinks()
.UsePipeTables()
.UseListExtras()
.UseGenericAttributes().Build());
yield break;
}
var extensionGroups = extensionsGroupText.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var extensionsText in extensionGroups)
{
var builder = new MarkdownPipelineBuilder();
builder.DebugLog = Console.Out;
if (extensionsText == "jiralinks")
{
builder.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"));
}
else
{
builder = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
}
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, builder.Build());
}
}
public static string DisplaySpaceAndTabs(string text)
{
// Output special characters to check correctly the results
return text.Replace('\t', '→').Replace(' ', '·');
}
private static string Compact(string html)
{
// Normalize the output to make it compatible with CommonMark specs
html = html.Replace("\r\n", "\n").Replace(@"\r", @"\n").Trim();
html = Regex.Replace(html, @"\s+</li>", "</li>");
html = Regex.Replace(html, @"<li>\s+", "<li>");
html = html.Normalize(NormalizationForm.FormKD);
return html;
}
public static readonly bool IsContinuousIntegration = Environment.GetEnvironmentVariable("CI") != null;
public static readonly string TestsDirectory = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(TestParser).Assembly.Location), "../../.."));
static TestParser()
{
const string RunningInsideVisualStudioPath = "\\src\\.vs\\markdig\\";
int index = TestsDirectory.IndexOf(RunningInsideVisualStudioPath);
if (index != -1)
{
TestsDirectory = TestsDirectory.Substring(0, index) + "\\src\\Markdig.Tests";
}
}
}
}

View File

@@ -1,21 +1,26 @@
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using NUnit.Framework;
using System.Linq;
namespace Markdig.Tests;
[TestFixture]
public sealed class TestPipeTable
namespace Markdig.Tests
{
[TestCase("| S | T |\r\n|---|---| \r\n| G | H |")]
[TestCase("| S | T |\r\n|---|---|\t\r\n| G | H |")]
[TestCase("| S | T |\r\n|---|---|\f\r\n| G | H |")]
[TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)]
public void TestTableBug(string markdown, int tableCount = 1)
[TestFixture]
public sealed class TestPipeTable
{
MarkdownDocument document = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
[TestCase("| S | T |\r\n|---|---| \r\n| G | H |")]
[TestCase("| S | T |\r\n|---|---|\t\r\n| G | H |")]
[TestCase("| S | T |\r\n|---|---|\v\r\n| G | H |")]
[TestCase("| S | T |\r\n|---|---|\f\r\n| G | H |")]
[TestCase("| S | T |\r\n|---|---|\f\v\t \r\n| G | H |")]
[TestCase("| S | \r\n|---|\r\n| G |\r\n\r\n| D | D |\r\n| ---| ---| \r\n| V | V |", 2)]
public void TestTableBug(string markdown, int tableCount = 1)
{
MarkdownDocument document = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
Table[] tables = document.Descendants().OfType<Table>().ToArray();
Table[] tables = document.Descendants().OfType<Table>().ToArray();
Assert.AreEqual(tableCount, tables.Length);
Assert.AreEqual(tableCount, tables.Length);
}
}
}

View File

@@ -1,42 +1,45 @@
namespace Markdig.Tests;
using NUnit.Framework;
[TestFixture]
public class TestPlainText
namespace Markdig.Tests
{
[Test]
[TestCase(/* markdownText: */ "foo bar", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "foo\nbar", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "*foo\nbar*", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "[foo\nbar](http://example.com)", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "<http://foo.bar.baz>", /* expected: */ "http://foo.bar.baz\n")]
[TestCase(/* markdownText: */ "# foo bar", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "# foo\nbar", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "> foo", /* expected: */ "foo\n")]
[TestCase(/* markdownText: */ "> foo\nbar\n> baz", /* expected: */ "foo\nbar\nbaz\n")]
[TestCase(/* markdownText: */ "`foo`", /* expected: */ "foo\n")]
[TestCase(/* markdownText: */ "`foo\nbar`", /* expected: */ "foo bar\n")] // new line within codespan is treated as whitespace (Example317)
[TestCase(/* markdownText: */ "```\nfoo bar\n```", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "- foo\n- bar\n- baz", /* expected: */ "foo\nbar\nbaz\n")]
[TestCase(/* markdownText: */ "- foo<baz", /* expected: */ "foo<baz\n")]
[TestCase(/* markdownText: */ "- foo&lt;baz", /* expected: */ "foo<baz\n")]
[TestCase(/* markdownText: */ "## foo `bar::baz >`", /* expected: */ "foo bar::baz >\n")]
public void TestPlainEnsureNewLine(string markdownText, string expected)
[TestFixture]
public class TestPlainText
{
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(/* markdownText: */ "foo bar", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "foo\nbar", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "*foo\nbar*", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "[foo\nbar](http://example.com)", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "<http://foo.bar.baz>", /* expected: */ "http://foo.bar.baz\n")]
[TestCase(/* markdownText: */ "# foo bar", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "# foo\nbar", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "> foo", /* expected: */ "foo\n")]
[TestCase(/* markdownText: */ "> foo\nbar\n> baz", /* expected: */ "foo\nbar\nbaz\n")]
[TestCase(/* markdownText: */ "`foo`", /* expected: */ "foo\n")]
[TestCase(/* markdownText: */ "`foo\nbar`", /* expected: */ "foo bar\n")] // new line within codespan is treated as whitespace (Example317)
[TestCase(/* markdownText: */ "```\nfoo bar\n```", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "- foo\n- bar\n- baz", /* expected: */ "foo\nbar\nbaz\n")]
[TestCase(/* markdownText: */ "- foo<baz", /* expected: */ "foo<baz\n")]
[TestCase(/* markdownText: */ "- foo&lt;baz", /* expected: */ "foo<baz\n")]
[TestCase(/* markdownText: */ "## foo `bar::baz >`", /* expected: */ "foo bar::baz >\n")]
public void TestPlainEnsureNewLine(string markdownText, string expected)
{
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(/* markdownText: */ ":::\nfoo\n:::", /* expected: */ "foo\n", /*extensions*/ "customcontainers|advanced")]
[TestCase(/* markdownText: */ ":::bar\nfoo\n:::", /* expected: */ "foo\n", /*extensions*/ "customcontainers+attributes|advanced")]
[TestCase(/* markdownText: */ "| Header1 | Header2 | Header3 |\n|--|--|--|\nt**es**t|value2|value3", /* expected: */ "Header1 Header2 Header3 test value2 value3","pipetables")]
public void TestPlainWithExtensions(string markdownText, string expected, string extensions)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true);
}
[Test]
[TestCase(/* markdownText: */ ":::\nfoo\n:::", /* expected: */ "foo\n", /*extensions*/ "customcontainers|advanced")]
[TestCase(/* markdownText: */ ":::bar\nfoo\n:::", /* expected: */ "foo\n", /*extensions*/ "customcontainers+attributes|advanced")]
[TestCase(/* markdownText: */ "| Header1 | Header2 | Header3 |\n|--|--|--|\nt**es**t|value2|value3", /* expected: */ "Header1 Header2 Header3 test value2 value3","pipetables")]
public void TestPlainWithExtensions(string markdownText, string expected, string extensions)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true);
}
public static void TestSpec(string markdownText, string expected, string extensions, string context = null)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true, context: context);
public static void TestSpec(string markdownText, string expected, string extensions, string context = null)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true, context: context);
}
}
}

View File

@@ -1,94 +1,97 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Linq;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests;
[TestFixture]
public class TestPlayParser
namespace Markdig.Tests
{
[Test]
public void TestBugWithEmphasisAndTable()
[TestFixture]
public class TestPlayParser
{
TestParser.TestSpec("**basics | 8:00**", "<p><strong>basics | 8:00</strong></p>", "advanced");
}
[Test]
public void TestBugWithEmphasisAndTable()
{
TestParser.TestSpec("**basics | 8:00**", "<p><strong>basics | 8:00</strong></p>", "advanced");
}
[Test]
public void TestLinksWithCarriageReturn()
{
var text = "[Link 1][link-1], [link 2][link-2].\r\n\r\n[link-1]: https://example.com\r\n[link-2]: https://example.com";
var result = Markdown.ToHtml(text).TrimEnd();
Assert.AreEqual("<p><a href=\"https://example.com\">Link 1</a>, <a href=\"https://example.com\">link 2</a>.</p>", result);
}
[Test]
public void TestLinksWithCarriageReturn()
{
var text = "[Link 1][link-1], [link 2][link-2].\r\n\r\n[link-1]: https://example.com\r\n[link-2]: https://example.com";
var result = Markdown.ToHtml(text).TrimEnd();
Assert.AreEqual("<p><a href=\"https://example.com\">Link 1</a>, <a href=\"https://example.com\">link 2</a>.</p>", result);
}
[Test]
public void TestLinksWithTitleAndCarriageReturn()
{
var text = "[Link 1][link-1], [link 2][link-2].\r\n\r\n[link-1]: https://example.com \"title 1\" \r\n[link-2]: https://example.com \"title 2\"";
var result = Markdown.ToHtml(text).TrimEnd();
Assert.AreEqual("<p><a href=\"https://example.com\" title=\"title 1\">Link 1</a>, <a href=\"https://example.com\" title=\"title 2\">link 2</a>.</p>", result);
}
[Test]
public void TestLinksWithTitleAndCarriageReturn()
{
var text = "[Link 1][link-1], [link 2][link-2].\r\n\r\n[link-1]: https://example.com \"title 1\" \r\n[link-2]: https://example.com \"title 2\"";
var result = Markdown.ToHtml(text).TrimEnd();
Assert.AreEqual("<p><a href=\"https://example.com\" title=\"title 1\">Link 1</a>, <a href=\"https://example.com\" title=\"title 2\">link 2</a>.</p>", result);
}
[Test]
public void TestLink()
{
var doc = Markdown.Parse("There is a ![link](/yoyo)");
var link = doc.Descendants<ParagraphBlock>().SelectMany(x => x.Inline.Descendants<LinkInline>()).FirstOrDefault(l => l.IsImage);
Assert.AreEqual("/yoyo", link?.Url);
}
[Test]
public void TestLink()
{
var doc = Markdown.Parse("There is a ![link](/yoyo)");
var link = doc.Descendants<ParagraphBlock>().SelectMany(x => x.Inline.Descendants<LinkInline>()).FirstOrDefault(l => l.IsImage);
Assert.AreEqual("/yoyo", link?.Url);
}
[Test]
public void TestListBug2()
{
TestParser.TestSpec("10.\t*test* test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
[Test]
public void TestListBug2()
{
TestParser.TestSpec("10.\t*test* test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
<li><p><em>test</em> test</p>
</li>
<li><p><strong>test</strong> test</p>
</li>
</ol>
");
}
}
[Test]
public void TestSimple()
{
var text = @" *[HTML]: Hypertext Markup Language
[Test]
public void TestSimple()
{
var text = @" *[HTML]: Hypertext Markup Language
Later in a text we are using HTML and it becomes an abbr tag HTML
";
// var reader = new StringReader(@"> > toto tata
//> titi toto
//");
// var reader = new StringReader(@"> > toto tata
//> titi toto
//");
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtras());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviations().Build());
//File.WriteAllText("test.html", result, Encoding.UTF8);
//Console.WriteLine(result);
}
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtras());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviations().Build());
//File.WriteAllText("test.html", result, Encoding.UTF8);
//Console.WriteLine(result);
}
[Test]
public void TestEmptyLiteral()
{
var text = @"> *some text*
[Test]
public void TestEmptyLiteral()
{
var text = @"> *some text*
> some other text";
var doc = Markdown.Parse(text);
var doc = Markdown.Parse(text);
Assert.True(doc.Descendants<LiteralInline>().All(x => !x.Content.IsEmpty),
"There should not have any empty literals");
}
Assert.True(doc.Descendants<LiteralInline>().All(x => !x.Content.IsEmpty),
"There should not have any empty literals");
}
[Test]
public void TestSelfPipeline1()
{
var text = @" <!--markdig:pipetables-->
[Test]
public void TestSelfPipeline1()
{
var text = @" <!--markdig:pipetables-->
a | b
- | -
0 | 1
";
TestParser.TestSpec(text, @"<!--markdig:pipetables-->
TestParser.TestSpec(text, @"<!--markdig:pipetables-->
<table>
<thead>
<tr>
@@ -104,17 +107,17 @@ a | b
</tbody>
</table>
", "self");
}
}
[Test]
public void TestListBug()
{
// TODO: Add this test back to the CommonMark specs
var text = @"- item1
[Test]
public void TestListBug()
{
// TODO: Add this test back to the CommonMark specs
var text = @"- item1
- item2
- item3
- item4";
TestParser.TestSpec(text, @"<ul>
TestParser.TestSpec(text, @"<ul>
<li>item1
<ul>
<li>item2
@@ -127,13 +130,13 @@ a | b
</ul></li>
</ul>
");
}
}
[Test]
public void TestHtmlBug()
{
TestParser.TestSpec(@" # header1
[Test]
public void TestHtmlBug()
{
TestParser.TestSpec(@" # header1
<pre class='copy'>
blabla
@@ -145,43 +148,43 @@ blabla
blabla
</pre>
<h1>header2</h1>");
}
}
[Test]
public void TestHtmlh4Bug()
{
TestParser.TestSpec(@"<h4>foobar</h4>", @"<h4>foobar</h4>");
}
[Test]
public void TestHtmlh4Bug()
{
TestParser.TestSpec(@"<h4>foobar</h4>", @"<h4>foobar</h4>");
}
[Test]
public void TestStandardUriEscape()
{
TestParser.TestSpec(@"![你好](你好.png)", "<p><img src=\"你好.png\" alt=\"你好\" /></p>", "nonascii-noescape");
}
[Test]
public void TestStandardUriEscape()
{
TestParser.TestSpec(@"![你好](你好.png)", "<p><img src=\"你好.png\" alt=\"你好\" /></p>", "nonascii-noescape");
}
[Test]
public void TestBugAdvanced()
{
TestParser.TestSpec(@"`https://{domain}/callbacks`
[Test]
public void TestBugAdvanced()
{
TestParser.TestSpec(@"`https://{domain}/callbacks`
#### HEADING
Paragraph
", "<p><code>https://{domain}/callbacks</code></p>\n<h4 id=\"heading\">HEADING</h4>\n<p>Paragraph</p>", "advanced");
}
}
[Test]
public void TestBugEmphAttribute()
{
// https://github.com/lunet-io/markdig/issues/108
TestParser.TestSpec(@"*test*{name=value}", "<p><em name=\"value\">test</em></p>", "advanced");
}
[Test]
public void TestBugEmphAttribute()
{
// https://github.com/lunet-io/markdig/issues/108
TestParser.TestSpec(@"*test*{name=value}", "<p><em name=\"value\">test</em></p>", "advanced");
}
[Test]
public void TestBugPipeTables()
{
// https://github.com/lunet-io/markdig/issues/73
TestParser.TestSpec(@"| abc | def |
[Test]
public void TestBugPipeTables()
{
// https://github.com/lunet-io/markdig/issues/73
TestParser.TestSpec(@"| abc | def |
| --- | --- |
| 1 | ~3 |
", @"<table>
@@ -198,12 +201,12 @@ Paragraph
</tr>
</tbody>
</table>", "advanced");
}
}
[Test]
public void TestGridTableWithCustomAttributes() {
[Test]
public void TestGridTableWithCustomAttributes() {
var input = @"
var input = @"
{.table}
+---+---+
| a | b |
@@ -212,7 +215,7 @@ Paragraph
+---+---+
";
var expected = @"<table class=""table"">
var expected = @"<table class=""table"">
<col style=""width:50%"" />
<col style=""width:50%"" />
<thead>
@@ -229,75 +232,76 @@ Paragraph
</tbody>
</table>
";
TestParser.TestSpec(input, expected, "advanced");
TestParser.TestSpec(input, expected, "advanced");
}
[Test]
public void TestSamePipelineAllExtensions()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
// Reuse the same pipeline
var result1 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
var result2 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
Assert.AreEqual("<p>This is a <cite>citation</cite></p>", result1.Trim());
Assert.AreEqual(result1, result2);
}
// Test for emoji and smileys
// var text = @" This is a test with a :) and a :angry: smiley";
// Test for definition lists:
//
// var text = @"
//Term 1
//: This is a definition item
// With a paragraph
// > This is a block quote
// - This is a list
// - item2
// ```java
// Test
// ```
// And a lazy line
//: This ia another definition item
//Term2
//Term3 *with some inline*
//: This is another definition for term2
//";
// Test for grid table
// var text = @"
//+-----------------------------------+--------------------------------------+
//| - this is a list | > We have a blockquote
//| - this is a second item |
//| |
//| ``` |
//| Yes |
//| ``` |
//+===================================+======================================+
//| This is a second line |
//+-----------------------------------+--------------------------------------+
//:::spoiler {#yessss}
//This is a spoiler
//:::
///| we have mult | paragraph |
///| we have a new colspan with a long line
///| and lots of text
//";
}
[Test]
public void TestSamePipelineAllExtensions()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
// Reuse the same pipeline
var result1 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
var result2 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
Assert.AreEqual("<p>This is a <cite>citation</cite></p>", result1.Trim());
Assert.AreEqual(result1, result2);
}
// Test for emoji and smileys
// var text = @" This is a test with a :) and a :angry: smiley";
// Test for definition lists:
//
// var text = @"
//Term 1
//: This is a definition item
// With a paragraph
// > This is a block quote
// - This is a list
// - item2
// ```java
// Test
// ```
// And a lazy line
//: This ia another definition item
//Term2
//Term3 *with some inline*
//: This is another definition for term2
//";
// Test for grid table
// var text = @"
//+-----------------------------------+--------------------------------------+
//| - this is a list | > We have a blockquote
//| - this is a second item |
//| |
//| ``` |
//| Yes |
//| ``` |
//+===================================+======================================+
//| This is a second line |
//+-----------------------------------+--------------------------------------+
//:::spoiler {#yessss}
//This is a spoiler
//:::
///| we have mult | paragraph |
///| we have a new colspan with a long line
///| and lots of text
//";
}

View File

@@ -1,18 +1,19 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests;
[TestFixture]
public class TestPragmaLines
namespace Markdig.Tests
{
[Test]
public void TestFindClosest()
[TestFixture]
public class TestPragmaLines
{
var doc = Markdown.Parse(
[Test]
public void TestFindClosest()
{
var doc = Markdown.Parse(
"test1\n" + // 0
"\n" + // 1
"test2\n" + // 2
@@ -36,27 +37,27 @@ public class TestPragmaLines
"- item3\n" + // 20
"\n" + // 21
"This is a last paragraph\n" // 22
, new MarkdownPipelineBuilder().UsePragmaLines().Build());
, new MarkdownPipelineBuilder().UsePragmaLines().Build());
foreach (var exact in new int[] {0, 2, 4, 6, 8, 10, 18, 19, 20, 22})
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
foreach (var exact in new int[] {0, 2, 4, 6, 8, 10, 18, 19, 20, 22})
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
}
Assert.AreEqual(22, doc.FindClosestLine(23));
Assert.AreEqual(10, doc.FindClosestLine(11));
Assert.AreEqual(10, doc.FindClosestLine(12));
Assert.AreEqual(10, doc.FindClosestLine(13));
Assert.AreEqual(18, doc.FindClosestLine(14)); // > 50% of the paragraph, we switch to next
Assert.AreEqual(18, doc.FindClosestLine(15));
Assert.AreEqual(18, doc.FindClosestLine(16));
}
Assert.AreEqual(22, doc.FindClosestLine(23));
Assert.AreEqual(10, doc.FindClosestLine(11));
Assert.AreEqual(10, doc.FindClosestLine(12));
Assert.AreEqual(10, doc.FindClosestLine(13));
Assert.AreEqual(18, doc.FindClosestLine(14)); // > 50% of the paragraph, we switch to next
Assert.AreEqual(18, doc.FindClosestLine(15));
Assert.AreEqual(18, doc.FindClosestLine(16));
}
[Test]
public void TestFindClosest1()
{
var text =
[Test]
public void TestFindClosest1()
{
var text =
"- item1\n" + // 0
" - item11\n" + // 1
" - item12\n" + // 2
@@ -65,14 +66,15 @@ public class TestPragmaLines
" - item131\n" + // 5
" - item1311\n"; // 6
var pipeline = new MarkdownPipelineBuilder().UsePragmaLines().Build();
var doc = Markdown.Parse(text, pipeline);
var pipeline = new MarkdownPipelineBuilder().UsePragmaLines().Build();
var doc = Markdown.Parse(text, pipeline);
for (int exact = 0; exact < 7; exact++)
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
for (int exact = 0; exact < 7; exact++)
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
}
Assert.AreEqual(6, doc.FindClosestLine(50));
}
Assert.AreEqual(6, doc.FindClosestLine(50));
}
}

View File

@@ -1,54 +1,57 @@
namespace Markdig.Tests;
using NUnit.Framework;
[TestFixture]
public class TestReferralLinks
namespace Markdig.Tests
{
[Test]
public void TestLinksWithNoFollowRel()
[TestFixture]
public class TestReferralLinks
{
var markdown = "[world](http://example.com)";
var expected = "nofollow";
[Test]
public void TestLinksWithNoFollowRel()
{
var markdown = "[world](http://example.com)";
var expected = "nofollow";
#pragma warning disable 0618
var pipeline = new MarkdownPipelineBuilder()
.UseNoFollowLinks()
var pipeline = new MarkdownPipelineBuilder()
.UseNoFollowLinks()
#pragma warning restore 0618
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
}
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
}
[Test]
[TestCase(new[] { "nofollow" }, "nofollow")]
[TestCase(new[] { "noopener" }, "noopener")]
[TestCase(new[] { "nofollow", "noopener"}, "nofollow noopener")]
public void TestLinksWithCustomRel(string[] rels, string expected)
{
var markdown = "[world](http://example.com)";
[Test]
[TestCase(new[] { "nofollow" }, "nofollow")]
[TestCase(new[] { "noopener" }, "noopener")]
[TestCase(new[] { "nofollow", "noopener"}, "nofollow noopener")]
public void TestLinksWithCustomRel(string[] rels, string expected)
{
var markdown = "[world](http://example.com)";
var pipeline = new MarkdownPipelineBuilder()
.UseReferralLinks(rels)
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
var pipeline = new MarkdownPipelineBuilder()
.UseReferralLinks(rels)
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
}
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
}
[Test]
[TestCase(new[] { "noopener" }, "noopener")]
[TestCase(new[] { "nofollow" }, "nofollow")]
[TestCase(new[] { "nofollow", "noopener" }, "nofollow noopener")]
public void TestAutoLinksWithCustomRel(string[] rels, string expected)
{
var markdown = "http://example.com";
[Test]
[TestCase(new[] { "noopener" }, "noopener")]
[TestCase(new[] { "nofollow" }, "nofollow")]
[TestCase(new[] { "nofollow", "noopener" }, "nofollow noopener")]
public void TestAutoLinksWithCustomRel(string[] rels, string expected)
{
var markdown = "http://example.com";
var pipeline = new MarkdownPipelineBuilder()
.UseAutoLinks()
.UseReferralLinks(rels)
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
var pipeline = new MarkdownPipelineBuilder()
.UseAutoLinks()
.UseReferralLinks(rels)
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
}
}
}

View File

@@ -1,44 +1,48 @@
using System;
using System.IO;
using Markdig.Parsers;
using Markdig.Renderers;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestRelativeUrlReplacement
namespace Markdig.Tests
{
[Test]
public void ReplacesRelativeLinks()
public class TestRelativeUrlReplacement
{
TestSpec("https://example.com", "Link: [hello](/relative.jpg)", "https://example.com/relative.jpg");
TestSpec("https://example.com", "Link: [hello](relative.jpg)", "https://example.com/relative.jpg");
TestSpec("https://example.com/", "Link: [hello](/relative.jpg?a=b)", "https://example.com/relative.jpg?a=b");
TestSpec("https://example.com/", "Link: [hello](relative.jpg#x)", "https://example.com/relative.jpg#x");
TestSpec(null, "Link: [hello](relative.jpg)", "relative.jpg");
TestSpec(null, "Link: [hello](/relative.jpg)", "/relative.jpg");
TestSpec("https://example.com", "Link: [hello](/relative.jpg)", "https://example.com/relative.jpg");
}
[Test]
public void ReplacesRelativeLinks()
{
TestSpec("https://example.com", "Link: [hello](/relative.jpg)", "https://example.com/relative.jpg");
TestSpec("https://example.com", "Link: [hello](relative.jpg)", "https://example.com/relative.jpg");
TestSpec("https://example.com/", "Link: [hello](/relative.jpg?a=b)", "https://example.com/relative.jpg?a=b");
TestSpec("https://example.com/", "Link: [hello](relative.jpg#x)", "https://example.com/relative.jpg#x");
TestSpec(null, "Link: [hello](relative.jpg)", "relative.jpg");
TestSpec(null, "Link: [hello](/relative.jpg)", "/relative.jpg");
TestSpec("https://example.com", "Link: [hello](/relative.jpg)", "https://example.com/relative.jpg");
}
[Test]
public void ReplacesRelativeImageSources()
{
TestSpec("https://example.com", "Image: ![alt text](/image.jpg)", "https://example.com/image.jpg");
TestSpec("https://example.com", "Image: ![alt text](image.jpg \"title\")", "https://example.com/image.jpg");
TestSpec(null, "Image: ![alt text](/image.jpg)", "/image.jpg");
}
[Test]
public void ReplacesRelativeImageSources()
{
TestSpec("https://example.com", "Image: ![alt text](/image.jpg)", "https://example.com/image.jpg");
TestSpec("https://example.com", "Image: ![alt text](image.jpg \"title\")", "https://example.com/image.jpg");
TestSpec(null, "Image: ![alt text](/image.jpg)", "/image.jpg");
}
public static void TestSpec(string baseUrl, string markdown, string expectedLink)
{
var pipeline = new MarkdownPipelineBuilder().Build();
public static void TestSpec(string baseUrl, string markdown, string expectedLink)
{
var pipeline = new MarkdownPipelineBuilder().Build();
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
if (baseUrl != null)
renderer.BaseUrl = new Uri(baseUrl);
pipeline.Setup(renderer);
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
if (baseUrl != null)
renderer.BaseUrl = new Uri(baseUrl);
pipeline.Setup(renderer);
var document = MarkdownParser.Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
var document = MarkdownParser.Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
Assert.That(writer.ToString(), Contains.Substring("=\"" + expectedLink + "\""));
}
}
}

View File

@@ -1,29 +1,32 @@
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests;
internal static class TestRoundtrip
namespace Markdig.Tests
{
internal static void TestSpec(string markdownText, string expected, string extensions, string context = null)
internal static class TestRoundtrip
{
RoundTrip(markdownText, context);
}
internal static void TestSpec(string markdownText, string expected, string extensions, string context = null)
{
RoundTrip(markdownText, context);
}
internal static void RoundTrip(string markdown, string context = null)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
pipelineBuilder.UseYamlFrontMatter();
MarkdownPipeline pipeline = pipelineBuilder.Build();
MarkdownDocument markdownDocument = Markdown.Parse(markdown, pipeline);
var sw = new StringWriter();
var nr = new RoundtripRenderer(sw);
pipeline.Setup(nr);
internal static void RoundTrip(string markdown, string context = null)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
pipelineBuilder.UseYamlFrontMatter();
MarkdownPipeline pipeline = pipelineBuilder.Build();
MarkdownDocument markdownDocument = Markdown.Parse(markdown, pipeline);
var sw = new StringWriter();
var nr = new RoundtripRenderer(sw);
pipeline.Setup(nr);
nr.Write(markdownDocument);
nr.Write(markdownDocument);
var result = sw.ToString();
TestParser.PrintAssertExpected("", result, markdown, context);
var result = sw.ToString();
TestParser.PrintAssertExpected("", result, markdown, context);
}
}
}

View File

@@ -1,34 +1,36 @@
using Markdig.Extensions.SmartyPants;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestSmartyPants
namespace Markdig.Tests
{
[Test]
public void MappingCanBeReconfigured()
public class TestSmartyPants
{
var options = new SmartyPantOptions();
options.Mapping[SmartyPantType.LeftAngleQuote] = "foo";
options.Mapping[SmartyPantType.RightAngleQuote] = "bar";
[Test]
public void MappingCanBeReconfigured()
{
SmartyPantOptions options = new SmartyPantOptions();
options.Mapping[SmartyPantType.LeftAngleQuote] = "foo";
options.Mapping[SmartyPantType.RightAngleQuote] = "bar";
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants(options)
.Build();
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants(options)
.Build();
TestParser.TestSpec("<<test>>", "<p>footestbar</p>", pipeline);
}
TestParser.TestSpec("<<test>>", "<p>footestbar</p>", pipeline);
}
[Test]
public void MappingCanBeReconfigured_HandlesRemovedMappings()
{
var options = new SmartyPantOptions();
options.Mapping.Remove(SmartyPantType.LeftAngleQuote);
options.Mapping.Remove(SmartyPantType.RightAngleQuote);
[Test]
public void MappingCanBeReconfigured_HandlesRemovedMappings()
{
SmartyPantOptions options = new SmartyPantOptions();
options.Mapping.Remove(SmartyPantType.LeftAngleQuote);
options.Mapping.Remove(SmartyPantType.RightAngleQuote);
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants(options)
.Build();
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants(options)
.Build();
TestParser.TestSpec("<<test>>", "<p>&laquo;test&raquo;</p>", pipeline);
TestParser.TestSpec("<<test>>", "<p>&laquo;test&raquo;</p>", pipeline);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,242 +1,185 @@
using System.Collections;
using NUnit.Framework;
using System.Text;
using Markdig.Helpers;
using System;
namespace Markdig.Tests;
[TestFixture]
public class TestStringSliceList
namespace Markdig.Tests
{
// TODO: Add tests for StringSlice
// TODO: Add more tests for StringLineGroup
[Test]
public void TestStringLineGroupSimple()
[TestFixture]
public class TestStringSliceList
{
var text = new StringLineGroup(4)
// TODO: Add tests for StringSlice
// TODO: Add more tests for StringLineGroup
[Test]
public void TestStringLineGroupSimple()
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
};
var text = new StringLineGroup(4)
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
};
var iterator = text.ToCharIterator();
Assert.AreEqual("ABC\nE\nF".Length, iterator.End - iterator.Start + 1);
var iterator = text.ToCharIterator();
Assert.AreEqual("ABC\nE\nF".Length, iterator.End - iterator.Start + 1);
var chars = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
var chars = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
TextAssert.AreEqual("ABC\nE\nF", text.ToString());
}
TextAssert.AreEqual("ABC\nE\nF", text.ToString());
}
[Test]
public void TestStringLineGroupWithSlices()
{
var text = new StringLineGroup(4)
[Test]
public void TestStringLineGroupWithSlices()
{
new StringSlice("XABC", NewLine.LineFeed) { Start = 1},
new StringSlice("YYE", NewLine.LineFeed) { Start = 2},
new StringSlice("ZZZF") { Start = 3 }
};
var text = new StringLineGroup(4)
{
new StringSlice("XABC", NewLine.LineFeed) { Start = 1},
new StringSlice("YYE", NewLine.LineFeed) { Start = 2},
new StringSlice("ZZZF") { Start = 3 }
};
var chars = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
}
var chars = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
}
private static string ToString(StringLineGroup.Iterator text)
{
var chars = new StringBuilder();
while (text.CurrentChar != '\0')
private static string ToString(StringLineGroup.Iterator text)
{
chars.Append(text.CurrentChar);
var chars = new StringBuilder();
while (text.CurrentChar != '\0')
{
chars.Append(text.CurrentChar);
text.NextChar();
}
return chars.ToString();
}
[Test]
public void TestStringLineGroupSaveAndRestore()
{
var text = new StringLineGroup(4)
{
new StringSlice("ABCD", NewLine.LineFeed),
new StringSlice("EF"),
}.ToCharIterator();
Assert.AreEqual('A', text.CurrentChar);
Assert.AreEqual(0, text.SliceIndex);
text.NextChar(); // B
text.NextChar(); // C
text.NextChar(); // D
text.NextChar(); // \n
text.NextChar();
Assert.AreEqual('E', text.CurrentChar);
Assert.AreEqual(1, text.SliceIndex);
}
return chars.ToString();
}
[Test]
public void TestStringLineGroupSaveAndRestore()
{
var text = new StringLineGroup(4)
[Test]
public void TestSkipWhitespaces()
{
new StringSlice("ABCD", NewLine.LineFeed),
new StringSlice("EF"),
}.ToCharIterator();
var text = new StringLineGroup(" ABC").ToCharIterator();
Assert.False(text.TrimStart());
Assert.AreEqual('A', text.CurrentChar);
Assert.AreEqual('A', text.CurrentChar);
Assert.AreEqual(0, text.SliceIndex);
text = new StringLineGroup(" ").ToCharIterator();
Assert.True(text.TrimStart());
Assert.AreEqual('\0', text.CurrentChar);
text.NextChar(); // B
var slice = new StringSlice(" ABC");
Assert.False(slice.TrimStart());
text.NextChar(); // C
text.NextChar(); // D
text.NextChar(); // \n
text.NextChar();
Assert.AreEqual('E', text.CurrentChar);
Assert.AreEqual(1, text.SliceIndex);
}
slice = new StringSlice(" ");
Assert.True(slice.TrimStart());
}
[Test]
public void TestSkipWhitespaces()
{
var text = new StringLineGroup(" ABC").ToCharIterator();
Assert.False(text.TrimStart());
Assert.AreEqual('A', text.CurrentChar);
text = new StringLineGroup(" ").ToCharIterator();
Assert.True(text.TrimStart());
Assert.AreEqual('\0', text.CurrentChar);
var slice = new StringSlice(" ABC");
Assert.False(slice.TrimStart());
slice = new StringSlice(" ");
Assert.True(slice.TrimStart());
}
[Test]
public void TestStringLineGroupWithModifiedStart()
{
var line1 = new StringSlice(" ABC", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
var line2 = new StringSlice(" DEF ");
line2.Trim();
var text = new StringLineGroup(4) {line1, line2};
var result = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nDEF", result);
}
[Test]
public void TestStringLineGroupWithTrim()
{
var line1 = new StringSlice(" ABC ", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
var line2 = new StringSlice(" DEF ");
var text = new StringLineGroup(4) { line1, line2}.ToCharIterator();
text.TrimStart();
var result = ToString(text);
TextAssert.AreEqual("ABC \n DEF ", result);
}
[Test]
public void TestStringLineGroupIteratorPeekChar()
{
var iterator = new StringLineGroup(4)
[Test]
public void TestStringLineGroupWithModifiedStart()
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
}.ToCharIterator();
var line1 = new StringSlice(" ABC", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
Assert.AreEqual('A', iterator.CurrentChar);
Assert.AreEqual('A', iterator.PeekChar(0));
Assert.AreEqual('B', iterator.PeekChar());
Assert.AreEqual('B', iterator.PeekChar(1));
Assert.AreEqual('C', iterator.PeekChar(2));
Assert.AreEqual('\n', iterator.PeekChar(3));
Assert.AreEqual('E', iterator.PeekChar(4));
Assert.AreEqual('\n', iterator.PeekChar(5));
Assert.AreEqual('F', iterator.PeekChar(6));
Assert.AreEqual('\0', iterator.PeekChar(7)); // There is no \n appended to the last line
Assert.AreEqual('\0', iterator.PeekChar(8));
Assert.AreEqual('\0', iterator.PeekChar(100));
var line2 = new StringSlice(" DEF ");
line2.Trim();
Assert.Throws<ArgumentOutOfRangeException>(() => iterator.PeekChar(-1));
}
var text = new StringLineGroup(4) {line1, line2};
[Test]
public void TestIteratorSkipChar()
{
var lineGroup = new StringLineGroup(4)
var result = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nDEF", result);
}
[Test]
public void TestStringLineGroupWithTrim()
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed)
};
var line1 = new StringSlice(" ABC ", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
Test(lineGroup.ToCharIterator());
var line2 = new StringSlice(" DEF ");
Test(new StringSlice("ABC\nE\n"));
var text = new StringLineGroup(4) { line1, line2}.ToCharIterator();
text.TrimStart();
Test(new StringSlice("Foo\nABC\nE\n", 4, 9));
var result = ToString(text);
TextAssert.AreEqual("ABC \n DEF ", result);
}
static void Test<T>(T iterator) where T : ICharIterator
[Test]
public void TestStringLineGroupIteratorPeekChar()
{
Assert.AreEqual('A', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('B', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('C', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\n', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('E', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\n', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\0', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\0', iterator.CurrentChar); iterator.SkipChar();
var iterator = new StringLineGroup(4)
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
}.ToCharIterator();
Assert.AreEqual('A', iterator.CurrentChar);
Assert.AreEqual('A', iterator.PeekChar(0));
Assert.AreEqual('B', iterator.PeekChar());
Assert.AreEqual('B', iterator.PeekChar(1));
Assert.AreEqual('C', iterator.PeekChar(2));
Assert.AreEqual('\n', iterator.PeekChar(3));
Assert.AreEqual('E', iterator.PeekChar(4));
Assert.AreEqual('\n', iterator.PeekChar(5));
Assert.AreEqual('F', iterator.PeekChar(6));
Assert.AreEqual('\0', iterator.PeekChar(7)); // There is no \n appended to the last line
Assert.AreEqual('\0', iterator.PeekChar(8));
Assert.AreEqual('\0', iterator.PeekChar(100));
Assert.Throws<ArgumentOutOfRangeException>(() => iterator.PeekChar(-1));
}
[Test]
public void TestIteratorSkipChar()
{
var lineGroup = new StringLineGroup(4)
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed)
};
Test(lineGroup.ToCharIterator());
Test(new StringSlice("ABC\nE\n"));
Test(new StringSlice("Foo\nABC\nE\n", 4, 9));
static void Test<T>(T iterator) where T : ICharIterator
{
Assert.AreEqual('A', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('B', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('C', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\n', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('E', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\n', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\0', iterator.CurrentChar); iterator.SkipChar();
Assert.AreEqual('\0', iterator.CurrentChar); iterator.SkipChar();
}
}
}
[Test]
public void TestStringLineGroupCharIteratorAtCapacity()
{
string str = "ABCDEFGHI";
var text = new StringLineGroup(1)
{
// Will store the following line at capacity
new StringSlice(str, NewLine.CarriageReturnLineFeed) { Start = 0, End = 2 },
};
var iterator = text.ToCharIterator();
var chars = ToString(iterator);
TextAssert.AreEqual("ABC\r\n", chars.ToString());
TextAssert.AreEqual("ABC", text.ToString());
}
[Test]
public void TestStringLineGroupCharIteratorForcingIncreaseCapacity()
{
string str = "ABCDEFGHI";
var text = new StringLineGroup(1)
{
// Will store the following line at capacity
new StringSlice(str, NewLine.CarriageReturnLineFeed) { Start = 0, End = 2 },
// Will force increase capacity to 2 and store the line at capacity
new StringSlice(str, NewLine.CarriageReturnLineFeed) { Start = 3, End = 3 },
};
var iterator = text.ToCharIterator();
var chars = ToString(iterator);
TextAssert.AreEqual("ABC\r\nD\r\n", chars.ToString());
TextAssert.AreEqual("ABC\r\nD", text.ToString());
}
[Test]
public void TestStringLineGroup_EnumeratorReturnsRealLines()
{
string str = "A\r\n";
var text = new StringLineGroup(4)
{
new StringSlice(str, NewLine.CarriageReturnLineFeed) { Start = 0, End = 0 }
};
var enumerator = ((IEnumerable)text).GetEnumerator();
Assert.True(enumerator.MoveNext());
StringLine currentLine = (StringLine)enumerator.Current;
TextAssert.AreEqual("A", currentLine.ToString());
Assert.False(enumerator.MoveNext());
var nonBoxedEnumerator = text.GetEnumerator();
Assert.True(nonBoxedEnumerator.MoveNext());
currentLine = (StringLine)nonBoxedEnumerator.Current;
TextAssert.AreEqual("A", currentLine.ToString());
Assert.False(nonBoxedEnumerator.MoveNext());
}
}
}

View File

@@ -1,98 +1,102 @@
using System;
using System.Linq;
using Markdig.Helpers;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestTransformedStringCache
namespace Markdig.Tests
{
[Test]
public void GetRunsTransformationCallback()
public class TestTransformedStringCache
{
var cache = new TransformedStringCache(static s => "callback-" + s);
Assert.AreEqual("callback-foo", cache.Get("foo"));
Assert.AreEqual("callback-bar", cache.Get("bar"));
Assert.AreEqual("callback-baz", cache.Get("baz"));
}
[Test]
public void CachesTransformedInstance()
{
var cache = new TransformedStringCache(static s => "callback-" + s);
string transformedBar = cache.Get("bar");
Assert.AreSame(transformedBar, cache.Get("bar"));
string transformedFoo = cache.Get("foo".AsSpan());
Assert.AreSame(transformedFoo, cache.Get("foo"));
Assert.AreSame(cache.Get("baz"), cache.Get("baz".AsSpan()));
Assert.AreSame(transformedBar, cache.Get("bar"));
Assert.AreSame(transformedFoo, cache.Get("foo"));
Assert.AreSame(transformedBar, cache.Get("bar".AsSpan()));
Assert.AreSame(transformedFoo, cache.Get("foo".AsSpan()));
}
[Test]
public void DoesNotCacheEmptyInputs()
{
var cache = new TransformedStringCache(static s => new string('a', 4));
string cached = cache.Get("");
string cached2 = cache.Get("");
string cached3 = cache.Get(ReadOnlySpan<char>.Empty);
Assert.AreEqual("aaaa", cached);
Assert.AreEqual(cached, cached2);
Assert.AreEqual(cached, cached3);
Assert.AreNotSame(cached, cached2);
Assert.AreNotSame(cached, cached3);
Assert.AreNotSame(cached2, cached3);
}
[Test]
[TestCase(TransformedStringCache.InputLengthLimit, true)]
[TestCase(TransformedStringCache.InputLengthLimit + 1, false)]
public void DoesNotCacheLongInputs(int length, bool shouldBeCached)
{
var cache = new TransformedStringCache(static s => "callback-" + s);
string input = new string('a', length);
string cached = cache.Get(input);
string cached2 = cache.Get(input);
Assert.AreEqual("callback-" + input, cached);
Assert.AreEqual(cached, cached2);
if (shouldBeCached)
[Test]
public void GetRunsTransformationCallback()
{
Assert.AreSame(cached, cached2);
var cache = new TransformedStringCache(static s => "callback-" + s);
Assert.AreEqual("callback-foo", cache.Get("foo"));
Assert.AreEqual("callback-bar", cache.Get("bar"));
Assert.AreEqual("callback-baz", cache.Get("baz"));
}
else
[Test]
public void CachesTransformedInstance()
{
var cache = new TransformedStringCache(static s => "callback-" + s);
string transformedBar = cache.Get("bar");
Assert.AreSame(transformedBar, cache.Get("bar"));
string transformedFoo = cache.Get("foo".AsSpan());
Assert.AreSame(transformedFoo, cache.Get("foo"));
Assert.AreSame(cache.Get("baz"), cache.Get("baz".AsSpan()));
Assert.AreSame(transformedBar, cache.Get("bar"));
Assert.AreSame(transformedFoo, cache.Get("foo"));
Assert.AreSame(transformedBar, cache.Get("bar".AsSpan()));
Assert.AreSame(transformedFoo, cache.Get("foo".AsSpan()));
}
[Test]
public void DoesNotCacheEmptyInputs()
{
var cache = new TransformedStringCache(static s => new string('a', 4));
string cached = cache.Get("");
string cached2 = cache.Get("");
string cached3 = cache.Get(ReadOnlySpan<char>.Empty);
Assert.AreEqual("aaaa", cached);
Assert.AreEqual(cached, cached2);
Assert.AreEqual(cached, cached3);
Assert.AreNotSame(cached, cached2);
Assert.AreNotSame(cached, cached3);
Assert.AreNotSame(cached2, cached3);
}
}
[Test]
public void CachesAtMostNEntriesPerCharacter()
{
var cache = new TransformedStringCache(static s => "callback-" + s);
int limit = TransformedStringCache.MaxEntriesPerCharacter;
string[] a = Enumerable.Range(1, limit + 1).Select(i => $"a{i}").ToArray();
string[] cachedAs = a.Select(a => cache.Get(a)).ToArray();
for (int i = 0; i < limit; i++)
[Test]
[TestCase(TransformedStringCache.InputLengthLimit, true)]
[TestCase(TransformedStringCache.InputLengthLimit + 1, false)]
public void DoesNotCacheLongInputs(int length, bool shouldBeCached)
{
Assert.AreSame(cachedAs[i], cache.Get(a[i]));
var cache = new TransformedStringCache(static s => "callback-" + s);
string input = new string('a', length);
string cached = cache.Get(input);
string cached2 = cache.Get(input);
Assert.AreEqual("callback-" + input, cached);
Assert.AreEqual(cached, cached2);
if (shouldBeCached)
{
Assert.AreSame(cached, cached2);
}
else
{
Assert.AreNotSame(cached, cached2);
}
}
Assert.AreNotSame(cachedAs[limit], cache.Get(a[limit]));
[Test]
public void CachesAtMostNEntriesPerCharacter()
{
var cache = new TransformedStringCache(static s => "callback-" + s);
Assert.AreSame(cache.Get("b1"), cache.Get("b1"));
int limit = TransformedStringCache.MaxEntriesPerCharacter;
string[] a = Enumerable.Range(1, limit + 1).Select(i => $"a{i}").ToArray();
string[] cachedAs = a.Select(a => cache.Get(a)).ToArray();
for (int i = 0; i < limit; i++)
{
Assert.AreSame(cachedAs[i], cache.Get(a[i]));
}
Assert.AreNotSame(cachedAs[limit], cache.Get(a[limit]));
Assert.AreSame(cache.Get("b1"), cache.Get("b1"));
}
}
}

View File

@@ -1,113 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Markdig.Extensions.Yaml;
using Markdig.Renderers;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests;
public class TestYamlFrontMatterExtension
namespace Markdig.Tests
{
[TestCaseSource(nameof(TestCases))]
public void ProperYamlFrontMatterRenderersAdded(IMarkdownObjectRenderer[] objectRenderers, bool hasYamlFrontMatterHtmlRenderer, bool hasYamlFrontMatterRoundtripRenderer)
public class TestYamlFrontMatterExtension
{
var builder = new MarkdownPipelineBuilder();
builder.Extensions.Add(new YamlFrontMatterExtension());
var markdownRenderer = new DummyRenderer();
markdownRenderer.ObjectRenderers.AddRange(objectRenderers);
builder.Build().Setup(markdownRenderer);
Assert.That(markdownRenderer.ObjectRenderers.Contains<YamlFrontMatterHtmlRenderer>(), Is.EqualTo(hasYamlFrontMatterHtmlRenderer));
Assert.That(markdownRenderer.ObjectRenderers.Contains<YamlFrontMatterRoundtripRenderer>(), Is.EqualTo(hasYamlFrontMatterRoundtripRenderer));
}
[Test]
public void AllowYamlFrontMatterInMiddleOfDocument()
{
var pipeline = new MarkdownPipelineBuilder()
.Use(new YamlFrontMatterExtension { AllowInMiddleOfDocument = true })
.Build();
TestParser.TestSpec(
"This is a text1\n---\nthis: is a frontmatter\n---\nThis is a text2",
"<p>This is a text1</p>\n<p>This is a text2</p>",
pipeline);
}
private static IEnumerable<TestCaseData> TestCases()
{
yield return new TestCaseData(new IMarkdownObjectRenderer[]
[TestCaseSource(nameof(TestCases))]
public void ProperYamlFrontMatterRenderersAdded(IMarkdownObjectRenderer[] objectRenderers, bool hasYamlFrontMatterHtmlRenderer, bool hasYamlFrontMatterRoundtripRenderer)
{
}, false, false) {TestName = "No ObjectRenderers"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer()
}, true, false) {TestName = "Html CodeBlock"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Roundtrip.CodeBlockRenderer()
}, false, true) {TestName = "Roundtrip CodeBlock"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer(),
new Markdig.Renderers.Roundtrip.CodeBlockRenderer()
}, true, true) {TestName = "Html/Roundtrip CodeBlock"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer(),
new Markdig.Renderers.Roundtrip.CodeBlockRenderer(),
new YamlFrontMatterHtmlRenderer()
}, true, true) {TestName = "Html/Roundtrip CodeBlock, Yaml Html"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer(),
new Markdig.Renderers.Roundtrip.CodeBlockRenderer(),
new YamlFrontMatterRoundtripRenderer()
}, true, true) { TestName = "Html/Roundtrip CodeBlock, Yaml Roundtrip" };
}
private class DummyRenderer : IMarkdownRenderer
{
public DummyRenderer()
{
ObjectRenderers = new ObjectRendererCollection();
var builder = new MarkdownPipelineBuilder();
builder.Extensions.Add(new YamlFrontMatterExtension());
var markdownRenderer = new DummyRenderer();
markdownRenderer.ObjectRenderers.AddRange(objectRenderers);
builder.Build().Setup(markdownRenderer);
Assert.That(markdownRenderer.ObjectRenderers.Contains<YamlFrontMatterHtmlRenderer>(), Is.EqualTo(hasYamlFrontMatterHtmlRenderer));
Assert.That(markdownRenderer.ObjectRenderers.Contains<YamlFrontMatterRoundtripRenderer>(), Is.EqualTo(hasYamlFrontMatterRoundtripRenderer));
}
#pragma warning disable CS0067 // ObjectWriteBefore/ObjectWriteAfter is never used
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteBefore;
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteAfter;
#pragma warning restore CS0067
public ObjectRendererCollection ObjectRenderers { get; }
public object Render(MarkdownObject markdownObject)
private static IEnumerable<TestCaseData> TestCases()
{
return null;
}
}
[TestCase("---\nkey1: value1\nkey2: value2\n---\n\n# Content\n")]
[TestCase("---\nkey1: value1\nkey2: value2\nkey3: value3\nkey4: value4\nkey5: value5\nkey6: value6\nkey7: value7\nkey8: value8\n---\n\n# Content\n")]
public void FrontMatterBlockLinesCharIterator(string value)
{
var builder = new MarkdownPipelineBuilder();
builder.Extensions.Add(new YamlFrontMatterExtension());
var markdownDocument = Markdown.Parse(value, builder.Build());
var yamlBlocks = markdownDocument.Descendants<YamlFrontMatterBlock>();
Assert.True(yamlBlocks.Any());
foreach (var yamlBlock in yamlBlocks)
{
var iterator = yamlBlock.Lines.ToCharIterator();
while(iterator.CurrentChar != '\0')
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
iterator.NextChar();
}, false, false) {TestName = "No ObjectRenderers"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer()
}, true, false) {TestName = "Html CodeBlock"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Roundtrip.CodeBlockRenderer()
}, false, true) {TestName = "Roundtrip CodeBlock"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer(),
new Markdig.Renderers.Roundtrip.CodeBlockRenderer()
}, true, true) {TestName = "Html/Roundtrip CodeBlock"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer(),
new Markdig.Renderers.Roundtrip.CodeBlockRenderer(),
new YamlFrontMatterHtmlRenderer()
}, true, true) {TestName = "Html/Roundtrip CodeBlock, Yaml Html"};
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
new Markdig.Renderers.Html.CodeBlockRenderer(),
new Markdig.Renderers.Roundtrip.CodeBlockRenderer(),
new YamlFrontMatterRoundtripRenderer()
}, true, true) { TestName = "Html/Roundtrip CodeBlock, Yaml Roundtrip" };
}
private class DummyRenderer : IMarkdownRenderer
{
public DummyRenderer()
{
ObjectRenderers = new ObjectRendererCollection();
}
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteBefore;
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteAfter;
public ObjectRendererCollection ObjectRenderers { get; }
public object Render(MarkdownObject markdownObject)
{
return null;
}
}
Assert.Pass("No exception parsing and iterating through YAML front matter block lines");
}
}

View File

@@ -1,115 +1,119 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using NUnit.Framework;
namespace Markdig.Tests;
/// <summary>
/// Pretty text assert from https://gist.github.com/Haacked/1610603
/// Modified version to only print +-10 characters around the first diff
/// </summary>
public static class TextAssert
namespace Markdig.Tests
{
public enum DiffStyle
/// <summary>
/// Pretty text assert from https://gist.github.com/Haacked/1610603
/// Modified version to only print +-10 characters around the first diff
/// </summary>
public static class TextAssert
{
Full,
Minimal
}
public static void AreEqual(string expectedValue, string actualValue)
{
AreEqual(expectedValue, actualValue, DiffStyle.Full, Console.Out);
}
public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle)
{
AreEqual(expectedValue, actualValue, diffStyle, Console.Out);
}
public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle, TextWriter output)
{
if (actualValue == null || expectedValue == null)
public enum DiffStyle
{
Full,
Minimal
}
public static void AreEqual(string expectedValue, string actualValue)
{
AreEqual(expectedValue, actualValue, DiffStyle.Full, Console.Out);
}
public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle)
{
AreEqual(expectedValue, actualValue, diffStyle, Console.Out);
}
public static void AreEqual(string expectedValue, string actualValue, DiffStyle diffStyle, TextWriter output)
{
if (actualValue == null || expectedValue == null)
{
Assert.AreEqual(expectedValue, actualValue);
return;
}
if (actualValue.Equals(expectedValue, StringComparison.Ordinal))
{
return;
}
Console.WriteLine();
output.WriteLine("Index Expected Actual");
output.WriteLine("----------------------------");
int maxLen = Math.Max(actualValue.Length, expectedValue.Length);
int minLen = Math.Min(actualValue.Length, expectedValue.Length);
if (diffStyle != DiffStyle.Minimal)
{
int startDifferAt = 0;
for (int i = 0; i < maxLen; i++)
{
if (i >= minLen || actualValue[i] != expectedValue[i])
{
startDifferAt = i;
break;
}
}
var endDifferAt = Math.Min(startDifferAt + 10, maxLen);
startDifferAt = Math.Max(startDifferAt - 10, 0);
bool isFirstDiff = true;
for (int i = startDifferAt; i < endDifferAt; i++)
{
if (i >= minLen || actualValue[i] != expectedValue[i])
{
output.WriteLine("{0,-3} {1,-3} {2,-4} {3,-3} {4,-4} {5,-3}",
i < minLen && actualValue[i] == expectedValue[i] ? " " : isFirstDiff ? ">>>": "***",
// put a mark beside a differing row
i, // the index
i < expectedValue.Length ? ((int) expectedValue[i]).ToString() : "",
// character decimal value
i < expectedValue.Length ? expectedValue[i].ToSafeString() : "", // character safe string
i < actualValue.Length ? ((int) actualValue[i]).ToString() : "", // character decimal value
i < actualValue.Length ? actualValue[i].ToSafeString() : "" // character safe string
);
isFirstDiff = false;
}
}
//output.WriteLine();
}
Assert.AreEqual(expectedValue, actualValue);
return;
}
if (actualValue.Equals(expectedValue, StringComparison.Ordinal))
private static string ToSafeString(this char c)
{
return;
}
Console.WriteLine();
output.WriteLine("Index Expected Actual");
output.WriteLine("----------------------------");
int maxLen = Math.Max(actualValue.Length, expectedValue.Length);
int minLen = Math.Min(actualValue.Length, expectedValue.Length);
if (diffStyle != DiffStyle.Minimal)
{
int startDifferAt = 0;
for (int i = 0; i < maxLen; i++)
if (char.IsControl(c) || char.IsWhiteSpace(c))
{
if (i >= minLen || actualValue[i] != expectedValue[i])
switch (c)
{
startDifferAt = i;
break;
case '\b':
return @"\b";
case '\r':
return @"\r";
case '\n':
return @"\n";
case '\t':
return @"\t";
case '\a':
return @"\a";
case '\v':
return @"\v";
case '\f':
return @"\f";
default:
return $"\\u{(int) c:X};";
}
}
var endDifferAt = Math.Min(startDifferAt + 10, maxLen);
startDifferAt = Math.Max(startDifferAt - 10, 0);
bool isFirstDiff = true;
for (int i = startDifferAt; i < endDifferAt; i++)
{
if (i >= minLen || actualValue[i] != expectedValue[i])
{
output.WriteLine("{0,-3} {1,-3} {2,-4} {3,-3} {4,-4} {5,-3}",
i < minLen && actualValue[i] == expectedValue[i] ? " " : isFirstDiff ? ">>>": "***",
// put a mark beside a differing row
i, // the index
i < expectedValue.Length ? ((int) expectedValue[i]).ToString() : "",
// character decimal value
i < expectedValue.Length ? expectedValue[i].ToSafeString() : "", // character safe string
i < actualValue.Length ? ((int) actualValue[i]).ToString() : "", // character decimal value
i < actualValue.Length ? actualValue[i].ToSafeString() : "" // character safe string
);
isFirstDiff = false;
}
}
//output.WriteLine();
return c.ToString(CultureInfo.InvariantCulture);
}
Assert.AreEqual(expectedValue, actualValue);
}
private static string ToSafeString(this char c)
{
if (char.IsControl(c) || char.IsWhiteSpace(c))
{
switch (c)
{
case '\b':
return @"\b";
case '\r':
return @"\r";
case '\n':
return @"\n";
case '\t':
return @"\t";
case '\a':
return @"\a";
case '\v':
return @"\v";
case '\f':
return @"\f";
default:
return $"\\u{(int) c:X};";
}
}
return c.ToString(CultureInfo.InvariantCulture);
}
}

View File

@@ -1,54 +1,55 @@
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace Markdig.WebApp;
public class ApiController : Controller
namespace Markdig.WebApp
{
[HttpGet()]
[Route("")]
public string Empty()
public class ApiController : Controller
{
return string.Empty;
}
// GET api/to_html?text=xxx&extension=advanced
[Route("api/to_html")]
[HttpGet()]
public object Get([FromQuery] string text, [FromQuery] string extension)
{
try
[HttpGet()]
[Route("")]
public string Empty()
{
if (text == null)
return string.Empty;
}
// GET api/to_html?text=xxx&extension=advanced
[Route("api/to_html")]
[HttpGet()]
public object Get([FromQuery] string text, [FromQuery] string extension)
{
try
{
text = string.Empty;
}
if (text == null)
{
text = string.Empty;
}
if (text.Length > 1000)
if (text.Length > 1000)
{
text = text.Substring(0, 1000);
}
var pipeline = new MarkdownPipelineBuilder().Configure(extension).Build();
var result = Markdown.ToHtml(text, pipeline);
return new {name = "markdig", html = result, version = Markdown.Version};
}
catch (Exception ex)
{
text = text.Substring(0, 1000);
return new { name = "markdig", html = "exception: " + GetPrettyMessageFromException(ex), version = Markdown.Version };
}
var pipeline = new MarkdownPipelineBuilder().Configure(extension).Build();
var result = Markdown.ToHtml(text, pipeline);
return new {name = "markdig", html = result, version = Markdown.Version};
}
catch (Exception ex)
private static string GetPrettyMessageFromException(Exception exception)
{
return new { name = "markdig", html = "exception: " + GetPrettyMessageFromException(ex), version = Markdown.Version };
var builder = new StringBuilder();
while (exception != null)
{
builder.Append(exception.Message);
exception = exception.InnerException;
}
return builder.ToString();
}
}
private static string GetPrettyMessageFromException(Exception exception)
{
var builder = new StringBuilder();
while (exception != null)
{
builder.Append(exception.Message);
exception = exception.InnerException;
}
return builder.ToString();
}
}

View File

@@ -4,7 +4,6 @@
<TargetFramework>net6.0</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Markdig.WebApp</AssemblyName>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Exe</OutputType>
<PackageId>Markdig.WebApp</PackageId>
<ApplicationInsightsResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsResourceId>
@@ -14,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.20.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,16 +1,22 @@
namespace Markdig.WebApp;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
public class Program
namespace Markdig.WebApp
{
public static void Main(string[] args)
public class Program
{
CreateHostBuilder(args).Build().Run();
}
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@@ -1,52 +1,60 @@
namespace Markdig.WebApp;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public class Startup
namespace Markdig.WebApp
{
public Startup(IWebHostEnvironment env)
public class Startup
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsEnvironment("Development"))
public Startup(IWebHostEnvironment env)
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
if (env.IsEnvironment("Development"))
{
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
builder.AddApplicationInsightsSettings(developerMode: true);
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
// This method gets called by the runtime. Use this method to add services to the container
public void ConfigureServices(IServiceCollection services)
{
app.UseDeveloperExceptionPage();
services.AddControllers();
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
endpoints.MapControllers();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

View File

@@ -7,35 +7,36 @@ using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
namespace Markdig.Extensions.Abbreviations;
/// <summary>
/// An abbreviation object stored at the document level. See extension methods in <see cref="AbbreviationHelper"/>.
/// </summary>
/// <seealso cref="LeafBlock" />
[DebuggerDisplay("Abbr {Label} => {Text}")]
public class Abbreviation : LeafBlock
namespace Markdig.Extensions.Abbreviations
{
/// <summary>
/// Initializes a new instance of the <see cref="Abbreviation"/> class.
/// An abbreviation object stored at the document level. See extension methods in <see cref="AbbreviationHelper"/>.
/// </summary>
/// <param name="parser">The parser used to create this block.</param>
public Abbreviation(BlockParser parser) : base(parser)
/// <seealso cref="LeafBlock" />
[DebuggerDisplay("Abbr {Label} => {Text}")]
public class Abbreviation : LeafBlock
{
/// <summary>
/// Initializes a new instance of the <see cref="Abbreviation"/> class.
/// </summary>
/// <param name="parser">The parser used to create this block.</param>
public Abbreviation(BlockParser parser) : base(parser)
{
}
/// <summary>
/// Gets or sets the label.
/// </summary>
public string? Label { get; set; }
/// <summary>
/// The text associated to this label.
/// </summary>
public StringSlice Text;
/// <summary>
/// The label span
/// </summary>
public SourceSpan LabelSpan;
}
/// <summary>
/// Gets or sets the label.
/// </summary>
public string? Label { get; set; }
/// <summary>
/// The text associated to this label.
/// </summary>
public StringSlice Text;
/// <summary>
/// The label span
/// </summary>
public SourceSpan LabelSpan;
}

View File

@@ -4,25 +4,26 @@
using Markdig.Renderers;
namespace Markdig.Extensions.Abbreviations;
/// <summary>
/// Extension to allow abbreviations.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class AbbreviationExtension : IMarkdownExtension
namespace Markdig.Extensions.Abbreviations
{
public void Setup(MarkdownPipelineBuilder pipeline)
/// <summary>
/// Extension to allow abbreviations.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class AbbreviationExtension : IMarkdownExtension
{
pipeline.BlockParsers.AddIfNotAlready<AbbreviationParser>();
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlAbbreviationRenderer());
pipeline.BlockParsers.AddIfNotAlready<AbbreviationParser>();
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
{
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlAbbreviationRenderer());
}
}
}
}

View File

@@ -2,40 +2,42 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Syntax;
namespace Markdig.Extensions.Abbreviations;
/// <summary>
/// Extension methods for <see cref="Abbreviation"/>.
/// </summary>
public static class AbbreviationHelper
namespace Markdig.Extensions.Abbreviations
{
private static readonly object DocumentKey = typeof (Abbreviation);
public static bool HasAbbreviations(this MarkdownDocument document)
/// <summary>
/// Extension methods for <see cref="Abbreviation"/>.
/// </summary>
public static class AbbreviationHelper
{
return document.GetAbbreviations() != null;
}
private static readonly object DocumentKey = typeof (Abbreviation);
public static void AddAbbreviation(this MarkdownDocument document, string label, Abbreviation abbr)
{
if (document is null) ThrowHelper.ArgumentNullException(nameof(document));
if (label is null) ThrowHelper.ArgumentNullException_label();
if (abbr is null) ThrowHelper.ArgumentNullException(nameof(abbr));
var map = document.GetAbbreviations();
if (map is null)
public static bool HasAbbreviations(this MarkdownDocument document)
{
map = new Dictionary<string, Abbreviation>();
document.SetData(DocumentKey, map);
return document.GetAbbreviations() != null;
}
map[label] = abbr;
}
public static Dictionary<string, Abbreviation>? GetAbbreviations(this MarkdownDocument document)
{
return document.GetData(DocumentKey) as Dictionary<string, Abbreviation>;
public static void AddAbbreviation(this MarkdownDocument document, string label, Abbreviation abbr)
{
if (document is null) ThrowHelper.ArgumentNullException(nameof(document));
if (label is null) ThrowHelper.ArgumentNullException_label();
if (abbr is null) ThrowHelper.ArgumentNullException(nameof(abbr));
var map = document.GetAbbreviations();
if (map is null)
{
map = new Dictionary<string, Abbreviation>();
document.SetData(DocumentKey, map);
}
map[label] = abbr;
}
public static Dictionary<string, Abbreviation>? GetAbbreviations(this MarkdownDocument document)
{
return document.GetData(DocumentKey) as Dictionary<string, Abbreviation>;
}
}
}

View File

@@ -5,23 +5,24 @@
using System.Diagnostics;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Abbreviations;
/// <summary>
/// The inline abbreviation.
/// </summary>
/// <seealso cref="LeafInline" />
[DebuggerDisplay("{Abbreviation}")]
public class AbbreviationInline : LeafInline
namespace Markdig.Extensions.Abbreviations
{
/// <summary>
/// Initializes a new instance of the <see cref="AbbreviationInline"/> class.
/// The inline abbreviation.
/// </summary>
/// <param name="abbreviation">The abbreviation.</param>
public AbbreviationInline(Abbreviation abbreviation)
/// <seealso cref="LeafInline" />
[DebuggerDisplay("{Abbreviation}")]
public class AbbreviationInline : LeafInline
{
Abbreviation = abbreviation;
}
/// <summary>
/// Initializes a new instance of the <see cref="AbbreviationInline"/> class.
/// </summary>
/// <param name="abbreviation">The abbreviation.</param>
public AbbreviationInline(Abbreviation abbreviation)
{
Abbreviation = abbreviation;
}
public Abbreviation Abbreviation { get; set; }
public Abbreviation Abbreviation { get; set; }
}
}

View File

@@ -2,222 +2,225 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Abbreviations;
/// <summary>
/// A block parser for abbreviations.
/// </summary>
/// <seealso cref="BlockParser" />
public class AbbreviationParser : BlockParser
namespace Markdig.Extensions.Abbreviations
{
/// <summary>
/// Initializes a new instance of the <see cref="AbbreviationParser"/> class.
/// A block parser for abbreviations.
/// </summary>
public AbbreviationParser()
/// <seealso cref="BlockParser" />
public class AbbreviationParser : BlockParser
{
OpeningCharacters = new[] { '*' };
}
public override BlockState TryOpen(BlockProcessor processor)
{
if (processor.IsCodeIndent)
/// <summary>
/// Initializes a new instance of the <see cref="AbbreviationParser"/> class.
/// </summary>
public AbbreviationParser()
{
return BlockState.None;
OpeningCharacters = new[] { '*' };
}
// A link must be of the form *[Some Text]: An abbreviation
var slice = processor.Line;
var startPosition = slice.Start;
var c = slice.NextChar();
if (c != '[')
public override BlockState TryOpen(BlockProcessor processor)
{
return BlockState.None;
}
if (!LinkHelper.TryParseLabel(ref slice, out string? label, out SourceSpan labelSpan))
{
return BlockState.None;
}
c = slice.CurrentChar;
if (c != ':')
{
return BlockState.None;
}
slice.SkipChar();
slice.Trim();
var abbr = new Abbreviation(this)
{
Label = label,
Text = slice,
Span = new SourceSpan(startPosition, slice.End),
Line = processor.LineIndex,
Column = processor.Column,
LabelSpan = labelSpan,
};
if (!processor.Document.HasAbbreviations())
{
processor.Document.ProcessInlinesBegin += DocumentOnProcessInlinesBegin;
}
processor.Document.AddAbbreviation(abbr.Label, abbr);
return BlockState.BreakDiscard;
}
private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline? inline)
{
inlineProcessor.Document.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
var abbreviations = inlineProcessor.Document.GetAbbreviations();
// Should not happen, but another extension could decide to remove them, so...
if (abbreviations is null)
{
return;
}
// Build a text matcher from the abbreviations labels
var prefixTree = new CompactPrefixTree<Abbreviation>(abbreviations);
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
{
var literal = (LiteralInline)processor.Inline!;
var originalLiteral = literal;
ContainerInline? container = null;
// This is slow, but we don't have much the choice
var content = literal.Content;
var text = content.Text;
for (int i = content.Start; i <= content.End; i++)
if (processor.IsCodeIndent)
{
// Abbreviation must be a whole word == start at the start of a line or after a whitespace
if (i != 0)
return BlockState.None;
}
// A link must be of the form *[Some Text]: An abbreviation
var slice = processor.Line;
var startPosition = slice.Start;
var c = slice.NextChar();
if (c != '[')
{
return BlockState.None;
}
if (!LinkHelper.TryParseLabel(ref slice, out string? label, out SourceSpan labelSpan))
{
return BlockState.None;
}
c = slice.CurrentChar;
if (c != ':')
{
return BlockState.None;
}
slice.SkipChar();
slice.Trim();
var abbr = new Abbreviation(this)
{
Label = label,
Text = slice,
Span = new SourceSpan(startPosition, slice.End),
Line = processor.LineIndex,
Column = processor.Column,
LabelSpan = labelSpan,
};
if (!processor.Document.HasAbbreviations())
{
processor.Document.ProcessInlinesBegin += DocumentOnProcessInlinesBegin;
}
processor.Document.AddAbbreviation(abbr.Label, abbr);
return BlockState.BreakDiscard;
}
private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline? inline)
{
inlineProcessor.Document.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
var abbreviations = inlineProcessor.Document.GetAbbreviations();
// Should not happen, but another extension could decide to remove them, so...
if (abbreviations is null)
{
return;
}
// Build a text matcher from the abbreviations labels
var prefixTree = new CompactPrefixTree<Abbreviation>(abbreviations);
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
{
var literal = (LiteralInline)processor.Inline!;
var originalLiteral = literal;
ContainerInline? container = null;
// This is slow, but we don't have much the choice
var content = literal.Content;
var text = content.Text;
for (int i = content.Start; i <= content.End; i++)
{
for (i = i - 1; i <= content.End; i++)
// Abbreviation must be a whole word == start at the start of a line or after a whitespace
if (i != 0)
{
if (text[i].IsWhitespace())
for (i = i - 1; i <= content.End; i++)
{
i++;
goto ValidAbbreviationStart;
}
}
break;
}
ValidAbbreviationStart:;
if (prefixTree.TryMatchLongest(text.AsSpan(i, content.End - i + 1), out KeyValuePair<string, Abbreviation> abbreviationMatch))
{
var match = abbreviationMatch.Key;
if (!IsValidAbbreviationEnding(match, content, i))
{
continue;
}
var indexAfterMatch = i + match.Length;
// If we don't have a container, create a new one
if (container is null)
{
container = literal.Parent ??
new ContainerInline
if (text[i].IsWhitespace())
{
Span = originalLiteral.Span,
Line = originalLiteral.Line,
Column = originalLiteral.Column,
};
}
var abbrInline = new AbbreviationInline(abbreviationMatch.Value)
{
Span =
{
Start = processor.GetSourcePosition(i, out int line, out int column),
},
Line = line,
Column = column
};
abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1;
// Append the previous literal
if (i > content.Start && literal.Parent is null)
{
container.AppendChild(literal);
}
literal.Span.End = abbrInline.Span.Start - 1;
// Truncate it before the abbreviation
literal.Content.End = i - 1;
// Append the abbreviation
container.AppendChild(abbrInline);
// If this is the end of the string, clear the literal and exit
if (content.End == indexAfterMatch - 1)
{
literal = null;
i++;
goto ValidAbbreviationStart;
}
}
break;
}
// Process the remaining literal
literal = new LiteralInline()
ValidAbbreviationStart:;
if (prefixTree.TryMatchLongest(text.AsSpan(i, content.End - i + 1), out KeyValuePair<string, Abbreviation> abbreviationMatch))
{
Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End),
Line = line,
Column = column + match.Length,
};
content.Start = indexAfterMatch;
literal.Content = content;
var match = abbreviationMatch.Key;
if (!IsValidAbbreviationEnding(match, content, i))
{
continue;
}
i = indexAfterMatch - 1;
var indexAfterMatch = i + match.Length;
// If we don't have a container, create a new one
if (container is null)
{
container = literal.Parent ??
new ContainerInline
{
Span = originalLiteral.Span,
Line = originalLiteral.Line,
Column = originalLiteral.Column,
};
}
var abbrInline = new AbbreviationInline(abbreviationMatch.Value)
{
Span =
{
Start = processor.GetSourcePosition(i, out int line, out int column),
},
Line = line,
Column = column
};
abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1;
// Append the previous literal
if (i > content.Start && literal.Parent is null)
{
container.AppendChild(literal);
}
literal.Span.End = abbrInline.Span.Start - 1;
// Truncate it before the abbreviation
literal.Content.End = i - 1;
// Append the abbreviation
container.AppendChild(abbrInline);
// If this is the end of the string, clear the literal and exit
if (content.End == indexAfterMatch - 1)
{
literal = null;
break;
}
// Process the remaining literal
literal = new LiteralInline()
{
Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End),
Line = line,
Column = column + match.Length,
};
content.Start = indexAfterMatch;
literal.Content = content;
i = indexAfterMatch - 1;
}
}
}
if (container != null)
{
if (literal != null)
if (container != null)
{
container.AppendChild(literal);
if (literal != null)
{
container.AppendChild(literal);
}
processor.Inline = container;
}
processor.Inline = container;
}
};
}
private static bool IsValidAbbreviationEnding(string match, StringSlice content, int matchIndex)
{
// This will check if the next char at the end of the StringSlice is whitespace, punctuation or \0.
var contentNew = content;
contentNew.End = content.End + 1;
int index = matchIndex + match.Length;
while (index <= contentNew.End)
{
var c = contentNew.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
{
return false;
}
if (c.IsAlphaNumeric())
{
return false;
}
if (c.IsWhitespace())
{
break;
}
index++;
};
}
private static bool IsValidAbbreviationEnding(string match, StringSlice content, int matchIndex)
{
// This will check if the next char at the end of the StringSlice is whitespace, punctuation or \0.
var contentNew = content;
contentNew.End = content.End + 1;
int index = matchIndex + match.Length;
while (index <= contentNew.End)
{
var c = contentNew.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
{
return false;
}
if (c.IsAlphaNumeric())
{
return false;
}
if (c.IsWhitespace())
{
break;
}
index++;
}
return true;
}
return true;
}
}

View File

@@ -5,26 +5,27 @@
using Markdig.Renderers;
using Markdig.Renderers.Html;
namespace Markdig.Extensions.Abbreviations;
/// <summary>
/// A HTML renderer for a <see cref="AbbreviationInline"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CustomContainer}" />
public class HtmlAbbreviationRenderer : HtmlObjectRenderer<AbbreviationInline>
namespace Markdig.Extensions.Abbreviations
{
protected override void Write(HtmlRenderer renderer, AbbreviationInline obj)
/// <summary>
/// A HTML renderer for a <see cref="AbbreviationInline"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CustomContainer}" />
public class HtmlAbbreviationRenderer : HtmlObjectRenderer<AbbreviationInline>
{
// <abbr title="Hyper Text Markup Language">HTML</abbr>
var abbr = obj.Abbreviation;
if (renderer.EnableHtmlForInline)
protected override void Write(HtmlRenderer renderer, AbbreviationInline obj)
{
renderer.Write("<abbr").WriteAttributes(obj).Write(" title=\"").WriteEscape(ref abbr.Text).Write("\">");
}
renderer.Write(abbr.Label);
if (renderer.EnableHtmlForInline)
{
renderer.Write("</abbr>");
// <abbr title="Hyper Text Markup Language">HTML</abbr>
var abbr = obj.Abbreviation;
if (renderer.EnableHtmlForInline)
{
renderer.Write("<abbr").WriteAttributes(obj).Write(" title=\"").WriteEscape(ref abbr.Text).Write("\">");
}
renderer.Write(abbr.Label);
if (renderer.EnableHtmlForInline)
{
renderer.Write("</abbr>");
}
}
}
}

View File

@@ -2,8 +2,8 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using System.IO;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers;
@@ -11,210 +11,206 @@ using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoIdentifiers;
/// <summary>
/// The auto-identifier extension
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class AutoIdentifierExtension : IMarkdownExtension
namespace Markdig.Extensions.AutoIdentifiers
{
private const string AutoIdentifierKey = "AutoIdentifier";
private static readonly StripRendererCache _rendererCache = new();
private readonly AutoIdentifierOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
/// The auto-identifier extension
/// </summary>
/// <param name="options">The options.</param>
public AutoIdentifierExtension(AutoIdentifierOptions options)
/// <seealso cref="IMarkdownExtension" />
public class AutoIdentifierExtension : IMarkdownExtension
{
_options = options;
}
private const string AutoIdentifierKey = "AutoIdentifier";
private readonly AutoIdentifierOptions options;
private readonly StripRendererCache rendererCache = new StripRendererCache();
public void Setup(MarkdownPipelineBuilder pipeline)
{
var headingBlockParser = pipeline.BlockParsers.Find<HeadingBlockParser>();
if (headingBlockParser != null)
/// <summary>
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
/// </summary>
/// <param name="options">The options.</param>
public AutoIdentifierExtension(AutoIdentifierOptions options)
{
// Install a hook on the HeadingBlockParser when a HeadingBlock is actually processed
headingBlockParser.Closed -= HeadingBlockParser_Closed;
headingBlockParser.Closed += HeadingBlockParser_Closed;
}
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
if (paragraphBlockParser != null)
{
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
paragraphBlockParser.Closed -= HeadingBlockParser_Closed;
paragraphBlockParser.Closed += HeadingBlockParser_Closed;
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
/// <summary>
/// Process on a new <see cref="HeadingBlock"/>
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="block">The heading block.</param>
private void HeadingBlockParser_Closed(BlockProcessor processor, Block block)
{
// We may have a ParagraphBlock here as we have a hook on the ParagraphBlockParser
if (!(block is HeadingBlock headingBlock))
{
return;
this.options = options;
}
// If the AutoLink options is set, we register a LinkReferenceDefinition at the document level
if ((_options & AutoIdentifierOptions.AutoLink) != 0)
public void Setup(MarkdownPipelineBuilder pipeline)
{
var headingLine = headingBlock.Lines.Lines[0];
var text = headingLine.ToString();
var linkRef = new HeadingLinkReferenceDefinition(headingBlock)
var headingBlockParser = pipeline.BlockParsers.Find<HeadingBlockParser>();
if (headingBlockParser != null)
{
CreateLinkInline = CreateLinkInlineForHeading
};
// Install a hook on the HeadingBlockParser when a HeadingBlock is actually processed
headingBlockParser.Closed -= HeadingBlockParser_Closed;
headingBlockParser.Closed += HeadingBlockParser_Closed;
}
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
if (paragraphBlockParser != null)
{
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
paragraphBlockParser.Closed -= HeadingBlockParser_Closed;
paragraphBlockParser.Closed += HeadingBlockParser_Closed;
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
/// <summary>
/// Process on a new <see cref="HeadingBlock"/>
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="block">The heading block.</param>
private void HeadingBlockParser_Closed(BlockProcessor processor, Block block)
{
// We may have a ParagraphBlock here as we have a hook on the ParagraphBlockParser
if (!(block is HeadingBlock headingBlock))
{
return;
}
// If the AutoLink options is set, we register a LinkReferenceDefinition at the document level
if ((options & AutoIdentifierOptions.AutoLink) != 0)
{
var headingLine = headingBlock.Lines.Lines[0];
var text = headingLine.ToString();
var linkRef = new HeadingLinkReferenceDefinition(headingBlock)
{
CreateLinkInline = CreateLinkInlineForHeading
};
var doc = processor.Document;
var dictionary = doc.GetData(this) as Dictionary<string, HeadingLinkReferenceDefinition>;
if (dictionary is null)
{
dictionary = new Dictionary<string, HeadingLinkReferenceDefinition>();
doc.SetData(this, dictionary);
doc.ProcessInlinesBegin += DocumentOnProcessInlinesBegin;
}
dictionary[text] = linkRef;
}
// Then we register after inline have been processed to actually generate the proper #id
headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd;
}
private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline)
{
var doc = processor.Document;
var dictionary = doc.GetData(this) as Dictionary<string, HeadingLinkReferenceDefinition>;
if (dictionary is null)
doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this)!;
foreach (var keyPair in dictionary)
{
dictionary = new Dictionary<string, HeadingLinkReferenceDefinition>();
doc.SetData(this, dictionary);
doc.ProcessInlinesBegin += DocumentOnProcessInlinesBegin;
// Here we make sure that auto-identifiers will not override an existing link definition
// defined in the document
// If it is the case, we skip the auto identifier for the Heading
if (!doc.TryGetLinkReferenceDefinition(keyPair.Key, out var linkDef))
{
doc.SetLinkReferenceDefinition(keyPair.Key, keyPair.Value, true);
}
}
dictionary[text] = linkRef;
// Once we are done, we don't need to keep the intermediate dictionary around
doc.RemoveData(this);
}
// Then we register after inline have been processed to actually generate the proper #id
headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd;
}
private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline)
{
var doc = processor.Document;
doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this)!;
foreach (var keyPair in dictionary)
/// <summary>
/// Callback when there is a reference to found to a heading.
/// Note that reference are only working if they are declared after.
/// </summary>
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child)
{
// Here we make sure that auto-identifiers will not override an existing link definition
// defined in the document
// If it is the case, we skip the auto identifier for the Heading
if (!doc.TryGetLinkReferenceDefinition(keyPair.Key, out var linkDef))
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
return new LinkInline()
{
doc.SetLinkReferenceDefinition(keyPair.Key, keyPair.Value, true);
}
}
// Once we are done, we don't need to keep the intermediate dictionary around
doc.RemoveData(this);
}
/// <summary>
/// Callback when there is a reference to found to a heading.
/// Note that reference are only working if they are declared after.
/// </summary>
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child)
{
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
return new LinkInline()
{
// Use GetDynamicUrl to allow late binding of the Url (as a link may occur before the heading is declared and
// the inlines of the heading are actually processed by HeadingBlock_ProcessInlinesEnd)
GetDynamicUrl = () => HtmlHelper.Unescape("#" + headingRef.Heading.GetAttributes().Id),
Title = HtmlHelper.Unescape(linkRef.Title),
};
}
/// <summary>
/// Process the inlines of the heading to create a unique identifier
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="inline">The inline.</param>
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline? inline)
{
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
if (identifiers is null)
{
identifiers = new HashSet<string>();
processor.Document.SetData(AutoIdentifierKey, identifiers);
}
var headingBlock = (HeadingBlock) processor.Block!;
if (headingBlock.Inline is null)
{
return;
}
// If id is already set, don't try to modify it
var attributes = processor.Block!.GetAttributes();
if (attributes.Id != null)
{
return;
}
// Use internally a HtmlRenderer to strip links from a heading
var stripRenderer = _rendererCache.Get();
stripRenderer.Render(headingBlock.Inline);
ReadOnlySpan<char> rawHeadingText = ((FastStringWriter)stripRenderer.Writer).AsSpan();
// Urilize the link
string headingText = (_options & AutoIdentifierOptions.GitHub) != 0
? LinkHelper.UrilizeAsGfm(rawHeadingText)
: LinkHelper.Urilize(rawHeadingText, (_options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
_rendererCache.Release(stripRenderer);
// If the heading is empty, use the word "section" instead
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
// Add a trailing -1, -2, -3...etc. in case of collision
var headingId = baseHeadingId;
if (!identifiers.Add(headingId))
{
var headingBuffer = new ValueStringBuilder(stackalloc char[ValueStringBuilder.StackallocThreshold]);
headingBuffer.Append(baseHeadingId);
headingBuffer.Append('-');
uint index = 0;
do
{
index++;
headingBuffer.Append(index);
headingId = headingBuffer.AsSpan().ToString();
headingBuffer.Length = baseHeadingId.Length + 1;
}
while (!identifiers.Add(headingId));
headingBuffer.Dispose();
}
attributes.Id = headingId;
}
private sealed class StripRendererCache : ObjectCache<HtmlRenderer>
{
protected override HtmlRenderer NewInstance()
{
var headingWriter = new FastStringWriter();
var stripRenderer = new HtmlRenderer(headingWriter)
{
// Set to false both to avoid having any HTML tags in the output
EnableHtmlForInline = false,
EnableHtmlEscape = false
// Use GetDynamicUrl to allow late binding of the Url (as a link may occur before the heading is declared and
// the inlines of the heading are actually processed by HeadingBlock_ProcessInlinesEnd)
GetDynamicUrl = () => HtmlHelper.Unescape("#" + headingRef.Heading.GetAttributes().Id),
Title = HtmlHelper.Unescape(linkRef.Title),
};
return stripRenderer;
}
protected override void Reset(HtmlRenderer instance)
/// <summary>
/// Process the inlines of the heading to create a unique identifier
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="inline">The inline.</param>
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline? inline)
{
instance.ResetInternal();
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
if (identifiers is null)
{
identifiers = new HashSet<string>();
processor.Document.SetData(AutoIdentifierKey, identifiers);
}
((FastStringWriter)instance.Writer).Reset();
var headingBlock = (HeadingBlock) processor.Block!;
if (headingBlock.Inline is null)
{
return;
}
// If id is already set, don't try to modify it
var attributes = processor.Block!.GetAttributes();
if (attributes.Id != null)
{
return;
}
// Use internally a HtmlRenderer to strip links from a heading
var stripRenderer = rendererCache.Get();
stripRenderer.Render(headingBlock.Inline);
var headingText = stripRenderer.Writer.ToString()!;
rendererCache.Release(stripRenderer);
// Urilize the link
headingText = (options & AutoIdentifierOptions.GitHub) != 0
? LinkHelper.UrilizeAsGfm(headingText)
: LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
// If the heading is empty, use the word "section" instead
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
// Add a trailing -1, -2, -3...etc. in case of collision
var headingId = baseHeadingId;
if (!identifiers.Add(headingId))
{
var headingBuffer = new ValueStringBuilder(stackalloc char[ValueStringBuilder.StackallocThreshold]);
headingBuffer.Append(baseHeadingId);
headingBuffer.Append('-');
uint index = 0;
do
{
index++;
headingBuffer.Append(index);
headingId = headingBuffer.AsSpan().ToString();
headingBuffer.Length = baseHeadingId.Length + 1;
}
while (!identifiers.Add(headingId));
headingBuffer.Dispose();
}
attributes.Id = headingId;
}
private sealed class StripRendererCache : ObjectCache<HtmlRenderer>
{
protected override HtmlRenderer NewInstance()
{
var headingWriter = new StringWriter();
var stripRenderer = new HtmlRenderer(headingWriter)
{
// Set to false both to avoid having any HTML tags in the output
EnableHtmlForInline = false,
EnableHtmlEscape = false
};
return stripRenderer;
}
protected override void Reset(HtmlRenderer instance)
{
instance.Reset();
}
}
}
}

View File

@@ -2,36 +2,39 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
namespace Markdig.Extensions.AutoIdentifiers;
using System;
/// <summary>
/// Options for the <see cref="AutoIdentifierExtension"/>.
/// </summary>
[Flags]
public enum AutoIdentifierOptions
namespace Markdig.Extensions.AutoIdentifiers
{
/// <summary>
/// No options: does not apply any additional formatting and/or transformations.
/// Options for the <see cref="AutoIdentifierExtension"/>.
/// </summary>
None = 0,
[Flags]
public enum AutoIdentifierOptions
{
/// <summary>
/// No options: does not apply any additional formatting and/or transformations.
/// </summary>
None = 0,
/// <summary>
/// Default (<see cref="AutoLink"/>)
/// </summary>
Default = AutoLink | AllowOnlyAscii,
/// <summary>
/// Default (<see cref="AutoLink"/>)
/// </summary>
Default = AutoLink | AllowOnlyAscii,
/// <summary>
/// Allows to link to a header by using the same text as the header for the link label. Default is <c>true</c>
/// </summary>
AutoLink = 1,
/// <summary>
/// Allows to link to a header by using the same text as the header for the link label. Default is <c>true</c>
/// </summary>
AutoLink = 1,
/// <summary>
/// Allows only ASCII characters in the url (HTML 5 allows to have UTF8 characters). Default is <c>true</c>
/// </summary>
AllowOnlyAscii = 2,
/// <summary>
/// Allows only ASCII characters in the url (HTML 5 allows to have UTF8 characters). Default is <c>true</c>
/// </summary>
AllowOnlyAscii = 2,
/// <summary>
/// Renders auto identifiers like GitHub.
/// </summary>
GitHub = 4,
/// <summary>
/// Renders auto identifiers like GitHub.
/// </summary>
GitHub = 4,
}
}

View File

@@ -4,21 +4,22 @@
using Markdig.Syntax;
namespace Markdig.Extensions.AutoIdentifiers;
/// <summary>
/// A link reference definition to a <see cref="HeadingBlock"/> stored at the <see cref="MarkdownDocument"/> level.
/// </summary>
/// <seealso cref="LinkReferenceDefinition" />
public class HeadingLinkReferenceDefinition : LinkReferenceDefinition
namespace Markdig.Extensions.AutoIdentifiers
{
public HeadingLinkReferenceDefinition(HeadingBlock headling)
{
Heading = headling;
}
/// <summary>
/// Gets or sets the heading related to this link reference definition.
/// A link reference definition to a <see cref="HeadingBlock"/> stored at the <see cref="MarkdownDocument"/> level.
/// </summary>
public HeadingBlock Heading { get; set; }
/// <seealso cref="LinkReferenceDefinition" />
public class HeadingLinkReferenceDefinition : LinkReferenceDefinition
{
public HeadingLinkReferenceDefinition(HeadingBlock headling)
{
Heading = headling;
}
/// <summary>
/// Gets or sets the heading related to this link reference definition.
/// </summary>
public HeadingBlock Heading { get; set; }
}
}

View File

@@ -5,31 +5,32 @@
using Markdig.Renderers;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks;
/// <summary>
/// Extension to automatically create <see cref="LinkInline"/> when a link url http: or mailto: is found.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class AutoLinkExtension : IMarkdownExtension
namespace Markdig.Extensions.AutoLinks
{
public readonly AutoLinkOptions Options;
public AutoLinkExtension(AutoLinkOptions? options)
/// <summary>
/// Extension to automatically create <see cref="LinkInline"/> when a link url http: or mailto: is found.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class AutoLinkExtension : IMarkdownExtension
{
Options = options ?? new AutoLinkOptions();
}
public readonly AutoLinkOptions Options;
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<AutoLinkParser>())
public AutoLinkExtension(AutoLinkOptions? options)
{
Options = options ?? new AutoLinkOptions();
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<AutoLinkParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new AutoLinkParser(Options));
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new AutoLinkParser(Options));
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
}

View File

@@ -2,24 +2,25 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
namespace Markdig.Extensions.AutoLinks;
public class AutoLinkOptions
namespace Markdig.Extensions.AutoLinks
{
public AutoLinkOptions()
public class AutoLinkOptions
{
ValidPreviousCharacters = "*_~(";
public AutoLinkOptions()
{
ValidPreviousCharacters = "*_~(";
}
public string ValidPreviousCharacters { get; set; }
/// <summary>
/// Should the link open in a new window when clicked (false by default)
/// </summary>
public bool OpenInNewWindow { get; set; }
/// <summary>
/// Should a www link be prefixed with https:// instead of http:// (false by default)
/// </summary>
public bool UseHttpsForWWWLinks { get; set; }
}
public string ValidPreviousCharacters { get; set; }
/// <summary>
/// Should the link open in a new window when clicked (false by default)
/// </summary>
public bool OpenInNewWindow { get; set; }
/// <summary>
/// Should a www link be prefixed with https:// instead of http:// (false by default)
/// </summary>
public bool UseHttpsForWWWLinks { get; set; }
}

View File

@@ -2,278 +2,281 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks;
/// <summary>
/// The inline parser used to for autolinks.
/// </summary>
/// <seealso cref="InlineParser" />
public class AutoLinkParser : InlineParser
namespace Markdig.Extensions.AutoLinks
{
/// <summary>
/// Initializes a new instance of the <see cref="AutoLinkParser"/> class.
/// The inline parser used to for autolinks.
/// </summary>
public AutoLinkParser(AutoLinkOptions options)
/// <seealso cref="InlineParser" />
public class AutoLinkParser : InlineParser
{
Options = options ?? throw new ArgumentNullException(nameof(options));
OpeningCharacters = new char[]
/// <summary>
/// Initializes a new instance of the <see cref="AutoLinkParser"/> class.
/// </summary>
public AutoLinkParser(AutoLinkOptions options)
{
'h', // for http:// and https://
'f', // for ftp://
'm', // for mailto:
't', // for tel:
'w', // for www.
};
Options = options ?? throw new ArgumentNullException(nameof(options));
_listOfCharCache = new ListOfCharCache();
}
OpeningCharacters = new char[]
{
'h', // for http:// and https://
'f', // for ftp://
'm', // for mailto:
't', // for tel:
'w', // for www.
};
public readonly AutoLinkOptions Options;
private readonly ListOfCharCache _listOfCharCache;
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Previous char must be a whitespace or a punctuation
var previousChar = slice.PeekCharExtra(-1);
if (!previousChar.IsWhiteSpaceOrZero() && Options.ValidPreviousCharacters.IndexOf(previousChar) == -1)
{
return false;
_listOfCharCache = new ListOfCharCache();
}
var startPosition = slice.Start;
int domainOffset = 0;
public readonly AutoLinkOptions Options;
var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 't':
if (!slice.MatchLowercase("el:", 1))
{
return false;
}
domainOffset = 4;
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = 4; // www.
break;
}
private readonly ListOfCharCache _listOfCharCache;
List<char> pendingEmphasis = _listOfCharCache.Get();
try
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Check that an autolink is possible in the current context
if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis))
// Previous char must be a whitespace or a punctuation
var previousChar = slice.PeekCharExtra(-1);
if (!previousChar.IsWhiteSpaceOrZero() && Options.ValidPreviousCharacters.IndexOf(previousChar) == -1)
{
return false;
}
// Parse URL
if (!LinkHelper.TryParseUrl(ref slice, out string? link, out _, true))
{
return false;
}
var startPosition = slice.Start;
int domainOffset = 0;
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
if (pendingEmphasis.Count > 0)
{
for (int i = link.Length - 1; i >= 0; i--)
{
if (pendingEmphasis.Contains(link[i]))
{
slice.Start--;
}
else
{
if (i < link.Length - 1)
{
link = link.Substring(0, i + 1);
}
break;
}
}
}
// Post-check URL
var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'f':
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 't':
if (string.Equals(link, "tel", StringComparison.OrdinalIgnoreCase))
if (!slice.MatchLowercase("el:", 1))
{
return false;
}
domainOffset = 4;
break;
case 'm':
int atIndex = link.IndexOf('@');
if (atIndex == -1 ||
atIndex == 7) // mailto:@ - no email part
case 'w':
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = atIndex + 1;
domainOffset = 4; // www.
break;
}
// Do not need to check if a telephone number is a valid domain
if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset))
List<char> pendingEmphasis = _listOfCharCache.Get();
try
{
return false;
}
var inline = new LinkInline()
{
Span =
{
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
},
Line = line,
Column = column,
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
IsClosed = true,
IsAutoLink = true,
};
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
skipFromBeginning = c == 't' ? 4 : skipFromBeginning; // See above but for tel:
inline.Span.End = inline.Span.Start + link.Length - 1;
inline.UrlSpan = inline.Span;
inline.AppendChild(new LiteralInline()
{
Span = inline.Span,
Line = line,
Column = column,
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
IsClosed = true
});
processor.Inline = inline;
if (Options.OpenInNewWindow)
{
inline.GetAttributes().AddPropertyIfNotExist("target", "_blank");
}
return true;
}
finally
{
_listOfCharCache.Release(pendingEmphasis);
}
}
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List<char> pendingEmphasis)
{
// Case where there is a pending HtmlInline <a>
var currentInline = processor.Inline;
while (currentInline != null)
{
if (currentInline is HtmlInline htmlInline)
{
// If we have a </a> we don't expect nested <a>
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
{
break;
}
// If there is a pending <a>, we can't allow a link
if (htmlInline.Tag.StartsWith("<a", StringComparison.OrdinalIgnoreCase))
// Check that an autolink is possible in the current context
if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis))
{
return false;
}
}
// Check previous sibling and parents in the tree
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
}
// Parse URL
if (!LinkHelper.TryParseUrl(ref slice, out string? link, out _, true))
{
return false;
}
// Check that we don't have any pending brackets opened (where we could have a possible markdown link)
// NOTE: This assume that [ and ] are used for links, otherwise autolink will not work properly
currentInline = processor.Inline;
int countBrackets = 0;
while (currentInline != null)
{
if (currentInline is LinkDelimiterInline linkDelimiterInline && linkDelimiterInline.IsActive)
{
if (linkDelimiterInline.Type == DelimiterType.Open)
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
if (pendingEmphasis.Count > 0)
{
countBrackets++;
}
else if (linkDelimiterInline.Type == DelimiterType.Close)
{
countBrackets--;
}
}
else
{
// Record all pending characters for emphasis
if (currentInline is EmphasisDelimiterInline emphasisDelimiter)
{
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
for (int i = link.Length - 1; i >= 0; i--)
{
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
if (pendingEmphasis.Contains(link[i]))
{
slice.Start--;
}
else
{
if (i < link.Length - 1)
{
link = link.Substring(0, i + 1);
}
break;
}
}
}
// Post-check URL
switch (c)
{
case 'h':
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'f':
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 't':
if (string.Equals(link, "tel", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'm':
int atIndex = link.IndexOf('@');
if (atIndex == -1 ||
atIndex == 7) // mailto:@ - no email part
{
return false;
}
domainOffset = atIndex + 1;
break;
}
// Do not need to check if a telephone number is a valid domain
if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset))
{
return false;
}
var inline = new LinkInline()
{
Span =
{
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
},
Line = line,
Column = column,
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
IsClosed = true,
IsAutoLink = true,
};
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
skipFromBeginning = c == 't' ? 4 : skipFromBeginning; // See above but for tel:
inline.Span.End = inline.Span.Start + link.Length - 1;
inline.UrlSpan = inline.Span;
inline.AppendChild(new LiteralInline()
{
Span = inline.Span,
Line = line,
Column = column,
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
IsClosed = true
});
processor.Inline = inline;
if (Options.OpenInNewWindow)
{
inline.GetAttributes().AddPropertyIfNotExist("target", "_blank");
}
return true;
}
finally
{
_listOfCharCache.Release(pendingEmphasis);
}
currentInline = currentInline.Parent;
}
return countBrackets <= 0;
}
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
{
protected override void Reset(List<char> instance)
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List<char> pendingEmphasis)
{
instance.Clear();
// Case where there is a pending HtmlInline <a>
var currentInline = processor.Inline;
while (currentInline != null)
{
if (currentInline is HtmlInline htmlInline)
{
// If we have a </a> we don't expect nested <a>
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
{
break;
}
// If there is a pending <a>, we can't allow a link
if (htmlInline.Tag.StartsWith("<a", StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// Check previous sibling and parents in the tree
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
}
// Check that we don't have any pending brackets opened (where we could have a possible markdown link)
// NOTE: This assume that [ and ] are used for links, otherwise autolink will not work properly
currentInline = processor.Inline;
int countBrackets = 0;
while (currentInline != null)
{
if (currentInline is LinkDelimiterInline linkDelimiterInline && linkDelimiterInline.IsActive)
{
if (linkDelimiterInline.Type == DelimiterType.Open)
{
countBrackets++;
}
else if (linkDelimiterInline.Type == DelimiterType.Close)
{
countBrackets--;
}
}
else
{
// Record all pending characters for emphasis
if (currentInline is EmphasisDelimiterInline emphasisDelimiter)
{
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
{
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
}
}
}
currentInline = currentInline.Parent;
}
return countBrackets <= 0;
}
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
{
protected override void Reset(List<char> instance)
{
instance.Clear();
}
}
}
}

View File

@@ -7,56 +7,57 @@ using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Bootstrap;
/// <summary>
/// Extension for tagging some HTML elements with bootstrap classes.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class BootstrapExtension : IMarkdownExtension
namespace Markdig.Extensions.Bootstrap
{
public void Setup(MarkdownPipelineBuilder pipeline)
/// <summary>
/// Extension for tagging some HTML elements with bootstrap classes.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class BootstrapExtension : IMarkdownExtension
{
// Make sure we don't have a delegate twice
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
foreach (var node in document.Descendants())
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (node.IsInline)
// Make sure we don't have a delegate twice
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
foreach (var node in document.Descendants())
{
if (node.IsContainerInline && node is LinkInline link && link.IsImage)
if (node.IsInline)
{
link.GetAttributes().AddClass("img-fluid");
if (node.IsContainerInline && node is LinkInline link && link.IsImage)
{
link.GetAttributes().AddClass("img-fluid");
}
}
}
else if (node.IsContainerBlock)
{
if (node is Tables.Table)
else if (node.IsContainerBlock)
{
node.GetAttributes().AddClass("table");
if (node is Tables.Table)
{
node.GetAttributes().AddClass("table");
}
else if (node is QuoteBlock)
{
node.GetAttributes().AddClass("blockquote");
}
else if (node is Figures.Figure)
{
node.GetAttributes().AddClass("figure");
}
}
else if (node is QuoteBlock)
else
{
node.GetAttributes().AddClass("blockquote");
}
else if (node is Figures.Figure)
{
node.GetAttributes().AddClass("figure");
}
}
else
{
if (node is Figures.FigureCaption)
{
node.GetAttributes().AddClass("figure-caption");
if (node is Figures.FigureCaption)
{
node.GetAttributes().AddClass("figure-caption");
}
}
}
}

View File

@@ -8,41 +8,42 @@ using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Citations;
/// <summary>
/// Extension for cite ""...""
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class CitationExtension : IMarkdownExtension
namespace Markdig.Extensions.Citations
{
public void Setup(MarkdownPipelineBuilder pipeline)
/// <summary>
/// Extension for cite ""...""
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class CitationExtension : IMarkdownExtension
{
var parser = pipeline.InlineParsers.FindExact<EmphasisInlineParser>();
if (parser != null && !parser.HasEmphasisChar('"'))
public void Setup(MarkdownPipelineBuilder pipeline)
{
parser.EmphasisDescriptors.Add(new EmphasisDescriptor('"', 2, 2, false));
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
{
// Extend the rendering here.
var emphasisRenderer = renderer.ObjectRenderers.FindExact<EmphasisInlineRenderer>();
if (emphasisRenderer != null)
var parser = pipeline.InlineParsers.FindExact<EmphasisInlineParser>();
if (parser != null && !parser.HasEmphasisChar('"'))
{
// TODO: Use an ordered list instead as we don't know if this specific GetTag has been already added
var previousTag = emphasisRenderer.GetTag;
emphasisRenderer.GetTag = inline => GetTag(inline) ?? previousTag(inline);
parser.EmphasisDescriptors.Add(new EmphasisDescriptor('"', 2, 2, false));
}
}
}
private static string? GetTag(EmphasisInline emphasisInline)
{
Debug.Assert(emphasisInline.DelimiterCount <= 2);
return emphasisInline.DelimiterCount == 2 && emphasisInline.DelimiterChar == '"' ? "cite" : null;
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
{
// Extend the rendering here.
var emphasisRenderer = renderer.ObjectRenderers.FindExact<EmphasisInlineRenderer>();
if (emphasisRenderer != null)
{
// TODO: Use an ordered list instead as we don't know if this specific GetTag has been already added
var previousTag = emphasisRenderer.GetTag;
emphasisRenderer.GetTag = inline => GetTag(inline) ?? previousTag(inline);
}
}
}
private static string? GetTag(EmphasisInline emphasisInline)
{
Debug.Assert(emphasisInline.DelimiterCount <= 2);
return emphasisInline.DelimiterCount == 2 && emphasisInline.DelimiterChar == '"' ? "cite" : null;
}
}
}

View File

@@ -6,56 +6,57 @@ using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
namespace Markdig.Extensions.CustomContainers;
/// <summary>
/// A block custom container.
/// </summary>
/// <seealso cref="ContainerBlock" />
/// <seealso cref="IFencedBlock" />
public class CustomContainer : ContainerBlock, IFencedBlock
namespace Markdig.Extensions.CustomContainers
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomContainer"/> class.
/// A block custom container.
/// </summary>
/// <param name="parser">The parser used to create this block.</param>
public CustomContainer(BlockParser parser) : base(parser)
/// <seealso cref="ContainerBlock" />
/// <seealso cref="IFencedBlock" />
public class CustomContainer : ContainerBlock, IFencedBlock
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomContainer"/> class.
/// </summary>
/// <param name="parser">The parser used to create this block.</param>
public CustomContainer(BlockParser parser) : base(parser)
{
}
/// <inheritdoc />
public char FencedChar { get; set; }
/// <inheritdoc />
public int OpeningFencedCharCount { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterFencedChar { get; set; }
/// <inheritdoc />
public string? Info { get; set; }
/// <inheritdoc />
public StringSlice UnescapedInfo { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterInfo { get; set; }
/// <inheritdoc />
public string? Arguments { get; set; }
/// <inheritdoc />
public StringSlice UnescapedArguments { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterArguments { get; set; }
/// <inheritdoc />
public NewLine InfoNewLine { get; set; }
/// <inheritdoc />
public StringSlice TriviaBeforeClosingFence { get; set; }
/// <inheritdoc />
public int ClosingFencedCharCount { get; set; }
}
/// <inheritdoc />
public char FencedChar { get; set; }
/// <inheritdoc />
public int OpeningFencedCharCount { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterFencedChar { get; set; }
/// <inheritdoc />
public string? Info { get; set; }
/// <inheritdoc />
public StringSlice UnescapedInfo { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterInfo { get; set; }
/// <inheritdoc />
public string? Arguments { get; set; }
/// <inheritdoc />
public StringSlice UnescapedArguments { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterArguments { get; set; }
/// <inheritdoc />
public NewLine InfoNewLine { get; set; }
/// <inheritdoc />
public StringSlice TriviaBeforeClosingFence { get; set; }
/// <inheritdoc />
public int ClosingFencedCharCount { get; set; }
}

View File

@@ -5,53 +5,54 @@
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
namespace Markdig.Extensions.CustomContainers;
/// <summary>
/// Extension to allow custom containers.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class CustomContainerExtension : IMarkdownExtension
namespace Markdig.Extensions.CustomContainers
{
public void Setup(MarkdownPipelineBuilder pipeline)
/// <summary>
/// Extension to allow custom containers.
/// </summary>
/// <seealso cref="IMarkdownExtension" />
public class CustomContainerExtension : IMarkdownExtension
{
if (!pipeline.BlockParsers.Contains<CustomContainerParser>())
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Insert the parser before any other parsers
pipeline.BlockParsers.Insert(0, new CustomContainerParser());
}
// Plug the inline parser for CustomContainerInline
var inlineParser = pipeline.InlineParsers.Find<EmphasisInlineParser>();
if (inlineParser != null && !inlineParser.HasEmphasisChar(':'))
{
inlineParser.EmphasisDescriptors.Add(new EmphasisDescriptor(':', 2, 2, true));
inlineParser.TryCreateEmphasisInlineList.Add((emphasisChar, delimiterCount) =>
if (!pipeline.BlockParsers.Contains<CustomContainerParser>())
{
if (delimiterCount == 2 && emphasisChar == ':')
// Insert the parser before any other parsers
pipeline.BlockParsers.Insert(0, new CustomContainerParser());
}
// Plug the inline parser for CustomContainerInline
var inlineParser = pipeline.InlineParsers.Find<EmphasisInlineParser>();
if (inlineParser != null && !inlineParser.HasEmphasisChar(':'))
{
inlineParser.EmphasisDescriptors.Add(new EmphasisDescriptor(':', 2, 2, true));
inlineParser.TryCreateEmphasisInlineList.Add((emphasisChar, delimiterCount) =>
{
return new CustomContainerInline();
}
return null;
});
if (delimiterCount == 2 && emphasisChar == ':')
{
return new CustomContainerInline();
}
return null;
});
}
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (renderer is HtmlRenderer htmlRenderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlCustomContainerRenderer>())
if (renderer is HtmlRenderer htmlRenderer)
{
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlCustomContainerRenderer());
if (!htmlRenderer.ObjectRenderers.Contains<HtmlCustomContainerRenderer>())
{
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlCustomContainerRenderer());
}
if (!htmlRenderer.ObjectRenderers.Contains<HtmlCustomContainerInlineRenderer>())
{
// Must be inserted before EmphasisRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlCustomContainerInlineRenderer());
}
}
if (!htmlRenderer.ObjectRenderers.Contains<HtmlCustomContainerInlineRenderer>())
{
// Must be inserted before EmphasisRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlCustomContainerInlineRenderer());
}
}
}
}
}

View File

@@ -4,13 +4,14 @@
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.CustomContainers;
/// <summary>
/// An inline custom container
/// </summary>
/// <seealso cref="ContainerInline" />
/// <seealso cref="EmphasisInline" />
public class CustomContainerInline : EmphasisInline
namespace Markdig.Extensions.CustomContainers
{
/// <summary>
/// An inline custom container
/// </summary>
/// <seealso cref="ContainerInline" />
/// <seealso cref="EmphasisInline" />
public class CustomContainerInline : EmphasisInline
{
}
}

View File

@@ -4,27 +4,28 @@
using Markdig.Parsers;
namespace Markdig.Extensions.CustomContainers;
/// <summary>
/// The block parser for a <see cref="CustomContainer"/>.
/// </summary>
/// <seealso cref="FencedBlockParserBase{CustomContainer}" />
public class CustomContainerParser : FencedBlockParserBase<CustomContainer>
namespace Markdig.Extensions.CustomContainers
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomContainerParser"/> class.
/// The block parser for a <see cref="CustomContainer"/>.
/// </summary>
public CustomContainerParser()
/// <seealso cref="FencedBlockParserBase{CustomContainer}" />
public class CustomContainerParser : FencedBlockParserBase<CustomContainer>
{
OpeningCharacters = new [] {':'};
/// <summary>
/// Initializes a new instance of the <see cref="CustomContainerParser"/> class.
/// </summary>
public CustomContainerParser()
{
OpeningCharacters = new [] {':'};
// We don't need a prefix
InfoPrefix = null;
}
// We don't need a prefix
InfoPrefix = null;
}
protected override CustomContainer CreateFencedBlock(BlockProcessor processor)
{
return new CustomContainer(this);
protected override CustomContainer CreateFencedBlock(BlockProcessor processor)
{
return new CustomContainer(this);
}
}
}

View File

@@ -5,18 +5,19 @@
using Markdig.Renderers;
using Markdig.Renderers.Html;
namespace Markdig.Extensions.CustomContainers;
/// <summary>
/// A HTML renderer for a <see cref="CustomContainerInline"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CustomContainerInline}" />
public class HtmlCustomContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline>
namespace Markdig.Extensions.CustomContainers
{
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
/// <summary>
/// A HTML renderer for a <see cref="CustomContainerInline"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CustomContainerInline}" />
public class HtmlCustomContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline>
{
renderer.Write("<span").WriteAttributes(obj).Write('>');
renderer.WriteChildren(obj);
renderer.Write("</span>");
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
{
renderer.Write("<span").WriteAttributes(obj).Write('>');
renderer.WriteChildren(obj);
renderer.Write("</span>");
}
}
}

View File

@@ -5,26 +5,27 @@
using Markdig.Renderers;
using Markdig.Renderers.Html;
namespace Markdig.Extensions.CustomContainers;
/// <summary>
/// A HTML renderer for a <see cref="CustomContainer"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CustomContainer}" />
public class HtmlCustomContainerRenderer : HtmlObjectRenderer<CustomContainer>
namespace Markdig.Extensions.CustomContainers
{
protected override void Write(HtmlRenderer renderer, CustomContainer obj)
/// <summary>
/// A HTML renderer for a <see cref="CustomContainer"/>.
/// </summary>
/// <seealso cref="HtmlObjectRenderer{CustomContainer}" />
public class HtmlCustomContainerRenderer : HtmlObjectRenderer<CustomContainer>
{
renderer.EnsureLine();
if (renderer.EnableHtmlForBlock)
protected override void Write(HtmlRenderer renderer, CustomContainer obj)
{
renderer.Write("<div").WriteAttributes(obj).Write('>');
}
// We don't escape a CustomContainer
renderer.WriteChildren(obj);
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</div>");
renderer.EnsureLine();
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<div").WriteAttributes(obj).Write('>');
}
// We don't escape a CustomContainer
renderer.WriteChildren(obj);
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</div>");
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More