Compare commits

...

256 Commits

Author SHA1 Message Date
Martijn Laarman
d47fbc757f Optimize PipeTable parsing: O(n²) → O(n) for 3.7x–85x speedup, enables 10K+ row tables (#922)
* Optimize PipeTable parsing: O(n²) → O(n) for large tables

Pipe tables were creating deeply nested tree structures where each pipe
delimiter contained all subsequent content as children, causing O(n²)
traversal complexity for n cells. This change restructures the parser to
use a flat sibling-based structure, treating tables as matrices rather
than nested trees.

Key changes:
- Set IsClosed=true on PipeTableDelimiterInline to prevent nesting
- Add PromoteNestedPipesToRootLevel() to flatten pipes nested in emphasis
- Update cell boundary detection to use sibling traversal
- Move EmphasisInlineParser before PipeTableParser in processing order
- Fix EmphasisInlineParser to continue past IsClosed delimiters
- Add ContainsParentOrSiblingOfType<T>() helper for flat structure detection

Performance improvements (measured on typical markdown content):

| Rows | Before    | After   | Speedup |
|------|-----------|---------|---------|
| 100  | 542 μs    | 150 μs  | 3.6x    |
| 500  | 23,018 μs | 763 μs  | 30x     |
| 1000 | 89,418 μs | 1,596 μs| 56x     |
| 1500 | 201,593 μs| 2,740 μs| 74x     |
| 5000 | CRASH     | 10,588 μs| ∞      |
| 10000| CRASH     | 18,551 μs| ∞      |

Tables with 5000+ rows previously crashed due to stack overflow from
recursive depth. They now parse successfully with linear time complexity.

* remove baseline results file

* Do not use System.Index and fix nullabillity checks for older platforms
2026-01-30 22:05:18 +01:00
prozolic
3602433b84 Replace null checks with IsEmpty property for ReadOnlySpan<char> (#916)
This change suppresses CA2265 warnings.
2026-01-30 22:01:50 +01:00
prozolic
1bac4afc9b Use Dictionary.TryAdd instead of ContainsKey and indexer by reducing lookups. (#917)
* Use Dictionary.TryAdd instead of ContainsKey and indexer by reducing lookups.

* Update src/Markdig/Parsers/ParserList.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-30 22:01:27 +01:00
Tatsunori Uchino
a89056d961 Recognize supplementary characters (#913)
* Recognize supplementary characters

* Internatize Rune

* Fix failing tests

* Fix extra comment error

* Remove extra local variable c

* Reorganize classes around Rune

* Prepare both Rune and char variants / make Rune variant public for .NET

* Make APIs in StringSlice.cs public only in modern .NET

* Throw exception if cannot obtain first Rune

* Add comments

* Add comment on PeekRuneExtra

* Use `Rune.TryCreate`

* Remove backtrack

* Fix parameter name in XML comment

* Don't throw when error in `Rune.DecodeFromUtf16`

* Fix RuneAt

* Add tests of Rune-related methods of `StringSlice`

* Make comment more tolerant of changes

* Tweak comment

* Fix comment

* Add `readonly`

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Move namespace of polyfilled Rune out of System.Text

* Apply suggestions from code review

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Fix regression by review suggestion

* Prepare constant for .NET Standard test

* Don't call `IsPunctuationException` if unnecessary

* PR feedback

---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2026-01-12 11:08:03 +01:00
Miha Zupan
cd7b9ca0ef Test netstandard (#915)
* Add GH Action to test netstandard 2.0 and 2.1

* Account for TFM changes in tests project
2025-11-17 18:46:26 +01:00
Alexandre Mutel
fb698598e4 Use central package management 2025-11-17 08:19:42 +01:00
mos379
12590e5fbe feat(link-helper): improve ASCII normalization handling (#911)
* feat(link-helper): improve ASCII normalization handling

Enhanced the `Urilize` method to better handle ASCII normalization and special characters. Added support for decomposing characters when `allowOnlyAscii` is true and skipping diacritical marks. Introduced handling for special German, Scandinavian, and Icelandic characters via new helper methods: `IsSpecialScandinavianOrGermanChar` and `NormalizeScandinavianOrGermanChar`.

Reorganized `using` directives for better clarity. Updated the processing loop in `Urilize` to handle normalized spans and ASCII equivalents more effectively. These changes improve link generation compatibility across various languages.

* Add tests for Scandinavian and German character normalization

Added tests for NormalizeScandinavianOrGermanChar method to validate character normalization for various special characters in both ASCII and non-ASCII contexts.

* test(link-helper): update ASCII transliteration tests

Updated test cases in `TestUrilizeOnlyAscii_Simple` to reflect
changes in `LinkHelper.Urilize` behavior. Non-ASCII characters
like `æ` and `ø` are now transliterated to their ASCII
equivalents (`ae` and `oe`) instead of being removed.
2025-11-10 22:01:35 +01:00
Miha Zupan
8c01cf0549 Add another test for pipe tables (#907) 2025-10-21 08:37:43 +02:00
Miha Zupan
bcbd8e47ac Lazily allocate ProcessInlinesBegin/End delegates on Blocks (#906) 2025-10-21 08:37:02 +02:00
Miha Zupan
d6e88f16f7 Fix pipe table parsing with a leading paragraph (#905)
* Fix pipe table parsing with a leading paragraph

* Use the alternative approach
2025-10-20 21:43:25 +02:00
Miha Zupan
03bdf60086 Add a basic fuzzing project (#903)
* Add basic fuzzing project

* Mark the project as non-packable
2025-10-17 08:09:28 +02:00
Miha Zupan
5c78932f55 Fix edge cases in EmphasisInlineParser (#902) 2025-10-17 08:07:15 +02:00
Miha Zupan
191e33ab32 Fix build warnings (#899) 2025-10-16 17:25:47 +02:00
Miha Zupan
800235ba7a Fix IndexOutOfRangeException in CodeInlineParser (#900) 2025-10-16 17:25:30 +02:00
Miha Zupan
d5f8a809a0 Move sln to slnx (#901) 2025-10-16 17:24:33 +02:00
Asttear
781d9b5365 Remove leading newline in block attributes (#896)
* Remove leading newline in block attributes

fix #895

* Add handling logic for `\r\n`
2025-10-05 11:21:12 +02:00
Phillip Haydon
543570224e Fix issue where an inline code block that spans multiple lines doesn't parse correctly (#893)
* fixes issue where an inline code block that spans multiple lines doesn't get treated as code

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Apply suggestion from @MihaZupan

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* fix broken test

* removed unreachable code and added more tests

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Update src/Markdig.Tests/TestPipeTable.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* removed uncessary inline code check

* Update src/Markdig/Parsers/Inlines/CodeInlineParser.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
Co-authored-by: Alexandre Mutel <alexandre_mutel@live.com>
2025-10-03 09:34:24 +02:00
Daniel Klecha
4dc0be88b4 add options for link inline (#894)
* add options for link inline

* create LinkOptions and associate it with all four parsers

* set EnableHtmlParsing to true by default
2025-10-03 09:22:51 +02:00
Phillip Haydon
0e9e80e1cd Fix for table depth error when cell contains backticks (#891)
* failing test

* fixed bug with table containing back tick which causes depth error
2025-09-21 16:26:02 +02:00
Alexandre Mutel
1b04599c44 Merge pull request #888 from prozolic/pullreq
Fixes issue #845
2025-09-11 07:55:51 +02:00
prozolic
5e6fb2d1c5 Add test for issue #845 list item blank line 2025-09-08 22:36:09 +09:00
prozolic
14406bc60d Fixes issue #845 2025-09-06 21:10:51 +09:00
Alexandre Mutel
2aa6780a30 Merge pull request #883 from messani/master
Add source position tracking for grid tables
2025-08-28 09:04:44 +02:00
Alexandre Mutel
c43646586c Merge pull request #885 from dannyp32/supportTableWithoutExtraLine
Add support for a table without an extra new line before it
2025-08-28 09:02:29 +02:00
Daniel Pino
d548b82bcd Add support for a table without an extra new line before it 2025-08-09 08:50:49 +00:00
Tibor Peluch
aab5543cb5 Code cleanup 2025-07-14 20:17:50 +02:00
Tibor Peluch
2e1d741aaf Cleaned up code, added tests for source position 2025-07-14 10:23:15 +02:00
Tibor Peluch
80c50e31e2 Attempt to fix tracking of tree node positions (line, column) inside GridTable 2025-07-11 13:25:03 +02:00
Alexandre Mutel
7ff8db9016 Merge pull request #877 from Mertsch/Mertsch-patch-1
Update readme.md
2025-06-19 08:41:54 +02:00
Alexandre Mutel
c69fb9ae73 Merge pull request #879 from stylefish/issue878
Fixes #878: RoundtripRenderer: render indent and 0 blocks for ordered lists
2025-06-19 08:41:10 +02:00
stylefish
5a3c206076 Fixes #878: render indent and 0 blocks 2025-06-16 11:26:23 +02:00
Mertsch
b92890094c Update readme.md 2025-06-12 14:26:00 +02:00
Alexandre Mutel
682c727288 Merge pull request #876 from Akarinnnnn/fix-872
Fix #872 by reserve null title string.
2025-06-05 07:57:29 +02:00
Fa鸽
ec2eef25b2 Remove HtmlHelper.UnescapeNullable 2025-06-04 19:23:18 +08:00
Fa鸽
6261660d37 Explain why not to normalize link title into empty strings 2025-05-31 22:26:33 +08:00
Fa鸽
6d1fa96389 Changed link parsing tests for #872 2025-05-31 16:33:29 +08:00
Fa鸽
47c4e9b1e2 Fix #872 by reserve null title string. 2025-05-31 16:01:42 +08:00
Alexandre Mutel
3535701d70 Merge pull request #869 from prozolic/pullreq
Fix bug in `Markdown.ToPlainText` with code blocks
2025-04-27 18:52:57 +02:00
prozolic
c41b389053 Fix CodeBlockRenderer.Write 2025-04-27 16:49:05 +09:00
Alexandre Mutel
09a4b81a6e Update tests 2025-04-15 11:35:54 +02:00
Alexandre Mutel
7b14e2e091 Merge pull request #867 from MihaZupan/commonmark-0.31.2
Update to CommonMark 0.31.2
2025-04-15 10:59:22 +02:00
Alexandre Mutel
1e17dcdd08 Merge pull request #866 from MihaZupan/alert-perf
Improve Alert parsing perf
2025-04-15 10:58:40 +02:00
Alexandre Mutel
40e5ab1514 Merge pull request #863 from Amberg/master
Infer pipe table column widths from separator row
2025-04-15 10:57:47 +02:00
Alexandre Mutel
2953b026fc Merge pull request #865 from RamType0/patch-1
Fix `MathInline` is called "math block"
2025-04-15 10:56:27 +02:00
Miha Zupan
42ab98968d Update readme 2025-04-15 04:32:52 +02:00
Miha Zupan
b15cf582a5 Add 'search' HTML tag support 2025-04-15 04:31:13 +02:00
Miha Zupan
61e9be290b Allow empty HTML comments, double hyphens in text 2025-04-15 04:02:22 +02:00
Miha Zupan
a9ce0eb438 Update definition of punctuation to include symbols 2025-04-15 03:09:59 +02:00
Miha Zupan
023d93c091 Update CommonMark spec to 0.31.2 2025-04-14 23:32:22 +02:00
Miha Zupan
bbefce3b1f Sealed + ref struct 2025-04-14 22:11:53 +02:00
Miha Zupan
0d6343b421 Make AlertBlock parsing a bit cheaper 2025-04-14 22:02:21 +02:00
Ram.Type-0
f4effc25c0 Fix MathInline is called "math block" 2025-04-15 00:57:16 +09:00
Alexandre Mutel
7a83a1fd3d Merge pull request #864 from MihaZupan/net9-perf4
A couple perf improvements
2025-04-14 11:10:48 +02:00
Miha Zupan
8269ff1af5 Improve AutoLinkParser overhead for false-positive opening chars 2025-04-13 17:45:52 +02:00
Miha Zupan
0e6d0f4cb2 Fix style 2025-04-13 17:23:40 +02:00
Miha Zupan
8484420b72 Remove some branches from IsWhiteSpace and IsWhiteSpaceOrZero 2025-04-13 17:23:27 +02:00
Miha Zupan
c82a36884d Use the field keyword in a few places 2025-04-13 17:22:51 +02:00
Miha Zupan
da3d7f4f3a Improve some descriptions 2025-04-13 17:22:24 +02:00
Miha Zupan
eceb70c16a Avoid delegate allocations in AutoIdentifierExtension 2025-04-13 17:22:04 +02:00
Miha Zupan
7a9c192d7d Speed up FencedCodeBlock rendering 2025-04-13 17:21:43 +02:00
Miha Zupan
8cfa0cf0ae Improve more character tests with SearchValues 2025-04-13 16:59:55 +02:00
Miha Zupan
a82c3bd705 Improve some character tests 2025-04-13 16:59:29 +02:00
Miha Zupan
ecfda373b9 Avoid warnings in Markdig.WebApp 2025-04-13 16:11:30 +02:00
Miha Zupan
d8f69218db Commit FrozenDictionary polyfill 2025-04-13 16:11:02 +02:00
Miha Zupan
adfcf42529 Use FrozenDictionary in a couple places 2025-04-13 16:09:37 +02:00
Miha Zupan
dab1ca5483 Avoid unnecessary null check when reading trivia info 2025-04-13 16:09:24 +02:00
Manuel Amstutz
55f770cc07 feat: infer pipe table column widths from separator row
Adds support for calculating column widths in pipe tables based on the number of dashes in the header separator row.
Enabled via the InferColumnWidthsFromSeparator option in PipeTableOptions.
2025-04-09 20:55:54 +02:00
Alexandre Mutel
8b84542527 Merge pull request #861 from Meir017/patch-1
chore: update repository's github path
2025-03-20 16:38:19 +01:00
Meir Blachman
086440bcd3 update repository's github path 2025-03-20 16:59:50 +02:00
Alexandre Mutel
97470bd61f Merge pull request #859 from JamesNK/jamesnk/autolinks-domain-no-period
Add AutoLinkOptions.AllowDomainWithoutPeriod
2025-03-18 10:00:13 +01:00
James Newton-King
90c73b7754 Update src/Markdig/Helpers/LinkHelper.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2025-03-18 14:54:20 +08:00
James Newton-King
ee403ce28f Port tests 2025-03-17 08:26:51 +08:00
James Newton-King
8b403918b9 Update XML doc 2025-03-17 07:47:40 +08:00
James Newton-King
39b07d6bc5 Add AutoLinkOptions.AllowDomainWithoutPeriod 2025-03-17 07:46:23 +08:00
Alexandre Mutel
fb3fe8b261 Merge pull request #838 from Melodi17/master
Implemented better indent control in TextRendererBase
2025-02-28 09:23:27 +01:00
Alexandre Mutel
abb19ecf37 Merge pull request #851 from Akarinnnnn/encoding-ployfill
Replace encoding polyfill with NET5+ one.
2025-02-28 09:22:48 +01:00
Fa鸽
9dac60df73 Replace encoding polyfill with NET5+ one.
netstandard2.1 is a special TFM that .NET5+ doesn't mark themselves compitable, even if they mostly are.
2025-02-24 10:49:59 +08:00
Melodi
148278417f Added error throwing when stack is empty and PopIndent() is called 2025-01-14 14:25:20 +10:00
Alexandre Mutel
5b32391348 Update dependencies NuGet 2025-01-10 08:56:38 +01:00
Alexandre Mutel
5528023158 Merge pull request #844 from snnz/fix-gridtables
Prevent GridTableParser from looking beyond the end of a line.
2025-01-09 18:15:11 +01:00
Alexandre Mutel
f93b9d79d9 Merge branch 'master' into fix-gridtables 2025-01-06 08:43:45 +01:00
Alexandre Mutel
d53fd0e870 Merge pull request #843 from snnz/fix-deflists
Fixes exception in DefinitionListParser.GetCurrentDefinitionList()
2025-01-06 08:42:36 +01:00
Alexandre Mutel
c488aca96c Merge branch 'master' into fix-deflists 2025-01-05 21:12:33 +01:00
Alexandre Mutel
9b3f442765 Merge pull request #842 from snnz/fix-alerts
Check that the alert candidate is not already in an alert block or nested within other elements.
2025-01-05 21:11:11 +01:00
Sergey Nozhenko
7b6d659bbd A test has been added. 2025-01-03 07:03:28 +03:00
Sergey Nozhenko
bc8ba4fecb A test has been added. 2025-01-03 07:02:38 +03:00
Sergey Nozhenko
d87bb7292d A test has been added. 2025-01-03 07:01:29 +03:00
Sergey Nozhenko
118d28f886 Prevent GridTableParser from looking beyond the end of a line. 2025-01-03 04:29:24 +03:00
Sergey Nozhenko
3e0c72f043 Fixes exception in DefinitionListParser.GetCurrentDefinitionList() 2025-01-03 03:30:49 +03:00
Sergey Nozhenko
f2590e7b80 Check that the alert candidate is not already in an alert block or nested within other elements. 2025-01-03 01:27:11 +03:00
Melodi
88c5b5cb41 Added method for clearing indents in TextRendererBase as well as added case handling to PopIndent() 2024-12-31 22:57:02 +10:00
Alexandre Mutel
d1233ffe66 Merge pull request #837 from snnz/fix-links
Fix errors in LinkHelper and LinkInlineParser.
2024-12-27 09:49:04 +01:00
Sergey Nozhenko
ab8e85b06e Remove additional condition, since a carriage return constitute a line ending regardless of whether it is followed by a line feed or not. 2024-12-21 06:56:23 +03:00
snnz
90bc15c016 Update src/Markdig.Tests/TestPlayParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:14:16 +03:00
snnz
7f604bef30 Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:14:07 +03:00
snnz
54783b8f65 Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:13:56 +03:00
snnz
ad0770a594 Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:13:22 +03:00
snnz
90365bfeee Update src/Markdig/Parsers/Inlines/LinkInlineParser.cs
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-12-21 06:13:09 +03:00
Sergey Nozhenko
c35f7fff17 Fixed errors in LinkHelper and LinkInlineParser. 2024-12-21 03:29:31 +03:00
Alexandre Mutel
fdaef77474 Update ci badge 2024-12-19 05:48:36 +01:00
Alexandre Mutel
733c028311 Merge pull request #836 from snnz/fix-abbreviation
Fix an error in the AbbreviationParser.
2024-12-19 05:46:48 +01:00
Sergey Nozhenko
bc41b0c2a3 Existing test has been extended. 2024-12-19 00:44:29 +03:00
Alexandre Mutel
a8de2087d8 Merge pull request #835 from snnz/fix-pipetable-span
Include opening and closing pipes in the table span
2024-12-18 20:36:31 +01:00
Sergey Nozhenko
2cff6c5194 It's necessary to keep a copy of the original literal.Span.End, because otherwise it is just lost in some cases. 2024-12-18 13:09:00 +03:00
Sergey Nozhenko
5e4a917dbd Fixes an error in the AbbreviationParser. 2024-12-18 12:38:13 +03:00
Sergey Nozhenko
aff8a6823a A test has been added. 2024-12-18 10:21:29 +03:00
Alexandre Mutel
b8a3c270cc Merge pull request #834 from snnz/fix-gridtables
Fix an incorrect offset in GridTableParser.
2024-12-18 06:47:17 +01:00
Sergey Nozhenko
68659f4037 Include opening and closing pipes in the table span 2024-12-18 07:43:22 +03:00
Sergey Nozhenko
e92a8097d0 Fixes an incorrect offset in GridTableParser. 2024-12-18 02:36:56 +03:00
Alexandre Mutel
57fad6fc1a Merge pull request #828 from MihaZupan/net90
Add .NET 9 target, drop .NET 6
2024-12-17 08:43:00 +01:00
Alexandre Mutel
260f4d5acc Merge pull request #832 from snnz/footnote
Set the correct source location in Footnote and FootnoteLinkReferenceDefinition
2024-12-17 08:41:22 +01:00
Alexandre Mutel
102d02a6c1 Merge pull request #831 from snnz/custom-container
Set delimiter char and count in CustomContainerInline instances.
2024-12-17 08:40:55 +01:00
Alexandre Mutel
5ae8ab7a74 Merge pull request #829 from snnz/fix-emphasis-span
Fix incorrect emphasis span calculation.
2024-12-17 08:40:24 +01:00
Sergey Nozhenko
eb28f76588 Set the correct source location to the Footnote and FootnoteLinkReferenceDefinition. 2024-12-15 20:13:35 +03:00
Sergey Nozhenko
d0311b4cea Set delimiter in the CustomContainer instance. 2024-12-15 19:49:56 +03:00
Sergey Nozhenko
a11899a350 Fixes emphasis span calculation. A test is added. 2024-12-09 22:20:32 +03:00
Miha Zupan
40781737c3 Tweak SpecFileGen paths 2024-11-30 03:19:49 +01:00
Miha Zupan
455f8f333d Fix dotnet-versions format 2024-11-29 20:22:40 +01:00
Miha Zupan
98a060f2a3 Add .NET 9 target, drop .NET 6 2024-11-29 19:06:58 +01:00
snnz
49cf59b819 Fix extra line feeds in link title (#826)
* Fix extra line feeds in link title

* Test added.
2024-11-24 11:51:56 +01:00
Alexandre Mutel
310a55c724 Update readme.md 2024-10-30 19:08:35 +01:00
Alexandre Mutel
f734e91568 Merge pull request #823 from xoofx/fix-mermaid
Update DiagramExtension.cs
2024-10-25 22:01:40 +02:00
Alexandre Mutel
090e6d791a Update DiagramExtension.cs
Update tests
2024-10-25 21:58:28 +02:00
Alexandre Mutel
41bdb0f0ab Merge pull request #817 from ehsankalafchi/rename-variable
Rename a variable
2024-10-01 20:53:47 +02:00
Ehsan Kalafchi
b27ef11240 Rename a variable 2024-09-26 13:51:18 +02:00
Alexandre Mutel
dfa2c94b88 Merge pull request #808 from digvijayad/master
Fix mermaid link in readme.md
2024-07-18 06:50:19 +02:00
Digvijay Naruka
89330f3524 Update mermaid link in readme.md
Updated broken old GitHub pages link to the new domain https://mermaid.js.org/
2024-07-17 12:44:26 +05:30
Alexandre Mutel
1a1bbecc46 Merge pull request #786 from MartinZikmund/feature/youtube-short-support
Support for YouTube Shorts embedding
2024-04-09 21:11:30 +02:00
Martin Zikmund
68bd3074b2 Add support for YouTube Shorts embedding 2024-04-01 17:42:46 +02:00
Martin Zikmund
e486903687 Test support for YouTube Shorts embedding 2024-04-01 17:42:12 +02:00
Alexandre Mutel
8e22754db4 Merge pull request #785 from toothache/fix_issues
Fix issues for math span calculation
2024-03-29 21:23:31 +01:00
teethache
93d88ab994 Fix math span calculation. 2024-03-27 07:06:56 +08:00
Alexandre Mutel
000393f46a Fix invalid setext heading (#785) 2024-03-26 21:02:48 +01:00
Alexandre Mutel
a5796890e1 Merge pull request #784 from Abrynos/case-invariant-alerts
Make alert block headers case-invariant
2024-03-25 21:26:19 +01:00
Sebastian Göls
c19ba5b0eb Add fallback value in order to mark unknown alert kinds in some way as well 2024-03-25 12:29:22 +01:00
Sebastian Göls
03390e4f71 Misc. 2024-03-18 10:28:36 +01:00
Sebastian Göls
42bd65caaf Apply feedback 2024-03-18 09:18:39 +01:00
Sebastian Göls
b7ae04bdba Make alert block headers case-invariant 2024-03-18 09:04:47 +01:00
Alexandre Mutel
391f376fa2 Merge pull request #782 from Abrynos/bootstrap-alerts
Add bootstrap alert renderer
2024-03-18 08:11:19 +01:00
Sebastian Göls
f9e96bc9c9 Apply feedback 2024-03-18 07:48:22 +01:00
Alexandre Mutel
c75a11ec32 Update parsing-extensions.md 2024-03-17 15:01:14 +01:00
Alexandre Mutel
fd226d53e9 Update readme.md 2024-03-17 14:59:05 +01:00
Sebastian Göls
7132584996 Add bootstrap alert renderer 2024-03-15 11:28:48 +01:00
Alexandre Mutel
f48331d6c7 Fix missing code in commit for #780 2024-03-14 18:34:44 +01:00
Alexandre Mutel
6549d3b726 Fixes #780 where only the first paragraph of an alert block is processed. 2024-03-14 18:31:11 +01:00
Alexandre Mutel
d434f00355 Merge pull request #779 from toothache/fix_math_span
Fix math source span
2024-03-14 08:14:58 +01:00
Alexandre Mutel
b62a12d32d Add support for GitHub alert blocks (#776)
* Add support for GitHub alert blocks

* Fix alert for "must come first in a quote block"

* Fix comment

* Update src/Markdig/Extensions/Alerts/AlertBlockRenderer.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Update src/Markdig/MarkdownExtensions.cs

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>

* Fix parsing of alert block with multiple children blocks

* Allow null for BlockParser ctor argument of QuoteBlock

---------

Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2024-03-14 08:09:42 +01:00
teethache
3c7edaa82d Fix math source span. 2024-03-11 20:35:54 +08:00
Alexandre Mutel
fcb56fb037 Merge pull request #773 from hhyyrylainen/patch-1
Fixed a typo in MathSpecs.md
2024-02-29 08:21:10 +01:00
Henri Hyyryläinen
50bc6cadfc Fix typo in MathSpecs.md 2024-02-18 18:58:04 +02:00
Alexandre Mutel
201aa4ef73 Try to use the reusable workflow 2024-02-17 07:11:33 +01:00
Alexandre Mutel
adce9797d6 Use ubuntu for the CI 2024-02-17 06:49:39 +01:00
Alexandre Mutel
fb71dff0ec Fix tests 2024-02-17 06:48:47 +01:00
Alexandre Mutel
399570941a Remove net7.0 from CI 2024-02-17 06:47:31 +01:00
Alexandre Mutel
2f903697e2 Update projects to net8.0 2024-02-17 06:43:07 +01:00
Alexandre Mutel
eb8fe15679 Update ci.yml with nuget config 2024-02-13 09:29:26 +01:00
Alexandre Mutel
8f008e45ca Add nuget.org only config 2024-02-13 09:29:26 +01:00
Alexandre Mutel
0014ec4138 Merge pull request #769 from carbon/cq3
Eliminate various string allocations
2024-02-13 08:25:40 +01:00
Jason Nelson
2ca05ccad7 Eliminate string allocation in CodeInlineRenderer 2024-02-09 13:13:31 -08:00
Jason Nelson
6a15c804bc Add test coverage for headlines with > 6 # characters 2024-02-09 12:54:52 -08:00
Jason Nelson
0446959623 Add TextRendererBase.Write(char c, int count) method, and eliminate various string allocations 2024-02-09 12:54:30 -08:00
Alexandre Mutel
e6afddbaa0 Merge pull request #761 from carbon/collection-expressions
Use C# 12 syntax
2023-12-18 21:11:50 +01:00
Jason Nelson
a377239e91 Use null-coalescing assignment operator 2023-12-14 20:08:53 -08:00
Jason Nelson
35aa304faf Remove unused using statement 2023-12-14 19:56:08 -08:00
Jason Nelson
e4568979ec Fix typo 2023-12-14 19:55:45 -08:00
Jason Nelson
3470ec0d54 Make various members readonly on SourceSpan 2023-12-14 19:55:37 -08:00
Jason Nelson
113ef7f215 Use primary constructors (part 2) 2023-12-14 19:50:09 -08:00
Jason Nelson
4cb4b68883 Use collection expressions (part 5) 2023-12-14 19:43:15 -08:00
Jason Nelson
64ae344b74 Use collection expressions (part 4) 2023-12-14 15:41:07 -08:00
Jason Nelson
b5f3c9fc67 Use collection expressions (part 3) 2023-12-14 12:57:11 -08:00
Jason Nelson
8a88fd0557 Use collection expressions (part 2) 2023-12-14 12:46:40 -08:00
Jason Nelson
cc7623989d Fix typo on private method 2023-12-14 12:39:19 -08:00
Jason Nelson
b6a7acf5fc Use primary constructors 2023-12-14 12:35:22 -08:00
Jason Nelson
804a6f0dbc Use accelerated IndexOfAny in one more case 2023-12-14 12:32:52 -08:00
Jason Nelson
342e264988 Use collection expressions 2023-12-14 12:32:34 -08:00
Alexandre Mutel
f52ecee0b9 Update packages 2023-12-14 06:24:19 +01:00
Alexandre Mutel
a092ec23b3 Merge pull request #760 from zickb/fix_source_span_calculation_for_linebreak_inline
Fix source span calculation for LineBreakInline
2023-12-14 06:18:09 +01:00
Benni
6f1dce6306 Fix last LineBreakInline source span in multi block scenario 2023-12-14 02:53:02 +01:00
Alexandre Mutel
040a778d87 Merge pull request #759 from Akarinnnnn/fix-757
Make StringLineGroup returns a count limited Enumerator
2023-12-12 19:13:52 +01:00
Fa鸽
2ae2cf9263 Add tests for non-boxed enumerator of StringLineGroup. 2023-12-12 16:18:10 +08:00
Fa鸽
ba1e562d2f Fix complication error of StringLineGroup. 2023-12-12 16:15:55 +08:00
Fa鸽
65a02e44ec Add Enumerator GetEnumerator() for StringLineGroup 2023-12-12 15:45:30 +08:00
Alexandre Mutel
e78833ae30 Update src/Markdig/Helpers/StringLineGroup.cs 2023-12-12 08:00:54 +01:00
Fa鸽
2ab716bec1 Make StringLineGroup returns a count limited Enumerator
Fixes #757, before we return the array enumerator directly, enumerate it will get phantom empty lines.
2023-12-11 19:57:51 +08:00
Alexandre Mutel
feeb1867ce Merge pull request #753 from MihaZupan/perf-nov23-3
A few more perf improvements
2023-11-29 10:00:01 +01:00
Miha Zupan
f3aa7e73e3 Avoid Dictionary lookups in RendererBase.Write 2023-11-26 02:27:26 +01:00
Miha Zupan
dce5572356 Create inlining boundaries in MarkdownParser.Parse 2023-11-26 00:59:12 +01:00
Miha Zupan
dbbabd2221 Avoid redundant work in FencedBlockParserBase.TryContinue 2023-11-26 00:59:06 +01:00
Miha Zupan
22145c2fb0 Add UnicodeUtility helper 2023-11-26 00:58:57 +01:00
Miha Zupan
2517003edc Speed up code block arguments string parsing 2023-11-25 21:36:04 +01:00
Miha Zupan
50a3d02c2c Remove NoInlining from throw helper 2023-11-25 20:15:40 +01:00
Alexandre Mutel
40fb2b8249 Merge pull request #751 from MihaZupan/net8
.NET 8.0 and a few other perf improvements
2023-11-25 10:08:32 +01:00
Miha Zupan
5c54968807 Fix polyfill namespace 2023-11-25 03:25:37 +01:00
Miha Zupan
58ea46d58b Also install 6.0, 7.0 SDKs 2023-11-25 02:33:59 +01:00
Miha Zupan
f557e57ab1 Optimize WriteEscapeUrl 2023-11-24 03:34:45 +01:00
Miha Zupan
87aa32e1bd Optimize WriteEscape 2023-11-24 02:57:12 +01:00
Miha Zupan
4f1cb9da08 Avoid allocating strings for known emphasis character fallbacks 2023-11-24 02:44:47 +01:00
Miha Zupan
5cff880c90 Remove temporary string allocations in AutoIdentifierExtension 2023-11-24 02:44:08 +01:00
Miha Zupan
c7aec822b0 Speed up a few character checks 2023-11-24 02:41:08 +01:00
Miha Zupan
b0bde46cc1 Defer position calculations in LiteralInlineParser 2023-11-24 02:39:16 +01:00
Miha Zupan
7803417e5c Rewrite CodeInline matching to make use of vectorization 2023-11-24 02:37:45 +01:00
Miha Zupan
047c4cbcbb Skip _lineBits read on MarkdownObject creation 2023-11-24 02:28:25 +01:00
Miha Zupan
e4f57ca21e Fix build warnings 2023-11-24 02:26:51 +01:00
Miha Zupan
1f1364e69b Add SearchValues polyfill and use it in CharacterMap 2023-11-24 02:23:55 +01:00
Miha Zupan
4eea9db35c Add .NET 8.0 target 2023-11-24 02:17:47 +01:00
Alexandre Mutel
cce7284b84 Merge pull request #749 from michaelvolz/patch-1
Update Visual Studio editor link
2023-11-14 18:36:26 +01:00
Michael A. Volz (Flynn)
8e1e0b9bf3 Update Visual Studio editor link
Markdown Editor v2 (Visual Studio 2022)

This is a complete rewrite of the original Markdown Editor with tons of fixes, tweeks, and performance improvements.
2023-11-14 17:50:51 +01:00
Alexandre Mutel
7d40bc118b Merge pull request #736 from zickb/better_literal_delimiter_content_string_slice
Better literal delimiter content string slice
2023-08-30 07:29:44 +02:00
Benni
dba94a2371 Add documentation for the new constructor. 2023-08-30 00:55:35 +02:00
Benni
6d75eed3bb Don't break external users of the public constructor 2023-08-30 00:53:05 +02:00
Benni
ccb75fd5f0 Merge branch 'master' into better_literal_delimiter_content_string_slice 2023-08-30 00:50:18 +02:00
Benni
06eb6ba774 Better content string slice of delimiter literals:
instead of creating a new StringSlice only containing the delimiter chars, use the provided StringSlice from the match method with an appropriate start and end index
2023-08-30 00:40:10 +02:00
Alexandre Mutel
f15e9f020e Merge pull request #733 from zickb/fix_source_span_calculation
Fix source span calculation
2023-08-26 16:04:01 +02:00
Benni
a70ca6304f Fix source span of paragraphs in table cells and cleanup InlineProcessor 2023-08-25 08:54:30 +02:00
Benni
d26822be05 fix inline source spans calculation 2023-08-22 23:38:37 +02:00
Alexandre Mutel
5e3416f8b7 Merge pull request #726 from mkapahnke/make_allow_null_internal
Make AllowNull internal to prevent conflicts
2023-08-04 06:40:22 +02:00
Alexandre Mutel
012a57d361 Merge pull request #724 from DeveloPoel/markdown.cs_findings
Made the markdown class not partial and fixed the ToPlainText summary.
2023-08-04 06:39:58 +02:00
Alexandre Mutel
053a18c684 Merge pull request #723 from RickStrahl/MediaExtensions-RelativePathSupport
Add relative path support for Audio and Video Urls to  MediaLinks Extension
2023-08-04 06:39:38 +02:00
Maximilian Kapahnke
13265453ac make internal 2023-07-25 18:23:23 +02:00
developoel
8ea0783834 Made the markdown class not partial and fixed the ToPlainText summary. 2023-07-18 14:09:19 +02:00
Rick Strahl
3d29430337 Update src/Markdig/Extensions/MediaLinks/MediaLinkExtension.cs
Co-authored-by: Günther Foidl <gue@korporal.at>
2023-07-13 12:04:52 -07:00
Rick Strahl
81bc58c6c9 Add support for relative Urls for Video and Audio links. 2023-07-11 14:58:43 -07:00
Rick Strahl
bfe3800130 Allow for use of .NET 7.0 SDK (Major Version Roll Forward) 2023-07-11 12:13:05 -07:00
Alexandre Mutel
b7cb169fd3 Merge pull request #710 from valterc/fix-line-group-oob
Add line count check to avoid out of range
2023-04-22 13:23:04 +01:00
Valter Costa
512b28256a Improved tests 2023-04-18 17:44:41 +01:00
Valter Costa
cd5d11eeff Fix index check 2023-04-18 17:44:23 +01:00
Valter Costa
a9118774a8 Added line count check to avoid out of bounds 2023-04-18 12:46:48 +01:00
Alexandre Mutel
8155a1e3d6 Update specs 2023-02-27 07:39:46 +01:00
Alexandre Mutel
0a167248fd Merge pull request #701 from yufeih/yufeih/yamlfrontmatter
Allow YAML front matter in the middle of the document
2023-02-26 21:29:25 +01:00
Yufei Huang
9df67b7934 Allow YAML front matter in the middle of the document 2023-02-23 21:12:50 +08:00
Alexandre Mutel
8f8a145f0e Merge pull request #696 from artempyanykh/fix-setext-span
Fix incorrect setext heading source span
2023-02-16 06:41:11 +01:00
Artem Pyanykh
a6cd283183 Fix incorrect setext heading source span 2023-02-11 17:09:00 +00:00
Alexandre Mutel
851713fad9 Merge pull request #693 from carbon/fsn2
Enable ImplictUsings and use file-scoped namespaces
2023-01-24 07:46:44 +01:00
Jason Nelson
9422764f98 [Markdig] Use global usings 2023-01-21 16:41:18 -08:00
Jason Nelson
74f978ed2d [Markdig] Use file-scoped namespaces 2023-01-21 16:35:41 -08:00
Jason Nelson
66aaffaef1 [mctoc] Enable ImplictUsings and use file-scoped namespaces 2023-01-18 19:53:44 -08:00
Jason Nelson
a18d8dee4f [UnicodeNormDApp] Enable ImplictUsings and use file-scoped namespaces 2023-01-18 19:51:58 -08:00
Jason Nelson
96934214db [SpecFileGen] Enable ImplictUsings and use file-scoped namespaces 2023-01-18 19:51:19 -08:00
Jason Nelson
ca03b7df4f [Benchmarks] Enable ImplictUsings and use file-scoped namespaces 2023-01-18 19:50:40 -08:00
Jason Nelson
4660426719 [WebApp] Enable ImplictUsings and use file-scoped namespaces 2023-01-18 19:49:44 -08:00
Jason Nelson
7ef2959d6e [Tests] Enable ImplictUsings and use file-scoped namespaces 2023-01-18 19:48:52 -08:00
Alexandre Mutel
78c4efb9cc Merge pull request #690 from carbon/net46
Bump  .NET framework target to net462
2023-01-14 18:19:51 +01:00
Jason Nelson
641f3fe0c1 Remove ArrayHelper 2023-01-09 21:52:02 -08:00
Jason Nelson
83145a323b Add back missing using statement 2023-01-09 20:11:44 -08:00
Jason Nelson
1abc804228 Bump .NET framework target to net462 2023-01-09 20:03:49 -08:00
Alexandre Mutel
25323f080c Merge pull request #685 from carbon/netcoreapp3.1
Drop netcoreapp3.1 target
2022-12-20 21:03:43 +01:00
Jason Nelson
38a15ef2a9 Drop netcoreapp3.1 target 2022-12-16 20:17:49 -08:00
Alexandre Mutel
f9bed5d270 Merge pull request #678 from waldyrious/copyedit-grid-tables-docs
Copyedit the grid table extension documentation
2022-12-04 18:38:24 +01:00
Waldir Pimenta
3f7d5c68c2 Copyedit the grid table extension documentation 2022-11-25 11:47:14 +00:00
Alexandre Mutel
936fe35460 Merge pull request #672 from LukeTOBrien/feature/medialink-controls
Option to set controls attribute for MediaLinks
2022-10-18 08:47:46 +02:00
Alexandre Mutel
0dc4857213 Merge pull request #674 from wbaldoumas/contriuting-docs
Add Contributing Docs
2022-10-18 08:47:27 +02:00
Alexandre Mutel
956f109419 Update contributing.md 2022-10-18 08:47:11 +02:00
Will
b1f3229812 Link contributing docs from readme 2022-10-14 08:32:15 -07:00
Will
3ceff1d076 Add Contributing Docs 2022-10-14 08:27:08 -07:00
Luke
e2e557b1c4 Change to make option more explicit 2022-10-14 16:23:53 +01:00
Luke
e155792be5 Option to set controls attribute for MediaLinks 2022-10-13 15:01:51 +01:00
426 changed files with 41528 additions and 36580 deletions

View File

@@ -12,8 +12,9 @@ insert_final_newline = false
trim_trailing_whitespace = true
# Solution Files
[*.sln]
indent_style = tab
[*.slnx]
indent_size = 2
insert_final_newline = true
# XML Project Files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
@@ -35,3 +36,8 @@ insert_final_newline = true
# Bash Files
[*.sh]
end_of_line = lf
# C# files
[*.cs]
# License header
file_header_template = Copyright (c) Alexandre Mutel. All rights reserved.\nThis file is licensed under the BSD-Clause 2 license.\nSee the license.txt file in the project root for more information.

2
.gitattributes vendored
View File

@@ -1,3 +1,3 @@
* text=auto
*.cs text=auto diff=csharp
*.sln text=auto eol=crlf
*.slnx text=auto eol=crlf

View File

@@ -11,22 +11,11 @@ on:
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
- name: Install .NET 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Build, Test, Pack, Publish
shell: bash
run: |
dotnet tool install -g dotnet-releaser
dotnet-releaser run --nuget-token "${{secrets.NUGET_TOKEN}}" --github-token "${{secrets.GITHUB_TOKEN}}" src/dotnet-releaser.toml
uses: xoofx/.github/.github/workflows/dotnet.yml@main
with:
dotnet-version: |
6.0
8.0
9.0
secrets:
NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>

44
.github/workflows/test-netstandard.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Test netstandard
on: pull_request
jobs:
test-netstandard:
runs-on: ubuntu-latest
strategy:
matrix:
netstandard-version: ['netstandard2.0', 'netstandard2.1']
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- name: Patch build to test ${{ matrix.netstandard-version }}
run: |
cd src
sed -i 's/<TargetFrameworks>.*<\/TargetFrameworks>/<TargetFrameworks>${{ matrix.netstandard-version }}<\/TargetFrameworks>/' Markdig/Markdig.targets
sed -i 's/<TargetFrameworks>.*<\/TargetFrameworks>/<TargetFrameworks>net8.0;net9.0<\/TargetFrameworks>/' Markdig.Tests/Markdig.Tests.csproj
echo "Markdig.targets TFMs:"
grep "TargetFrameworks" Markdig/Markdig.targets
echo "Markdig.Tests.csproj TFMs:"
grep "TargetFrameworks" Markdig.Tests/Markdig.Tests.csproj
- name: Restore dependencies
run: dotnet restore src/Markdig.Tests/Markdig.Tests.csproj
- name: Test Debug
run: |
dotnet build src/Markdig.Tests/Markdig.Tests.csproj -c Debug --no-restore
dotnet test src/Markdig.Tests/Markdig.Tests.csproj -c Debug --no-build
- name: Test Release
run: |
dotnet build src/Markdig.Tests/Markdig.Tests.csproj -c Release --no-restore
dotnet test src/Markdig.Tests/Markdig.Tests.csproj -c Release --no-build

2
.gitignore vendored
View File

@@ -8,6 +8,8 @@
*.sln.docstates
*.nuget.props
*.nuget.targets
src/.idea
BenchmarkDotNet.Artifacts
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

36
contributing.md Normal file
View File

@@ -0,0 +1,36 @@
# 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

@@ -1,6 +1,6 @@
# Extensions and Parsers
Markdig was [implemented in such a way](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) as to be extremely pluggable, with even basic behaviors being mutable and extendable.
Markdig was [implemented in such a way](http://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/) as to be extremely pluggable, with even basic behaviors being mutable and extendable.
The basic mechanism for extension of Markdig is the `IMarkdownExtension` interface, which allows any implementing class to be registered with the pipeline builder and thus to directly modify the collections of `BlockParser` and `InlineParser` objects which end up in the pipeline.

View File

@@ -1,8 +1,8 @@
# Markdig [![Build Status](https://github.com/lunet-io/markdig/workflows/ci/badge.svg?branch=master)](https://github.com/lunet-io/markdig/actions) [![Coverage Status](https://coveralls.io/repos/github/xoofx/markdig/badge.svg?branch=master)](https://coveralls.io/github/xoofx/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
# Markdig [![ci](https://github.com/xoofx/markdig/actions/workflows/ci.yml/badge.svg)](https://github.com/xoofx/markdig/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/xoofx/markdig/badge.svg?branch=master)](https://coveralls.io/github/xoofx/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
<img align="right" width="160px" height="160px" src="img/markdig.png">
Markdig is a fast, powerful, [CommonMark](http://commonmark.org/) compliant, extensible Markdown processor for .NET.
Markdig is a fast, powerful, [CommonMark](https://commonmark.org/) compliant, extensible Markdown processor for .NET.
> NOTE: The repository is under construction. There will be a dedicated website and proper documentation at some point!
@@ -12,9 +12,9 @@ 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 [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
- Checkout [Markdown Editor v2 for Visual Studio 2022](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.MarkdownEditor2) powered by Markdig!
- Converter to **HTML**
- Passing more than **600+ tests** from the latest [CommonMark specs (0.30)](http://spec.commonmark.org/)
- Passing more than **600+ tests** from the latest [CommonMark specs (0.31.2)](https://spec.commonmark.org/)
- Includes all the core elements of CommonMark:
- including **GFM fenced code blocks**.
- **Extensible** architecture
@@ -22,9 +22,9 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**Roundtrip support**](./src/Markdig/Roundtrip.md): Parses trivia (whitespace, newlines and other characters) to support lossless parse ⭢ render roundtrip. This enables changing markdown documents without introducing undesired trivia changes.
- Built-in with **20+ extensions**, including:
- 2 kind of tables:
- [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from GitHub tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
- [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from GitHub tables and [PanDoc - Pipe Tables](https://pandoc.org/MANUAL.html#extension-pipe_tables))
- [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](https://pandoc.org/MANUAL.html#extension-grid_tables))
- [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](https://pandoc.org/MANUAL.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- strike through `~~`,
- Subscript `~`
- Superscript `^`
@@ -33,7 +33,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**Special attributes**](src/Markdig.Tests/Specs/GenericAttributesSpecs.md) or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- [**Definition lists**](src/Markdig.Tests/Specs/DefinitionListSpecs.md) (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- [**Footnotes**](src/Markdig.Tests/Specs/FootnotesSpecs.md) (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
- [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](https://pandoc.org/MANUAL.html#extension-auto_identifiers))
- [**Auto-links**](src/Markdig.Tests/Specs/AutoLinks.md) generates links if a text starts with `http://` or `https://` or `ftp://` or `mailto:` or `www.xxx.yyy`
- [**Task Lists**](src/Markdig.Tests/Specs/TaskListSpecs.md) inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments).
- [**Extra bullet lists**](src/Markdig.Tests/Specs/ListExtraSpecs.md), supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
@@ -48,7 +48,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://mermaid.js.org) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**YAML Front Matter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the front matter and to discard it from the HTML output (typically used for previewing without the front matter in MarkdownEditor)
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
- Starting with Markdig version `0.20.0+`, Markdig is compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`.
@@ -70,7 +70,7 @@ If you are looking for support for an old .NET Framework 3.5 or 4.0, you can dow
While there is not yet a dedicated documentation, you can find from the [specs documentation](src/Markdig.Tests/Specs/readme.md) how to use these extensions.
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](https://xoofx.github.io/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
## Download
@@ -101,6 +101,10 @@ 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)
@@ -140,16 +144,16 @@ AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
- Markdig is roughly **x100 times faster than MarkdownSharp**
- **20% faster than the reference cmark C implementation**
## Sponsors
## Donate
Supports this project with a monthly donation and help me continue improving it. \[[Become a sponsor](https://github.com/sponsors/xoofx)\]
If you are using this library and find it useful for your project, please consider a donation for it!
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
[<img src="https://github.com/lilith.png?size=200" width="64px;" style="border-radius: 50%" alt="lilith"/>](https://github.com/lilith) Lilith River, author of [Imageflow Server, an easy on-demand
image editing, optimization, and delivery server](https://github.com/imazen/imageflow-server)
## Credits
Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard!
Thanks to the fantastic work done by [John Mac Farlane](https://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard!
This project would not have been possible without this huge foundation.
@@ -157,7 +161,7 @@ Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/Bench
Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Helpers/EntityHelper.cs)) have been re-used from [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
Thanks to the work done by @clarkd on the [JIRA Link extension](https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
## Author
Alexandre MUTEL aka [xoofx](http://xoofx.com)
Alexandre MUTEL aka [xoofx](https://xoofx.github.io/)

View File

@@ -0,0 +1,23 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" />
<PackageVersion Include="CommonMark.NET" Version="0.15.1" />
<PackageVersion Include="Markdown" Version="2.2.1" />
<PackageVersion Include="MarkdownSharp" Version="2.0.5" />
<PackageVersion Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.23.0" />
<PackageVersion Include="Microsoft.Diagnostics.Runtime" Version="3.1.512801" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="MinVer" Version="6.0.0" />
<PackageVersion Include="NUnit" Version="4.4.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="5.2.0" />
<PackageVersion Include="SharpFuzz" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'netstandard2.0'">
<PackageVersion Include="System.Memory" Version="4.6.3" />
</ItemGroup>
</Project>

View File

@@ -1,40 +1,38 @@
// 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
{
public static string ToHtml(string text)
{
unsafe
{
var textAsArray = Encoding.UTF8.GetBytes(text);
namespace Testamina.Markdig.Benchmarks;
fixed (void* ptext = textAsArray)
public static class CommonMarkLib
{
public static string ToHtml(string text)
{
unsafe
{
var textAsArray = Encoding.UTF8.GetBytes(text);
fixed (void* ptext = textAsArray)
{
var ptr = (byte*)cmark_markdown_to_html(new IntPtr(ptext), text.Length);
int length = 0;
while (ptr[length] != 0)
{
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;
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;
}
}
// 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,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Remove="spec.md" />
@@ -18,12 +19,12 @@
</Content>
</ItemGroup>
<ItemGroup>
<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="2.0.226801" />
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" />
<PackageReference Include="CommonMark.NET" />
<PackageReference Include="Markdown" />
<PackageReference Include="MarkdownSharp" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />

View File

@@ -0,0 +1,81 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Markdig;
namespace Testamina.Markdig.Benchmarks.PipeTable;
/// <summary>
/// Benchmark for pipe table parsing performance, especially for large tables.
/// Tests the performance of PipeTableParser with varying table sizes.
/// </summary>
[MemoryDiagnoser]
[GcServer(true)] // Use server GC to get more comprehensive GC stats
public class PipeTableBenchmark
{
private string _100Rows = null!;
private string _500Rows = null!;
private string _1000Rows = null!;
private string _1500Rows = null!;
private string _5000Rows = null!;
private string _10000Rows = null!;
private MarkdownPipeline _pipeline = null!;
[GlobalSetup]
public void Setup()
{
// Pipeline with pipe tables enabled (part of advanced extensions)
_pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
// Generate tables of various sizes
// Note: Before optimization, 5000+ rows hit depth limit due to nested tree structure.
// After optimization, these should work.
_100Rows = PipeTableGenerator.Generate(rows: 100, columns: 5);
_500Rows = PipeTableGenerator.Generate(rows: 500, columns: 5);
_1000Rows = PipeTableGenerator.Generate(rows: 1000, columns: 5);
_1500Rows = PipeTableGenerator.Generate(rows: 1500, columns: 5);
_5000Rows = PipeTableGenerator.Generate(rows: 5000, columns: 5);
_10000Rows = PipeTableGenerator.Generate(rows: 10000, columns: 5);
}
[Benchmark(Description = "PipeTable 100 rows x 5 cols")]
public string Parse100Rows()
{
return Markdown.ToHtml(_100Rows, _pipeline);
}
[Benchmark(Description = "PipeTable 500 rows x 5 cols")]
public string Parse500Rows()
{
return Markdown.ToHtml(_500Rows, _pipeline);
}
[Benchmark(Description = "PipeTable 1000 rows x 5 cols")]
public string Parse1000Rows()
{
return Markdown.ToHtml(_1000Rows, _pipeline);
}
[Benchmark(Description = "PipeTable 1500 rows x 5 cols")]
public string Parse1500Rows()
{
return Markdown.ToHtml(_1500Rows, _pipeline);
}
[Benchmark(Description = "PipeTable 5000 rows x 5 cols")]
public string Parse5000Rows()
{
return Markdown.ToHtml(_5000Rows, _pipeline);
}
[Benchmark(Description = "PipeTable 10000 rows x 5 cols")]
public string Parse10000Rows()
{
return Markdown.ToHtml(_10000Rows, _pipeline);
}
}

View File

@@ -0,0 +1,61 @@
// 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.Text;
namespace Testamina.Markdig.Benchmarks.PipeTable;
/// <summary>
/// Generates pipe table markdown content for benchmarking purposes.
/// </summary>
public static class PipeTableGenerator
{
private const int DefaultCellWidth = 10;
/// <summary>
/// Generates a pipe table in markdown format.
/// </summary>
/// <param name="rows">Number of data rows (excluding header)</param>
/// <param name="columns">Number of columns</param>
/// <param name="cellWidth">Width of each cell content (default: 10)</param>
/// <returns>Pipe table markdown string</returns>
public static string Generate(int rows, int columns, int cellWidth = DefaultCellWidth)
{
var sb = new StringBuilder();
// Header row
sb.Append('|');
for (int col = 0; col < columns; col++)
{
sb.Append(' ');
sb.Append($"Header {col + 1}".PadRight(cellWidth));
sb.Append(" |");
}
sb.AppendLine();
// Separator row (with dashes)
sb.Append('|');
for (int col = 0; col < columns; col++)
{
sb.Append(new string('-', cellWidth + 2));
sb.Append('|');
}
sb.AppendLine();
// Data rows
for (int row = 0; row < rows; row++)
{
sb.Append('|');
for (int col = 0; col < columns; col++)
{
sb.Append(' ');
sb.Append($"R{row + 1}C{col + 1}".PadRight(cellWidth));
sb.Append(" |");
}
sb.AppendLine();
}
return sb.ToString();
}
}

View File

@@ -2,76 +2,85 @@
// 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;
using Testamina.Markdig.Benchmarks.PipeTable;
namespace Testamina.Markdig.Benchmarks
namespace Testamina.Markdig.Benchmarks;
//[BenchmarkTask(platform: BenchmarkPlatform.X64, jitVersion: BenchmarkJitVersion.RyuJit, processCount: 1, warmupIterationCount: 2)]
public class Program
{
//[BenchmarkTask(platform: BenchmarkPlatform.X64, jitVersion: BenchmarkJitVersion.RyuJit, processCount: 1, warmupIterationCount: 2)]
public class Program
private string text;
public Program()
{
private string text;
//text = File.ReadAllText("progit.md");
text = File.ReadAllText("spec.md");
}
public Program()
//[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 = "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 = "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 = "MarkdownSharp")]
public void TestMarkdownSharp()
{
new MarkdownSharp.Markdown().Transform(text);
}
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;
// Run specific benchmarks based on command line arguments
if (args.Length > 0 && args[0] == "--pipetable")
{
//text = File.ReadAllText("progit.md");
text = File.ReadAllText("spec.md");
BenchmarkRunner.Run<PipeTableBenchmark>(config);
}
//[Benchmark(Description = "TestMarkdig", OperationsPerInvoke = 4096)]
[Benchmark(Description = "markdig")]
public void TestMarkdig()
else
{
//var reader = new StreamReader(File.Open("spec.md", FileMode.Open));
Markdown.ToHtml(text);
//File.WriteAllText("spec.html", 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 = "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 = "MarkdownSharp")]
public void TestMarkdownSharp()
{
new MarkdownSharp.Markdown().Transform(text);
}
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>();
}
//BenchmarkRunner.Run<TestDictionary>(config);
//BenchmarkRunner.Run<TestMatchPerf>();
//BenchmarkRunner.Run<TestStringPerf>();
}
}

View File

@@ -2,81 +2,79 @@
// 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
namespace Testamina.Markdig.Benchmarks;
public class TextRegexHelper
{
public class TextRegexHelper
private readonly Dictionary<string, string> replacers;
private readonly Regex regex;
public TextRegexHelper(Dictionary<string, string> replacers)
{
private readonly Dictionary<string, string> replacers;
private readonly Regex regex;
this.replacers = replacers;
var builder = new StringBuilder();
public TextRegexHelper(Dictionary<string, string> replacers)
// (?<1>:value:?)|(?<1>:noo:?)
foreach (var replace in replacers)
{
this.replacers = replacers;
var builder = new StringBuilder();
// (?<1>:value:?)|(?<1>:noo:?)
foreach (var replace in replacers)
var matchStr = Regex.Escape(replace.Key);
if (builder.Length > 0)
{
var matchStr = Regex.Escape(replace.Key);
if (builder.Length > 0)
{
builder.Append('|');
}
builder.Append("(?<1>").Append(matchStr).Append("?)");
builder.Append('|');
}
regex = new Regex(builder.ToString());
builder.Append("(?<1>").Append(matchStr).Append("?)");
}
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)
{
return false;
}
matchText = result.Groups[1].Value;
replaceText = replacers[matchText];
return true;
}
regex = new Regex(builder.ToString());
}
/*
public class TestMatchPerf
public bool TryMatch(string text, int offset, out string matchText, out string replaceText)
{
private readonly TextMatchHelper matcher;
public TestMatchPerf()
replaceText = null;
matchText = null;
var result = regex.Match(text, offset);
if (!result.Success)
{
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));
return false;
}
[Benchmark]
public void TestMatch()
{
matchText = result.Groups[1].Value;
replaceText = replacers[matchText];
return true;
}
}
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);
}
/*
public class TestMatchPerf
{
private readonly TextMatchHelper matcher;
public TestMatchPerf()
{
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));
}
[Benchmark]
public void TestMatch()
{
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,58 +1,57 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Markdig.Renderers;
namespace Testamina.Markdig.Benchmarks
namespace Testamina.Markdig.Benchmarks;
public class TestStringPerf
{
public class TestStringPerf
private string text;
public TestStringPerf()
{
private string text;
public TestStringPerf()
{
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);
// }
//}
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);
// }
//}
}

4
src/Markdig.Fuzzing/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
corpus
libfuzzer-dotnet-windows.exe
crash-*
timeout-*

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SharpFuzz" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,71 @@
using Markdig;
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using SharpFuzz;
using System.Diagnostics;
using System.Text;
ReadOnlySpanAction fuzzTarget = ParseRenderFuzzer.FuzzTarget;
if (args.Length > 0)
{
// Run the target on existing inputs
string[] files = Directory.Exists(args[0])
? Directory.GetFiles(args[0])
: [args[0]];
Debugger.Launch();
foreach (string inputFile in files)
{
fuzzTarget(File.ReadAllBytes(inputFile));
}
}
else
{
Fuzzer.LibFuzzer.Run(fuzzTarget);
}
sealed class ParseRenderFuzzer
{
private static readonly MarkdownPipeline s_advancedPipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
private static readonly ResettableRoundtripRenderer _roundtripRenderer = new();
public static void FuzzTarget(ReadOnlySpan<byte> bytes)
{
string text = Encoding.UTF8.GetString(bytes);
try
{
MarkdownDocument document = Markdown.Parse(text);
_ = document.ToHtml();
document = Markdown.Parse(text, s_advancedPipeline);
_ = document.ToHtml(s_advancedPipeline);
document = Markdown.Parse(text, trackTrivia: true);
_ = document.ToHtml();
_roundtripRenderer.Reset();
_roundtripRenderer.Render(document);
_ = Markdown.Normalize(text);
_ = Markdown.ToPlainText(text);
}
catch (Exception ex) when (IsIgnorableException(ex)) { }
}
private static bool IsIgnorableException(Exception exception)
{
return exception.Message.Contains("Markdown elements in the input are too deeply nested", StringComparison.Ordinal);
}
private sealed class ResettableRoundtripRenderer : RoundtripRenderer
{
public ResettableRoundtripRenderer() : base(new StringWriter(new StringBuilder(1024 * 1024))) { }
public new void Reset() => base.Reset();
}
}

View File

@@ -0,0 +1,86 @@
param (
[string]$configuration = $null
)
Set-StrictMode -Version Latest
$libFuzzer = "libfuzzer-dotnet-windows.exe"
$outputDir = "bin"
function Get-LibFuzzer {
param (
[string]$Path
)
$libFuzzerUrl = "https://github.com/Metalnem/libfuzzer-dotnet/releases/download/v2025.05.02.0904/libfuzzer-dotnet-windows.exe"
$expectedHash = "17af5b3f6ff4d2c57b44b9a35c13051b570eb66f0557d00015df3832709050bf"
Write-Output "Downloading libFuzzer from $libFuzzerUrl..."
try {
$tempFile = "$Path.tmp"
Invoke-WebRequest -Uri $libFuzzerUrl -OutFile $tempFile -UseBasicParsing
$downloadedHash = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash
if ($downloadedHash -eq $ExpectedHash) {
Move-Item -Path $tempFile -Destination $Path -Force
Write-Output "libFuzzer downloaded successfully to $Path"
}
else {
Write-Error "Hash validation failed."
Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
exit 1
}
}
catch {
Write-Error "Failed to download libFuzzer: $($_.Exception.Message)"
Remove-Item -Path $tempFile -Force -ErrorAction SilentlyContinue
exit 1
}
}
# Check if libFuzzer exists, download if not
if (-not (Test-Path $libFuzzer)) {
Get-LibFuzzer -Path $libFuzzer
}
$toolListOutput = dotnet tool list --global sharpFuzz.CommandLine 2>$null
if (-not ($toolListOutput -match "sharpfuzz")) {
Write-Output "Installing sharpfuzz CLI"
dotnet tool install --global sharpFuzz.CommandLine
}
if (Test-Path $outputDir) {
Remove-Item -Recurse -Force $outputDir
}
if ($configuration -eq $null) {
$configuration = "Debug"
}
dotnet publish -c $configuration -o $outputDir
$project = Join-Path $outputDir "Markdig.Fuzzing.dll"
$fuzzingTarget = Join-Path $outputDir "Markdig.dll"
Write-Output "Instrumenting $fuzzingTarget"
& sharpfuzz $fuzzingTarget
if ($LastExitCode -ne 0) {
Write-Error "An error occurred while instrumenting $fuzzingTarget"
exit 1
}
New-Item -ItemType Directory -Force -Path corpus | Out-Null
$libFuzzerArgs = @("--target_path=dotnet", "--target_arg=$project", "-timeout=10", "corpus")
# Add any additional arguments passed to the script
if ($args) {
$libFuzzerArgs += $args
}
Write-Output "Starting libFuzzer with arguments: $libFuzzerArgs"
& ./$libFuzzer @libFuzzerArgs

View File

@@ -0,0 +1,5 @@
// 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,18 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>13.0</LangVersion>
<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>
<SpecExecutable>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.dll</SpecExecutable>
<SpecTimestamp>$(MSBuildProjectDirectory)\..\SpecFileGen\bin\$(Configuration)\$(TargetFramework)\SpecFileGen.timestamp</SpecTimestamp>
<NoWarn>$(NoWarn);NETSDK1138</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
</ItemGroup>
<ItemGroup>
@@ -20,6 +23,10 @@
<ProjectReference Include="..\SpecFileGen\SpecFileGen.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>
<ItemGroup>
<ItemSpecExecutable Include="$(SpecExecutable)" />
<InputSpecFiles Include="Specs\*.md" />
@@ -29,10 +36,10 @@
<InputSpecFiles Remove="Specs\readme.md" />
<!-- Allow Visual Studio up-to-date check to verify that nothing has changed - https://github.com/dotnet/project-system/blob/main/docs/up-to-date-check.md -->
<UpToDateCheckInput Include="@(InputSpecFiles)" />
<OutputSpecFiles Include="@(InputSpecFiles->'%(RelativeDir)%(Filename).generated.cs')" />
<OutputSpecFiles Include="@(InputSpecFiles-&gt;'%(RelativeDir)%(Filename).generated.cs')" />
</ItemGroup>
<Target Name="GeneratedSpecsFile" BeforeTargets="BeforeCompile;CoreCompile" Inputs="@(ItemSpecExecutable);@(InputSpecFiles)" Outputs="@(ItemSpecExecutable->'%(RelativeDir)%(Filename).timestamp');@(InputSpecFiles->'%(RelativeDir)%(Filename).generated.cs')">
<Target Name="GeneratedSpecsFile" BeforeTargets="BeforeCompile;CoreCompile" Inputs="@(ItemSpecExecutable);@(InputSpecFiles)" Outputs="@(ItemSpecExecutable-&gt;'%(RelativeDir)%(Filename).timestamp');@(InputSpecFiles-&gt;'%(RelativeDir)%(Filename).generated.cs')">
<Message Importance="high" Text="Regenerating Specs Files" />
<Exec Command="dotnet $(SpecExecutable)" />
<WriteLinesToFile File="$(SpecTimestamp)" Lines="$([System.DateTime]::Now)" />

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
namespace Markdig.Tests;
public class MiscTests
{
public class MiscTests
[Test]
public void LinkWithInvalidNonAsciiDomainNameIsIgnored()
{
[Test]
public void LinkWithInvalidNonAsciiDomainNameIsIgnored()
// 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))
{
// 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.) ");
string brokenLinkText = linkText.Remove(bracketIndex, 1);
// 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>");
markdown = $"[{brokenLinkText}](/uri)";
expected = $@"<p><a href=""/uri"">{brokenLinkText}</a></p>";
// Invalid IDN
TestParser.TestSpec("[foo](http://ünicode..com)", "<p><a href=\"http://%C3%BCnicode..com\">foo</a></p>");
string actual = Markdown.ToHtml(markdown);
Assert.AreNotEqual(expected, actual);
}
}
[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)
[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)
{
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);
}
Exception e = Assert.Throws<ArgumentException>(test);
Assert.True(e.Message.Contains("depth limit"));
}
[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)
else
{
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();
}
}
[Test]
public void IsIssue356Corrected()
[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)
{
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");
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 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 TestFixHang()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "hang.md"));
_ = Markdown.ToHtml(input);
}
TestParser.TestSpec(input, expected);
}
[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 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 TestInvalidCharacterHandling()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "ArgumentOutOfRangeException.md"));
_ = 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 TestInvalidCodeEscape()
{
var input = "```**Header** ";
_ = Markdown.ToHtml(input);
}
[Test]
public void TestFixHang()
{
var input = File.ReadAllText(Path.Combine(TestParser.TestsDirectory, "hang.md"));
_ = 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 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 :
[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,98 +199,214 @@ $$
<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);
}
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
var test1 = html.Contains("<p><span class=\"math\">\\(");
var test2 = html.Contains("\\)</span></p>");
if (!test1 || !test2)
{
Console.WriteLine(html);
}
[Test]
public void BlockMathExpression()
{
string math = @"Math expressions
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
[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);
}
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
[Test]
public void CanDisableParsingHeadings()
var html = Markdown.ToHtml(math, pl);
var test1 = html.Contains("<div class=\"math\">\n\\[");
var test2 = html.Contains("\\]</div>");
if (!test1 || !test2)
{
var noHeadingsPipeline = new MarkdownPipelineBuilder().DisableHeadings().Build();
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);
// 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);
Console.WriteLine(html);
}
[Test]
public void CanOpenAutoLinksInNewWindow()
{
var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
var newWindowPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { OpenInNewWindow = true }).Build();
Assert.IsTrue(test1, "Leading bracket missing");
Assert.IsTrue(test2, "Trailing bracket missing");
}
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 CanDisableParsingHeadings()
{
var noHeadingsPipeline = new MarkdownPipelineBuilder().DisableHeadings().Build();
[Test]
public void CanUseHttpsPrefixForWWWAutoLinks()
{
var pipeline = new MarkdownPipelineBuilder().UseAutoLinks().Build();
var httpsPipeline = new MarkdownPipelineBuilder().UseAutoLinks(new AutoLinkOptions() { UseHttpsForWWWLinks = true }).Build();
TestParser.TestSpec("Foo\n===", "<h1>Foo</h1>");
TestParser.TestSpec("Foo\n===", "<p>Foo\n===</p>", noHeadingsPipeline);
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);
}
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);
}
[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);
}
[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);
}
[Test]
public void TestGridTableShortLine()
{
var input = @"
+--+
| |
+-";
var expected = @"<table>
<col style=""width:100%"" />
<tbody>
<tr>
<td></td>
</tr>
</tbody>
</table>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseGridTables().Build());
}
[Test]
public void TestDefinitionListInListItemWithBlankLine()
{
var input = @"
-
term
: definition
";
var expected = @"<ul>
<li>
<dl>
<dt>term</dt>
<dd>definition</dd>
</dl>
</li>
</ul>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseDefinitionLists().Build());
}
[Test]
public void TestAlertWithinAlertOrNestedBlock()
{
var input = @"
>[!NOTE]
[!NOTE]
The second one is not a note.
>>[!NOTE]
Also not a note.
";
var expected = @"<div class=""markdown-alert markdown-alert-note"">
<p class=""markdown-alert-title""><svg viewBox=""0 0 16 16"" version=""1.1"" width=""16"" height=""16"" aria-hidden=""true""><path d=""M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z""></path></svg>Note</p>
<p>[!NOTE]
The second one is not a note.</p>
</div>
<blockquote>
<blockquote>
<p>[!NOTE]
Also not a note.</p>
</blockquote>
</blockquote>
";
TestParser.TestSpec(input, expected, new MarkdownPipelineBuilder().UseAlertBlocks().Build());
}
[Test]
public void TestIssue845ListItemBlankLine()
{
TestParser.TestSpec("-\n\n foo",@"
<ul>
<li></li>
</ul>
<p>foo</p>");
TestParser.TestSpec("-\n-\n\n foo",@"
<ul>
<li></li>
<li></li>
</ul>
<p>foo</p>");
TestParser.TestSpec("-\n\n-\n\n foo",@"
<ul>
<li></li>
<li></li>
</ul>
<p>foo</p>");
}
}

View File

@@ -2,18 +2,15 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
namespace Markdig.Tests;
namespace Markdig.Tests
class Program
{
class Program
public static void Main(string[] args)
{
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();
}
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,55 +1,53 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestAtxHeading
{
[TestFixture]
public class TestAtxHeading
[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)
{
[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);
}
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,107 +1,105 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestFencedCodeBlock
{
[TestFixture]
public class TestFencedCodeBlock
[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)
{
[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")]
RoundTrip(value);
}
[TestCase(" ```\nc\n````")]
[TestCase("```\nc\n````")]
[TestCase("p\n\n```\nc\n```")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~ ")]
public void TestTilde(string value)
{
RoundTrip(value);
}
[TestCase("```\n c\n```")]
[TestCase("```\nc \n```")]
[TestCase("```\n c \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("\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```\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("~~~ aa ``` ~~~\nfoo\n~~~")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~ ")]
public void TestTilde(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("```\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```\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```\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 a\n```")]
[TestCase("```i a a2\n```")]
[TestCase("```i a a2 a3\n```")]
[TestCase("```i a 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```")]
[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);
}
[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,20 +1,18 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestHtmlBlock
{
[TestFixture]
public class TestHtmlBlock
[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)
{
[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);
}
RoundTrip(value);
}
}

View File

@@ -1,86 +1,84 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestIndentedCodeBlock
{
[TestFixture]
public class TestIndentedCodeBlock
// 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)
{
// 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")]
RoundTrip(value);
}
[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")]
[TestCase(" l\r")]
[TestCase(" l\r\n")]
[TestCase(" l\n l")]
[TestCase(" l\n l\n l")]
[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")]
// 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\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("p\n\n l\n l")]
[TestCase("p\n\n l\n l")]
[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(" l\n\np\n\n l")]
[TestCase(" l l\n\np\n\n l l")]
public void Test(string value)
{
RoundTrip(value);
}
Assert.AreEqual(1, markdownDocument.Count);
}
[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);
}
[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,214 +1,212 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestLinkReferenceDefinition
{
[TestFixture]
public class TestLinkReferenceDefinition
[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)
{
[TestCase(@"[a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
[TestCase(@" [a]: /r")]
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 ""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\"")]
[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]:\t/r")]
[TestCase("[a]:\t/r\t")]
[TestCase("[a]:\t/r\t\"l\"")]
[TestCase("[a]:\t/r\t\"l\"\t")]
[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 \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);
}
[TestCase("[a]: /r\n===\n[a]")]
public void TestSetextHeader(string value)
{
RoundTrip(value);
}
}

View File

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

View File

@@ -1,191 +1,189 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestOrderedList
{
[TestFixture]
public class TestOrderedList
[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)
{
[TestCase("1. i")]
[TestCase("1. i")]
[TestCase("1. i ")]
[TestCase("1. i ")]
[TestCase("1. i ")]
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("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,247 +1,245 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestParagraph
{
[TestFixture]
public class TestParagraph
[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)
{
[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);
}
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,286 +1,284 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestQuoteBlock
{
[TestFixture]
public class TestQuoteBlock
[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)
{
[TestCase(">q")]
[TestCase(" >q")]
[TestCase(" >q")]
[TestCase(" >q")]
[TestCase("> q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase("> q")]
[TestCase(" > q")]
[TestCase(" > q")]
[TestCase(" > q")]
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")] // 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\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(">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\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);
}
[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,53 +1,51 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestSetextHeading
{
[TestFixture]
public class TestSetextHeading
[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)
{
[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);
}
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,53 +1,51 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestThematicBreak
{
[TestFixture]
public class TestThematicBreak
[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)
{
[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);
}
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,183 +1,182 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestUnorderedList
{
[TestFixture]
public class TestUnorderedList
// 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")]
[TestCase("- 1.\n- 2.")]
public void Test(string 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")]
RoundTrip(value);
}
[TestCase("-\ti1")]
[TestCase("-\ti1\n-\ti2")]
[TestCase("-\ti1\n- i2\n-\ti3")]
public void Test(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("- > 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\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("- 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("- i1\n\n---\n")]
[TestCase("- i1\n\n\n---\n")]
public void TestThematicBreak(string value)
{
RoundTrip(value);
}
[TestCase("- i1\n\n---\n")]
[TestCase("- i1\n\n\n---\n")]
public void TestThematicBreak(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("- 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("- ```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("- ```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")]
[TestCase("\r- i")]
[TestCase("\r\n- i")]
[TestCase("\n- i")]
[TestCase("\r- i")]
[TestCase("\r\n- i")]
[TestCase("\n- i\n")]
[TestCase("\r- i\n")]
[TestCase("\r\n- i\n")]
[TestCase("\n- i\n")]
[TestCase("\r- i\n")]
[TestCase("\r\n- i\n")]
[TestCase("\n- i\r")]
[TestCase("\r- i\r")]
[TestCase("\r\n- i\r")]
[TestCase("\n- i\r")]
[TestCase("\r- i\r")]
[TestCase("\r\n- i\r")]
[TestCase("\n- i\r\n")]
[TestCase("\r- i\r\n")]
[TestCase("\r\n- i\r\n")]
[TestCase("\n- i\r\n")]
[TestCase("\r- i\r\n")]
[TestCase("\r\n- i\r\n")]
[TestCase("- i\n- j")]
[TestCase("- i\r- j")]
[TestCase("- i\r\n- j")]
[TestCase("- i\n- j")]
[TestCase("- i\r- j")]
[TestCase("- i\r\n- j")]
[TestCase("\n- i\n- j")]
[TestCase("\n- i\r- j")]
[TestCase("\n- i\r\n- j")]
[TestCase("\n- i\n- j")]
[TestCase("\n- i\r- j")]
[TestCase("\n- i\r\n- j")]
[TestCase("\r- i\n- j")]
[TestCase("\r- i\r- j")]
[TestCase("\r- i\r\n- j")]
[TestCase("\r- i\n- j")]
[TestCase("\r- i\r- j")]
[TestCase("\r- 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\n- i\n- j")]
[TestCase("\r\n- i\r- j")]
[TestCase("\r\n- i\r\n- j")]
[TestCase("- i\n- j\n")]
[TestCase("- i\r- j\n")]
[TestCase("- i\r\n- j\n")]
[TestCase("- i\n- j\n")]
[TestCase("- i\r- j\n")]
[TestCase("- 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("\n- i\n- j\n")]
[TestCase("\n- i\r- j\n")]
[TestCase("\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("\r- i\n- j\n")]
[TestCase("\r- i\r- j\n")]
[TestCase("\r- 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\n- i\n- j\n")]
[TestCase("\r\n- i\r- j\n")]
[TestCase("\r\n- i\r\n- j\n")]
[TestCase("- i\n- j\r")]
[TestCase("- i\r- j\r")]
[TestCase("- i\r\n- j\r")]
[TestCase("- i\n- j\r")]
[TestCase("- i\r- j\r")]
[TestCase("- 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("\n- i\n- j\r")]
[TestCase("\n- i\r- j\r")]
[TestCase("\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("\r- i\n- j\r")]
[TestCase("\r- i\r- j\r")]
[TestCase("\r- 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\n- i\n- j\r")]
[TestCase("\r\n- i\r- j\r")]
[TestCase("\r\n- 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("- i\n- j\r\n")]
[TestCase("- i\r- j\r\n")]
[TestCase("- 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("\n- i\n- j\r\n")]
[TestCase("\n- i\r- j\r\n")]
[TestCase("\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("\r- i\n- j\r\n")]
[TestCase("\r- i\r- j\r\n")]
[TestCase("\r- 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\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);
}
[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,25 +1,15 @@
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
namespace Markdig.Tests.RoundtripSpecs;
[TestFixture]
public class TestYamlFrontMatterBlock
{
[TestFixture]
public class TestYamlFrontMatterBlock
[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)
{
[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);
}
RoundTrip(value);
}
}

View File

@@ -0,0 +1,179 @@
// --------------------------------
// Alert Blocks
// --------------------------------
using System;
using NUnit.Framework;
namespace Markdig.Tests.Specs.AlertBlocks
{
[TestFixture]
public class TestExtensionsAlertBlocks
{
// # Extensions
//
// This section describes the different extensions supported:
//
// ## Alert Blocks
//
// This is supporting the [GitHub Alert blocks](https://github.com/orgs/community/discussions/16925)
[Test]
public void ExtensionsAlertBlocks_Example001()
{
// Example 1
// Section: Extensions / Alert Blocks
//
// The following Markdown:
// > [!NOTE]
// > Highlights information that users should take into account, even when skimming.
//
// > [!TIP]
// > Optional information to help a user be more successful.
//
// > [!IMPORTANT]
// > Crucial information necessary for users to succeed.
//
// > [!WARNING]
// > Critical content demanding immediate user attention due to potential risks.
//
// > [!CAUTION]
// > Negative potential consequences of an action.
//
// Should be rendered as:
// <div class="markdown-alert markdown-alert-note">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
// <p>Highlights information that users should take into account, even when skimming.</p>
// </div>
// <div class="markdown-alert markdown-alert-tip">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"></path></svg>Tip</p>
// <p>Optional information to help a user be more successful.</p>
// </div>
// <div class="markdown-alert markdown-alert-important">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Important</p>
// <p>Crucial information necessary for users to succeed.</p>
// </div>
// <div class="markdown-alert markdown-alert-warning">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</p>
// <p>Critical content demanding immediate user attention due to potential risks.</p>
// </div>
// <div class="markdown-alert markdown-alert-caution">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Caution</p>
// <p>Negative potential consequences of an action.</p>
// </div>
TestParser.TestSpec("> [!NOTE] \n> Highlights information that users should take into account, even when skimming.\n\n> [!TIP]\n> Optional information to help a user be more successful.\n\n> [!IMPORTANT] \n> Crucial information necessary for users to succeed.\n\n> [!WARNING] \n> Critical content demanding immediate user attention due to potential risks.\n\n> [!CAUTION]\n> Negative potential consequences of an action.", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-tip\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z\"></path></svg>Tip</p>\n<p>Optional information to help a user be more successful.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-important\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path></svg>Important</p>\n<p>Crucial information necessary for users to succeed.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-warning\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z\"></path></svg>Warning</p>\n<p>Critical content demanding immediate user attention due to potential risks.</p>\n</div>\n<div class=\"markdown-alert markdown-alert-caution\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Caution</p>\n<p>Negative potential consequences of an action.</p>\n</div>", "advanced", context: "Example 1\nSection Extensions / Alert Blocks\n");
}
// Example with code blocks and mix formatting:
[Test]
public void ExtensionsAlertBlocks_Example002()
{
// Example 2
// Section: Extensions / Alert Blocks
//
// The following Markdown:
// > [!NOTE]
// > Highlights information that users should take into account, even when skimming.
// > Testing rendering for multiple lines
// > ```csharp
// > var test = "I can also add code to panels
// > ```
// > `Inline code testing`
//
// Should be rendered as:
// <div class="markdown-alert markdown-alert-note">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
// <p>Highlights information that users should take into account, even when skimming.
// Testing rendering for multiple lines</p>
// <pre><code class="language-csharp">var test = &quot;I can also add code to panels
// </code></pre>
// <p><code>Inline code testing</code></p>
// </div>
TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> Testing rendering for multiple lines\n> ```csharp\n> var test = \"I can also add code to panels\n> ```\n> `Inline code testing`", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.\nTesting rendering for multiple lines</p>\n<pre><code class=\"language-csharp\">var test = &quot;I can also add code to panels\n</code></pre>\n<p><code>Inline code testing</code></p>\n</div>", "advanced", context: "Example 2\nSection Extensions / Alert Blocks\n");
}
// Multiline:
[Test]
public void ExtensionsAlertBlocks_Example003()
{
// Example 3
// Section: Extensions / Alert Blocks
//
// The following Markdown:
// > [!NOTE]
// > Highlights information that users should take into account, even when skimming.
// >
// > Testing rendering for multiple lines
// >
// > `Inline code testing`
// >
// > Other line
// >
// > > Nested quote
// > >
// > > Final nested quote line
// >
// > Final line of alert
//
// Should be rendered as:
// <div class="markdown-alert markdown-alert-note">
// <p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
// <p>Highlights information that users should take into account, even when skimming.</p>
// <p>Testing rendering for multiple lines</p>
// <p><code>Inline code testing</code></p>
// <p>Other line</p>
// <blockquote>
// <p>Nested quote</p>
// <p>Final nested quote line</p>
// </blockquote>
// <p>Final line of alert</p>
// </div>
TestParser.TestSpec("> [!NOTE]\n> Highlights information that users should take into account, even when skimming.\n> \n> Testing rendering for multiple lines\n> \n> `Inline code testing`\n> \n> Other line\n> \n> > Nested quote\n> >\n> > Final nested quote line\n> \n> Final line of alert", "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\"><svg viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"><path d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"></path></svg>Note</p>\n<p>Highlights information that users should take into account, even when skimming.</p>\n<p>Testing rendering for multiple lines</p>\n<p><code>Inline code testing</code></p>\n<p>Other line</p>\n<blockquote>\n<p>Nested quote</p>\n<p>Final nested quote line</p>\n</blockquote>\n<p>Final line of alert</p>\n</div>", "advanced", context: "Example 3\nSection Extensions / Alert Blocks\n");
}
// An alert inline (e.g `[!NOTE]`) must come first in a quote block, and must be followed by optional spaces with a new line. If no new lines are found, it will not be considered as an alert block.
//
// Followed by space and new line:
[Test]
public void ExtensionsAlertBlocks_Example004()
{
// Example 4
// Section: Extensions / Alert Blocks
//
// The following Markdown:
// > [!NOTE] This is invalid because no new line
// > Highlights information that users should take into account, even when skimming.
//
// Should be rendered as:
// <blockquote>
// <p>[!NOTE] This is invalid because no new line
// Highlights information that users should take into account, even when skimming.</p>
// </blockquote>
TestParser.TestSpec("> [!NOTE] This is invalid because no new line\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>[!NOTE] This is invalid because no new line\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 4\nSection Extensions / Alert Blocks\n");
}
// Must come first in a quote block:
[Test]
public void ExtensionsAlertBlocks_Example005()
{
// Example 5
// Section: Extensions / Alert Blocks
//
// The following Markdown:
// > This is not a [!NOTE]
// > Highlights information that users should take into account, even when skimming.
//
// Should be rendered as:
// <blockquote>
// <p>This is not a [!NOTE]
// Highlights information that users should take into account, even when skimming.</p>
// </blockquote>
TestParser.TestSpec("> This is not a [!NOTE]\n> Highlights information that users should take into account, even when skimming.", "<blockquote>\n<p>This is not a [!NOTE]\nHighlights information that users should take into account, even when skimming.</p>\n</blockquote>", "advanced", context: "Example 5\nSection Extensions / Alert Blocks\n");
}
}
}

View File

@@ -0,0 +1,127 @@
# Extensions
This section describes the different extensions supported:
## Alert Blocks
This is supporting the [GitHub Alert blocks](https://github.com/orgs/community/discussions/16925)
```````````````````````````````` example
> [!NOTE]
> Highlights information that users should take into account, even when skimming.
> [!TIP]
> Optional information to help a user be more successful.
> [!IMPORTANT]
> Crucial information necessary for users to succeed.
> [!WARNING]
> Critical content demanding immediate user attention due to potential risks.
> [!CAUTION]
> Negative potential consequences of an action.
.
<div class="markdown-alert markdown-alert-note">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p>Highlights information that users should take into account, even when skimming.</p>
</div>
<div class="markdown-alert markdown-alert-tip">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z"></path></svg>Tip</p>
<p>Optional information to help a user be more successful.</p>
</div>
<div class="markdown-alert markdown-alert-important">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Important</p>
<p>Crucial information necessary for users to succeed.</p>
</div>
<div class="markdown-alert markdown-alert-warning">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg>Warning</p>
<p>Critical content demanding immediate user attention due to potential risks.</p>
</div>
<div class="markdown-alert markdown-alert-caution">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Caution</p>
<p>Negative potential consequences of an action.</p>
</div>
````````````````````````````````
Example with code blocks and mix formatting:
```````````````````````````````` example
> [!NOTE]
> Highlights information that users should take into account, even when skimming.
> Testing rendering for multiple lines
> ```csharp
> var test = "I can also add code to panels
> ```
> `Inline code testing`
.
<div class="markdown-alert markdown-alert-note">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p>Highlights information that users should take into account, even when skimming.
Testing rendering for multiple lines</p>
<pre><code class="language-csharp">var test = &quot;I can also add code to panels
</code></pre>
<p><code>Inline code testing</code></p>
</div>
````````````````````````````````
Multiline:
```````````````````````````````` example
> [!NOTE]
> Highlights information that users should take into account, even when skimming.
>
> Testing rendering for multiple lines
>
> `Inline code testing`
>
> Other line
>
> > Nested quote
> >
> > Final nested quote line
>
> Final line of alert
.
<div class="markdown-alert markdown-alert-note">
<p class="markdown-alert-title"><svg viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg>Note</p>
<p>Highlights information that users should take into account, even when skimming.</p>
<p>Testing rendering for multiple lines</p>
<p><code>Inline code testing</code></p>
<p>Other line</p>
<blockquote>
<p>Nested quote</p>
<p>Final nested quote line</p>
</blockquote>
<p>Final line of alert</p>
</div>
````````````````````````````````
An alert inline (e.g `[!NOTE]`) must come first in a quote block, and must be followed by optional spaces with a new line. If no new lines are found, it will not be considered as an alert block.
Followed by space and new line:
```````````````````````````````` example
> [!NOTE] This is invalid because no new line
> Highlights information that users should take into account, even when skimming.
.
<blockquote>
<p>[!NOTE] This is invalid because no new line
Highlights information that users should take into account, even when skimming.</p>
</blockquote>
````````````````````````````````
Must come first in a quote block:
```````````````````````````````` example
> This is not a [!NOTE]
> Highlights information that users should take into account, even when skimming.
.
<blockquote>
<p>This is not a [!NOTE]
Highlights information that users should take into account, even when skimming.</p>
</blockquote>
````````````````````````````````

View File

@@ -533,5 +533,28 @@ namespace Markdig.Tests.Specs.AutoLinks
TestParser.TestSpec("<http://foö.bar.`baz>`", "<p><a href=\"http://xn--fo-gka.bar.%60baz\">http://foö.bar.`baz</a>`</p>", "autolinks|advanced", context: "Example 25\nSection Extensions / AutoLinks / Unicode support\n");
}
// Unicode punctuation characters are not allowed, but symbols are.
// Note that this does _not_ exactly match CommonMark's "Unicode punctuation character" definition.
[Test]
public void ExtensionsAutoLinksUnicodeSupport_Example026()
{
// Example 26
// Section: Extensions / AutoLinks / Unicode support
//
// The following Markdown:
// http://☃.net?☃ // OtherSymbol
//
// http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol
//
// http://‰.net?‰ // OtherPunctuation
//
// Should be rendered as:
// <p><a href="http://xn--n3h.net?%E2%98%83">http://☃.net?☃</a> // OtherSymbol</p>
// <p><a href="http://xn--ji8h.net?%F0%9F%8D%89">http://🍉.net?🍉</a> // A UTF-16 surrogate pair, but code point is OtherSymbol</p>
// <p>http://‰.net?‰ // OtherPunctuation</p>
TestParser.TestSpec("http://☃.net?☃ // OtherSymbol\n\nhttp://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol\n\nhttp://‰.net?‰ // OtherPunctuation", "<p><a href=\"http://xn--n3h.net?%E2%98%83\">http://☃.net?☃</a> // OtherSymbol</p>\n<p><a href=\"http://xn--ji8h.net?%F0%9F%8D%89\">http://🍉.net?🍉</a> // A UTF-16 surrogate pair, but code point is OtherSymbol</p>\n<p>http://‰.net?‰ // OtherPunctuation</p>", "autolinks|advanced", context: "Example 26\nSection Extensions / AutoLinks / Unicode support\n");
}
}
}

View File

@@ -303,4 +303,19 @@ This will therefore be seen as an autolink and not as code inline.
<http://foö.bar.`baz>`
.
<p><a href="http://xn--fo-gka.bar.%60baz">http://foö.bar.`baz</a>`</p>
````````````````````````````````
Unicode punctuation characters are not allowed, but symbols are.
Note that this does _not_ exactly match CommonMark's "Unicode punctuation character" definition.
```````````````````````````````` example
http://☃.net?☃ // OtherSymbol
http://🍉.net?🍉 // A UTF-16 surrogate pair, but code point is OtherSymbol
http://‰.net?‰ // OtherPunctuation
.
<p><a href="http://xn--n3h.net?%E2%98%83">http://☃.net?☃</a> // OtherSymbol</p>
<p><a href="http://xn--ji8h.net?%F0%9F%8D%89">http://🍉.net?🍉</a> // A UTF-16 surrogate pair, but code point is OtherSymbol</p>
<p>http://‰.net?‰ // OtherPunctuation</p>
````````````````````````````````

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
---
title: CommonMark Spec
author: John MacFarlane
version: '0.30'
date: '2021-06-19'
license: '[CC-BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/)'
version: '0.31.2'
date: '2024-01-28'
license: '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)'
...
# Introduction
@@ -14,7 +14,7 @@ Markdown is a plain text format for writing structured documents,
based on conventions for indicating formatting in email
and usenet posts. It was developed by John Gruber (with
help from Aaron Swartz) and released in 2004 in the form of a
[syntax description](http://daringfireball.net/projects/markdown/syntax)
[syntax description](https://daringfireball.net/projects/markdown/syntax)
and a Perl script (`Markdown.pl`) for converting Markdown to
HTML. In the next decade, dozens of implementations were
developed in many languages. Some extended the original
@@ -34,10 +34,10 @@ As Gruber writes:
> Markdown-formatted document should be publishable as-is, as
> plain text, without looking like it's been marked up with tags
> or formatting instructions.
> (<http://daringfireball.net/projects/markdown/>)
> (<https://daringfireball.net/projects/markdown/>)
The point can be illustrated by comparing a sample of
[AsciiDoc](http://www.methods.co.nz/asciidoc/) with
[AsciiDoc](https://asciidoc.org/) with
an equivalent sample of Markdown. Here is a sample of
AsciiDoc from the AsciiDoc manual:
@@ -103,7 +103,7 @@ source, not just in the processed document.
## Why is a spec needed?
John Gruber's [canonical description of Markdown's
syntax](http://daringfireball.net/projects/markdown/syntax)
syntax](https://daringfireball.net/projects/markdown/syntax)
does not specify the syntax unambiguously. Here are some examples of
questions it does not answer:
@@ -316,9 +316,9 @@ A line containing no characters, or a line containing only spaces
The following definitions of character classes will be used in this spec:
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`).
A [Unicode whitespace character](@) is a character in the Unicode `Zs` general
category, or a tab (`U+0009`), line feed (`U+000A`), form feed (`U+000C`), or
carriage return (`U+000D`).
[Unicode whitespace](@) is a sequence of one or more
[Unicode whitespace characters].
@@ -337,9 +337,8 @@ is `!`, `"`, `#`, `$`, `%`, `&`, `'`, `(`, `)`,
`[`, `\`, `]`, `^`, `_`, `` ` `` (U+005B0060),
`{`, `|`, `}`, or `~` (U+007B007E).
A [Unicode punctuation character](@) is an [ASCII
punctuation character] or anything in
the general Unicode categories `Pc`, `Pd`, `Pe`, `Pf`, `Pi`, `Po`, or `Ps`.
A [Unicode punctuation character](@) is a character in the Unicode `P`
(puncuation) or `S` (symbol) general categories.
## Tabs
@@ -579,9 +578,9 @@ raw HTML:
```````````````````````````````` example
<http://example.com?find=\*>
<https://example.com?find=\*>
.
<p><a href="http://example.com?find=%5C*">http://example.com?find=\*</a></p>
<p><a href="https://example.com?find=%5C*">https://example.com?find=\*</a></p>
````````````````````````````````
@@ -1964,7 +1963,7 @@ has been found, the code block contains all of the lines after the
opening code fence until the end of the containing block (or
document). (An alternative spec would require backtracking in the
event that a closing code fence is not found. But this makes parsing
much less efficient, and there seems to be no real down side to the
much less efficient, and there seems to be no real downside to the
behavior described here.)
A fenced code block may interrupt a paragraph, and does not require
@@ -2403,7 +2402,7 @@ followed by one of the strings (case-insensitive) `address`,
`h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `head`, `header`, `hr`,
`html`, `iframe`, `legend`, `li`, `link`, `main`, `menu`, `menuitem`,
`nav`, `noframes`, `ol`, `optgroup`, `option`, `p`, `param`,
`section`, `source`, `summary`, `table`, `tbody`, `td`,
`search`, `section`, `summary`, `table`, `tbody`, `td`,
`tfoot`, `th`, `thead`, `title`, `tr`, `track`, `ul`, followed
by a space, a tab, the end of the line, the string `>`, or
the string `/>`.\
@@ -4115,7 +4114,7 @@ The following rules define [list items]:
blocks *Bs* starting with a character other than a space or tab, and *M* is
a list marker of width *W* followed by 1 ≤ *N* ≤ 4 spaces of indentation,
then the result of prepending *M* and the following spaces to the first line
of Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a
of *Ls*, and indenting subsequent lines of *Ls* by *W + N* spaces, is a
list item with *Bs* as its contents. The type of the list item
(bullet or ordered) is determined by the type of its list marker.
If the list item is ordered, then it is also assigned a start
@@ -5350,11 +5349,11 @@ by itself should be a paragraph followed by a nested sublist.
Since it is well established Markdown practice to allow lists to
interrupt paragraphs inside list items, the [principle of
uniformity] requires us to allow this outside list items as
well. ([reStructuredText](http://docutils.sourceforge.net/rst.html)
well. ([reStructuredText](https://docutils.sourceforge.net/rst.html)
takes a different approach, requiring blank lines before lists
even inside other list items.)
In order to solve of unwanted lists in paragraphs with
In order to solve the problem of unwanted lists in paragraphs with
hard-wrapped numerals, we allow only lists starting with `1` to
interrupt paragraphs. Thus,
@@ -6055,18 +6054,18 @@ But this is an HTML tag:
And this is code:
```````````````````````````````` example
`<http://foo.bar.`baz>`
`<https://foo.bar.`baz>`
.
<p><code>&lt;http://foo.bar.</code>baz&gt;`</p>
<p><code>&lt;https://foo.bar.</code>baz&gt;`</p>
````````````````````````````````
But this is an autolink:
```````````````````````````````` example
<http://foo.bar.`baz>`
<https://foo.bar.`baz>`
.
<p><a href="http://foo.bar.%60baz">http://foo.bar.`baz</a>`</p>
<p><a href="https://foo.bar.%60baz">https://foo.bar.`baz</a>`</p>
````````````````````````````````
@@ -6099,7 +6098,7 @@ closing backtick strings to be equal in length:
## Emphasis and strong emphasis
John Gruber's original [Markdown syntax
description](http://daringfireball.net/projects/markdown/syntax#em) says:
description](https://daringfireball.net/projects/markdown/syntax#em) says:
> Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
> emphasis. Text wrapped with one `*` or `_` will be wrapped with an HTML
@@ -6201,7 +6200,7 @@ Here are some examples of delimiter runs.
(The idea of distinguishing left-flanking and right-flanking
delimiter runs based on the character before and the character
after comes from Roopesh Chander's
[vfmd](http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags).
[vfmd](https://web.archive.org/web/20220608143320/http://www.vfmd.org/vfmd-spec/specification/#procedure-for-identifying-emphasis-tags).
vfmd uses the terminology "emphasis indicator string" instead of "delimiter
run," and its rules for distinguishing left- and right-flanking runs
are a bit more complex than the ones given here.)
@@ -6343,6 +6342,21 @@ Unicode nonbreaking spaces count as whitespace, too:
````````````````````````````````
Unicode symbols count as punctuation, too:
```````````````````````````````` example
*$*alpha.
*£*bravo.
*€*charlie.
.
<p>*$*alpha.</p>
<p>*£*bravo.</p>
<p>*€*charlie.</p>
````````````````````````````````
Intraword emphasis with `*` is permitted:
```````````````````````````````` example
@@ -7428,16 +7442,16 @@ _a `_`_
```````````````````````````````` example
**a<http://foo.bar/?q=**>
**a<https://foo.bar/?q=**>
.
<p>**a<a href="http://foo.bar/?q=**">http://foo.bar/?q=**</a></p>
<p>**a<a href="https://foo.bar/?q=**">https://foo.bar/?q=**</a></p>
````````````````````````````````
```````````````````````````````` example
__a<http://foo.bar/?q=__>
__a<https://foo.bar/?q=__>
.
<p>__a<a href="http://foo.bar/?q=__">http://foo.bar/?q=__</a></p>
<p>__a<a href="https://foo.bar/?q=__">https://foo.bar/?q=__</a></p>
````````````````````````````````
@@ -7685,13 +7699,13 @@ A link can contain fragment identifiers and queries:
```````````````````````````````` example
[link](#fragment)
[link](http://example.com#fragment)
[link](https://example.com#fragment)
[link](http://example.com?foo=3#frag)
[link](https://example.com?foo=3#frag)
.
<p><a href="#fragment">link</a></p>
<p><a href="http://example.com#fragment">link</a></p>
<p><a href="http://example.com?foo=3#frag">link</a></p>
<p><a href="https://example.com#fragment">link</a></p>
<p><a href="https://example.com?foo=3#frag">link</a></p>
````````````````````````````````
@@ -7935,9 +7949,9 @@ and autolinks over link grouping:
```````````````````````````````` example
[foo<http://example.com/?search=](uri)>
[foo<https://example.com/?search=](uri)>
.
<p>[foo<a href="http://example.com/?search=%5D(uri)">http://example.com/?search=](uri)</a></p>
<p>[foo<a href="https://example.com/?search=%5D(uri)">https://example.com/?search=](uri)</a></p>
````````````````````````````````
@@ -8091,11 +8105,11 @@ and autolinks over link grouping:
```````````````````````````````` example
[foo<http://example.com/?search=][ref]>
[foo<https://example.com/?search=][ref]>
[ref]: /uri
.
<p>[foo<a href="http://example.com/?search=%5D%5Bref%5D">http://example.com/?search=][ref]</a></p>
<p>[foo<a href="https://example.com/?search=%5D%5Bref%5D">https://example.com/?search=][ref]</a></p>
````````````````````````````````
@@ -8295,7 +8309,7 @@ A [collapsed reference link](@)
consists of a [link label] that [matches] a
[link reference definition] elsewhere in the
document, followed by the string `[]`.
The contents of the first link label are parsed as inlines,
The contents of the link label are parsed as inlines,
which are used as the link's text. The link's URI and title are
provided by the matching reference link definition. Thus,
`[foo][]` is equivalent to `[foo][foo]`.
@@ -8348,7 +8362,7 @@ A [shortcut reference link](@)
consists of a [link label] that [matches] a
[link reference definition] elsewhere in the
document and is not followed by `[]` or a link label.
The contents of the first link label are parsed as inlines,
The contents of the link label are parsed as inlines,
which are used as the link's text. The link's URI and title
are provided by the matching link reference definition.
Thus, `[foo]` is equivalent to `[foo][]`.
@@ -8435,7 +8449,7 @@ following closing bracket:
````````````````````````````````
Full and compact references take precedence over shortcut
Full and collapsed references take precedence over shortcut
references:
```````````````````````````````` example
@@ -8771,9 +8785,9 @@ Here are some valid autolinks:
```````````````````````````````` example
<http://foo.bar.baz/test?q=hello&id=22&boolean>
<https://foo.bar.baz/test?q=hello&id=22&boolean>
.
<p><a href="http://foo.bar.baz/test?q=hello&amp;id=22&amp;boolean">http://foo.bar.baz/test?q=hello&amp;id=22&amp;boolean</a></p>
<p><a href="https://foo.bar.baz/test?q=hello&amp;id=22&amp;boolean">https://foo.bar.baz/test?q=hello&amp;id=22&amp;boolean</a></p>
````````````````````````````````
@@ -8813,9 +8827,9 @@ with their syntax:
```````````````````````````````` example
<http://../>
<https://../>
.
<p><a href="http://../">http://../</a></p>
<p><a href="https://../">https://../</a></p>
````````````````````````````````
@@ -8829,18 +8843,18 @@ with their syntax:
Spaces are not allowed in autolinks:
```````````````````````````````` example
<http://foo.bar/baz bim>
<https://foo.bar/baz bim>
.
<p>&lt;http://foo.bar/baz bim&gt;</p>
<p>&lt;https://foo.bar/baz bim&gt;</p>
````````````````````````````````
Backslash-escapes do not work inside autolinks:
```````````````````````````````` example
<http://example.com/\[\>
<https://example.com/\[\>
.
<p><a href="http://example.com/%5C%5B%5C">http://example.com/\[\</a></p>
<p><a href="https://example.com/%5C%5B%5C">https://example.com/\[\</a></p>
````````````````````````````````
@@ -8892,9 +8906,9 @@ These are not autolinks:
```````````````````````````````` example
< http://foo.bar >
< https://foo.bar >
.
<p>&lt; http://foo.bar &gt;</p>
<p>&lt; https://foo.bar &gt;</p>
````````````````````````````````
@@ -8913,9 +8927,9 @@ These are not autolinks:
```````````````````````````````` example
http://example.com
https://example.com
.
<p>http://example.com</p>
<p>https://example.com</p>
````````````````````````````````
@@ -8977,10 +8991,9 @@ A [closing tag](@) consists of the string `</`, a
[tag name], optional spaces, tabs, and up to one line ending, and the character
`>`.
An [HTML comment](@) consists of `<!--` + *text* + `-->`,
where *text* does not start with `>` or `->`, does not end with `-`,
and does not contain `--`. (See the
[HTML5 spec](http://www.w3.org/TR/html5/syntax.html#comments).)
An [HTML comment](@) consists of `<!-->`, `<!--->`, or `<!--`, a string of
characters not including the string `-->`, and `-->` (see the
[HTML spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)).
A [processing instruction](@)
consists of the string `<?`, a string
@@ -9119,30 +9132,20 @@ Illegal attributes in closing tag:
Comments:
```````````````````````````````` example
foo <!-- this is a
comment - with hyphen -->
foo <!-- this is a --
comment - with hyphens -->
.
<p>foo <!-- this is a
comment - with hyphen --></p>
<p>foo <!-- this is a --
comment - with hyphens --></p>
````````````````````````````````
```````````````````````````````` example
foo <!-- not a comment -- two hyphens -->
.
<p>foo &lt;!-- not a comment -- two hyphens --&gt;</p>
````````````````````````````````
Not comments:
```````````````````````````````` example
foo <!--> foo -->
foo <!-- foo--->
foo <!---> foo -->
.
<p>foo &lt;!--&gt; foo --&gt;</p>
<p>foo &lt;!-- foo---&gt;</p>
<p>foo <!--> foo --&gt;</p>
<p>foo <!---> foo --&gt;</p>
````````````````````````````````
@@ -9671,7 +9674,7 @@ through the stack for an opening `[` or `![` delimiter.
delimiter from the stack, and return a literal text node `]`.
- If we find one and it's active, then we parse ahead to see if
we have an inline link/image, reference link/image, compact reference
we have an inline link/image, reference link/image, collapsed reference
link/image, or shortcut reference link/image.
+ If we don't, then we remove the opening delimiter from the

View File

@@ -17,7 +17,7 @@ namespace Markdig.Tests.Specs.Diagrams
//
// ## Mermaid diagrams
//
// Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
// Using a fenced code block with the `mermaid` language info will output a `<pre class='mermaid'>` block (which is the default for other code block):
[Test]
public void ExtensionsMermaidDiagrams_Example001()
{
@@ -34,14 +34,14 @@ namespace Markdig.Tests.Specs.Diagrams
// ```
//
// Should be rendered as:
// <div class="mermaid">graph TD;
// <pre class="mermaid">graph TD;
// A-->B;
// A-->C;
// B-->D;
// C-->D;
// </div>
// </pre>
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<div class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</div>", "diagrams|advanced", context: "Example 1\nSection Extensions / Mermaid diagrams\n");
TestParser.TestSpec("```mermaid\ngraph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n```", "<pre class=\"mermaid\">graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n</pre>", "diagrams|advanced", context: "Example 1\nSection Extensions / Mermaid diagrams\n");
}
}

View File

@@ -4,7 +4,7 @@ Adds support for diagrams extension:
## Mermaid diagrams
Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
Using a fenced code block with the `mermaid` language info will output a `<pre class='mermaid'>` block (which is the default for other code block):
```````````````````````````````` example
```mermaid
@@ -15,12 +15,12 @@ graph TD;
C-->D;
```
.
<div class="mermaid">graph TD;
<pre class="mermaid">graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
</div>
</pre>
````````````````````````````````
## nomnoml diagrams

View File

@@ -123,6 +123,8 @@ namespace Markdig.Tests.Specs.EmphasisExtra
public class TestExtensionsEmphasisOnHtmlEntities
{
// ## Emphasis on Html Entities
//
// Note that Unicode symbols are treated as punctuation, which are not allowed to open the emphasis unless they are preceded by a space.
[Test]
public void ExtensionsEmphasisOnHtmlEntities_Example006()
{
@@ -132,14 +134,14 @@ namespace Markdig.Tests.Specs.EmphasisExtra
// The following Markdown:
// This is text MyBrand ^&reg;^ and MyTrademark ^&trade;^
// This is text MyBrand^&reg;^ and MyTrademark^&trade;^
// This is text MyBrand~&reg;~ and MyCopyright^&copy;^
// This is text MyBrand ~&reg;~ and MyCopyright ^&copy;^
//
// Should be rendered as:
// <p>This is text MyBrand <sup>®</sup> and MyTrademark <sup>TM</sup>
// This is text MyBrand<sup>®</sup> and MyTrademark<sup>TM</sup>
// This is text MyBrand<sub>®</sub> and MyCopyright<sup>©</sup></p>
// This is text MyBrand^®^ and MyTrademark^TM^
// This is text MyBrand <sub>®</sub> and MyCopyright <sup>©</sup></p>
TestParser.TestSpec("This is text MyBrand ^&reg;^ and MyTrademark ^&trade;^\nThis is text MyBrand^&reg;^ and MyTrademark^&trade;^\nThis is text MyBrand~&reg;~ and MyCopyright^&copy;^", "<p>This is text MyBrand <sup>®</sup> and MyTrademark <sup>TM</sup>\nThis is text MyBrand<sup>®</sup> and MyTrademark<sup>TM</sup>\nThis is text MyBrand<sub>®</sub> and MyCopyright<sup>©</sup></p>", "emphasisextras|advanced", context: "Example 6\nSection Extensions / Emphasis on Html Entities\n");
TestParser.TestSpec("This is text MyBrand ^&reg;^ and MyTrademark ^&trade;^\nThis is text MyBrand^&reg;^ and MyTrademark^&trade;^\nThis is text MyBrand ~&reg;~ and MyCopyright ^&copy;^", "<p>This is text MyBrand <sup>®</sup> and MyTrademark <sup>TM</sup>\nThis is text MyBrand^®^ and MyTrademark^TM^\nThis is text MyBrand <sub>®</sub> and MyCopyright <sup>©</sup></p>", "emphasisextras|advanced", context: "Example 6\nSection Extensions / Emphasis on Html Entities\n");
}
}
}

View File

@@ -52,16 +52,17 @@ Marked text can be used to specify that a text has been marked in a document. T
.
<p><mark>Marked text</mark></p>
````````````````````````````````
## Emphasis on Html Entities
Note that Unicode symbols are treated as punctuation, which are not allowed to open the emphasis unless they are preceded by a space.
```````````````````````````````` example
This is text MyBrand ^&reg;^ and MyTrademark ^&trade;^
This is text MyBrand^&reg;^ and MyTrademark^&trade;^
This is text MyBrand~&reg;~ and MyCopyright^&copy;^
This is text MyBrand ~&reg;~ and MyCopyright ^&copy;^
.
<p>This is text MyBrand <sup>®</sup> and MyTrademark <sup>TM</sup>
This is text MyBrand<sup>®</sup> and MyTrademark<sup>TM</sup>
This is text MyBrand<sub>®</sub> and MyCopyright<sup>©</sup></p>
This is text MyBrand^®^ and MyTrademark^TM^
This is text MyBrand <sub>®</sub> and MyCopyright <sup>©</sup></p>
````````````````````````````````

View File

@@ -98,5 +98,22 @@ namespace Markdig.Tests.Specs.GenericAttributes
TestParser.TestSpec("[Foo](url){data-x=1}\n\n[Foo](url){data-x='1'}\n\n[Foo](url){data-x=11}", "<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"11\">Foo</a></p>", "attributes|advanced", context: "Example 3\nSection Extensions / Generic Attributes\n");
}
// Attributes that occur immediately before a block element, on a line by themselves, affect that element
[Test]
public void ExtensionsGenericAttributes_Example004()
{
// Example 4
// Section: Extensions / Generic Attributes
//
// The following Markdown:
// {.center}
// A paragraph
//
// Should be rendered as:
// <p class="center">A paragraph</p>
TestParser.TestSpec("{.center}\nA paragraph", "<p class=\"center\">A paragraph</p>", "attributes|advanced", context: "Example 4\nSection Extensions / Generic Attributes\n");
}
}
}

View File

@@ -61,3 +61,12 @@ Attribute values can be one character long
<p><a href="url" data-x="1">Foo</a></p>
<p><a href="url" data-x="11">Foo</a></p>
````````````````````````````````
Attributes that occur immediately before a block element, on a line by themselves, affect that element
```````````````````````````````` example
{.center}
A paragraph
.
<p class="center">A paragraph</p>
````````````````````````````````

View File

@@ -17,7 +17,8 @@ namespace Markdig.Tests.Specs.GridTables
//
// ## 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
// A grid table allows having multiple lines per cells and allows spanning cells over multiple columns.
// The following shows a simple grid table:
//
// ```
// +---------+---------+
@@ -38,17 +39,20 @@ namespace Markdig.Tests.Specs.GridTables
// ```
//
// **Rule #1**
// 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
// 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 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.
// 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 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()
{
@@ -74,7 +78,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()
{
@@ -93,7 +97,8 @@ namespace Markdig.Tests.Specs.GridTables
}
// **Rule #2**
// 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:
// 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:
[Test]
public void ExtensionsGridTable_Example003()
{
@@ -187,15 +192,13 @@ 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 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:
// 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:
//
// Total size is : 16
// - `----` → 4 characters
// - `--------` → 8 characters
// - `----` → 4 characters
//
// - `----` -> 4
// - `--------` -> 8
// - `----` -> 4
//
// So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
// The total size is 16 characters, so the widths would be 4/16 = 25%, 8/16 = 50%, and 4/16 = 25%.
[Test]
public void ExtensionsGridTable_Example006()
{
@@ -296,7 +299,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()
{
@@ -383,5 +386,34 @@ namespace Markdig.Tests.Specs.GridTables
TestParser.TestSpec("+", "<ul>\n<li></li>\n</ul>", "gridtables|advanced", context: "Example 11\nSection Extensions / Grid Table\n");
}
// A table may begin right after a paragraph without an empty line in between:
[Test]
public void ExtensionsGridTable_Example012()
{
// Example 12
// Section: Extensions / Grid Table
//
// The following Markdown:
// Some
// **text**.
// +---+
// | A |
// +---+
//
// Should be rendered as:
// <p>Some
// <strong>text</strong>.</p>
// <table>
// <col style="width:100%" />
// <tbody>
// <tr>
// <td>A</td>
// </tr>
// </tbody>
// </table>
TestParser.TestSpec("Some\n**text**.\n+---+\n| A |\n+---+", "<p>Some\n<strong>text</strong>.</p>\n<table>\n<col style=\"width:100%\" />\n<tbody>\n<tr>\n<td>A</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced", context: "Example 12\nSection Extensions / Grid Table\n");
}
}
}

View File

@@ -4,7 +4,8 @@ This section describes the different extensions supported:
## 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
A grid table allows having multiple lines per cells and allows spanning cells over multiple columns.
The following shows a simple grid table:
```
+---------+---------+
@@ -25,17 +26,20 @@ A grid table allows to have multiple lines per cells and allows to span cells ov
```
**Rule #1**
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
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 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.
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 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
+---------+---------+
@@ -53,8 +57,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
@@ -64,7 +68,8 @@ The following is not a valid row separator
````````````````````````````````
**Rule #2**
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:
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:
```````````````````````````````` example
+---------+---------+---------+
@@ -134,15 +139,13 @@ The last column separator `|` may be omitted:
</table>
````````````````````````````````
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:
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:
Total size is : 16
- `----` → 4 characters
- `--------` → 8 characters
- `----` → 4 characters
- `----` -> 4
- `--------` -> 8
- `----` -> 4
So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
The total size is 16 characters, so the widths would be 4/16 = 25%, 8/16 = 50%, and 4/16 = 25%.
```````````````````````````````` example
+----+--------+----+
@@ -165,7 +168,6 @@ So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
Alignment might be specified on the first row using the character `:`:
```````````````````````````````` example
+-----+:---:+-----+
| A | B | C |
@@ -220,7 +222,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
+---+---+---+
@@ -276,7 +278,6 @@ A grid table may not have irregularly shaped cells:
An empty `+` on a line should result in a simple empty list output:
```````````````````````````````` example
+
.
@@ -284,3 +285,24 @@ An empty `+` on a line should result in a simple empty list output:
<li></li>
</ul>
````````````````````````````````
A table may begin right after a paragraph without an empty line in between:
```````````````````````````````` example
Some
**text**.
+---+
| A |
+---+
.
<p>Some
<strong>text</strong>.</p>
<table>
<col style="width:100%" />
<tbody>
<tr>
<td>A</td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -17,7 +17,7 @@ namespace Markdig.Tests.Specs.Math
//
// ## Math Inline
//
// Allows to define a mathematic block embraced by `$...$`
// Allows to define a mathematic inline block embraced by `$...$`
[Test]
public void ExtensionsMathInline_Example001()
{
@@ -25,12 +25,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $math block$
// This is a $math inline$
//
// Should be rendered as:
// <p>This is a <span class="math">\(math block\)</span></p>
// <p>This is a <span class="math">\(math inline\)</span></p>
TestParser.TestSpec("This is a $math block$", "<p>This is a <span class=\"math\">\\(math block\\)</span></p>", "mathematics|advanced", context: "Example 1\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $math inline$", "<p>This is a <span class=\"math\">\\(math inline\\)</span></p>", "mathematics|advanced", context: "Example 1\nSection Extensions / Math Inline\n");
}
// Or by `$$...$$` embracing it by:
@@ -41,12 +41,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $$math block$$
// This is a $$math inline$$
//
// Should be rendered as:
// <p>This is a <span class="math">\(math block\)</span></p>
// <p>This is a <span class="math">\(math inline\)</span></p>
TestParser.TestSpec("This is a $$math block$$", "<p>This is a <span class=\"math\">\\(math block\\)</span></p>", "mathematics|advanced", context: "Example 2\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $$math inline$$", "<p>This is a <span class=\"math\">\\(math inline\\)</span></p>", "mathematics|advanced", context: "Example 2\nSection Extensions / Math Inline\n");
}
// Newlines inside an inline math are not allowed:
@@ -58,13 +58,13 @@ namespace Markdig.Tests.Specs.Math
//
// The following Markdown:
// This is not a $$math
// block$$ and? this is a $$math block$$
// inline$$ and? this is a $$math inline$$
//
// Should be rendered as:
// <p>This is not a $$math
// block$$ and? this is a <span class="math">\(math block\)</span></p>
// inline$$ and? this is a <span class="math">\(math inline\)</span></p>
TestParser.TestSpec("This is not a $$math \nblock$$ and? this is a $$math block$$", "<p>This is not a $$math\nblock$$ and? this is a <span class=\"math\">\\(math block\\)</span></p>", "mathematics|advanced", context: "Example 3\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is not a $$math \ninline$$ and? this is a $$math inline$$", "<p>This is not a $$math\ninline$$ and? this is a <span class=\"math\">\\(math inline\\)</span></p>", "mathematics|advanced", context: "Example 3\nSection Extensions / Math Inline\n");
}
[Test]
@@ -75,13 +75,13 @@ namespace Markdig.Tests.Specs.Math
//
// The following Markdown:
// This is not a $math
// block$ and? this is a $math block$
// inline$ and? this is a $math inline$
//
// Should be rendered as:
// <p>This is not a $math
// block$ and? this is a <span class="math">\(math block\)</span></p>
// inline$ and? this is a <span class="math">\(math inline\)</span></p>
TestParser.TestSpec("This is not a $math \nblock$ and? this is a $math block$", "<p>This is not a $math\nblock$ and? this is a <span class=\"math\">\\(math block\\)</span></p>", "mathematics|advanced", context: "Example 4\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is not a $math \ninline$ and? this is a $math inline$", "<p>This is not a $math\ninline$ and? this is a <span class=\"math\">\\(math inline\\)</span></p>", "mathematics|advanced", context: "Example 4\nSection Extensions / Math Inline\n");
}
// An opening `$` can be followed by a space if the closing is also preceded by a space `$`:
@@ -92,12 +92,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $ math block $
// This is a $ math inline $
//
// Should be rendered as:
// <p>This is a <span class="math">\(math block\)</span></p>
// <p>This is a <span class="math">\(math inline\)</span></p>
TestParser.TestSpec("This is a $ math block $", "<p>This is a <span class=\"math\">\\(math block\\)</span></p>", "mathematics|advanced", context: "Example 5\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $ math inline $", "<p>This is a <span class=\"math\">\\(math inline\\)</span></p>", "mathematics|advanced", context: "Example 5\nSection Extensions / Math Inline\n");
}
[Test]
@@ -107,12 +107,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $ math block $ after
// This is a $ math inline $ after
//
// Should be rendered as:
// <p>This is a <span class="math">\(math block\)</span> after</p>
// <p>This is a <span class="math">\(math inline\)</span> after</p>
TestParser.TestSpec("This is a $ math block $ after", "<p>This is a <span class=\"math\">\\(math block\\)</span> after</p>", "mathematics|advanced", context: "Example 6\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $ math inline $ after", "<p>This is a <span class=\"math\">\\(math inline\\)</span> after</p>", "mathematics|advanced", context: "Example 6\nSection Extensions / Math Inline\n");
}
[Test]
@@ -122,12 +122,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $$ math block $$ after
// This is a $$ math inline $$ after
//
// Should be rendered as:
// <p>This is a <span class="math">\(math block\)</span> after</p>
// <p>This is a <span class="math">\(math inline\)</span> after</p>
TestParser.TestSpec("This is a $$ math block $$ after", "<p>This is a <span class=\"math\">\\(math block\\)</span> after</p>", "mathematics|advanced", context: "Example 7\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $$ math inline $$ after", "<p>This is a <span class=\"math\">\\(math inline\\)</span> after</p>", "mathematics|advanced", context: "Example 7\nSection Extensions / Math Inline\n");
}
[Test]
@@ -137,12 +137,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a not $ math block$ because there is not a whitespace before the closing
// This is a not $ math inline$ because there is not a whitespace before the closing
//
// Should be rendered as:
// <p>This is a not $ math block$ because there is not a whitespace before the closing</p>
// <p>This is a not $ math inline$ because there is not a whitespace before the closing</p>
TestParser.TestSpec("This is a not $ math block$ because there is not a whitespace before the closing", "<p>This is a not $ math block$ because there is not a whitespace before the closing</p>", "mathematics|advanced", context: "Example 8\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a not $ math inline$ because there is not a whitespace before the closing", "<p>This is a not $ math inline$ because there is not a whitespace before the closing</p>", "mathematics|advanced", context: "Example 8\nSection Extensions / Math Inline\n");
}
// For the opening `$` it requires a space or a punctuation before (but cannot be used within a word):
@@ -153,12 +153,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is not a m$ath block$
// This is not a m$ath inline$
//
// Should be rendered as:
// <p>This is not a m$ath block$</p>
// <p>This is not a m$ath inline$</p>
TestParser.TestSpec("This is not a m$ath block$", "<p>This is not a m$ath block$</p>", "mathematics|advanced", context: "Example 9\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is not a m$ath inline$", "<p>This is not a m$ath inline$</p>", "mathematics|advanced", context: "Example 9\nSection Extensions / Math Inline\n");
}
// For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word):
@@ -169,12 +169,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is not a $math bloc$k
// This is not a $math inlin$e
//
// Should be rendered as:
// <p>This is not a $math bloc$k</p>
// <p>This is not a $math inlin$e</p>
TestParser.TestSpec("This is not a $math bloc$k", "<p>This is not a $math bloc$k</p>", "mathematics|advanced", context: "Example 10\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is not a $math inlin$e", "<p>This is not a $math inlin$e</p>", "mathematics|advanced", context: "Example 10\nSection Extensions / Math Inline\n");
}
// For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word):
@@ -201,12 +201,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $math \$ block$
// This is a $math \$ inline$
//
// Should be rendered as:
// <p>This is a <span class="math">\(math \$ block\)</span></p>
// <p>This is a <span class="math">\(math \$ inline\)</span></p>
TestParser.TestSpec("This is a $math \\$ block$", "<p>This is a <span class=\"math\">\\(math \\$ block\\)</span></p>", "mathematics|advanced", context: "Example 12\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $math \\$ inline$", "<p>This is a <span class=\"math\">\\(math \\$ inline\\)</span></p>", "mathematics|advanced", context: "Example 12\nSection Extensions / Math Inline\n");
}
// At most, two `$` will be matched for the opening and closing:
@@ -217,12 +217,12 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $$$math block$$$
// This is a $$$math inline$$$
//
// Should be rendered as:
// <p>This is a <span class="math">\($math block$\)</span></p>
// <p>This is a <span class="math">\($math inline$\)</span></p>
TestParser.TestSpec("This is a $$$math block$$$", "<p>This is a <span class=\"math\">\\($math block$\\)</span></p>", "mathematics|advanced", context: "Example 13\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $$$math inline$$$", "<p>This is a <span class=\"math\">\\($math inline$\\)</span></p>", "mathematics|advanced", context: "Example 13\nSection Extensions / Math Inline\n");
}
// Regular text can come both before and after the math inline
@@ -233,15 +233,15 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is a $math block$ with text on both sides.
// This is a $math inline$ with text on both sides.
//
// Should be rendered as:
// <p>This is a <span class="math">\(math block\)</span> with text on both sides.</p>
// <p>This is a <span class="math">\(math inline\)</span> with text on both sides.</p>
TestParser.TestSpec("This is a $math block$ with text on both sides.", "<p>This is a <span class=\"math\">\\(math block\\)</span> with text on both sides.</p>", "mathematics|advanced", context: "Example 14\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is a $math inline$ with text on both sides.", "<p>This is a <span class=\"math\">\\(math inline\\)</span> with text on both sides.</p>", "mathematics|advanced", context: "Example 14\nSection Extensions / Math Inline\n");
}
// A mathematic block takes precedence over standard emphasis `*` `_`:
// A mathematic inline block takes precedence over standard emphasis `*` `_`:
[Test]
public void ExtensionsMathInline_Example015()
{
@@ -249,15 +249,15 @@ namespace Markdig.Tests.Specs.Math
// Section: Extensions / Math Inline
//
// The following Markdown:
// This is *a $math* block$
// This is *a $math* inline$
//
// Should be rendered as:
// <p>This is *a <span class="math">\(math* block\)</span></p>
// <p>This is *a <span class="math">\(math* inline\)</span></p>
TestParser.TestSpec("This is *a $math* block$", "<p>This is *a <span class=\"math\">\\(math* block\\)</span></p>", "mathematics|advanced", context: "Example 15\nSection Extensions / Math Inline\n");
TestParser.TestSpec("This is *a $math* inline$", "<p>This is *a <span class=\"math\">\\(math* inline\\)</span></p>", "mathematics|advanced", context: "Example 15\nSection Extensions / Math Inline\n");
}
// An opening $$ at the beginning of a line should not be interpreted as a Math block:
// An opening $$ at the beginning of a line should not be interpreted as a Math inline:
[Test]
public void ExtensionsMathInline_Example016()
{
@@ -279,7 +279,7 @@ namespace Markdig.Tests.Specs.Math
{
// ## Math Block
//
// The match block can spawn on multiple lines by having a $$ starting on a line.
// The math block can spawn on multiple lines by having a $$ starting on a line.
// It is working as a fenced code block.
[Test]
public void ExtensionsMathBlock_Example017()

View File

@@ -4,79 +4,79 @@ Adds support for mathematics spans:
## Math Inline
Allows to define a mathematic block embraced by `$...$`
Allows to define a mathematic inline block embraced by `$...$`
```````````````````````````````` example
This is a $math block$
This is a $math inline$
.
<p>This is a <span class="math">\(math block\)</span></p>
<p>This is a <span class="math">\(math inline\)</span></p>
````````````````````````````````
Or by `$$...$$` embracing it by:
```````````````````````````````` example
This is a $$math block$$
This is a $$math inline$$
.
<p>This is a <span class="math">\(math block\)</span></p>
<p>This is a <span class="math">\(math inline\)</span></p>
````````````````````````````````
Newlines inside an inline math are not allowed:
```````````````````````````````` example
This is not a $$math
block$$ and? this is a $$math block$$
inline$$ and? this is a $$math inline$$
.
<p>This is not a $$math
block$$ and? this is a <span class="math">\(math block\)</span></p>
inline$$ and? this is a <span class="math">\(math inline\)</span></p>
````````````````````````````````
```````````````````````````````` example
This is not a $math
block$ and? this is a $math block$
inline$ and? this is a $math inline$
.
<p>This is not a $math
block$ and? this is a <span class="math">\(math block\)</span></p>
inline$ and? this is a <span class="math">\(math inline\)</span></p>
````````````````````````````````
An opening `$` can be followed by a space if the closing is also preceded by a space `$`:
```````````````````````````````` example
This is a $ math block $
This is a $ math inline $
.
<p>This is a <span class="math">\(math block\)</span></p>
<p>This is a <span class="math">\(math inline\)</span></p>
````````````````````````````````
```````````````````````````````` example
This is a $ math block $ after
This is a $ math inline $ after
.
<p>This is a <span class="math">\(math block\)</span> after</p>
<p>This is a <span class="math">\(math inline\)</span> after</p>
````````````````````````````````
```````````````````````````````` example
This is a $$ math block $$ after
This is a $$ math inline $$ after
.
<p>This is a <span class="math">\(math block\)</span> after</p>
<p>This is a <span class="math">\(math inline\)</span> after</p>
````````````````````````````````
```````````````````````````````` example
This is a not $ math block$ because there is not a whitespace before the closing
This is a not $ math inline$ because there is not a whitespace before the closing
.
<p>This is a not $ math block$ because there is not a whitespace before the closing</p>
<p>This is a not $ math inline$ because there is not a whitespace before the closing</p>
````````````````````````````````
For the opening `$` it requires a space or a punctuation before (but cannot be used within a word):
```````````````````````````````` example
This is not a m$ath block$
This is not a m$ath inline$
.
<p>This is not a m$ath block$</p>
<p>This is not a m$ath inline$</p>
````````````````````````````````
For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word):
```````````````````````````````` example
This is not a $math bloc$k
This is not a $math inlin$e
.
<p>This is not a $math bloc$k</p>
<p>This is not a $math inlin$e</p>
````````````````````````````````
For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word):
@@ -90,34 +90,34 @@ This is should not match a 16$ or a $15
A `$` can be escaped between a math inline block by using the escape `\\`
```````````````````````````````` example
This is a $math \$ block$
This is a $math \$ inline$
.
<p>This is a <span class="math">\(math \$ block\)</span></p>
<p>This is a <span class="math">\(math \$ inline\)</span></p>
````````````````````````````````
At most, two `$` will be matched for the opening and closing:
```````````````````````````````` example
This is a $$$math block$$$
This is a $$$math inline$$$
.
<p>This is a <span class="math">\($math block$\)</span></p>
<p>This is a <span class="math">\($math inline$\)</span></p>
````````````````````````````````
Regular text can come both before and after the math inline
```````````````````````````````` example
This is a $math block$ with text on both sides.
This is a $math inline$ with text on both sides.
.
<p>This is a <span class="math">\(math block\)</span> with text on both sides.</p>
<p>This is a <span class="math">\(math inline\)</span> with text on both sides.</p>
````````````````````````````````
A mathematic block takes precedence over standard emphasis `*` `_`:
A mathematic inline block takes precedence over standard emphasis `*` `_`:
```````````````````````````````` example
This is *a $math* block$
This is *a $math* inline$
.
<p>This is *a <span class="math">\(math* block\)</span></p>
<p>This is *a <span class="math">\(math* inline\)</span></p>
````````````````````````````````
An opening $$ at the beginning of a line should not be interpreted as a Math block:
An opening $$ at the beginning of a line should not be interpreted as a Math inline:
```````````````````````````````` example
$$ math $$ starting at a line
@@ -127,7 +127,7 @@ $$ math $$ starting at a line
## Math Block
The match block can spawn on multiple lines by having a $$ starting on a line.
The math block can spawn on multiple lines by having a $$ starting on a line.
It is working as a fenced code block.
```````````````````````````````` example

View File

@@ -825,5 +825,190 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("a | b\n-- | - \n0 | 1 | 2", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n<td>2</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced", context: "Example 25\nSection Extensions / Pipe Table\n");
}
// A table may begin right after a paragraph without an empty line in between:
[Test]
public void ExtensionsPipeTable_Example026()
{
// Example 26
// Section: Extensions / Pipe Table
//
// The following Markdown:
// Some
// **text**.
// | A |
// |---|
// | B |
//
// Should be rendered as:
// <p>Some
// <strong>text</strong>.</p>
// <table>
// <thead>
// <tr>
// <th>A</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>B</td>
// </tr>
// </tbody>
// </table>
TestParser.TestSpec("Some\n**text**.\n| A |\n|---|\n| B |", "<p>Some\n<strong>text</strong>.</p>\n<table>\n<thead>\n<tr>\n<th>A</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>B</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced", context: "Example 26\nSection Extensions / Pipe Table\n");
}
// Tables can be nested inside other blocks, like lists:
[Test]
public void ExtensionsPipeTable_Example027()
{
// Example 27
// Section: Extensions / Pipe Table
//
// The following Markdown:
// Bullet list
// * Table 1
//
// | Header 1 | Header 2 |
// |----------------|----------------|
// | Row 1 Column 1 | Row 1 Column 2 |
//
// * Table 2
// | Header 1 | Header 2 |
// |----------------|----------------|
// | Row 1 Column 1 | Row 1 Column 2 |
//
// * Table 3
// Lorem ipsum ...
// Lorem ipsum ...
// | Header 1 | Header 2 |
// |----------------|----------------|
// | Row 1 Column 1 | Row 1 Column 2 |
//
//
// Ordered list
// 1. Table 1
//
// | Header 1 | Header 2 |
// |----------------|----------------|
// | Row 1 Column 1 | Row 1 Column 2 |
//
// 2. Table 2
// | Header 1 | Header 2 |
// |----------------|----------------|
// | Row 1 Column 1 | Row 1 Column 2 |
//
// 3. Table 3
// Lorem ipsum ...
// Lorem ipsum ...
// | Header 1 | Header 2 |
// |----------------|----------------|
// | Row 1 Column 1 | Row 1 Column 2 |
//
// Should be rendered as:
// <p>Bullet list</p>
// <ul>
// <li><p>Table 1</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 2</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 3
// Lorem ipsum ...
// Lorem ipsum ...</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// </ul>
// <p>Ordered list</p>
// <ol>
// <li><p>Table 1</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 2</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// <li><p>Table 3
// Lorem ipsum ...
// Lorem ipsum ...</p>
// <table>
// <thead>
// <tr>
// <th>Header 1</th>
// <th>Header 2</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>Row 1 Column 1</td>
// <td>Row 1 Column 2</td>
// </tr>
// </tbody>
// </table></li>
// </ol>
TestParser.TestSpec("Bullet list\n* Table 1\n\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n* Table 2\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n* Table 3\n Lorem ipsum ...\n Lorem ipsum ...\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n\nOrdered list\n1. Table 1\n\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n2. Table 2\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |\n\n3. Table 3\n Lorem ipsum ...\n Lorem ipsum ...\n | Header 1 | Header 2 |\n |----------------|----------------|\n | Row 1 Column 1 | Row 1 Column 2 |", "<p>Bullet list</p>\n<ul>\n<li><p>Table 1</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 2</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 3\nLorem ipsum ...\nLorem ipsum ...</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n</ul>\n<p>Ordered list</p>\n<ol>\n<li><p>Table 1</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 2</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n<li><p>Table 3\nLorem ipsum ...\nLorem ipsum ...</p>\n<table>\n<thead>\n<tr>\n<th>Header 1</th>\n<th>Header 2</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Row 1 Column 1</td>\n<td>Row 1 Column 2</td>\n</tr>\n</tbody>\n</table></li>\n</ol>", "pipetables|advanced", context: "Example 27\nSection Extensions / Pipe Table\n");
}
}
}

View File

@@ -612,4 +612,173 @@ a | b
</tr>
</tbody>
</table>
````````````````````````````````
A table may begin right after a paragraph without an empty line in between:
```````````````````````````````` example
Some
**text**.
| A |
|---|
| B |
.
<p>Some
<strong>text</strong>.</p>
<table>
<thead>
<tr>
<th>A</th>
</tr>
</thead>
<tbody>
<tr>
<td>B</td>
</tr>
</tbody>
</table>
````````````````````````````````
Tables can be nested inside other blocks, like lists:
```````````````````````````````` example
Bullet list
* Table 1
| Header 1 | Header 2 |
|----------------|----------------|
| Row 1 Column 1 | Row 1 Column 2 |
* Table 2
| Header 1 | Header 2 |
|----------------|----------------|
| Row 1 Column 1 | Row 1 Column 2 |
* Table 3
Lorem ipsum ...
Lorem ipsum ...
| Header 1 | Header 2 |
|----------------|----------------|
| Row 1 Column 1 | Row 1 Column 2 |
Ordered list
1. Table 1
| Header 1 | Header 2 |
|----------------|----------------|
| Row 1 Column 1 | Row 1 Column 2 |
2. Table 2
| Header 1 | Header 2 |
|----------------|----------------|
| Row 1 Column 1 | Row 1 Column 2 |
3. Table 3
Lorem ipsum ...
Lorem ipsum ...
| Header 1 | Header 2 |
|----------------|----------------|
| Row 1 Column 1 | Row 1 Column 2 |
.
<p>Bullet list</p>
<ul>
<li><p>Table 1</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 2</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 3
Lorem ipsum ...
Lorem ipsum ...</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
</ul>
<p>Ordered list</p>
<ol>
<li><p>Table 1</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 2</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
<li><p>Table 3
Lorem ipsum ...
Lorem ipsum ...</p>
<table>
<thead>
<tr>
<th>Header 1</th>
<th>Header 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1 Column 1</td>
<td>Row 1 Column 2</td>
</tr>
</tbody>
</table></li>
</ol>
````````````````````````````````

View File

@@ -0,0 +1,24 @@
using Markdig.Extensions.AutoLinks;
namespace Markdig.Tests;
[TestFixture]
public class TestAutoLinks
{
[Test]
[TestCase("https://localhost", "<p><a href=\"https://localhost\">https://localhost</a></p>")]
[TestCase("http://localhost", "<p><a href=\"http://localhost\">http://localhost</a></p>")]
[TestCase("https://l", "<p><a href=\"https://l\">https://l</a></p>")]
[TestCase("www.l", "<p><a href=\"http://www.l\">www.l</a></p>")]
[TestCase("https://localhost:5000", "<p><a href=\"https://localhost:5000\">https://localhost:5000</a></p>")]
[TestCase("www.l:5000", "<p><a href=\"http://www.l:5000\">www.l:5000</a></p>")]
public void TestLinksWithAllowDomainWithoutPeriod(string markdown, string expected)
{
var pipeline = new MarkdownPipelineBuilder()
.UseAutoLinks(new AutoLinkOptions { AllowDomainWithoutPeriod = true })
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Is.EqualTo(expected).IgnoreWhiteSpace);
}
}

View File

@@ -1,91 +1,261 @@
using System.Collections.Generic;
using System.Globalization;
using Markdig.Helpers;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestCharHelper
{
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()
{
// 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
};
// A Unicode punctuation character is a character in the Unicode P (punctuation) or S (symbol) general categories.
private static readonly HashSet<UnicodeCategory> s_punctuationCategories =
[
UnicodeCategory.ConnectorPunctuation,
UnicodeCategory.DashPunctuation,
UnicodeCategory.OpenPunctuation,
UnicodeCategory.ClosePunctuation,
UnicodeCategory.InitialQuotePunctuation,
UnicodeCategory.FinalQuotePunctuation,
UnicodeCategory.OtherPunctuation,
UnicodeCategory.MathSymbol,
UnicodeCategory.CurrencySymbol,
UnicodeCategory.ModifierSymbol,
UnicodeCategory.OtherSymbol,
];
private static bool ExpectedIsPunctuation(char c)
private static readonly HashSet<UnicodeCategory> s_punctuationWithoutSymbolsCategories =
[
UnicodeCategory.ConnectorPunctuation,
UnicodeCategory.DashPunctuation,
UnicodeCategory.OpenPunctuation,
UnicodeCategory.ClosePunctuation,
UnicodeCategory.InitialQuotePunctuation,
UnicodeCategory.FinalQuotePunctuation,
UnicodeCategory.OtherPunctuation,
];
private static bool ExpectedIsPunctuation(char c)
{
return c <= 127
? s_asciiPunctuation.Contains(c)
: s_punctuationCategories.Contains(CharUnicodeInfo.GetUnicodeCategory(c));
}
private static bool ExpectedIsPunctuationWithoutSymbols(char c)
{
return c <= 127
? s_asciiPunctuation.Contains(c)
: s_punctuationWithoutSymbolsCategories.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 == '\f' || c == '\r' ||
CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator;
}
[Test]
public void IsAcrossTab()
{
Assert.False(CharHelper.IsAcrossTab(0));
Assert.True(CharHelper.IsAcrossTab(1));
Assert.True(CharHelper.IsAcrossTab(2));
Assert.True(CharHelper.IsAcrossTab(3));
Assert.False(CharHelper.IsAcrossTab(4));
}
[Test]
public void AddTab()
{
Assert.AreEqual(4, CharHelper.AddTab(0));
Assert.AreEqual(4, CharHelper.AddTab(1));
Assert.AreEqual(4, CharHelper.AddTab(2));
Assert.AreEqual(4, CharHelper.AddTab(3));
Assert.AreEqual(8, CharHelper.AddTab(4));
Assert.AreEqual(8, CharHelper.AddTab(5));
}
[Test]
public void IsWhitespace()
{
Test(
ExpectedIsWhitespace,
CharHelper.IsWhitespace);
Test(
ExpectedIsWhitespace,
CharHelper.WhitespaceChars.Contains);
}
[Test]
public void IsWhiteSpaceOrZero()
{
Test(
c => ExpectedIsWhitespace(c) || c == 0,
CharHelper.IsWhiteSpaceOrZero);
}
[Test]
public void IsAsciiPunctuation()
{
Test(
c => char.IsAscii(c) && ExpectedIsPunctuation(c),
CharHelper.IsAsciiPunctuation);
}
[Test]
public void IsAsciiPunctuationOrZero()
{
Test(
c => char.IsAscii(c) && (ExpectedIsPunctuation(c) || c == 0),
CharHelper.IsAsciiPunctuationOrZero);
}
[Test]
public void IsSpaceOrPunctuationForGFMAutoLink()
{
Test(
c => c == 0 || ExpectedIsWhitespace(c) || ExpectedIsPunctuationWithoutSymbols(c),
CharHelper.IsSpaceOrPunctuationForGFMAutoLink);
}
[Test]
public void InvalidAutoLinkCharacters()
{
// 6.5 Autolinks - https://spec.commonmark.org/0.31.2/#autolinks
// An absolute URI, for these purposes, consists of a scheme followed by a colon (:) followed by
// zero or more characters other than ASCII control characters, space, <, and >.
//
// 2.1 Characters and lines
// An ASCII control character is a character between U+00001F (both including) or U+007F.
Test(
c => c != 0 && c is < (char)0x20 or ' ' or '<' or '>' or '\u007F',
CharHelper.InvalidAutoLinkCharacters.Contains);
}
[Test]
public void CheckUnicodeCategory()
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
return c <= 127
? s_asciiPunctuation.Contains(c)
: s_punctuationCategories.Contains(CharUnicodeInfo.GetUnicodeCategory(c));
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);
}
}
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 IsControl()
{
Test(
char.IsControl,
CharHelper.IsControl);
}
[Test]
public void IsWhitespace()
[Test]
public void IsAlpha()
{
Test(
c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'),
CharHelper.IsAlpha);
}
[Test]
public void IsAlphaUpper()
{
Test(
c => c >= 'A' && c <= 'Z',
CharHelper.IsAlphaUpper);
}
[Test]
public void IsAlphaNumeric()
{
Test(
c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'),
CharHelper.IsAlphaNumeric);
}
[Test]
public void IsDigit()
{
Test(
c => c >= '0' && c <= '9',
CharHelper.IsDigit);
}
[Test]
public void IsNewLineOrLineFeed()
{
Test(
c => c is '\r' or '\n',
CharHelper.IsNewLineOrLineFeed);
}
[Test]
public void IsSpaceOrTab()
{
Test(
c => c is ' ' or '\t',
CharHelper.IsSpaceOrTab);
}
[Test]
public void IsEscapableSymbol()
{
Test(
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~•".Contains,
CharHelper.IsEscapableSymbol);
}
[Test]
public void IsEmailUsernameSpecialChar()
{
Test(
".!#$%&'*+/=?^_`{|}~-+.~".Contains,
CharHelper.IsEmailUsernameSpecialChar);
}
[Test]
public void IsEmailUsernameSpecialCharOrDigit()
{
Test(
c => CharHelper.IsDigit(c) || ".!#$%&'*+/=?^_`{|}~-+.~".Contains(c),
CharHelper.IsEmailUsernameSpecialCharOrDigit);
}
private static void Test(Func<char, bool> expected, Func<char, bool> actual)
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
{
for (int i = char.MinValue; i <= char.MaxValue; i++)
char c = (char)i;
bool expectedResult = expected(c);
bool actualResult = actual(c);
if (expectedResult != actualResult)
{
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));
Assert.AreEqual(expectedResult, actualResult, $"Char: '{c}' ({i})");
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Markdig.Tests;
public class TestCodeInline
{
[Test]
public void UnpairedCodeInlineWithTrailingChars()
{
TestParser.TestSpec("*`\n\f", "<p>*`</p>");
}
}

View File

@@ -1,42 +1,39 @@
using NUnit.Framework;
namespace Markdig.Tests;
namespace Markdig.Tests
[TestFixture]
public class TestConfigureNewLine
{
[TestFixture]
public class TestConfigureNewLine
[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)
{
[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 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,189 +1,186 @@
using System;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestContainerBlocks
{
public class TestContainerBlocks
private class MockContainerBlock : ContainerBlock
{
private class MockContainerBlock : ContainerBlock
public MockContainerBlock()
: base(null)
{
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,41 +1,38 @@
using System;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestContainerInlines
{
public class TestContainerInlines
private class MockLeafBlock : LeafBlock
{
private class MockLeafBlock : LeafBlock
public MockLeafBlock()
: base(null)
{
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,128 +1,124 @@
using System;
using System.Collections.Generic;
using Markdig.Extensions.Emoji;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestCustomEmojis
{
[TestFixture]
public class TestCustomEmojis
[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)
{
[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>();
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,150 +1,145 @@
using NUnit.Framework;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Linq;
using System.Collections.Generic;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestDescendantsOrder
{
[TestFixture]
public class TestDescendantsOrder
public static void TestSchemas(MarkdownDocument[] specsSyntaxTrees)
{
public static void TestSchemas(MarkdownDocument[] specsSyntaxTrees)
foreach (var syntaxTree in specsSyntaxTrees)
{
foreach (var syntaxTree in 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>())
{
AssertIEnumerablesAreEqual(
Descendants_Legacy(syntaxTree),
syntaxTree.Descendants());
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
syntaxTree.Descendants<ParagraphBlock>());
containerInline.FindDescendants<LiteralInline>(),
(containerInline as MarkdownObject).Descendants<LiteralInline>());
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>())
if (containerInline.FirstChild is null)
{
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<LiteralInline>(), containerInline.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerInline as MarkdownObject).Descendants<LiteralInline>());
}
foreach (ContainerInline containerInline in syntaxTree.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)
{
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());
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 as MarkdownObject).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);
if (containerInline.FirstChild is null)
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))
{
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>());
yield return subInline;
}
}
}
}
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
else
{
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++)
var inline = markdownObject as ContainerInline;
if (inline != null)
{
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)
var child = inline.FirstChild;
while (child != null)
{
yield return subBlock;
var next = child.NextSibling;
yield return child;
foreach (var sub in Descendants_Legacy(subBlock))
foreach (var sub in Descendants_Legacy(child))
{
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))
{
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;
}
child = next;
}
}
}

View File

@@ -1,169 +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
namespace Markdig.Tests;
[TestFixture]
public class TestEmphasisExtended
{
[TestFixture]
public class TestEmphasisExtended
class EmphasisTestExtension : IMarkdownExtension
{
class EmphasisTestExtension : IMarkdownExtension
public void Setup(MarkdownPipelineBuilder pipeline)
{
public void Setup(MarkdownPipelineBuilder pipeline)
var emphasisParser = pipeline.InlineParsers.Find<EmphasisInlineParser>();
Debug.Assert(emphasisParser != null);
foreach (var emphasis in EmphasisTestDescriptors)
{
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.EmphasisDescriptors.Add(
new EmphasisDescriptor(emphasis.Character, emphasis.Minimum, emphasis.Maximum, true));
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
emphasisParser.TryCreateEmphasisInlineList.Add((delimiterChar, delimiterCount) =>
{
renderer.ObjectRenderers.Insert(0, new EmphasisRenderer());
}
class EmphasisRenderer : HtmlObjectRenderer<CustomEmphasisInline>
{
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);
}
}
return delimiterChar is '*' or '_'
? null
: new CustomEmphasisInline() { DelimiterChar = delimiterChar, DelimiterCount = delimiterCount };
});
}
class Tag
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
#pragma warning disable CS0649
public int Level;
#pragma warning restore CS0649
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);
renderer.ObjectRenderers.Insert(0, new EmphasisRenderer());
}
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)
class EmphasisRenderer : HtmlObjectRenderer<CustomEmphasisInline>
{
protected override void Write(HtmlRenderer renderer, CustomEmphasisInline obj)
{
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"),
};
var tag = EmphasisTestDescriptors.First(test => test.Character == obj.DelimiterChar).Tags[obj.DelimiterCount];
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);
renderer.Write(tag.OpeningTag);
renderer.WriteChildren(obj);
renderer.Write(tag.ClosingTag);
}
}
}
class Tag
{
#pragma warning disable CS0649
public int Level;
#pragma warning restore CS0649
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)
{
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"),
};
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("2223222", "2<two-only>32</two-only>")]
[TestCase("22223222", "22<two-only>32</two-only>")]
[TestCase("22223223222", "22223<two-only>3</two-only>2")]
[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,45 +1,43 @@
using NUnit.Framework;
using Markdig.Extensions.EmphasisExtras;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestEmphasisExtraOptions
{
[TestFixture]
public class TestEmphasisExtraOptions
[Test]
public void OnlyStrikethrough_Single()
{
[Test]
public void OnlyStrikethrough_Single()
{
TestParser.TestSpec("~foo~", "<p>~foo~</p>", new MarkdownPipelineBuilder().UseEmphasisExtras(EmphasisExtraOptions.Strikethrough).Build());
}
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,23 +1,49 @@
// 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;
namespace Markdig.Tests
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Tests;
[TestFixture]
public partial class TestEmphasisPlus
{
[TestFixture]
public partial class TestEmphasisPlus
[Test]
public void StrongNormal()
{
[Test]
public void StrongNormal()
{
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
}
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 NormalStrongNormal()
{
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
}
[Test]
public void SupplementaryPunctuation()
{
TestParser.TestSpec("a*a∇*a\n\na*∇a*a\n\na*a𝜵*a\n\na*𝜵a*a\n\na*𐬼a*a\n\na*a𐬼*a", "<p>a*a∇*a</p>\n<p>a*∇a*a</p>\n<p>a*a𝜵*a</p>\n<p>a*𝜵a*a</p>\n<p>a*𐬼a*a</p>\n<p>a*a𐬼*a</p>", "");
}
[Test]
public void RecognizeSupplementaryChars()
{
TestParser.TestSpec("🌶️**𰻞**🍜**𰻞**🌶️**麺**🍜", "<p>🌶️<strong>𰻞</strong>🍜<strong>𰻞</strong>🌶️<strong>麺</strong>🍜</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);
}
}

View File

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

View File

@@ -1,189 +1,186 @@
using Markdig.Helpers;
using NUnit.Framework;
using System;
using System.Text;
using System.Threading.Tasks;
namespace Markdig.Tests
using Markdig.Helpers;
namespace Markdig.Tests;
[TestFixture]
public class TestFastStringWriter
{
[TestFixture]
public class TestFastStringWriter
private const string NewLineReplacement = "~~NEW_LINE~~";
private FastStringWriter _writer = new();
[SetUp]
public void Setup()
{
private const string NewLineReplacement = "~~NEW_LINE~~";
private FastStringWriter _writer = new();
[SetUp]
public void Setup()
_writer = new FastStringWriter
{
_writer = new FastStringWriter
{
NewLine = NewLineReplacement
};
}
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()
[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++)
{
_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++)
{
_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));
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));
}
}

View File

@@ -1,46 +1,43 @@
using System.Linq;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestFencedCodeBlocks
{
public class TestFencedCodeBlocks
[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)
{
[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)
Test('`');
Test('~');
void Test(char fencedChar)
{
Test('`');
Test('~');
const string Contents = "Foo\nBar\n";
void Test(char fencedChar)
{
const string Contents = "Foo\nBar\n";
var fence = new string(fencedChar, 3);
string markdownText = $"{fence}{infoString}\n{Contents}\n{fence}\n";
string fence = new string(fencedChar, 3);
string markdownText = $"{fence}{infoString}\n{Contents}\n{fence}\n";
MarkdownDocument document = Markdown.Parse(markdownText);
MarkdownDocument document = Markdown.Parse(markdownText);
FencedCodeBlock codeBlock = document.Descendants<FencedCodeBlock>().Single();
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());
}
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,94 +3,91 @@
// 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
namespace Markdig.Tests;
[TestFixture()]
public class TestHtmlAttributes
{
[TestFixture()]
public class TestHtmlAttributes
[Test]
public void TestAddClass()
{
[Test]
public void TestAddClass()
{
var attributes = new HtmlAttributes();
attributes.AddClass("test");
Assert.NotNull(attributes.Classes);
Assert.AreEqual(new List<string>() { "test" }, attributes.Classes);
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

@@ -0,0 +1,35 @@
using Markdig.Syntax;
namespace Markdig.Tests;
public class TestHtmlCodeBlocks
{
// Start condition: line begins with the string < or </ followed by one of the strings (case-insensitive)
// {list of all tags}, followed by a space, a tab, the end of the line, the string >, or the string />.
public static string[] KnownSimpleHtmlTags =>
[
"address", "article", "aside", "base", "basefont", "blockquote", "body", "caption", "center", "col", "colgroup", "dd", "details",
"dialog", "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "frame", "frameset",
"h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "iframe", "legend", "li", "link",
"main", "menu", "menuitem", "nav", "noframes", "ol", "optgroup", "option", "p", "param",
"search", "section", "summary", "table", "tbody", "td", "tfoot", "th", "thead", "title", "tr", "track", "ul",
];
[Theory]
[TestCaseSource(nameof(KnownSimpleHtmlTags))]
public void TestKnownTags(string tag)
{
MarkdownDocument document = Markdown.Parse(
$"""
Hello
<{tag} />
World
""".ReplaceLineEndings("\n"));
HtmlBlock[] htmlBlocks = document.Descendants<HtmlBlock>().ToArray();
Assert.AreEqual(1, htmlBlocks.Length);
Assert.AreEqual(7, htmlBlocks[0].Span.Start);
Assert.AreEqual(10 + tag.Length, htmlBlocks[0].Span.Length);
}
}

View File

@@ -1,30 +1,28 @@
// 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
{
[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);
}
namespace Markdig.Tests;
[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);
}
[TestFixture]
public class TestHtmlHelper
{
[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);
}
}

View File

@@ -1,25 +1,23 @@
using NUnit.Framework;
using System.Text.RegularExpressions;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestImageAltText
{
[TestFixture]
public class TestImageAltText
[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)
{
[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);
}
string html = Markdown.ToHtml(markdown);
string actualAltText = Regex.Match(html, "alt=\"(.*?)\"").Groups[1].Value;
Assert.AreEqual(expectedAltText, actualAltText);
}
}

View File

@@ -1,67 +1,65 @@
using Markdig.Helpers;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestLazySubstring
{
public class TestLazySubstring
[Theory]
[TestCase("")]
[TestCase("a")]
[TestCase("foo")]
public void LazySubstring_ReturnsCorrectSubstring(string text)
{
[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);
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,126 +2,124 @@
// 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
namespace Markdig.Tests;
/// <summary>
/// Test for <see cref="LineReader"/>.
/// </summary>
[TestFixture]
public class TestLineReader
{
/// <summary>
/// Test for <see cref="LineReader"/>.
/// </summary>
[TestFixture]
public class TestLineReader
[Test]
public void TestEmpty()
{
[Test]
public void TestEmpty()
{
var lineReader = new LineReader("");
Assert.Null(lineReader.ReadLine().Text);
}
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,374 +2,464 @@
// 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
namespace Markdig.Tests;
[TestFixture]
public class TestLinkHelper
{
[TestFixture]
public class TestLinkHelper
[Test]
public void TestUrlSimple()
{
[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]
[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 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 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 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 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 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 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 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 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 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 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")]
[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("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("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("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));
}
[Test]
public void TestUnicodeInDomainNameOfLinkReferenceDefinition()
{
TestParser.TestSpec("[Foo]\n\n[Foo]: http://ünicode.com", "<p><a href=\"http://xn--nicode-2ya.com\">Foo</a></p>");
}
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]
[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 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 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 TestTitleMultiline()
{
var text = new StringSlice("'this\ris\r\na\ntitle'");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual("this\ris\r\na\ntitle", title);
}
[Test]
public void TestTitleMultilineWithSpaceAndBackslash()
{
var text = new StringSlice("'a\n\\ \\\ntitle'");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual("a\n\\ \\\ntitle", title);
}
[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 TestUrlEmptyAndTitleNull()
{
// 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(null, title);
Assert.AreEqual(new SourceSpan(1, 2), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlEmptyAndTitleNull2()
{
// 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(null, 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 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(null, 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 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 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 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 TestlLinkReferenceDefinitionInvalid()
{
var text = new StringSlice("[foo]: /url (title) x\n");
Assert.False(LinkHelper.TryParseLinkReferenceDefinition(ref text, out _, out _, out _, out _, out _, out _));
}
[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 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")]
[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("bær", "baer")]
[TestCase("bør", "boer")]
[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));
}
// Tests for NormalizeScandinavianOrGermanChar method mappings
// These special characters are always normalized (both allowOnlyAscii=true and false)
//
// Note: When allowOnlyAscii=true, NFD (Canonical Decomposition) is applied first:
// - German umlauts ä,ö,ü decompose to base letter + combining mark (ü -> u + ¨)
// The combining mark is then stripped, leaving just the base letter (ü -> u)
// - å decomposes similarly (å -> a + ˚ -> a)
// - But ø, æ, ß, þ, ð do NOT decompose, so they use NormalizeScandinavianOrGermanChar
//
// When allowOnlyAscii=false, NormalizeScandinavianOrGermanChar is used for ALL special chars
// German ß (Eszett/sharp s) - does NOT decompose with NFD
[TestCase("Straße", "strasse")] // ß -> ss (both allowOnlyAscii=true and false)
// Scandinavian æ, ø - do NOT decompose with NFD
[TestCase("æble", "aeble")] // æ -> ae (both modes)
[TestCase("Ærø", "aeroe")] // Æ -> Ae, ø -> oe (both modes, then lowercase)
[TestCase("København", "koebenhavn")] // ø -> oe (both modes)
[TestCase("Øresund", "oeresund")] // Ø -> Oe (both modes, then lowercase)
// Icelandic þ, ð - do NOT decompose with NFD
[TestCase("þing", "thing")] // þ (thorn) -> th (both modes)
[TestCase("bað", "bad")] // ð (eth) -> d (both modes)
// Mixed special characters (only chars that behave same in both modes)
[TestCase("øst-æble", "oest-aeble")] // ø->oe, æ->ae (both modes)
public void TestUrilizeScandinavianGermanChars(string input, string expectedResult)
{
// These transformations apply regardless of allowOnlyAscii flag
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
// Tests specific to allowOnlyAscii=true behavior
// German umlauts (ä, ö, ü) and å decompose with NFD, so they become base letter only
[TestCase("schön", "schon")] // ö decomposes to o (NFD strips combining mark)
[TestCase("Mädchen", "madchen")] // ä decomposes to a
[TestCase("Übung", "ubung")] // Ü decomposes to U (then lowercase to u)
[TestCase("Düsseldorf", "dusseldorf")] // ü decomposes to u
[TestCase("Käse", "kase")] // ä decomposes to a
[TestCase("gå", "ga")] // å decomposes to a
[TestCase("Ålesund", "alesund")] // Å decomposes to A (then lowercase)
[TestCase("grüßen", "grussen")] // ü decomposes to u, ß -> ss
[TestCase("Þór", "thor")] // Þ -> Th, ó decomposes to o (then lowercase)
[TestCase("Íslandsbanki", "islandsbanki")] // Í decomposes to I (then lowercase)
public void TestUrilizeOnlyAscii_GermanUmlautsDecompose(string input, string expectedResult)
{
// With allowOnlyAscii=true, these characters decompose via NFD and lose their diacritics
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
// Tests specific to allowOnlyAscii=false behavior
// All special chars use NormalizeScandinavianOrGermanChar (including ä, ö, ü, å)
[TestCase("schön", "schoen")] // ö -> oe (NormalizeScandinavianOrGermanChar)
[TestCase("Mädchen", "maedchen")] // ä -> ae
[TestCase("Übung", "uebung")] // Ü -> Ue (then lowercase)
[TestCase("Düsseldorf", "duesseldorf")] // ü -> ue
[TestCase("Käse", "kaese")] // ä -> ae
[TestCase("gå", "gaa")] // å -> aa
[TestCase("Ålesund", "aalesund")] // Å -> Aa (then lowercase)
[TestCase("grüßen", "gruessen")] // ü -> ue, ß -> ss
[TestCase("Þór", "thór")] // Þ -> Th (then lowercase 'th'), ó is kept as-is
[TestCase("Íslandsbanki", "íslandsbanki")] // í is kept as-is when allowOnlyAscii=false
public void TestUrilizeNonAscii_GermanUmlautsExpanded(string input, string expectedResult)
{
// With allowOnlyAscii=false, these characters use NormalizeScandinavianOrGermanChar
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[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("bær", "baer")]
[TestCase("æ5el", "ae5el")]
[TestCase("-æ5el", "ae5el")]
[TestCase("-frø-", "froe")]
[TestCase("-fr-ø", "fr-oe")]
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));
}
[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,44 +1,40 @@
using System;
using System.IO;
using Markdig.Parsers;
using Markdig.Renderers;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestLinkRewriter
{
public class TestLinkRewriter
[Test]
public void ReplacesRelativeLinks()
{
[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");
}
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,217 +1,214 @@
using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestMarkdigCoreApi
{
public class TestMarkdigCoreApi
[Test]
public void TestToHtml()
{
[Test]
public void TestToHtml()
for (int i = 0; i < 5; i++)
{
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);
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);
}
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);
}
[Test]
public void TestToHtmlWithPipeline()
pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < 5; i++)
{
var pipeline = new MarkdownPipelineBuilder()
.Build();
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);
}
}
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);
[Test]
public void TestToHtmlWithWriter()
{
var writer = new StringWriter();
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);
}
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 TestToHtmlWithWriter()
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++)
{
var writer = new StringWriter();
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;
}
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;
}
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestDocumentToHtmlWithWriter()
[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();
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);
}
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
string plainText = writer.ToString();
Assert.AreEqual("Hello, world!\n", plainText);
}
}
}

View File

@@ -1,104 +1,113 @@
using Markdig.Extensions.MediaLinks;
using NUnit.Framework;
using System;
using System.Text.RegularExpressions;
namespace Markdig.Tests
using Markdig.Extensions.MediaLinks;
namespace Markdig.Tests;
[TestFixture]
public class TestMediaLinks
{
[TestFixture]
public class TestMediaLinks
private MarkdownPipeline GetPipeline(MediaOptions options = null)
{
private MarkdownPipeline GetPipeline(MediaOptions options = null)
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 short](https://www.youtube.com/shorts/6BUptHVuvyI?feature=share)", "<p><iframe src=\"https://www.youtube.com/embed/6BUptHVuvyI\" class=\"youtubeshort\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></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(expected, html);
}
[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(expected, html);
}
private class TestHostProvider : IHostProvider
{
public string Class { get; } = "regex";
public bool AllowFullScreen { get; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
{
return new MarkdownPipelineBuilder()
.UseMediaLinks(options)
.Build();
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;
}
private MarkdownPipeline GetPipelineWithBootstrap(MediaOptions options = null)
private Regex matcher;
private string replacement;
public TestHostProvider(string provider, string replace)
{
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);
}
private class TestHostProvider : IHostProvider
{
public string Class { get; } = "regex";
public bool AllowFullScreen { get; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
{
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;
}
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)
{
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 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);
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)
{
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 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,31 +1,28 @@
using System.Linq;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestNewLine
{
[TestFixture]
public class TestNewLine
[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)
{
[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")));
}
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,180 +2,187 @@
// 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 NUnit.Framework;
using Markdig.Helpers;
using Markdig.Renderers.Normalize;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.IO;
using Markdig.Renderers.Normalize;
using Markdig.Helpers;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestNormalize
{
[TestFixture]
public class TestNormalize
[Test]
public void SyntaxCodeBlock()
{
[Test]
public void SyntaxCodeBlock()
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
{
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
FencedChar = '`',
OpeningFencedCharCount = 4,
ClosingFencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
{
FencedChar = '`',
OpeningFencedCharCount = 4,
ClosingFencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
AssertSyntax(" public void HelloWorld()\n {\n }", new CodeBlock(null)
AssertSyntax(" public void HelloWorld()\n {\n }", new CodeBlock(null)
{
Lines = new StringLineGroup(4)
{
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
}
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
}
[Test]
public void SyntaxHeadline()
[Test]
public void SyntaxHeadline()
{
AssertSyntax("## Headline", new HeadingBlock(null)
{
AssertSyntax("## Headline", new HeadingBlock(null)
{
HeaderChar = '#',
Level = 2,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
HeaderChar = '#',
Level = 2,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
[Test]
public void SyntaxParagraph()
[Test]
public void SyntaxHeadlineLevel7()
{
AssertSyntax("####### Headline", new HeadingBlock(null) {
HeaderChar = '#',
Level = 7,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
[Test]
public void SyntaxParagraph()
{
AssertSyntax("This is a normal paragraph", new ParagraphBlock()
{
AssertSyntax("This is a normal paragraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a normal paragraph")),
});
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a normal paragraph")),
});
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()
AssertSyntax("This is a\nnormal\nparagraph", new ParagraphBlock()
{
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````");
}
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 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 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````");
}
AssertNormalizeNoTrim("Heading\n=======\n\ntext after two newlines", "# Heading\n\ntext after two newlines");
}
[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 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");
}
AssertNormalizeNoTrim("Heading\n=======\n\ntext after two newlines", "# Heading\n\ntext after two newlines");
}
[Test]
public void HtmlBlock()
{
/*AssertNormalizeNoTrim(@"<div id=""foo"" class=""bar
baz"">
[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"">
</ 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
@@ -185,22 +192,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
@@ -218,169 +225,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
@@ -400,100 +407,99 @@ This is a code block
line 2 of indented
This is a last line";
AssertNormalizeNoTrim(input);
}
AssertNormalizeNoTrim(input);
}
[Test]
public void TaskLists()
[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)
{
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");
document.Add(syntax as Block);
}
[Test]
public void JiraLinks()
else
{
AssertNormalizeNoTrim("FOO-1234");
AssertNormalizeNoTrim("AB-1");
AssertNormalizeNoTrim("**Hello World AB-1**");
throw new InvalidOperationException();
}
normalizer.Render(document);
[Test]
public void AutoLinks()
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))
{
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');
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');
}
}

View File

@@ -1,43 +1,41 @@
// 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
namespace Markdig.Tests;
[TestFixture]
public class TestOrderedList
{
[TestFixture]
public class TestOrderedList
[Test]
public void TestReplace()
{
[Test]
public void TestReplace()
var list = new OrderedList<ITest>
{
var list = new OrderedList<ITest>
{
new A(),
new B(),
new C(),
};
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);
}
#region Test fixtures
private interface ITest { }
private class A : ITest { }
private class B : ITest { }
private class C : ITest { }
private class D : ITest { }
#endregion
// 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
}

View File

@@ -1,201 +1,196 @@
// 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
namespace Markdig.Tests;
public class TestParser
{
public class TestParser
[Test]
public void EnsureSpecsAreUpToDate()
{
[Test]
public void EnsureSpecsAreUpToDate()
// 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++)
{
// 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++)
{
string markdown = specsMarkdown[i] = File.ReadAllText(specsFilePaths[i]);
specsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
foreach (var specFilePath in specsFilePaths)
{
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.");
}
TestDescendantsOrder.TestSchemas(specsSyntaxTrees);
string markdown = specsMarkdown[i] = File.ReadAllText(specsFilePaths[i]);
specsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
[Test]
public void ParseEmptyDocumentWithTrackTriviaEnabled()
foreach (var specFilePath in specsFilePaths)
{
var document = Markdown.Parse("", trackTrivia: true);
using var sw = new StringWriter();
new RoundtripRenderer(sw).Render(document);
Assert.AreEqual("", sw.ToString());
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.");
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null, bool plainText = false, string context = null)
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 ??= 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)
{
context += "\n";
Console.WriteLine(context);
}
foreach (var pipeline in GetPipeline(extensions))
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")
{
TestSpec(inputText, expectedOutputText, pipeline.Value, plainText, context: context + $"Pipeline configured with extensions: {pipeline.Key}");
builder.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"));
}
}
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)
else
{
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);
builder = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
}
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, builder.Build());
}
}
public static IEnumerable<KeyValuePair<string, MarkdownPipeline>> GetPipeline(string extensionsGroupText)
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)
{
// 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";
}
TestsDirectory = TestsDirectory.Substring(0, index) + "\\src\\Markdig.Tests";
}
}
}

View File

@@ -1,24 +1,206 @@
using Markdig;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using NUnit.Framework;
using System.Linq;
using Markdig.Syntax.Inlines;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public sealed class TestPipeTable
{
[TestFixture]
public sealed class TestPipeTable
[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)]
[TestCase("a\r| S | T |\r|---|---|")]
[TestCase("a\n| S | T |\r|---|---|")]
[TestCase("a\r\n| S | T |\r|---|---|")]
public void TestTableBug(string markdown, int tableCount = 1)
{
[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)
MarkdownDocument document =
Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
Table[] tables = document.Descendants().OfType<Table>().ToArray();
Assert.AreEqual(tableCount, tables.Length);
}
[TestCase("A | B\r\n---|---", new[] {50.0f, 50.0f})]
[TestCase("A | B\r\n-|---", new[] {25.0f, 75.0f})]
[TestCase("A | B\r\n-|---\r\nA | B\r\n---|---", new[] {25.0f, 75.0f})]
[TestCase("A | B\r\n---|---|---", new[] {33.33f, 33.33f, 33.33f})]
[TestCase("A | B\r\n---|---|---|", new[] {33.33f, 33.33f, 33.33f})]
public void TestColumnWidthByHeaderLines(string markdown, float[] expectedWidth)
{
var pipeline = new MarkdownPipelineBuilder()
.UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = true})
.Build();
var document = Markdown.Parse(markdown, pipeline);
var table = document.Descendants().OfType<Table>().FirstOrDefault();
Assert.IsNotNull(table);
var actualWidths = table.ColumnDefinitions.Select(x => x.Width).ToList();
Assert.AreEqual(actualWidths.Count, expectedWidth.Length);
for (int i = 0; i < expectedWidth.Length; i++)
{
MarkdownDocument document = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
Table[] tables = document.Descendants().OfType<Table>().ToArray();
Assert.AreEqual(tableCount, tables.Length);
Assert.AreEqual(actualWidths[i], expectedWidth[i], 0.01);
}
}
[Test]
public void TestColumnWidthIsNotSetWithoutConfigurationFlag()
{
var pipeline = new MarkdownPipelineBuilder()
.UsePipeTables(new PipeTableOptions() {InferColumnWidthsFromSeparator = false})
.Build();
var document = Markdown.Parse("| A | B | C |\r\n|---|---|---|", pipeline);
var table = document.Descendants().OfType<Table>().FirstOrDefault();
Assert.IsNotNull(table);
foreach (var column in table.ColumnDefinitions)
{
Assert.AreEqual(0, column.Width);
}
}
[Test]
public void TableWithUnbalancedCodeSpanParsesWithoutDepthLimitError()
{
const string markdown = """
| Count | A | B | C | D | E |
|-------|---|---|---|---|---|
| 0 | B | C | D | E | F |
| 1 | B | `C | D | E | F |
| 2 | B | `C | D | E | F |
| 3 | B | C | D | E | F |
| 4 | B | C | D | E | F |
| 5 | B | C | D | E | F |
| 6 | B | C | D | E | F |
| 7 | B | C | D | E | F |
| 8 | B | C | D | E | F |
| 9 | B | C | D | E | F |
| 10 | B | C | D | E | F |
| 11 | B | C | D | E | F |
| 12 | B | C | D | E | F |
| 13 | B | C | D | E | F |
| 14 | B | C | D | E | F |
| 15 | B | C | D | E | F |
| 16 | B | C | D | E | F |
| 17 | B | C | D | E | F |
| 18 | B | C | D | E | F |
| 19 | B | C | D | E | F |
""";
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
MarkdownDocument document = null!;
Assert.DoesNotThrow(() => document = Markdown.Parse(markdown, pipeline));
var tables = document.Descendants().OfType<Table>().ToArray();
Assert.That(tables, Has.Length.EqualTo(1));
string html = string.Empty;
Assert.DoesNotThrow(() => html = Markdown.ToHtml(markdown, pipeline));
Assert.That(html, Does.Contain("<table"));
Assert.That(html, Does.Contain("<td>`C</td>"));
}
[Test]
public void CodeInlineWithPipeDelimitersRemainsCodeInline()
{
const string markdown = "`|| hidden text ||`";
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var document = Markdown.Parse(markdown, pipeline);
var codeInline = document.Descendants().OfType<CodeInline>().SingleOrDefault();
Assert.IsNotNull(codeInline);
Assert.That(codeInline!.Content, Is.EqualTo("|| hidden text ||"));
Assert.That(document.ToHtml(), Is.EqualTo("<p><code>|| hidden text ||</code></p>\n"));
}
[Test]
public void MultiLineCodeInlineWithPipeDelimitersRendersAsCode()
{
string markdown =
"""
`
|| hidden text ||
`
""".ReplaceLineEndings("\n");
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Is.EqualTo("<p><code>|| hidden text ||</code></p>\n"));
}
[Test]
public void TableCellWithCodeInlineRendersCorrectly()
{
const string markdown =
"""
| Count | A | B | C | D | E |
|-------|---|---|---|---|---|
| 0 | B | C | D | E | F |
| 1 | B | `Code block` | D | E | F |
| 2 | B | C | D | E | F |
""";
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Does.Contain("<td><code>Code block</code></td>"));
}
[Test]
public void CodeInlineWithIndentedContentPreservesWhitespace()
{
const string markdown = "`\n foo\n`";
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var document = Markdown.Parse(markdown, pipeline);
var codeInline = document.Descendants().OfType<CodeInline>().Single();
Assert.That(codeInline.Content, Is.EqualTo("foo"));
Assert.That(Markdown.ToHtml(markdown, pipeline), Is.EqualTo("<p><code>foo</code></p>\n"));
}
[Test]
public void TableWithIndentedPipeAfterCodeInlineParsesCorrectly()
{
var markdown =
"""
`
|| hidden text ||
`
| Count | Value |
|-------|-------|
| 0 | B |
""".ReplaceLineEndings("\n");
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, Does.Contain("<p><code>|| hidden text ||</code></p>"));
Assert.That(html, Does.Contain("<table"));
Assert.That(html, Does.Contain("<td>B</td>"));
}
}

View File

@@ -1,45 +1,50 @@
using NUnit.Framework;
namespace Markdig.Tests;
namespace Markdig.Tests
[TestFixture]
public class TestPlainText
{
[TestFixture]
public class TestPlainText
[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)
{
[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);
}
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: */ "```\nConsole.WriteLine(\"Hello, World!\");\n```", /* expected: */ "Console.WriteLine(\"Hello, World!\");\n")]
public void TestPlainCodeBlock(string markdownText, string expected)
{
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
public static void TestSpec(string markdownText, string expected, string extensions, string context = null)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true, context: context);
}
[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);
}
}

View File

@@ -1,97 +1,109 @@
// 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
namespace Markdig.Tests;
[TestFixture]
public class TestPlayParser
{
[TestFixture]
public class TestPlayParser
[Test]
public void TestInvalidSetext()
{
[Test]
public void TestBugWithEmphasisAndTable()
{
TestParser.TestSpec("**basics | 8:00**", "<p><strong>basics | 8:00</strong></p>", "advanced");
}
TestParser.TestSpec("test\n===n", "<p>test\n===n</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 TestBugWithEmphasisAndTable()
{
TestParser.TestSpec("**basics | 8:00**", "<p><strong>basics | 8:00</strong></p>", "advanced");
}
[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 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 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 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 TestListBug2()
{
TestParser.TestSpec("10.\t*test* test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
[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 TestLinkWithMultipleBackslashesInTitle()
{
var doc = Markdown.Parse(@"[link](/uri '\\\\127.0.0.1')");
var link = doc.Descendants<LinkInline>().FirstOrDefault();
Assert.AreEqual(@"\\127.0.0.1", link?.Title);
}
[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>
@@ -107,17 +119,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
@@ -130,13 +142,13 @@ a | b
</ul></li>
</ul>
");
}
}
[Test]
public void TestHtmlBug()
{
TestParser.TestSpec(@" # header1
[Test]
public void TestHtmlBug()
{
TestParser.TestSpec(@" # header1
<pre class='copy'>
blabla
@@ -148,43 +160,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>
@@ -201,12 +213,12 @@ Paragraph
</tr>
</tbody>
</table>", "advanced");
}
}
[Test]
public void TestGridTableWithCustomAttributes() {
[Test]
public void TestGridTableWithCustomAttributes() {
var input = @"
var input = @"
{.table}
+---+---+
| a | b |
@@ -215,7 +227,7 @@ Paragraph
+---+---+
";
var expected = @"<table class=""table"">
var expected = @"<table class=""table"">
<col style=""width:50%"" />
<col style=""width:50%"" />
<thead>
@@ -232,76 +244,75 @@ Paragraph
</tbody>
</table>
";
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
//";
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
//";
}

View File

@@ -1,19 +1,18 @@
// 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
namespace Markdig.Tests;
[TestFixture]
public class TestPragmaLines
{
[TestFixture]
public class TestPragmaLines
[Test]
public void TestFindClosest()
{
[Test]
public void TestFindClosest()
{
var doc = Markdown.Parse(
var doc = Markdown.Parse(
"test1\n" + // 0
"\n" + // 1
"test2\n" + // 2
@@ -37,27 +36,27 @@ namespace Markdig.Tests
"- 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));
}
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));
foreach (var exact in new int[] {0, 2, 4, 6, 8, 10, 18, 19, 20, 22})
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
}
[Test]
public void TestFindClosest1()
{
var text =
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 =
"- item1\n" + // 0
" - item11\n" + // 1
" - item12\n" + // 2
@@ -66,15 +65,14 @@ namespace Markdig.Tests
" - 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));
}
Assert.AreEqual(6, doc.FindClosestLine(50));
for (int exact = 0; exact < 7; exact++)
{
Assert.AreEqual(exact, doc.FindClosestLine(exact));
}
Assert.AreEqual(6, doc.FindClosestLine(50));
}
}

View File

@@ -1,57 +1,54 @@
using NUnit.Framework;
namespace Markdig.Tests;
namespace Markdig.Tests
[TestFixture]
public class TestReferralLinks
{
[TestFixture]
public class TestReferralLinks
[Test]
public void TestLinksWithNoFollowRel()
{
[Test]
public void TestLinksWithNoFollowRel()
{
var markdown = "[world](http://example.com)";
var expected = "nofollow";
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,48 +1,44 @@
using System;
using System.IO;
using Markdig.Parsers;
using Markdig.Renderers;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestRelativeUrlReplacement
{
public class TestRelativeUrlReplacement
[Test]
public void ReplacesRelativeLinks()
{
[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");
}
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,32 +1,29 @@
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests
namespace Markdig.Tests;
internal static class TestRoundtrip
{
internal static class TestRoundtrip
internal static void TestSpec(string markdownText, string expected, string extensions, string context = null)
{
internal static void TestSpec(string markdownText, string expected, string extensions, string context = null)
{
RoundTrip(markdownText, context);
}
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,36 +1,44 @@
using Markdig.Extensions.SmartyPants;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestSmartyPants
{
public class TestSmartyPants
[Test]
public void MappingCanBeReconfigured()
{
[Test]
public void MappingCanBeReconfigured()
{
SmartyPantOptions options = new SmartyPantOptions();
options.Mapping[SmartyPantType.LeftAngleQuote] = "foo";
options.Mapping[SmartyPantType.RightAngleQuote] = "bar";
var 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()
{
SmartyPantOptions options = new SmartyPantOptions();
options.Mapping.Remove(SmartyPantType.LeftAngleQuote);
options.Mapping.Remove(SmartyPantType.RightAngleQuote);
[Test]
public void MappingCanBeReconfigured_HandlesRemovedMappings()
{
var 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);
}
[Test]
public void RecognizesSupplementaryCharacters()
{
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants()
.Build();
TestParser.TestSpec("\"𝜵\"𠮷\"𝜵\"𩸽\"", "<p>&quot;𝜵&ldquo;𠮷&rdquo;𝜵&ldquo;𩸽&rdquo;</p>", pipeline);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,165 @@
// 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;
namespace Markdig.Tests;
[TestFixture]
public class TestStringSlice
{
#if NET
[Test]
public void TestRuneBmp()
{
var slice = new StringSlice("01234");
Assert.AreEqual('0', slice.CurrentRune.Value);
Assert.AreEqual(0, slice.Start);
Assert.AreEqual('1', slice.NextRune().Value);
Assert.AreEqual(1, slice.Start);
Assert.AreEqual('2', slice.NextRune().Value);
Assert.AreEqual(2, slice.Start);
Assert.AreEqual('2', slice.CurrentRune.Value);
Assert.AreEqual("234", slice.ToString());
Assert.AreEqual('3', slice.PeekRuneExtra(1).Value);
Assert.AreEqual('4', slice.PeekRuneExtra(2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(3).Value);
Assert.AreEqual('1', slice.PeekRuneExtra(-1).Value);
Assert.AreEqual('0', slice.PeekRuneExtra(-2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-3).Value);
Assert.AreEqual('0', slice.RuneAt(0).Value);
Assert.AreEqual('1', slice.RuneAt(1).Value);
Assert.AreEqual('2', slice.RuneAt(2).Value);
Assert.AreEqual('3', slice.RuneAt(3).Value);
Assert.AreEqual('4', slice.RuneAt(4).Value);
Assert.AreEqual(2, slice.Start);
}
[Test]
public void TestRuneSupplementaryOnly()
{
var slice = new StringSlice("𝟎𝟏𝟐𝟑𝟒");
Assert.AreEqual(10, slice.Length);
// 𝟎 = U+1D7CE, 𝟐 = U+1D7D0
Assert.AreEqual(0x1D7CE, slice.CurrentRune.Value); // 𝟎
Assert.AreEqual(0, slice.Start);
Assert.AreEqual(0x1D7CF, slice.NextRune().Value); // 𝟏
Assert.AreEqual(2, slice.Start);
Assert.AreEqual(0x1D7D0, slice.NextRune().Value); // 𝟐
Assert.AreEqual(4, slice.Start);
Assert.AreEqual(0x1D7D0, slice.CurrentRune.Value); // 𝟐
Assert.AreEqual("𝟐𝟑𝟒", slice.ToString());
// CurrentRune occupies 2 `char`s, so next Rune starts at index 2
Assert.AreEqual(0x1D7D1, slice.PeekRuneExtra(2).Value); // 𝟑
Assert.AreEqual(0x1D7D2, slice.PeekRuneExtra(4).Value); // 𝟒
Assert.AreEqual(0, slice.PeekRuneExtra(6).Value);
Assert.AreEqual(0x1D7CF, slice.PeekRuneExtra(-1).Value); // 𝟏
Assert.AreEqual(0x1D7CE, slice.PeekRuneExtra(-3).Value); // 𝟎
Assert.AreEqual(0, slice.PeekRuneExtra(-5).Value);
Assert.AreEqual(0x1D7CE, slice.RuneAt(0).Value); // 𝟎
Assert.AreEqual(0x1D7CF, slice.RuneAt(2).Value); // 𝟏
Assert.AreEqual(0x1D7D0, slice.RuneAt(4).Value); // 𝟐
Assert.AreEqual(0x1D7D1, slice.RuneAt(6).Value); // 𝟑
Assert.AreEqual(0x1D7D2, slice.RuneAt(8).Value); // 𝟒
// The following usages are not expected. You should take into consideration the `char`s that the Rune you just acquired occupies.
Assert.AreEqual(0, slice.PeekRuneExtra(-4).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(1).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(3).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(5).Value);
Assert.AreEqual(0, slice.RuneAt(1).Value);
Assert.AreEqual(0, slice.RuneAt(3).Value);
Assert.AreEqual(0, slice.RuneAt(5).Value);
Assert.AreEqual(0, slice.RuneAt(7).Value);
Assert.AreEqual(0, slice.RuneAt(9).Value);
Assert.AreEqual(4, slice.Start);
}
[Test]
public void TestRuneIsolatedHighSurrogate()
{
var slice = new StringSlice("\ud800\ud801\ud802\ud803\ud804");
Assert.AreEqual(0, slice.CurrentRune.Value);
Assert.AreEqual(0, slice.Start);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual(0, slice.CurrentRune.Value);
Assert.AreEqual('\ud801', slice.CurrentChar);
Assert.AreEqual(1, slice.Start);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual(2, slice.Start);
Assert.AreEqual('\ud802', slice.CurrentChar);
Assert.AreEqual(0, slice.CurrentRune.Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-3).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-1).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(1).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(3).Value);
Assert.AreEqual(0, slice.RuneAt(0).Value);
Assert.AreEqual(0, slice.RuneAt(1).Value);
Assert.AreEqual(0, slice.RuneAt(2).Value);
Assert.AreEqual(0, slice.RuneAt(3).Value);
Assert.AreEqual(0, slice.RuneAt(4).Value);
Assert.AreEqual(2, slice.Start);
}
[Test]
public void TestRuneIsolatedLowSurrogate()
{
var slice = new StringSlice("\udc00\udc01\udc02\udc03\udc04");
Assert.AreEqual(0, slice.CurrentRune.Value);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual('\udc01', slice.CurrentChar);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual('\udc02', slice.CurrentChar);
Assert.AreEqual(0, slice.CurrentRune.Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-3).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(-1).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(1).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(2).Value);
Assert.AreEqual(0, slice.PeekRuneExtra(3).Value);
Assert.AreEqual(0, slice.RuneAt(0).Value);
Assert.AreEqual(0, slice.RuneAt(1).Value);
Assert.AreEqual(0, slice.RuneAt(2).Value);
Assert.AreEqual(0, slice.RuneAt(3).Value);
Assert.AreEqual(0, slice.RuneAt(4).Value);
}
[Test]
public void TestMixedInput()
{
var slice = new StringSlice("a\udc00bc𝟑d𝟒\udc00");
Assert.AreEqual(10, slice.Length);
Assert.AreEqual('a', slice.CurrentRune.Value);
Assert.AreEqual(0, slice.Start);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual(1, slice.Start);
Assert.AreEqual('b', slice.NextRune().Value);
Assert.AreEqual(2, slice.Start);
Assert.AreEqual('c', slice.NextRune().Value);
Assert.AreEqual(3, slice.Start);
Assert.AreEqual(0x1D7D1, slice.NextRune().Value);
Assert.AreEqual(4, slice.Start);
Assert.AreEqual('d', slice.NextRune().Value);
Assert.AreEqual(6, slice.Start);
Assert.AreEqual(0x1D7D2, slice.NextRune().Value);
Assert.AreEqual(7, slice.Start);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual(9, slice.Start);
Assert.False(slice.IsEmpty);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual(10, slice.Start);
Assert.True(slice.IsEmpty);
slice = new StringSlice(slice.Text + 'a', 7, 10);
Assert.AreEqual(0x1D7D2, slice.CurrentRune.Value);
Assert.AreEqual(0, slice.NextRune().Value);
Assert.AreEqual(9, slice.Start);
Assert.AreEqual('a', slice.NextRune().Value);
}
#endif
}

View File

@@ -1,185 +1,241 @@
using NUnit.Framework;
using System.Collections;
using System.Text;
using Markdig.Helpers;
using System;
namespace Markdig.Tests
namespace Markdig.Tests;
[TestFixture]
public class TestStringSliceList
{
[TestFixture]
public class TestStringSliceList
// TODO: Add more tests for StringLineGroup
[Test]
public void TestStringLineGroupSimple()
{
// TODO: Add tests for StringSlice
// TODO: Add more tests for StringLineGroup
[Test]
public void TestStringLineGroupSimple()
var text = new StringLineGroup(4)
{
var text = new StringLineGroup(4)
{
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
};
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()
[Test]
public void TestStringLineGroupWithSlices()
{
var text = new StringLineGroup(4)
{
var text = new StringLineGroup(4)
{
new StringSlice("XABC", NewLine.LineFeed) { Start = 1},
new StringSlice("YYE", NewLine.LineFeed) { Start = 2},
new StringSlice("ZZZF") { Start = 3 }
};
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)
private static string ToString(StringLineGroup.Iterator text)
{
var chars = new StringBuilder();
while (text.CurrentChar != '\0')
{
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
chars.Append(text.CurrentChar);
text.NextChar();
Assert.AreEqual('E', text.CurrentChar);
Assert.AreEqual(1, text.SliceIndex);
}
return chars.ToString();
}
[Test]
public void TestSkipWhitespaces()
[Test]
public void TestStringLineGroupSaveAndRestore()
{
var text = new StringLineGroup(4)
{
var text = new StringLineGroup(" ABC").ToCharIterator();
Assert.False(text.TrimStart());
Assert.AreEqual('A', text.CurrentChar);
new StringSlice("ABCD", NewLine.LineFeed),
new StringSlice("EF"),
}.ToCharIterator();
text = new StringLineGroup(" ").ToCharIterator();
Assert.True(text.TrimStart());
Assert.AreEqual('\0', text.CurrentChar);
Assert.AreEqual('A', text.CurrentChar);
Assert.AreEqual(0, text.SliceIndex);
var slice = new StringSlice(" ABC");
Assert.False(slice.TrimStart());
text.NextChar(); // B
slice = new StringSlice(" ");
Assert.True(slice.TrimStart());
}
text.NextChar(); // C
text.NextChar(); // D
text.NextChar(); // \n
text.NextChar();
Assert.AreEqual('E', text.CurrentChar);
Assert.AreEqual(1, text.SliceIndex);
}
[Test]
public void TestStringLineGroupWithModifiedStart()
[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)
{
var line1 = new StringSlice(" ABC", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
}.ToCharIterator();
var line2 = new StringSlice(" DEF ");
line2.Trim();
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 text = new StringLineGroup(4) {line1, line2};
Assert.Throws<ArgumentOutOfRangeException>(() => iterator.PeekChar(-1));
}
var result = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nDEF", result);
}
[Test]
public void TestStringLineGroupWithTrim()
[Test]
public void TestIteratorSkipChar()
{
var lineGroup = new StringLineGroup(4)
{
var line1 = new StringSlice(" ABC ", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed)
};
var line2 = new StringSlice(" DEF ");
Test(lineGroup.ToCharIterator());
var text = new StringLineGroup(4) { line1, line2}.ToCharIterator();
text.TrimStart();
Test(new StringSlice("ABC\nE\n"));
var result = ToString(text);
TextAssert.AreEqual("ABC \n DEF ", result);
}
Test(new StringSlice("Foo\nABC\nE\n", 4, 9));
[Test]
public void TestStringLineGroupIteratorPeekChar()
static void Test<T>(T iterator) where T : ICharIterator
{
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();
}
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,102 +1,98 @@
using System;
using System.Linq;
using Markdig.Helpers;
using NUnit.Framework;
namespace Markdig.Tests
namespace Markdig.Tests;
public class TestTransformedStringCache
{
public class TestTransformedStringCache
[Test]
public void GetRunsTransformationCallback()
{
[Test]
public void GetRunsTransformationCallback()
{
var cache = new TransformedStringCache(static s => "callback-" + s);
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"));
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)
{
Assert.AreSame(cached, cached2);
}
[Test]
public void CachesTransformedInstance()
else
{
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)
{
Assert.AreSame(cached, cached2);
}
else
{
Assert.AreNotSame(cached, cached2);
}
}
[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++)
{
Assert.AreSame(cachedAs[i], cache.Get(a[i]));
}
Assert.AreNotSame(cachedAs[limit], cache.Get(a[limit]));
Assert.AreSame(cache.Get("b1"), cache.Get("b1"));
}
}
[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++)
{
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,78 +1,113 @@
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
namespace Markdig.Tests;
public class TestYamlFrontMatterExtension
{
public class TestYamlFrontMatterExtension
[TestCaseSource(nameof(TestCases))]
public void ProperYamlFrontMatterRenderersAdded(IMarkdownObjectRenderer[] objectRenderers, bool hasYamlFrontMatterHtmlRenderer, bool hasYamlFrontMatterRoundtripRenderer)
{
[TestCaseSource(nameof(TestCases))]
public void ProperYamlFrontMatterRenderersAdded(IMarkdownObjectRenderer[] objectRenderers, bool hasYamlFrontMatterHtmlRenderer, bool hasYamlFrontMatterRoundtripRenderer)
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[]
{
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));
}, 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();
}
private static IEnumerable<TestCaseData> TestCases()
#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)
{
yield return new TestCaseData(new IMarkdownObjectRenderer[]
{
}, 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;
}
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')
{
iterator.NextChar();
}
}
// No exception parsing and iterating through YAML front matter block lines
}
}

View File

@@ -1,119 +1,115 @@
// 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
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
{
/// <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
public enum DiffStyle
{
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)
{
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;
}
private static string ToSafeString(this char c)
if (actualValue.Equals(expectedValue, StringComparison.Ordinal))
{
if (char.IsControl(c) || char.IsWhiteSpace(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++)
{
switch (c)
if (i >= minLen || actualValue[i] != expectedValue[i])
{
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};";
startDifferAt = i;
break;
}
}
return c.ToString(CultureInfo.InvariantCulture);
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);
}
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,55 +1,54 @@
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace Markdig.WebApp
namespace Markdig.WebApp;
public class ApiController : Controller
{
public class ApiController : Controller
[HttpGet()]
[Route("")]
public new string Empty()
{
[HttpGet()]
[Route("")]
public string Empty()
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
{
return string.Empty;
if (text == null)
{
text = string.Empty;
}
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};
}
// GET api/to_html?text=xxx&extension=advanced
[Route("api/to_html")]
[HttpGet()]
public object Get([FromQuery] string text, [FromQuery] string extension)
catch (Exception ex)
{
try
{
if (text == null)
{
text = string.Empty;
}
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)
{
return new { name = "markdig", html = "exception: " + GetPrettyMessageFromException(ex), version = Markdown.Version };
}
}
private static string GetPrettyMessageFromException(Exception exception)
{
var builder = new StringBuilder();
while (exception != null)
{
builder.Append(exception.Message);
exception = exception.InnerException;
}
return builder.ToString();
return new { name = "markdig", html = "exception: " + GetPrettyMessageFromException(ex), version = Markdown.Version };
}
}
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

@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.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>
@@ -13,7 +14,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.20.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" />
</ItemGroup>
<ItemGroup>

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