Compare commits

...

221 Commits

Author SHA1 Message Date
Alexandre Mutel
c0ee97a803 Bump to 0.26.0 2021-08-27 20:34:30 +02:00
Alexandre Mutel
63ce549ea2 Merge pull request #570 from Sleepyowl/master
Fixes xoofx/markdig#567
2021-08-27 20:24:45 +02:00
Dmtry Soloviev
0faf0ef430 Make Mathematics extension respect EnableHtml* options 2021-08-24 23:03:02 +02:00
Alexandre Mutel
cdd4b40469 Merge pull request #560 from yufeih/yufeih/fix-crlf
Fix rendering diff between line endings
2021-07-06 18:36:32 +02:00
Yufei Huang
54d85ebac6 Fix rendering diff between line endings 2021-06-29 16:18:42 +08:00
Alexandre Mutel
64875c3dd9 Bump to 0.25.0 2021-06-10 09:02:03 +02:00
Alexandre Mutel
4c92fe5a3b Fix regression when parsing link reference definitions (#543) 2021-06-10 08:58:53 +02:00
Alexandre Mutel
27f625f15b Merge pull request #548 from Mysteryduck001/master
JiraLinkInlineParser.cs - Make digits in JiraKey's posible
2021-05-08 12:01:32 +02:00
Mysteryduck001
aca085703e JiraLinks.Generated.cs - add test for JiraKey's starting with a digit (not allowed) 2021-05-03 17:28:11 +02:00
Mysteryduck001
8aa0948b20 JiraLinkInlineParser.cs - prevent Jira key's to start with a digit 2021-05-03 17:22:13 +02:00
Mysteryduck001
8ce6f4d9ad JiraLinks.md - Do not allow a digit to be the first Char of the Key 2021-05-03 17:18:36 +02:00
Mysteryduck001
3a47a5115a JiraLinks.generated.cs - updated tests for JiraLink 2021-05-03 17:14:25 +02:00
Mysteryduck001
a8737e8481 JiraLinks.md - add examples with JiraKeys using Digits 2021-05-03 17:03:31 +02:00
Mysteryduck001
f56b8e6ba7 JiraLinkInlineParser.cs - Make digits in JiraKey's posible 2021-05-03 16:32:08 +02:00
Alexandre Mutel
0a0040450f Merge pull request #544 from generateui/fix_535
ignore whitespace at end of pipe table row header
2021-04-14 16:38:40 +02:00
Alexandre Mutel
fb12be5ab0 Merge pull request #540 from MihaZupan/coverage-link
Update coverage badge link
2021-04-13 07:35:27 +02:00
Ruud Poutsma
2277596e2e ignore whitespace at end of pipe table row header 2021-04-10 17:59:32 +02:00
MihaZupan
a10f6f6b8c Update coverage badge link 2021-04-07 00:18:57 +02:00
Alexandre Mutel
203cfd6508 Bump to 0.24.0 2021-03-20 08:47:20 +01:00
Alexandre Mutel
5fefcbb5b3 Merge pull request #530 from carbon/cq
Improve Code Quality
2021-03-15 20:40:54 +01:00
Alexandre Mutel
7ad7b55c48 Merge pull request #529 from MihaZupan/cache-parsers
Cache the MarkdownParser & Processors
2021-03-15 06:34:36 +01:00
Alexandre Mutel
e9ea103e32 Merge pull request #532 from MihaZupan/more-allocs
Avoid char[] & string allocations in HtmlBlockParser
2021-03-15 06:33:02 +01:00
Alexandre Mutel
c680910828 Merge pull request #531 from MihaZupan/random-perf-3
A handful of codegen improvements
2021-03-15 06:32:26 +01:00
Jason Nelson
4526d886c8 Use WriteLine char overload 2021-03-14 09:24:09 -07:00
Jason Nelson
397de86e2f Remove obvious parameter label 2021-03-14 09:15:41 -07:00
MihaZupan
f985750b82 Don't use the Roundtrip InfoParser by default 2021-03-14 11:21:34 +01:00
MihaZupan
00c175a79c Remove char[], string allocations in HtmlBlockParser 2021-03-14 11:21:14 +01:00
MihaZupan
6204095261 Remove static ctor from CharNormalizer 2021-03-14 04:13:27 +01:00
MihaZupan
63fddf4511 Add quick range check in CharNormalizer 2021-03-14 04:10:58 +01:00
MihaZupan
c964659085 Optimize Globalization StartsWithRtlCharacter check 2021-03-14 03:56:36 +01:00
MihaZupan
eabfe74e92 Avoid struct copy in Roundtrip CodeBlockRenderer 2021-03-14 03:17:59 +01:00
MihaZupan
bd1dcd952c Improve HtmlHelper 2021-03-14 03:06:47 +01:00
MihaZupan
54e2514778 Avoid struct copies in StringLineGroup 2021-03-14 02:31:08 +01:00
MihaZupan
4b7a4d21de Optimize NewLine.Length() 2021-03-14 02:07:45 +01:00
MihaZupan
6b1399ba23 Use IsEmpty instead of Length == 0 or CurrentChar == '\0' 2021-03-14 02:00:37 +01:00
MihaZupan
07467d6c30 Add ICharIterator SkipChar and PeekChar
PeekChar() already existed on StringSlice, but not on the interface, so it wasn't always getting used
2021-03-14 01:50:56 +01:00
MihaZupan
2f9588498c Add missing null check 2021-03-13 20:44:55 +01:00
MihaZupan
80ed85e2a8 Cache the MarkdownParser & Processors 2021-03-13 20:41:35 +01:00
Jason Nelson
b2306db388 Make CodeInline.Content nonnull 2021-03-12 11:31:11 -08:00
Jason Nelson
8db238797b Enable nullable on AutolinkInline 2021-03-12 11:29:53 -08:00
Jason Nelson
780e16a9c9 Simplify list initialization 2021-03-12 11:28:05 -08:00
Jason Nelson
8fdc0d59d7 Format TableState 2021-03-12 11:21:15 -08:00
Jason Nelson
f1cd0cb1b8 Enable nullable on CharacterMap 2021-03-12 11:20:57 -08:00
Jason Nelson
a724783e3f CodeBlockLines is always defined. Remove lazy initializer 2021-03-12 11:20:42 -08:00
Jason Nelson
1c862a1e07 Remove unused using statement 2021-03-12 11:10:17 -08:00
Jason Nelson
35d3160ad2 Eliminate a few allocations to hold indents 2021-03-12 11:09:53 -08:00
Jason Nelson
e0a2f9e52d Add notes on why we continue to support net452 2021-03-12 11:05:54 -08:00
Jason Nelson
c9ba236dbc Write chars, instead of strings, where possible 2021-03-12 11:01:06 -08:00
Jason Nelson
0c9b5dddc9 Use is (not) pattern to match constant strings 2021-03-12 10:50:18 -08:00
Jason Nelson
bd2bb98631 Compare strings using StringComparison.Ordinal 2021-03-12 10:49:26 -08:00
Alexandre Mutel
da0ba34165 Merge pull request #527 from MihaZupan/last-nullable
A few nullable changes
2021-03-12 06:08:54 +01:00
MihaZupan
51c5bec315 A few nullable changes 2021-03-11 20:56:55 +01:00
Alexandre Mutel
13bdab4570 Merge pull request #526 from carbon/nullability2
Complete nullable annotations
2021-03-11 20:20:50 +01:00
Jason Nelson
6b9433c7d8 Remove temporary #nullable enable directives 2021-03-11 10:41:14 -08:00
Jason Nelson
eedbb494fc Revert accidental readonly removal 2021-03-11 10:27:02 -08:00
Jason Nelson
9d36a74312 Replace == null with is null 2021-03-10 22:45:49 -08:00
Jason Nelson
4009c89321 Enable nullable at project level and add remaining nullable annotations 2021-03-10 22:44:42 -08:00
Alexandre Mutel
6b433d9352 Merge pull request #525 from carbon/nullability2
Enable nullability, round 3 of x
2021-03-11 07:05:12 +01:00
Jason Nelson
f6e6001d94 Remove unnecessary suppression 2021-03-10 18:18:41 -08:00
Jason Nelson
1474b7b29a Fix default NewLine 2021-03-10 15:28:31 -08:00
Jason Nelson
61b29b6d41 Fix incorrect logic 2021-03-10 15:20:43 -08:00
Jason Nelson
6684c8257c Enable nullable, round 3 2021-03-10 15:12:30 -08:00
Alexandre Mutel
247cd92926 Merge pull request #524 from carbon/nullability2
Enable nullable annotations, round 2 of x
2021-03-10 22:55:24 +01:00
Jason Nelson
404a94f284 Fix nullability in MarkdownParser 2021-03-10 10:01:05 -08:00
Jason Nelson
1cc8a40473 Use pattern matching to test for non-empty collections 2021-03-10 09:39:12 -08:00
Jason Nelson
dc4968d5ab Reapply nullable annotations after merge 2021-03-10 09:32:02 -08:00
Jason Nelson
b2b36038ff Merge remote-tracking branch 'upstream/master' into nullability2 2021-03-10 09:27:12 -08:00
Alexandre Mutel
4a57035aec Merge pull request #521 from MihaZupan/random-perf-2
Random perf improvements
2021-03-10 07:27:10 +01:00
Jason Nelson
1752178631 Make attachedDatas nullable 2021-03-09 17:07:02 -08:00
Jason Nelson
39c05f34d1 Enable nullable annotations, round 2 of x 2021-03-09 16:55:13 -08:00
Alexandre Mutel
640196a18f Merge pull request #520 from MihaZupan/cleanup
Some minor cleanup
2021-03-09 07:55:19 +01:00
MihaZupan
4f9119fc96 Merge master 2021-03-09 07:54:20 +01:00
MihaZupan
5eb600afc3 Expose TrackTrivia on PipelineBuilder as get-only 2021-03-09 07:35:49 +01:00
Alexandre Mutel
64ebff4012 Merge pull request #519 from grishat/document-to-html
ADD: Markdown.ToHtml from MarkdownDocument
2021-03-09 07:13:49 +01:00
Alexandre Mutel
9e5d30cd4c Merge pull request #522 from carbon/master
Enable nullability
2021-03-09 07:13:18 +01:00
Alexandre Mutel
4324caaaea Update src/Markdig/Markdown.cs 2021-03-09 07:08:15 +01:00
Jason Nelson
46c2d49243 Begin nullability enablement 2021-03-08 11:43:02 -08:00
MihaZupan
80b1cf6020 Rename NoBlockFoundBlockRenderer.cs => EmptyBlockRenderer.cs 2021-03-08 12:19:43 +01:00
MihaZupan
264f7f2132 Update Roundtrip.md 2021-03-08 12:18:12 +01:00
MihaZupan
80ec8da7d3 Add missing license headers 2021-03-08 11:54:43 +01:00
MihaZupan
1e2399669d Fix most build warnings 2021-03-08 11:37:29 +01:00
MihaZupan
ab53969f06 Revert "Fix some build warnings"
This reverts commit 325fb7158e.
2021-03-08 11:16:31 +01:00
MihaZupan
168217b4e0 Expose TrackTrivia on Pipeline as get-only 2021-03-08 11:12:55 +01:00
MihaZupan
a3ce1903c1 Cache renderers for custom writers 2021-03-07 21:28:39 +01:00
MihaZupan
db1021a979 Avoid minor allocations in ProcessInlines loop 2021-03-07 20:08:04 +01:00
MihaZupan
cbd00a45af Remove test dependency on source file's line endings
This test uses '@' strings so if source files are checked out with different line endings, it will fail
2021-03-07 19:53:57 +01:00
MihaZupan
fef4719e41 Don't change Writer's NewLine 2021-03-07 19:29:17 +01:00
MihaZupan
ae25a8f12c Reduce the size of MarkdownObject by 1 pointer size
Since this is the base type of every node on the AST, it amounts to ~3% allocated bytes reduction
2021-03-07 19:19:03 +01:00
MihaZupan
bb5403c795 Hide TrackTrivia from PipelineBuilder
EnableTrackTrivia is exposed to enable trivia
2021-03-07 19:13:34 +01:00
MihaZupan
325fb7158e Fix some build warnings 2021-03-07 18:59:23 +01:00
MihaZupan
67416e4b45 Hide TrackTrivia from Pipeline 2021-03-07 18:52:41 +01:00
Alexandre Mutel
8b48accb7e Merge pull request #481 from generateui/cst
Roundtrip implementation
2021-03-07 16:18:35 +01:00
Ruud Poutsma
bb42ee42ca code review feedback 2021-03-07 16:14:48 +01:00
Rishat Gildanov
d17660fe5d ADD: Markdown.ToHtml from MarkdownDocument (already parsed Markdown text) 2021-03-07 17:39:43 +05:00
Ruud Poutsma
19e409ceca correctly calculate newlines in StringLineGroup PeekChar 2021-03-04 23:28:37 +01:00
Ruud Poutsma
59df6d1a2e primarily define TrackTrivia on MarkdownPipeline 2021-03-04 22:57:57 +01:00
Ruud Poutsma
763ed32212 NewLine renaming remnants 2021-03-04 22:42:48 +01:00
Ruud Poutsma
8aa522c4bf Merge branch 'cst' of https://github.com/generateui/markdig into cst 2021-03-04 22:36:00 +01:00
Ruud Poutsma
9031be96f8 rename Newline to NewLine 2021-03-04 22:35:48 +01:00
Ruud Poutsma
4bc2e847d5 PR feedback 2021-03-04 22:04:26 +01:00
Alexandre Mutel
80d4e6f344 Fix StringSlice overlaps
Co-authored-by: Miha Zupan <mihazupan.zupan1@gmail.com>
2021-03-03 05:32:28 +01:00
Ruud Poutsma
b6e38a0a96 fix ContainerBlocksBlockQuotes_Example210 2021-03-01 23:26:55 +01:00
Ruud Poutsma
1d79ad8436 rerun specFileGen 2021-03-01 23:11:31 +01:00
Ruud Poutsma
383b197490 document roundtrip parsing feature 2021-03-01 23:07:40 +01:00
Ruud Poutsma
7aeee0681f rename Whitespace* member names to Trvia* member names 2021-03-01 21:12:51 +01:00
Ruud Poutsma
e017adae84 remove TODO: RTPs 2021-02-27 23:38:48 +01:00
Ruud Poutsma
7035aed74a Refactor Newline struct into enum 2021-02-27 23:32:09 +01:00
Ruud Poutsma
21b6a3869a implement NoBlocksFoundBlock, fix null character tests 2021-02-27 22:00:43 +01:00
Ruud Poutsma
aa92e59b2c merge from master 2021-02-27 21:25:44 +01:00
Alexandre Mutel
5f87e56651 Merge pull request #518 from grishat/master
FIX: Markdown.ToHtml doesnt get MarkdownParserContext parameter
2021-02-26 08:12:34 +01:00
Rishat Gildanov
9934c0033a FIX: Markdown.ToHtml doesnt get MarkdownParserContext parameter 2021-02-26 10:55:59 +05:00
Alexandre Mutel
5d1da2deac Merge pull request #511 from AndreyChechel/issue-508
Expose `TextRendererBase.Reset` method to inheritors
2021-02-17 20:57:48 +01:00
Andrey Chechel
4e424c43fb Expose TextRendererBase.Reset method to inheritors 2021-02-09 17:27:02 +03:00
Ruud Poutsma
9fbe5eb21d merge from lunet-io/master 2020-12-12 14:26:02 +01:00
Ruud Poutsma
fb4abdfae3 fix broken multi-empty-line after UnorderedList and make naming of before/after consistent 2020-11-07 23:49:25 +01:00
Ruud Poutsma
7ab34d5cef fix paragraph with more then 2 newlines after 2020-11-07 18:58:37 +01:00
Ruud Poutsma
b576b08332 document MarkdownParser assigning empty lines 2020-11-07 12:46:22 +01:00
Ruud Poutsma
9c43b802bd update Roundtrip.md todo list 2020-11-07 12:38:33 +01:00
Ruud Poutsma
7dda864e4a document and cleanup 2020-11-07 12:36:07 +01:00
Ruud Poutsma
b3953dbe23 use StringSlice instead of string in InlineLink and LinkReferenceDefinition 2020-11-07 11:47:12 +01:00
Ruud Poutsma
453e8239d2 use StringSlice in IFencedBlock 2020-11-06 17:33:27 +01:00
Ruud Poutsma
8e8b95c3bb fix broken newline parsing for LineBreakInline and PipeTableParser 2020-11-06 13:27:46 +01:00
Ruud Poutsma
35b64052d6 fix whitespaces for QuoteBlock 2020-11-01 22:48:04 +01:00
Ruud Poutsma
ef495da097 fix whitespace after atx heading char 2020-11-01 20:03:13 +01:00
Ruud Poutsma
a6b9c9a41e don't trip up SpecFileGen test by placing docs in *Spec folder in .Test project 2020-11-01 17:52:26 +01:00
Ruud Poutsma
5bb2d92180 fix quoteblock > orderedlist > quoteblock rendering 2020-11-01 17:38:12 +01:00
Ruud Poutsma
929ec36c76 fix FencedCodeBlock parsing 2020-11-01 13:48:08 +01:00
Ruud Poutsma
8e17d9c94e fix newlines surrounding IndentedCodeBlock 2020-11-01 13:04:44 +01:00
Ruud Poutsma
a11cd8c28c merge from main 2020-10-31 15:48:00 +01:00
Ruud Poutsma
68d2a20d20 fix broken PeekChar() of StringLineGroup.Iterator 2020-10-31 15:38:00 +01:00
Ruud Poutsma
e3531413e1 fix broken StringSlice tests 2020-10-31 15:07:10 +01:00
Ruud Poutsma
ef534224f8 fix heading SoureceSpan bugs 2020-10-31 14:34:25 +01:00
Ruud Poutsma
2f3e1451b8 ensure to write indents when indents are added to the text renderer 2020-10-27 20:56:57 +01:00
Ruud Poutsma
a34e257d2c fix ParagraphBlockParser parsing into paragraphs instead of setext headings 2020-10-26 22:59:29 +01:00
Ruud Poutsma
9d83631cfe fix horribly broken OrderedList 2020-10-26 22:37:44 +01:00
Ruud Poutsma
61b3ffde91 better respect TrackTrivia flag 2020-10-25 21:11:37 +01:00
Ruud Poutsma
bebdf0179e respect TrackTrivia flag much more widely, restore intentionally broken behaviors 2020-10-25 20:26:56 +01:00
Ruud Poutsma
ab5e8ae9e2 apply review feedback 2020-10-25 16:12:06 +01:00
Ruud Poutsma
c8da430134 cleanup RoundtripRenderer 2020-10-25 15:48:19 +01:00
Ruud Poutsma
0e8c312fda extract RoundtripRenderer and restore NormalizeRenderer 2020-10-25 15:43:27 +01:00
Ruud Poutsma
4814e9cea5 introduce trackTrivia feature flag 2020-10-25 15:21:13 +01:00
Ruud Poutsma
068a6e7af5 check IsNewline calls 2020-10-25 14:54:52 +01:00
Ruud Poutsma
e4f2892a23 do "TODO: RTP:" todos 2020-10-25 14:52:23 +01:00
Ruud Poutsma
c2cfb05d8d fix whitespace between heading and headingchar for atx headings 2020-10-25 14:31:25 +01:00
Ruud Poutsma
813647ca10 place newline on LinkReferenceDefinition instead of leaf whitespace properties 2020-10-25 14:16:53 +01:00
Ruud Poutsma
83357dc929 fix whitespace in nested QuoteBlock 2020-10-25 13:33:06 +01:00
Ruud Poutsma
420aa79a48 fix LinkReferenceDefinition bugs 2020-10-25 13:15:46 +01:00
Ruud Poutsma
24be820827 omit LinkReferenceDefinitionGroup and insert LinkReferenceDefinitions at proper indexes of document/parent 2020-10-25 11:32:25 +01:00
Ruud Poutsma
694a764f96 fix escaped within local label of InlineLink 2020-10-24 16:18:33 +02:00
Ruud Poutsma
bb1da73da2 fix whitespace in HtmlBlock within ListItemBlock 2020-10-24 15:53:34 +02:00
Ruud Poutsma
e6b591c035 fix escapes in FencedCodeBlock info and arguments 2020-10-24 15:32:14 +02:00
Ruud Poutsma
5a19cdfebb implement ordered list item bullet rendering 2020-10-24 15:27:37 +02:00
Ruud Poutsma
34e439f494 fix whitespace rendering of FencedCodeBlock as child of ListItemBlock 2020-10-24 15:27:10 +02:00
Ruud Poutsma
b0602a7bb0 fix HtmlBlock whitespace regression 2020-10-24 14:51:09 +02:00
Ruud Poutsma
d7558f0442 fix FencedCodeBlock beforewhitespace regression 2020-10-24 14:41:35 +02:00
Ruud Poutsma
eac0ab3cca fix broken InlineLink rendering 2020-10-24 14:37:49 +02:00
Ruud Poutsma
147cd48a8d fix escapes in InlineLink regression 2020-10-24 14:29:39 +02:00
Ruud Poutsma
1b44256fc0 improve whitespace handling in FencedCodeBlock 2020-10-24 14:13:38 +02:00
Ruud Poutsma
db2856daf7 fix label rendering on LinkInline 2020-10-24 13:53:29 +02:00
Ruud Poutsma
9beb0c1613 fix whitespace on empty lines 2020-10-24 00:24:00 +02:00
Ruud Poutsma
ef343158b7 fix escapes in LinkReferenceDefinition 2020-10-24 00:11:35 +02:00
Ruud Poutsma
de5526877d fix broken AtxHeading whitespace 2020-10-23 23:31:31 +02:00
Ruud Poutsma
b00d75607b fix tabs in IndentedCodeBlock 2020-10-23 23:21:11 +02:00
Ruud Poutsma
8c37a1bef5 fix HtmlBlock whitespace before 2020-10-23 23:00:38 +02:00
Ruud Poutsma
7a9405ec9e fix ThematicBreakParser 2020-10-23 22:55:55 +02:00
Ruud Poutsma
6506e4594c fix escaped characters in LinkInline 2020-10-23 22:47:39 +02:00
Ruud Poutsma
7b20299d2b fix empty ListItem 2020-10-23 19:34:16 +02:00
Ruud Poutsma
55eaadce67 fix optional space after Atx heading leaders 2020-10-23 19:29:12 +02:00
Ruud Poutsma
87269d88b9 fix newlines after LinkReferenceDefinition 2020-10-23 18:52:48 +02:00
Ruud Poutsma
fb8162f5d7 fix whitespace surrounding closing fences of FencedCodeBlock 2020-10-23 18:28:28 +02:00
Ruud Poutsma
a04aa8a4de fix empty QuoteBlock lines 2020-10-23 17:50:07 +02:00
Ruud Poutsma
352443d9cd fix unclosed FencedCodeBlock in QuoteBlock 2020-10-23 11:32:15 +02:00
Ruud Poutsma
cd798b8d95 fix newlines after links 2020-10-18 22:40:17 +02:00
Ruud Poutsma
c5b260f708 fix rendering of empty lines with whitespace 2020-10-18 16:09:24 +02:00
Ruud Poutsma
2583463ea2 fix broken links 2020-10-18 15:05:41 +02:00
Ruud Poutsma
777ac71bd1 fix HardLineBreak, CodeInline tests 2020-10-18 14:11:47 +02:00
Ruud Poutsma
810ae49cc7 fix SetextHeading tests 2020-10-18 13:08:40 +02:00
Ruud Poutsma
225e308438 fix AtxHeading tests 2020-10-18 12:40:57 +02:00
Ruud Poutsma
05dd543d16 fix HtmlBlock tests 2020-10-18 12:17:33 +02:00
Ruud Poutsma
c711201b34 implement codegen for roundtrip parsing 2020-10-17 23:49:44 +02:00
Ruud Poutsma
47b3ac5d99 fix Autlink tests 2020-10-17 23:19:44 +02:00
Ruud Poutsma
49aa856f52 implement LinkInline 2020-10-17 23:09:53 +02:00
Ruud Poutsma
5e15575929 implement LinkReferenceDefinition 2020-10-17 15:48:36 +02:00
Ruud Poutsma
c9fc608598 update todo 2020-10-11 14:07:32 +02:00
Ruud Poutsma
6ba3c3d683 implement newline 2020-10-11 14:00:09 +02:00
Ruud Poutsma
d15edb79fa fix broken whitespace starting on newlines within a paragraph 2020-10-10 17:25:22 +02:00
Ruud Poutsma
fa3b67342d add some tests, update todolist 2020-10-10 17:10:18 +02:00
Ruud Poutsma
033f156b2b add many tests, create todolist 2020-10-10 16:37:17 +02:00
Ruud Poutsma
f3db5e882e fix FencedCodeBlock 2020-10-09 19:47:16 +02:00
Ruud Poutsma
c9365f3551 correctly render whitespace for IndentedCodeBlock 2020-10-09 18:30:03 +02:00
Ruud Poutsma
9ecb5b9950 test nested indented codeblock in quoteblock 2020-10-09 17:20:58 +02:00
Ruud Poutsma
95fb53cbc9 fix IndentedCodeBlock newlines 2020-10-09 16:44:16 +02:00
Ruud Poutsma
9d82088e03 fix quoteblock 2020-10-09 14:14:41 +02:00
Ruud Poutsma
340b75b557 add some tests, fix nested blockquote in listitem 2020-10-09 13:24:44 +02:00
Ruud Poutsma
0b79389b14 fix CodeInline parsing & rendering 2020-10-04 21:49:54 +02:00
Ruud Poutsma
111d3d3362 fix multiline quote blocks, split tests into file per node type 2020-10-04 21:20:32 +02:00
Ruud Poutsma
ac6ceb23e8 restore whitespace parsing in BlockProcessor 2020-10-03 23:13:04 +02:00
Ruud Poutsma
d31fa47cd1 add some testcases 2020-10-03 18:55:02 +02:00
Ruud Poutsma
d7f6a94f12 cleanup, add some testcases 2020-10-03 18:54:45 +02:00
Ruud Poutsma
dbdd752b73 Keep line info when parsing quoteblocks and render accordingly 2020-10-03 16:11:01 +02:00
Ruud Poutsma
4c2b46e0fc fix listblock-paragraph 2020-09-27 13:27:14 +02:00
Ruud Poutsma
68d12d0212 fix newlines and list blocks 2020-09-27 13:16:53 +02:00
Ruud Poutsma
fa1c117011 experiemnt with list rendering 2020-09-26 14:24:58 +02:00
Ruud Poutsma
6b1c5bc816 fix thematic break 2020-09-26 14:07:57 +02:00
Ruud Poutsma
e2cafc6b3d fix newline after listblock 2020-09-26 13:46:33 +02:00
Ruud Poutsma
aff7604b4b fix completely broken unordered list 2020-09-26 13:34:37 +02:00
Ruud Poutsma
68530aa4e0 improve linebreak handling 2020-09-26 12:59:48 +02:00
Ruud Poutsma
bfc1152b8a default newline after Html block, fix newlines before list block 2020-09-25 22:35:49 +02:00
Ruud Poutsma
37af8f8ecb implement newline with HtmlBlock 2020-09-25 22:03:34 +02:00
Ruud Poutsma
6792bffb5e better thematic break handling 2020-09-25 21:39:53 +02:00
Ruud Poutsma
976855a4c3 implement TheamticBreak renderer 2020-09-25 21:23:28 +02:00
Ruud Poutsma
acf2ba9502 allow CodeInline with multiple delimiter characters 2020-09-25 19:23:11 +02:00
Ruud Poutsma
cadbc67825 fix newline between blocks 2020-09-25 18:55:54 +02:00
Ruud Poutsma
0d86a93200 fix block nodes 2020-09-25 17:58:58 +02:00
Ruud Poutsma
f78b5c83cd handle whitespace before and after paragraphs correctly 2020-09-25 17:07:10 +02:00
Ruud Poutsma
0234d60d74 fix broken whitespace calculation 2020-08-08 18:25:08 +02:00
Ruud Poutsma
cd18087e29 implement cst for header 2020-08-08 18:18:55 +02:00
Ruud Poutsma
30f670bf5f fix NRE 2020-08-08 17:24:16 +02:00
Ruud Poutsma
c73785372b implement cst for Paragraph 2020-08-08 17:22:53 +02:00
Ruud Poutsma
147698daab revert LinkInlineRenderer 2020-08-08 14:54:57 +02:00
Ruud Poutsma
2b07e9a5b9 add naive cst implementation 2020-08-08 14:41:02 +02:00
217 changed files with 33892 additions and 2962 deletions

View File

@@ -1,5 +1,18 @@
# Changelog
## 0.26.0 (27 Aug 2021)
- Fix rendering diff between line endings ([PR #560](https://github.com/lunet-io/markdig/pull/560))
- Make Mathematics extension respect EnableHtml* options ([PR #570](https://github.com/lunet-io/markdig/pull/570))
## 0.25.0 (10 June 2021)
- Fix regression when parsing link reference definitions (#543)
- Make digits in JiraKey's posible ([PR #548](https://github.com/lunet-io/markdig/pull/548))
## 0.24.0 (20 Mar 2021)
- Add support for roundtrip Markdown ([PR #481](https://github.com/lunet-io/markdig/pull/481))
- Introduction of nullability ([PR #522](https://github.com/lunet-io/markdig/pull/522) [PR #524](https://github.com/lunet-io/markdig/pull/524) [PR #525](https://github.com/lunet-io/markdig/pull/525) [PR #526](https://github.com/lunet-io/markdig/pull/526) [PR #527](https://github.com/lunet-io/markdig/pull/527))
- Various internal cleanup and small performance improvements ([PR #521](https://github.com/lunet-io/markdig/pull/521) [PR #524](https://github.com/lunet-io/markdig/pull/524) [PR #525](https://github.com/lunet-io/markdig/pull/525) [PR #529](https://github.com/lunet-io/markdig/pull/529) [PR #531](https://github.com/lunet-io/markdig/pull/531) [PR #532](https://github.com/lunet-io/markdig/pull/532))
## 0.23.0 (16 Jan 2021)
- Add depth limits to avoid pathological-case parsing times/StackOverflows (#500)
- Breaking change: rename AutolineInlineParser to AutolinkInlineParser
@@ -9,10 +22,10 @@
## 0.22.0 (05 Oct 2020)
- Fix Setext headings in block quotes.
- Fix tel: treated as autolink ([PR #478](https://github.com/lunet-io/markdig/pull/478)
- Make Inline.FirstParentOfType public ([PR #474](https://github.com/lunet-io/markdig/pull/474)
- Fix `&` to be parsed as a punctuation while it was detected as a html entity in certain cases ([PR #471](https://github.com/lunet-io/markdig/pull/471)
- Add ParentBlock property to ContainerInline ([PR #468](https://github.com/lunet-io/markdig/pull/468)
- Fix tel: treated as autolink ([PR #478](https://github.com/lunet-io/markdig/pull/478))
- Make Inline.FirstParentOfType public ([PR #474](https://github.com/lunet-io/markdig/pull/474))
- Fix `&` to be parsed as a punctuation while it was detected as a html entity in certain cases ([PR #471](https://github.com/lunet-io/markdig/pull/471))
- Add ParentBlock property to ContainerInline ([PR #468](https://github.com/lunet-io/markdig/pull/468))
## 0.21.1 (17 Aug 2020)
- Fix Markdig.Signed on GitHub Actions

View File

@@ -1,4 +1,4 @@
# 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/lunet-io/markdig/badge.svg?branch=master)](https://coveralls.io/github/lunet-io/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 [![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)
<img align="right" width="160px" height="160px" src="img/markdig.png">
@@ -19,6 +19,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- including **GFM fenced code blocks**.
- **Extensible** architecture
- Even the core Markdown/CommonMark parsing is pluggable, so it allows to disable builtin Markdown/Commonmark parsing (e.g [Disable HTML parsing](https://github.com/lunet-io/markdig/blob/7964bd0160d4c18e4155127a4c863d61ebd8944a/src/Markdig/MarkdownExtensions.cs#L306)) or change behaviour (e.g change matching `#` of a headers with `@`)
- [**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))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,23 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestAutoLinkInline
{
[TestCase("<http://a>")]
[TestCase(" <http://a>")]
[TestCase("<http://a> ")]
[TestCase(" <http://a> ")]
[TestCase("<example@example.com>")]
[TestCase(" <example@example.com>")]
[TestCase("<example@example.com> ")]
[TestCase(" <example@example.com> ")]
[TestCase("p http://a p")]
public void Test(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,67 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestBackslashEscapeInline
{
[TestCase(@"\!")]
[TestCase(@"\""")]
[TestCase(@"\#")]
[TestCase(@"\$")]
[TestCase(@"\&")]
[TestCase(@"\'")]
[TestCase(@"\(")]
[TestCase(@"\)")]
[TestCase(@"\*")]
[TestCase(@"\+")]
[TestCase(@"\,")]
[TestCase(@"\-")]
[TestCase(@"\.")]
[TestCase(@"\/")]
[TestCase(@"\:")]
[TestCase(@"\;")]
[TestCase(@"\<")]
[TestCase(@"\=")]
[TestCase(@"\>")]
[TestCase(@"\?")]
[TestCase(@"\@")]
[TestCase(@"\[")]
[TestCase(@"\\")]
[TestCase(@"\]")]
[TestCase(@"\^")]
[TestCase(@"\_")]
[TestCase(@"\`")]
[TestCase(@"\{")]
[TestCase(@"\|")]
[TestCase(@"\}")]
[TestCase(@"\~")]
// below test breaks visual studio
//[TestCase(@"\!\""\#\$\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\@\[\\\]\^\_\`\{\|\}\~")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase(@"# \#\#h1")]
[TestCase(@"# \#\#h1\#")]
public void TestHeading(string value)
{
RoundTrip(value);
}
[TestCase(@"`\``")]
[TestCase(@"` \``")]
[TestCase(@"`\` `")]
[TestCase(@"` \` `")]
[TestCase(@" ` \` `")]
[TestCase(@"` \` ` ")]
[TestCase(@" ` \` ` ")]
public void TestCodeSpanInline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,77 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestCodeInline
{
[TestCase("``")]
[TestCase(" ``")]
[TestCase("`` ")]
[TestCase(" `` ")]
[TestCase("`c`")]
[TestCase(" `c`")]
[TestCase("`c` ")]
[TestCase(" `c` ")]
[TestCase("` c`")]
[TestCase(" ` c`")]
[TestCase("` c` ")]
[TestCase(" ` c` ")]
[TestCase("`c `")]
[TestCase(" `c `")]
[TestCase("`c ` ")]
[TestCase(" `c ` ")]
[TestCase("`c``")] // 1, 2
[TestCase("``c`")] // 2, 1
[TestCase("``c``")] // 2, 2
[TestCase("```c``")] // 2, 3
[TestCase("``c```")] // 3, 2
[TestCase("```c```")] // 3, 3
[TestCase("```c````")] // 3, 4
[TestCase("````c```")] // 4, 3
[TestCase("````c````")] // 4, 4
[TestCase("```a``` p")]
[TestCase("```a`b`c```")]
[TestCase("```a``` p\n```a``` p")]
[TestCase("` a `")]
[TestCase(" ` a `")]
[TestCase("` a ` ")]
[TestCase(" ` a ` ")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("p `a` p")]
[TestCase("p ``a`` p")]
[TestCase("p ```a``` p")]
[TestCase("p\n\n```a``` p")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
[TestCase("`\na\n`")]
[TestCase("`\na\r`")]
[TestCase("`\na\r\n`")]
[TestCase("`\ra\r`")]
[TestCase("`\ra\n`")]
[TestCase("`\ra\r\n`")]
[TestCase("`\r\na\n`")]
[TestCase("`\r\na\r`")]
[TestCase("`\r\na\r\n`")]
public void Test_Newlines(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,132 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestEmphasisInline
{
[TestCase("_t_")]
[TestCase("_t_t")]
[TestCase("t_t_")]
[TestCase("_t t_")]
[TestCase("_t\tt_")]
[TestCase("*t*")]
[TestCase("t*t*")]
[TestCase("*t*t")]
[TestCase("*t t*")]
[TestCase("*t\tt*")]
[TestCase(" _t_")]
[TestCase(" _t_t")]
[TestCase(" t_t_")]
[TestCase(" _t t_")]
[TestCase(" _t\tt_")]
[TestCase(" *t*")]
[TestCase(" t*t*")]
[TestCase(" *t*t")]
[TestCase(" *t t*")]
[TestCase(" *t\tt*")]
[TestCase("_t_")]
[TestCase("_t_t ")]
[TestCase("t_t_ ")]
[TestCase("_t t_ ")]
[TestCase("_t\tt_ ")]
[TestCase("*t* ")]
[TestCase("t*t* ")]
[TestCase("*t*t ")]
[TestCase("*t t* ")]
[TestCase("*t\tt* ")]
[TestCase(" _t_")]
[TestCase(" _t_t ")]
[TestCase(" t_t_ ")]
[TestCase(" _t t_ ")]
[TestCase(" _t\tt_ ")]
[TestCase(" *t* ")]
[TestCase(" t*t* ")]
[TestCase(" *t*t ")]
[TestCase(" *t t* ")]
[TestCase(" *t\tt* ")]
[TestCase("_t_\t")]
[TestCase("_t_t\t")]
[TestCase("t_t_\t")]
[TestCase("_t t_\t")]
[TestCase("_t\tt_\t")]
[TestCase("*t*\t")]
[TestCase("t*t*\t")]
[TestCase("*t*t\t")]
[TestCase("*t t*\t")]
[TestCase("*t\tt*\t")]
public void Test_Emphasis(string value)
{
RoundTrip(value);
}
[TestCase("__t__")]
[TestCase("__t__t")]
[TestCase("t__t__")]
[TestCase("__t t__")]
[TestCase("__t\tt__")]
[TestCase("**t**")]
[TestCase("**t**t")]
[TestCase("t**t**")]
[TestCase("**t\tt**")]
[TestCase(" __t__")]
[TestCase(" __t__t")]
[TestCase(" t__t__")]
[TestCase(" __t t__")]
[TestCase(" __t\tt__")]
[TestCase(" **t**")]
[TestCase(" **t**t")]
[TestCase(" t**t**")]
[TestCase(" **t\tt**")]
[TestCase("__t__ ")]
[TestCase("__t__t ")]
[TestCase("t__t__ ")]
[TestCase("__t t__ ")]
[TestCase("__t\tt__ ")]
[TestCase("**t** ")]
[TestCase("**t**t ")]
[TestCase("t**t** ")]
[TestCase("**t\tt** ")]
[TestCase(" __t__ ")]
[TestCase(" __t__t ")]
[TestCase(" t__t__ ")]
[TestCase(" __t t__ ")]
[TestCase(" __t\tt__ ")]
[TestCase(" **t** ")]
[TestCase(" **t** t")]
[TestCase(" t**t** ")]
[TestCase(" **t\tt** ")]
[TestCase("__t__\t")]
[TestCase("__t__t\t")]
[TestCase("t__t__\t ")]
[TestCase("__t t__\t ")]
[TestCase("__t\tt__\t ")]
[TestCase("**t**\t ")]
[TestCase("**t**t\t ")]
[TestCase("t**t**\t ")]
[TestCase("**t\tt**\t ")]
[TestCase(" __t__\t ")]
[TestCase(" __t__t\t ")]
[TestCase(" t__t__\t ")]
[TestCase(" __t t__\t ")]
[TestCase(" __t\tt__\t ")]
[TestCase(" **t**\t ")]
[TestCase(" **t**\t t")]
[TestCase(" t**t**\t ")]
[TestCase(" **t\tt**\t ")]
public void Test_StrongEmphasis(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,53 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
/// <summary>
///
/// </summary>
/// <seealso cref="https://spec.commonmark.org/0.29/#entity-and-numeric-character-references"/>
[TestFixture]
public class TestHtmlEntityInline
{
[TestCase("&gt;")]
[TestCase("&lt;")]
[TestCase("&nbsp;")]
[TestCase("&heartsuit;")]
[TestCase("&#42;")]
[TestCase("&#0;")]
[TestCase("&#1234;")]
[TestCase("&#xcab;")]
[TestCase(" &gt;")]
[TestCase(" &lt;")]
[TestCase(" &nbsp;")]
[TestCase(" &heartsuit;")]
[TestCase(" &#42;")]
[TestCase(" &#0;")]
[TestCase(" &#1234;")]
[TestCase(" &#xcab;")]
[TestCase("&gt; ")]
[TestCase("&lt; ")]
[TestCase("&nbsp; ")]
[TestCase("&heartsuit; ")]
[TestCase("&#42; ")]
[TestCase("&#0; ")]
[TestCase("&#1234; ")]
[TestCase("&#xcab; ")]
[TestCase(" &gt; ")]
[TestCase(" &lt; ")]
[TestCase(" &nbsp; ")]
[TestCase(" &heartsuit; ")]
[TestCase(" &#42; ")]
[TestCase(" &#0; ")]
[TestCase(" &#1234; ")]
[TestCase(" &#xcab; ")]
public void Test(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,27 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestHtmlInline
{
[TestCase("<em>f</em>")]
[TestCase("<em> f</em>")]
[TestCase("<em>f </em>")]
[TestCase("<em> f </em>")]
[TestCase("<b>p</b>")]
[TestCase("<b></b>")]
[TestCase("<b> </b>")]
[TestCase("<b> </b>")]
[TestCase("<b> </b>")]
[TestCase("<b>\t</b>")]
[TestCase("<b> \t</b>")]
[TestCase("<b>\t </b>")]
[TestCase("<b> \t </b>")]
public void Test(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,25 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestImageInline
{
[TestCase("![](a)")]
[TestCase(" ![](a)")]
[TestCase("![](a) ")]
[TestCase(" ![](a) ")]
[TestCase(" ![description](http://example.com)")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("paragraph ![description](http://example.com)")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,18 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestLineBreakInline
{
[TestCase("p\n")]
[TestCase("p\r\n")]
[TestCase("p\r")]
[TestCase("[]() ![]() `` ` ` ` ` ![]() ![]()")]
public void Test(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,229 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestLinkInline
{
[TestCase("[a]")] // TODO: this is not a link but a paragraph
[TestCase("[a]()")]
[TestCase("[](b)")]
[TestCase(" [](b)")]
[TestCase("[](b) ")]
[TestCase(" [](b) ")]
[TestCase("[a](b)")]
[TestCase(" [a](b)")]
[TestCase("[a](b) ")]
[TestCase(" [a](b) ")]
[TestCase("[ a](b)")]
[TestCase(" [ a](b)")]
[TestCase("[ a](b) ")]
[TestCase(" [ a](b) ")]
[TestCase("[a ](b)")]
[TestCase(" [a ](b)")]
[TestCase("[a ](b) ")]
[TestCase(" [a ](b) ")]
[TestCase("[ a ](b)")]
[TestCase(" [ a ](b)")]
[TestCase("[ a ](b) ")]
[TestCase(" [ a ](b) ")]
// below cases are required for a full roundtrip but not have low prio for impl
[TestCase("[]( b)")]
[TestCase(" []( b)")]
[TestCase("[]( b) ")]
[TestCase(" []( b) ")]
[TestCase("[a]( b)")]
[TestCase(" [a]( b)")]
[TestCase("[a]( b) ")]
[TestCase(" [a]( b) ")]
[TestCase("[ a]( b)")]
[TestCase(" [ a]( b)")]
[TestCase("[ a]( b) ")]
[TestCase(" [ a]( b) ")]
[TestCase("[a ]( b)")]
[TestCase(" [a ]( b)")]
[TestCase("[a ]( b) ")]
[TestCase(" [a ]( b) ")]
[TestCase("[ a ]( b)")]
[TestCase(" [ a ]( b)")]
[TestCase("[ a ]( b) ")]
[TestCase(" [ a ]( b) ")]
[TestCase("[](b )")]
[TestCase(" [](b )")]
[TestCase("[](b ) ")]
[TestCase(" [](b ) ")]
[TestCase("[a](b )")]
[TestCase(" [a](b )")]
[TestCase("[a](b ) ")]
[TestCase(" [a](b ) ")]
[TestCase("[ a](b )")]
[TestCase(" [ a](b )")]
[TestCase("[ a](b ) ")]
[TestCase(" [ a](b ) ")]
[TestCase("[a ](b )")]
[TestCase(" [a ](b )")]
[TestCase("[a ](b ) ")]
[TestCase(" [a ](b ) ")]
[TestCase("[ a ](b )")]
[TestCase(" [ a ](b )")]
[TestCase("[ a ](b ) ")]
[TestCase(" [ a ](b ) ")]
[TestCase("[]( b )")]
[TestCase(" []( b )")]
[TestCase("[]( b ) ")]
[TestCase(" []( b ) ")]
[TestCase("[a]( b )")]
[TestCase(" [a]( b )")]
[TestCase("[a]( b ) ")]
[TestCase(" [a]( b ) ")]
[TestCase("[ a]( b )")]
[TestCase(" [ a]( b )")]
[TestCase("[ a]( b ) ")]
[TestCase(" [ a]( b ) ")]
[TestCase("[a ]( b )")]
[TestCase(" [a ]( b )")]
[TestCase("[a ]( b ) ")]
[TestCase(" [a ]( b ) ")]
[TestCase("[ a ]( b )")]
[TestCase(" [ a ]( b )")]
[TestCase("[ a ]( b ) ")]
[TestCase(" [ a ]( b ) ")]
public void Test(string value)
{
RoundTrip(value);
}
[TestCase("[a](b \"t\") ")]
[TestCase("[a](b \" t\") ")]
[TestCase("[a](b \"t \") ")]
[TestCase("[a](b \" t \") ")]
[TestCase("[a](b \"t\") ")]
[TestCase("[a](b \" t\") ")]
[TestCase("[a](b \"t \") ")]
[TestCase("[a](b \" t \") ")]
[TestCase("[a](b \"t\" ) ")]
[TestCase("[a](b \" t\" ) ")]
[TestCase("[a](b \"t \" ) ")]
[TestCase("[a](b \" t \" ) ")]
[TestCase("[a](b \"t\" ) ")]
[TestCase("[a](b \" t\" ) ")]
[TestCase("[a](b \"t \" ) ")]
[TestCase("[a](b \" t \" ) ")]
[TestCase("[a](b 't') ")]
[TestCase("[a](b ' t') ")]
[TestCase("[a](b 't ') ")]
[TestCase("[a](b ' t ') ")]
[TestCase("[a](b 't') ")]
[TestCase("[a](b ' t') ")]
[TestCase("[a](b 't ') ")]
[TestCase("[a](b ' t ') ")]
[TestCase("[a](b 't' ) ")]
[TestCase("[a](b ' t' ) ")]
[TestCase("[a](b 't ' ) ")]
[TestCase("[a](b ' t ' ) ")]
[TestCase("[a](b 't' ) ")]
[TestCase("[a](b ' t' ) ")]
[TestCase("[a](b 't ' ) ")]
[TestCase("[a](b ' t ' ) ")]
[TestCase("[a](b (t)) ")]
[TestCase("[a](b ( t)) ")]
[TestCase("[a](b (t )) ")]
[TestCase("[a](b ( t )) ")]
[TestCase("[a](b (t)) ")]
[TestCase("[a](b ( t)) ")]
[TestCase("[a](b (t )) ")]
[TestCase("[a](b ( t )) ")]
[TestCase("[a](b (t) ) ")]
[TestCase("[a](b ( t) ) ")]
[TestCase("[a](b (t ) ) ")]
[TestCase("[a](b ( t ) ) ")]
[TestCase("[a](b (t) ) ")]
[TestCase("[a](b ( t) ) ")]
[TestCase("[a](b (t ) ) ")]
[TestCase("[a](b ( t ) ) ")]
public void Test_Title(string value)
{
RoundTrip(value);
}
[TestCase("[a](<>)")]
[TestCase("[a]( <>)")]
[TestCase("[a](<> )")]
[TestCase("[a]( <> )")]
[TestCase("[a](< >)")]
[TestCase("[a]( < >)")]
[TestCase("[a](< > )")]
[TestCase("[a]( < > )")]
[TestCase("[a](<b>)")]
[TestCase("[a](<b >)")]
[TestCase("[a](< b>)")]
[TestCase("[a](< b >)")]
[TestCase("[a](<b b>)")]
[TestCase("[a](<b b >)")]
[TestCase("[a](< b b >)")]
public void Test_PointyBrackets(string value)
{
RoundTrip(value);
}
[TestCase("[*a*][a]")]
[TestCase("[a][b]")]
[TestCase("[a][]")]
[TestCase("[a]")]
public void Test_Inlines(string value)
{
RoundTrip(value);
}
// | [ a ]( b " t " ) |
[TestCase(" [ a ]( b \" t \" ) ")]
[TestCase("\v[\va\v](\vb\v\"\vt\v\"\v)\v")]
[TestCase("\f[\fa\f](\fb\f\"\ft\f\"\f)\f")]
[TestCase("\t[\ta\t](\tb\t\"\tt\t\"\t)\t")]
public void Test_UncommonWhitespace(string value)
{
RoundTrip(value);
}
[TestCase("[x]: https://example.com\r\n")]
public void Test_LinkReferenceDefinitionWithCarriageReturnLineFeed(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,37 @@
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests.RoundtripSpecs.Inlines
{
[TestFixture]
public class TestNullCharacterInline
{
[TestCase("\0", "\uFFFD")]
[TestCase("\0p", "\uFFFDp")]
[TestCase("p\0", "p\uFFFD")]
[TestCase("p\0p", "p\uFFFDp")]
[TestCase("p\0\0p", "p\uFFFD\uFFFDp")] // I promise you, this was not intentional
public void Test(string value, string expected)
{
RoundTrip(value, expected);
}
// this method is copied intentionally to ensure all other tests
// do not unintentionally use the expected parameter
private static void RoundTrip(string markdown, string expected)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
MarkdownPipeline pipeline = pipelineBuilder.Build();
MarkdownDocument markdownDocument = Markdown.Parse(markdown, pipeline);
var sw = new StringWriter();
var rr = new RoundtripRenderer(sw);
rr.Write(markdownDocument);
Assert.AreEqual(expected, sw.ToString());
}
}
}

View File

@@ -0,0 +1,55 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
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("\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("# h\n\n ")]
[TestCase("# h\n\n ")]
[TestCase("# h\n\n ")]
public void TestNewline(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,58 @@
using Markdig.Helpers;
using Markdig.Renderers.Roundtrip;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests.RoundtripSpecs
{
[TestFixture]
public class TestExample
{
[Test]
public void Test()
{
var markdown = $@"
# Test document
This document contains an unordered list. It uses tabs to indent. This test demonstrates
a method of making the input markdown uniform without altering any other markdown in the
resulting output file.
- item1
>look, ma:
> my space is not normalized!
";
MarkdownDocument markdownDocument = Markdown.Parse(markdown, trackTrivia: true);
var listBlock = markdownDocument[2] as ListBlock;
var listItem = listBlock[0] as ListItemBlock;
var paragraph = listItem[0] as ParagraphBlock;
var containerInline = new ContainerInline();
containerInline.AppendChild(new LiteralInline(" my own text!"));
containerInline.AppendChild(new LineBreakInline { NewLine = NewLine.CarriageReturnLineFeed });
paragraph.Inline = containerInline;
var sw = new StringWriter();
var rr = new RoundtripRenderer(sw);
rr.Write(markdownDocument);
var outputMarkdown = sw.ToString();
var expected = $@"
# Test document
This document contains an unordered list. It uses tabs to indent. This test demonstrates
a method of making the input markdown uniform without altering any other markdown in the
resulting output file.
- my own text!
>look, ma:
> my space is not normalized!
";
expected = expected.Replace("\r\n", "\n").Replace("\r", "\n");
outputMarkdown = outputMarkdown.Replace("\r\n", "\n").Replace("\r", "\n");
Assert.AreEqual(expected, outputMarkdown);
}
}
}

View File

@@ -0,0 +1,107 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~")]
[TestCase("~~~ aa ``` ~~~\nfoo\n~~~ ")]
public void TestTilde(string value)
{
RoundTrip(value);
}
[TestCase("```\n c \n```")]
[TestCase("```\n c \r```")]
[TestCase("```\n c \r\n```")]
[TestCase("```\r c \n```")]
[TestCase("```\r c \r```")]
[TestCase("```\r c \r\n```")]
[TestCase("```\r\n c \n```")]
[TestCase("```\r\n c \r```")]
[TestCase("```\r\n c \r\n```")]
[TestCase("```\n c \n```\n")]
[TestCase("```\n c \r```\n")]
[TestCase("```\n c \r\n```\n")]
[TestCase("```\r c \n```\n")]
[TestCase("```\r c \r```\n")]
[TestCase("```\r c \r\n```\n")]
[TestCase("```\r\n c \n```\n")]
[TestCase("```\r\n c \r```\n")]
[TestCase("```\r\n c \r\n```\n")]
[TestCase("```\n c \n```\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```\r\n")]
[TestCase("```\n c \r```\r\n")]
[TestCase("```\n c \r\n```\r\n")]
[TestCase("```\r c \n```\r\n")]
[TestCase("```\r c \r```\r\n")]
[TestCase("```\r c \r\n```\r\n")]
[TestCase("```\r\n c \n```\r\n")]
[TestCase("```\r\n c \r```\r\n")]
[TestCase("```\r\n c \r\n```\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase("```i a\n```")]
[TestCase("```i a a2\n```")]
[TestCase("```i a a2 a3\n```")]
[TestCase("```i a a2 a3 a4\n```")]
[TestCase("```i\ta\n```")]
[TestCase("```i\ta a2\n```")]
[TestCase("```i\ta a2 a3\n```")]
[TestCase("```i\ta a2 a3 a4\n```")]
[TestCase("```i\ta \n```")]
[TestCase("```i\ta a2 \n```")]
[TestCase("```i\ta a2 a3 \n```")]
[TestCase("```i\ta a2 a3 a4 \n```")]
public void TestInfoArguments(string value)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,20 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,86 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
[TestCase(" l\n")]
[TestCase(" l\r")]
[TestCase(" l\r\n")]
[TestCase(" l\n l")]
[TestCase(" l\n l\n")]
[TestCase(" l\n l\r")]
[TestCase(" l\n l\r\n")]
[TestCase(" l\r l")]
[TestCase(" l\r l\n")]
[TestCase(" l\r l\r")]
[TestCase(" l\r l\r\n")]
[TestCase(" l\r\n l")]
[TestCase(" l\r\n l\n")]
[TestCase(" l\r\n l\r")]
[TestCase(" l\r\n l\r\n")]
public void TestNewline(string value)
{
RoundTrip(value);
}
[TestCase(" l\n\n l\n")]
[TestCase(" l\n\n\n l\n")]
public void TestNewlinesInBetweenResultInOneCodeBlock(string value)
{
var pipelineBuilder = new MarkdownPipelineBuilder();
pipelineBuilder.EnableTrackTrivia();
MarkdownPipeline pipeline = pipelineBuilder.Build();
var markdownDocument = Markdown.Parse(value, pipeline);
Assert.AreEqual(1, markdownDocument.Count);
}
[TestCase(" l\n\np")]
[TestCase(" l\n\n\np")]
[TestCase(" l\n\n\n\np")]
public void TestParagraph(string value)
{
RoundTrip(value);
}
}
}

View File

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

View File

@@ -0,0 +1,23 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
}
}

View File

@@ -0,0 +1,191 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
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\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\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\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\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\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\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);
}
}
}

View File

@@ -0,0 +1,247 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
[TestCase("\n")]
[TestCase("\r\n")]
[TestCase("\r")]
[TestCase("p\n")]
[TestCase("p\r")]
[TestCase("p\r\n")]
[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\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("\np\n")]
[TestCase("\np\r")]
[TestCase("\np\r\n")]
[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\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("\rp\n")]
[TestCase("\rp\r")]
[TestCase("\rp\r\n")]
[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\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("\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\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\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(" \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(" \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 ")]
public void Test_WhitespaceWithNewline(string value)
{
RoundTrip(value);
}
}
}

View File

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

View File

@@ -0,0 +1,53 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
[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===\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\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

@@ -0,0 +1,53 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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)
{
RoundTrip(value);
}
[TestCase("\n---")]
[TestCase("\r---")]
[TestCase("\r\n---")]
[TestCase("\n---\n")]
[TestCase("\r---\n")]
[TestCase("\r\n---\n")]
[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);
}
}
}

View File

@@ -0,0 +1,183 @@
using NUnit.Framework;
using static Markdig.Tests.TestRoundtrip;
namespace Markdig.Tests.RoundtripSpecs
{
[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")]
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("- 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("- 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("\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\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("- 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("\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("- 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("\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("- 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("\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("- 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("\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("- 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

@@ -23,7 +23,7 @@ namespace Markdig.Tests.Specs.JiraLinks
//
// The rules for detecting a link are:
//
// - The project key must be composed of one or more capitalized ASCII letter `[A-Z]+`
// - The project key must be composed of one or more capitalized ASCII letters or digits `[A-Z,0-9]+`
// - A single hyphen `-` must separate the project key and issue number.
// - The issue number is composed of 1 or more digits `[0, 9]+`
// - The reference must be preceded by either `(` or whitespace or EOF.
@@ -53,13 +53,13 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is a KIRA-1 issue
// This is a ABC4-123 issue
//
// Should be rendered as:
// <p>This is a <a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a> issue</p>
// <p>This is a <a href="http://your.company.abc/browse/ABC4-123" target="blank">ABC4-123</a> issue</p>
Console.WriteLine("Example 2\nSection Jira Links\n");
TestParser.TestSpec("This is a KIRA-1 issue", "<p>This is a <a href=\"http://your.company.abc/browse/KIRA-1\" target=\"blank\">KIRA-1</a> issue</p>", "jiralinks");
TestParser.TestSpec("This is a ABC4-123 issue", "<p>This is a <a href=\"http://your.company.abc/browse/ABC4-123\" target=\"blank\">ABC4-123</a> issue</p>", "jiralinks");
}
[Test]
@@ -69,16 +69,15 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is a Z-1 issue
// This is a ABC45-123 issue
//
// Should be rendered as:
// <p>This is a <a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a> issue</p>
// <p>This is a <a href="http://your.company.abc/browse/ABC45-123" target="blank">ABC45-123</a> issue</p>
Console.WriteLine("Example 3\nSection Jira Links\n");
TestParser.TestSpec("This is a Z-1 issue", "<p>This is a <a href=\"http://your.company.abc/browse/Z-1\" target=\"blank\">Z-1</a> issue</p>", "jiralinks");
TestParser.TestSpec("This is a ABC45-123 issue", "<p>This is a <a href=\"http://your.company.abc/browse/ABC45-123\" target=\"blank\">ABC45-123</a> issue</p>", "jiralinks");
}
// These are also valid links with `(` and `)`:
[Test]
public void JiraLinks_Example004()
{
@@ -86,13 +85,13 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is a (ABCD-123) issue
// This is a KIRA-1 issue
//
// Should be rendered as:
// <p>This is a (<a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a>) issue</p>
// <p>This is a <a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a> issue</p>
Console.WriteLine("Example 4\nSection Jira Links\n");
TestParser.TestSpec("This is a (ABCD-123) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/ABCD-123\" target=\"blank\">ABCD-123</a>) issue</p>", "jiralinks");
TestParser.TestSpec("This is a KIRA-1 issue", "<p>This is a <a href=\"http://your.company.abc/browse/KIRA-1\" target=\"blank\">KIRA-1</a> issue</p>", "jiralinks");
}
[Test]
@@ -102,15 +101,16 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is a (KIRA-1) issue
// This is a Z-1 issue
//
// Should be rendered as:
// <p>This is a (<a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a>) issue</p>
// <p>This is a <a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a> issue</p>
Console.WriteLine("Example 5\nSection Jira Links\n");
TestParser.TestSpec("This is a (KIRA-1) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/KIRA-1\" target=\"blank\">KIRA-1</a>) issue</p>", "jiralinks");
TestParser.TestSpec("This is a Z-1 issue", "<p>This is a <a href=\"http://your.company.abc/browse/Z-1\" target=\"blank\">Z-1</a> issue</p>", "jiralinks");
}
// These are also valid links with `(` and `)`:
[Test]
public void JiraLinks_Example006()
{
@@ -118,16 +118,15 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is a (Z-1) issue
// This is a (ABCD-123) issue
//
// Should be rendered as:
// <p>This is a (<a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a>) issue</p>
// <p>This is a (<a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a>) issue</p>
Console.WriteLine("Example 6\nSection Jira Links\n");
TestParser.TestSpec("This is a (Z-1) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/Z-1\" target=\"blank\">Z-1</a>) issue</p>", "jiralinks");
TestParser.TestSpec("This is a (ABCD-123) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/ABCD-123\" target=\"blank\">ABCD-123</a>) issue</p>", "jiralinks");
}
// These are not valid links:
[Test]
public void JiraLinks_Example007()
{
@@ -135,13 +134,13 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is not aJIRA-123 issue
// This is a (ABC4-123) issue
//
// Should be rendered as:
// <p>This is not aJIRA-123 issue</p>
// <p>This is a (<a href="http://your.company.abc/browse/ABC4-123" target="blank">ABC4-123</a>) issue</p>
Console.WriteLine("Example 7\nSection Jira Links\n");
TestParser.TestSpec("This is not aJIRA-123 issue", "<p>This is not aJIRA-123 issue</p>", "jiralinks");
TestParser.TestSpec("This is a (ABC4-123) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/ABC4-123\" target=\"blank\">ABC4-123</a>) issue</p>", "jiralinks");
}
[Test]
@@ -151,13 +150,13 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is not JIRA-123a issue
// This is a (KIRA-1) issue
//
// Should be rendered as:
// <p>This is not JIRA-123a issue</p>
// <p>This is a (<a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a>) issue</p>
Console.WriteLine("Example 8\nSection Jira Links\n");
TestParser.TestSpec("This is not JIRA-123a issue", "<p>This is not JIRA-123a issue</p>", "jiralinks");
TestParser.TestSpec("This is a (KIRA-1) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/KIRA-1\" target=\"blank\">KIRA-1</a>) issue</p>", "jiralinks");
}
[Test]
@@ -167,13 +166,94 @@ namespace Markdig.Tests.Specs.JiraLinks
// Section: Jira Links
//
// The following Markdown:
// This is a (Z-1) issue
//
// Should be rendered as:
// <p>This is a (<a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a>) issue</p>
Console.WriteLine("Example 9\nSection Jira Links\n");
TestParser.TestSpec("This is a (Z-1) issue", "<p>This is a (<a href=\"http://your.company.abc/browse/Z-1\" target=\"blank\">Z-1</a>) issue</p>", "jiralinks");
}
// These are not valid links:
[Test]
public void JiraLinks_Example010()
{
// Example 10
// Section: Jira Links
//
// The following Markdown:
// This is not aJIRA-123 issue
//
// Should be rendered as:
// <p>This is not aJIRA-123 issue</p>
Console.WriteLine("Example 10\nSection Jira Links\n");
TestParser.TestSpec("This is not aJIRA-123 issue", "<p>This is not aJIRA-123 issue</p>", "jiralinks");
}
[Test]
public void JiraLinks_Example011()
{
// Example 11
// Section: Jira Links
//
// The following Markdown:
// This is not 4JIRA-123 issue
//
// Should be rendered as:
// <p>This is not 4JIRA-123 issue</p>
Console.WriteLine("Example 11\nSection Jira Links\n");
TestParser.TestSpec("This is not 4JIRA-123 issue", "<p>This is not 4JIRA-123 issue</p>", "jiralinks");
}
[Test]
public void JiraLinks_Example012()
{
// Example 12
// Section: Jira Links
//
// The following Markdown:
// This is not JIRA-123a issue
//
// Should be rendered as:
// <p>This is not JIRA-123a issue</p>
Console.WriteLine("Example 12\nSection Jira Links\n");
TestParser.TestSpec("This is not JIRA-123a issue", "<p>This is not JIRA-123a issue</p>", "jiralinks");
}
[Test]
public void JiraLinks_Example013()
{
// Example 13
// Section: Jira Links
//
// The following Markdown:
// This is not JIRA- issue
//
// Should be rendered as:
// <p>This is not JIRA- issue</p>
Console.WriteLine("Example 9\nSection Jira Links\n");
Console.WriteLine("Example 13\nSection Jira Links\n");
TestParser.TestSpec("This is not JIRA- issue", "<p>This is not JIRA- issue</p>", "jiralinks");
}
[Test]
public void JiraLinks_Example014()
{
// Example 14
// Section: Jira Links
//
// The following Markdown:
// This is not JIR4- issue
//
// Should be rendered as:
// <p>This is not JIR4- issue</p>
Console.WriteLine("Example 14\nSection Jira Links\n");
TestParser.TestSpec("This is not JIR4- issue", "<p>This is not JIR4- issue</p>", "jiralinks");
}
}
}

View File

@@ -10,7 +10,7 @@ var pipeline = new MarkdownPipelineBuilder()
The rules for detecting a link are:
- The project key must be composed of one or more capitalized ASCII letter `[A-Z]+`
- The project key must be composed of one or more capitalized ASCII letters or digits `[A-Z,0-9]+`
- A single hyphen `-` must separate the project key and issue number.
- The issue number is composed of 1 or more digits `[0, 9]+`
- The reference must be preceded by either `(` or whitespace or EOF.
@@ -24,6 +24,18 @@ This is a ABCD-123 issue
<p>This is a <a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a> issue</p>
````````````````````````````````
```````````````````````````````` example
This is a ABC4-123 issue
.
<p>This is a <a href="http://your.company.abc/browse/ABC4-123" target="blank">ABC4-123</a> issue</p>
````````````````````````````````
```````````````````````````````` example
This is a ABC45-123 issue
.
<p>This is a <a href="http://your.company.abc/browse/ABC45-123" target="blank">ABC45-123</a> issue</p>
````````````````````````````````
```````````````````````````````` example
This is a KIRA-1 issue
.
@@ -44,6 +56,12 @@ This is a (ABCD-123) issue
<p>This is a (<a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a>) issue</p>
````````````````````````````````
```````````````````````````````` example
This is a (ABC4-123) issue
.
<p>This is a (<a href="http://your.company.abc/browse/ABC4-123" target="blank">ABC4-123</a>) issue</p>
````````````````````````````````
```````````````````````````````` example
This is a (KIRA-1) issue
.
@@ -64,6 +82,12 @@ This is not aJIRA-123 issue
<p>This is not aJIRA-123 issue</p>
````````````````````````````````
```````````````````````````````` example
This is not 4JIRA-123 issue
.
<p>This is not 4JIRA-123 issue</p>
````````````````````````````````
```````````````````````````````` example
This is not JIRA-123a issue
.
@@ -75,3 +99,9 @@ This is not JIRA- issue
.
<p>This is not JIRA- issue</p>
````````````````````````````````
```````````````````````````````` example
This is not JIR4- issue
.
<p>This is not JIR4- issue</p>
````````````````````````````````

View File

@@ -1,4 +1,4 @@
// 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.

View File

@@ -14,7 +14,7 @@ namespace Markdig.Tests
{
var inputTag = "<a>";
var text = new StringSlice(inputTag);
Assert.True(HtmlHelper.TryParseHtmlTag(text, out string outputTag));
Assert.True(HtmlHelper.TryParseHtmlTag(ref text, out string outputTag));
Assert.AreEqual(inputTag, outputTag);
}
@@ -23,7 +23,7 @@ namespace Markdig.Tests
{
var inputTag = "<a href='http://google.com'>";
var text = new StringSlice(inputTag);
Assert.True(HtmlHelper.TryParseHtmlTag(text, out string outputTag));
Assert.True(HtmlHelper.TryParseHtmlTag(ref text, out string outputTag));
Assert.AreEqual(inputTag, outputTag);
}
}

View File

@@ -15,7 +15,7 @@ namespace Markdig.Tests
public void TestUrlSimple()
{
var text = new StringSlice("toto tutu");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("toto", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -24,7 +24,7 @@ namespace Markdig.Tests
public void TestUrlUrl()
{
var text = new StringSlice("http://google.com)");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual(')', text.CurrentChar);
}
@@ -35,7 +35,7 @@ namespace Markdig.Tests
public void TestUrlTrailingFullStop(string uri)
{
var text = new StringSlice(uri);
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, true));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _, true));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}
@@ -44,7 +44,7 @@ namespace Markdig.Tests
public void TestUrlNestedParenthesis()
{
var text = new StringSlice("(toto)tutu(tata) nooo");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("(toto)tutu(tata)", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -53,7 +53,7 @@ namespace Markdig.Tests
public void TestUrlAlternate()
{
var text = new StringSlice("<toto_tata_tutu> nooo");
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, out _));
Assert.AreEqual("toto_tata_tutu", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -62,14 +62,14 @@ namespace Markdig.Tests
public void TestUrlAlternateInvalid()
{
var text = new StringSlice("<toto_tata_tutu");
Assert.False(LinkHelper.TryParseUrl(ref text, out string link));
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));
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual(@"tata\tutu'", title);
}
@@ -77,7 +77,7 @@ namespace Markdig.Tests
public void TestTitleSimpleAlternate()
{
var text = new StringSlice(@"""tata\tutu\"""" ");
Assert.True(LinkHelper.TryParseTitle(ref text, out string title));
Assert.True(LinkHelper.TryParseTitle(ref text, out string title, out _));
Assert.AreEqual(@"tata\tutu""", title);
Assert.AreEqual(' ', text.CurrentChar);
}

View File

@@ -11,11 +11,14 @@ namespace Markdig.Tests
[Test]
public void TestToHtml()
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/");
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
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]
@@ -24,49 +27,66 @@ namespace Markdig.Tests
var pipeline = new MarkdownPipelineBuilder()
.Build();
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
for (int i = 0; i < 5; i++)
{
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/", 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();
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 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);
}
}
[Test]
public void TestToHtmlWithWriter()
{
StringWriter writer = new StringWriter();
var writer = new StringWriter();
_ = 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);
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();
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
html = writer.ToString();
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 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 TestConvert()
{
StringWriter writer = new StringWriter();
HtmlRenderer renderer = new HtmlRenderer(writer);
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
_ = 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);
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);
@@ -74,9 +94,13 @@ namespace Markdig.Tests
.UseAdvancedExtensions()
.Build();
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
html = writer.ToString();
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.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]
@@ -88,62 +112,77 @@ namespace Markdig.Tests
.UsePreciseSourceLocation()
.Build();
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
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.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);
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()
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
for (int i = 0; i < 5; i++)
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestNormalizeWithWriter()
{
StringWriter writer = new StringWriter();
for (int i = 0; i < 5; i++)
{
var writer = new StringWriter();
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
}
}
[Test]
public void TestToPlainText()
{
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
Assert.AreEqual("Hello, world!\n", plainText);
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()
{
StringWriter writer = new StringWriter();
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

@@ -0,0 +1,17 @@
using NUnit.Framework;
namespace Markdig.Tests
{
[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")]
public void Test(string value, string expectedHtml)
{
Assert.AreEqual(expectedHtml, Markdown.ToHtml(value));
Assert.AreEqual(expectedHtml, Markdown.ToHtml(value.Replace("\n", "\r\n")));
}
}
}

View File

@@ -21,7 +21,8 @@ namespace Markdig.Tests
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
{
FencedChar = '`',
FencedCharCount = 4,
OpeningFencedCharCount = 4,
ClosingFencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
{

View File

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

View File

@@ -12,6 +12,16 @@ namespace Markdig.Tests
[TestFixture]
public class TestPlayParser
{
[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()
{

View File

@@ -37,20 +37,6 @@ namespace Markdig.Tests
Assert.That(html, Contains.Substring($"rel=\"{expected}\""));
}
[Test]
public void TestLinksWithNullRel()
{
var markdown = "[world](http://example.com)";
var pipeline = new MarkdownPipelineBuilder()
.UseReferralLinks(null)
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
Assert.That(html, !Contains.Substring("rel="));
}
[Test]
[TestCase(new[] { "noopener" }, "noopener")]
[TestCase(new[] { "nofollow" }, "nofollow")]

View File

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

View File

@@ -16,8 +16,8 @@ namespace Markdig.Tests
{
var text = new StringLineGroup(4)
{
new StringSlice("ABC"),
new StringSlice("E"),
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
};
@@ -35,8 +35,8 @@ namespace Markdig.Tests
{
var text = new StringLineGroup(4)
{
new StringSlice("XABC") { Start = 1},
new StringSlice("YYE") { Start = 2},
new StringSlice("XABC", NewLine.LineFeed) { Start = 1},
new StringSlice("YYE", NewLine.LineFeed) { Start = 2},
new StringSlice("ZZZF") { Start = 3 }
};
@@ -61,7 +61,7 @@ namespace Markdig.Tests
{
var text = new StringLineGroup(4)
{
new StringSlice("ABCD"),
new StringSlice("ABCD", NewLine.LineFeed),
new StringSlice("EF"),
}.ToCharIterator();
@@ -99,7 +99,7 @@ namespace Markdig.Tests
[Test]
public void TestStringLineGroupWithModifiedStart()
{
var line1 = new StringSlice(" ABC");
var line1 = new StringSlice(" ABC", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
@@ -115,7 +115,7 @@ namespace Markdig.Tests
[Test]
public void TestStringLineGroupWithTrim()
{
var line1 = new StringSlice(" ABC ");
var line1 = new StringSlice(" ABC ", NewLine.LineFeed);
line1.NextChar();
line1.NextChar();
@@ -133,8 +133,8 @@ namespace Markdig.Tests
{
var iterator = new StringLineGroup(4)
{
new StringSlice("ABC"),
new StringSlice("E"),
new StringSlice("ABC", NewLine.LineFeed),
new StringSlice("E", NewLine.LineFeed),
new StringSlice("F")
}.ToCharIterator();
@@ -153,5 +153,33 @@ namespace Markdig.Tests
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();
}
}
}
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -27,7 +27,7 @@ namespace Markdig.Extensions.Abbreviations
/// <summary>
/// Gets or sets the label.
/// </summary>
public string Label { get; set; }
public string? Label { get; set; }
/// <summary>
/// The text associated to this label.

View File

@@ -22,12 +22,12 @@ namespace Markdig.Extensions.Abbreviations
public static void AddAbbreviation(this MarkdownDocument document, string label, Abbreviation abbr)
{
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
if (label == null) ThrowHelper.ArgumentNullException_label();
if (abbr == null) ThrowHelper.ArgumentNullException(nameof(abbr));
if (document is null) ThrowHelper.ArgumentNullException(nameof(document));
if (label is null) ThrowHelper.ArgumentNullException_label();
if (abbr is null) ThrowHelper.ArgumentNullException(nameof(abbr));
var map = document.GetAbbreviations();
if (map == null)
if (map is null)
{
map = new Dictionary<string, Abbreviation>();
document.SetData(DocumentKey, map);
@@ -35,7 +35,7 @@ namespace Markdig.Extensions.Abbreviations
map[label] = abbr;
}
public static Dictionary<string, Abbreviation> GetAbbreviations(this MarkdownDocument document)
public static Dictionary<string, Abbreviation>? GetAbbreviations(this MarkdownDocument document)
{
return document.GetData(DocumentKey) as Dictionary<string, Abbreviation>;
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -14,13 +14,6 @@ namespace Markdig.Extensions.Abbreviations
[DebuggerDisplay("{Abbreviation}")]
public class AbbreviationInline : LeafInline
{
/// <summary>
/// Initializes a new instance of the <see cref="AbbreviationInline"/> class.
/// </summary>
public AbbreviationInline()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AbbreviationInline"/> class.
/// </summary>

View File

@@ -41,7 +41,7 @@ namespace Markdig.Extensions.Abbreviations
return BlockState.None;
}
if (!LinkHelper.TryParseLabel(ref slice, out string label, out SourceSpan labelSpan))
if (!LinkHelper.TryParseLabel(ref slice, out string? label, out SourceSpan labelSpan))
{
return BlockState.None;
}
@@ -51,7 +51,7 @@ namespace Markdig.Extensions.Abbreviations
{
return BlockState.None;
}
slice.NextChar();
slice.SkipChar();
slice.Trim();
@@ -73,13 +73,13 @@ namespace Markdig.Extensions.Abbreviations
return BlockState.BreakDiscard;
}
private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline inline)
private void DocumentOnProcessInlinesBegin(InlineProcessor inlineProcessor, Inline? inline)
{
inlineProcessor.Document.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
var abbreviations = inlineProcessor.Document.GetAbbreviations();
// Should not happen, but another extension could decide to remove them, so...
if (abbreviations == null)
if (abbreviations is null)
{
return;
}
@@ -89,10 +89,10 @@ namespace Markdig.Extensions.Abbreviations
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
{
var literal = (LiteralInline)processor.Inline;
var literal = (LiteralInline)processor.Inline!;
var originalLiteral = literal;
ContainerInline container = null;
ContainerInline? container = null;
// This is slow, but we don't have much the choice
var content = literal.Content;
@@ -127,7 +127,7 @@ namespace Markdig.Extensions.Abbreviations
var indexAfterMatch = i + match.Length;
// If we don't have a container, create a new one
if (container == null)
if (container is null)
{
container = literal.Parent ??
new ContainerInline
@@ -150,7 +150,7 @@ namespace Markdig.Extensions.Abbreviations
abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1;
// Append the previous literal
if (i > content.Start && literal.Parent == null)
if (i > content.Start && literal.Parent is null)
{
container.AppendChild(literal);
}

View File

@@ -74,15 +74,14 @@ namespace Markdig.Extensions.AutoIdentifiers
var text = headingLine.ToString();
var linkRef = new HeadingLinkReferenceDefinition()
var linkRef = new HeadingLinkReferenceDefinition(headingBlock)
{
Heading = headingBlock,
CreateLinkInline = CreateLinkInlineForHeading
};
var doc = processor.Document;
var dictionary = doc.GetData(this) as Dictionary<string, HeadingLinkReferenceDefinition>;
if (dictionary == null)
if (dictionary is null)
{
dictionary = new Dictionary<string, HeadingLinkReferenceDefinition>();
doc.SetData(this, dictionary);
@@ -95,11 +94,11 @@ namespace Markdig.Extensions.AutoIdentifiers
headingBlock.ProcessInlinesEnd += HeadingBlock_ProcessInlinesEnd;
}
private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline inline)
private void DocumentOnProcessInlinesBegin(InlineProcessor processor, Inline? inline)
{
var doc = processor.Document;
doc.ProcessInlinesBegin -= DocumentOnProcessInlinesBegin;
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this);
var dictionary = (Dictionary<string, HeadingLinkReferenceDefinition>)doc.GetData(this)!;
foreach (var keyPair in dictionary)
{
// Here we make sure that auto-identifiers will not override an existing link definition
@@ -107,7 +106,7 @@ namespace Markdig.Extensions.AutoIdentifiers
// If it is the case, we skip the auto identifier for the Heading
if (!doc.TryGetLinkReferenceDefinition(keyPair.Key, out var linkDef))
{
doc.SetLinkReferenceDefinition(keyPair.Key, keyPair.Value);
doc.SetLinkReferenceDefinition(keyPair.Key, keyPair.Value, true);
}
}
// Once we are done, we don't need to keep the intermediate dictionary around
@@ -118,7 +117,7 @@ namespace Markdig.Extensions.AutoIdentifiers
/// Callback when there is a reference to found to a heading.
/// Note that reference are only working if they are declared after.
/// </summary>
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline child)
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline? child)
{
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
return new LinkInline()
@@ -135,23 +134,23 @@ namespace Markdig.Extensions.AutoIdentifiers
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="inline">The inline.</param>
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline inline)
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline? inline)
{
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
if (identifiers == null)
if (identifiers is null)
{
identifiers = new HashSet<string>();
processor.Document.SetData(AutoIdentifierKey, identifiers);
}
var headingBlock = (HeadingBlock) processor.Block;
if (headingBlock.Inline == null)
var headingBlock = (HeadingBlock) processor.Block!;
if (headingBlock.Inline is null)
{
return;
}
// If id is already set, don't try to modify it
var attributes = processor.Block.GetAttributes();
var attributes = processor.Block!.GetAttributes();
if (attributes.Id != null)
{
return;
@@ -161,7 +160,7 @@ namespace Markdig.Extensions.AutoIdentifiers
var stripRenderer = rendererCache.Get();
stripRenderer.Render(headingBlock.Inline);
var headingText = stripRenderer.Writer.ToString();
var headingText = stripRenderer.Writer.ToString()!;
rendererCache.Release(stripRenderer);
// Urilize the link

View File

@@ -12,6 +12,11 @@ namespace Markdig.Extensions.AutoIdentifiers
/// <seealso cref="LinkReferenceDefinition" />
public class HeadingLinkReferenceDefinition : LinkReferenceDefinition
{
public HeadingLinkReferenceDefinition(HeadingBlock headling)
{
Heading = headling;
}
/// <summary>
/// Gets or sets the heading related to this link reference definition.
/// </summary>

View File

@@ -17,7 +17,7 @@ namespace Markdig.Extensions.AutoLinks
{
public readonly AutoLinkOptions Options;
public AutoLinkExtension(AutoLinkOptions options)
public AutoLinkExtension(AutoLinkOptions? options)
{
Options = options ?? new AutoLinkOptions();
}

View File

@@ -105,7 +105,8 @@ namespace Markdig.Extensions.AutoLinks
break;
}
if (!LinkHelper.TryParseUrl(ref slice, out string link, true))
// Parse URL
if (!LinkHelper.TryParseUrl(ref slice, out string? link, out _, true))
{
return false;
}

View File

@@ -2,11 +2,11 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Diagnostics;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
using System.Diagnostics;
namespace Markdig.Extensions.Citations
{
@@ -40,7 +40,7 @@ namespace Markdig.Extensions.Citations
}
}
private static string GetTag(EmphasisInline emphasisInline)
private static string? GetTag(EmphasisInline emphasisInline)
{
Debug.Assert(emphasisInline.DelimiterCount <= 2);
return emphasisInline.DelimiterCount == 2 && emphasisInline.DelimiterChar == '"' ? "cite" : null;

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
@@ -22,12 +23,40 @@ namespace Markdig.Extensions.CustomContainers
{
}
public string Info { get; set; }
public string Arguments { get; set; }
public int FencedCharCount { get; set; }
/// <inheritdoc />
public char FencedChar { get; set; }
/// <inheritdoc />
public int OpeningFencedCharCount { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterFencedChar { get; set; }
/// <inheritdoc />
public string? Info { get; set; }
/// <inheritdoc />
public StringSlice UnescapedInfo { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterInfo { get; set; }
/// <inheritdoc />
public string? Arguments { get; set; }
/// <inheritdoc />
public StringSlice UnescapedArguments { get; set; }
/// <inheritdoc />
public StringSlice TriviaAfterArguments { get; set; }
/// <inheritdoc />
public NewLine InfoNewLine { get; set; }
/// <inheritdoc />
public StringSlice TriviaBeforeClosingFence { get; set; }
/// <inheritdoc />
public int ClosingFencedCharCount { get; set; }
}
}

View File

@@ -15,7 +15,7 @@ namespace Markdig.Extensions.CustomContainers
{
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)
{
renderer.Write("<span").WriteAttributes(obj).Write(">");
renderer.Write("<span").WriteAttributes(obj).Write('>');
renderer.WriteChildren(obj);
renderer.Write("</span>");
}

View File

@@ -18,7 +18,7 @@ namespace Markdig.Extensions.CustomContainers
renderer.EnsureLine();
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<div").WriteAttributes(obj).Write(">");
renderer.Write("<div").WriteAttributes(obj).Write('>');
}
// We don't escape a CustomContainer
renderer.WriteChildren(obj);

View File

@@ -25,7 +25,7 @@ namespace Markdig.Extensions.DefinitionLists
public override BlockState TryOpen(BlockProcessor processor)
{
var paragraphBlock = processor.LastBlock as ParagraphBlock;
if (processor.IsCodeIndent || paragraphBlock == null || paragraphBlock.LastLine - processor.LineIndex > 1)
if (processor.IsCodeIndent || paragraphBlock is null || paragraphBlock.LastLine - processor.LineIndex > 1)
{
return BlockState.None;
}
@@ -50,7 +50,7 @@ namespace Markdig.Extensions.DefinitionLists
processor.GoToColumn(column + 4);
}
var previousParent = paragraphBlock.Parent;
var previousParent = paragraphBlock.Parent!;
var currentDefinitionList = GetCurrentDefinitionList(paragraphBlock, previousParent);
processor.Discard(paragraphBlock);
@@ -61,7 +61,7 @@ namespace Markdig.Extensions.DefinitionLists
paragraphBlock.Parent.Remove(paragraphBlock);
}
if (currentDefinitionList == null)
if (currentDefinitionList is null)
{
currentDefinitionList = new DefinitionList(this)
{
@@ -90,7 +90,7 @@ namespace Markdig.Extensions.DefinitionLists
Span = new SourceSpan(paragraphBlock.Span.Start, paragraphBlock.Span.End),
IsOpen = false
};
term.AppendLine(ref line.Slice, line.Column, line.Line, line.Position);
term.AppendLine(ref line.Slice, line.Column, line.Line, line.Position, processor.TrackTrivia);
definitionItem.Add(term);
}
currentDefinitionList.Add(definitionItem);
@@ -102,7 +102,7 @@ namespace Markdig.Extensions.DefinitionLists
return BlockState.Continue;
}
private static DefinitionList GetCurrentDefinitionList(ParagraphBlock paragraphBlock, ContainerBlock previousParent)
private static DefinitionList? GetCurrentDefinitionList(ParagraphBlock paragraphBlock, ContainerBlock previousParent)
{
var index = previousParent.IndexOf(paragraphBlock) - 1;
if (index < 0) return null;
@@ -124,11 +124,11 @@ namespace Markdig.Extensions.DefinitionLists
return BlockState.Continue;
}
var list = (DefinitionList)definitionItem.Parent;
var list = (DefinitionList)definitionItem.Parent!;
var lastBlankLine = definitionItem.LastChild as BlankLineBlock;
// Check if we have another definition list
if (Array.IndexOf(OpeningCharacters, processor.CurrentChar) >= 0)
if (Array.IndexOf(OpeningCharacters!, processor.CurrentChar) >= 0)
{
var startPosition = processor.Start;
var column = processor.ColumnBeforeIndent;
@@ -145,7 +145,7 @@ namespace Markdig.Extensions.DefinitionLists
definitionItem.RemoveAt(definitionItem.Count - 1);
}
list.Span.End = list.LastChild.Span.End;
list.Span.End = list.LastChild!.Span.End;
return BlockState.None;
}
@@ -171,7 +171,7 @@ namespace Markdig.Extensions.DefinitionLists
var isBreakable = definitionItem.LastChild?.IsBreakable ?? true;
if (processor.IsBlankLine)
{
if (lastBlankLine == null && isBreakable)
if (lastBlankLine is null && isBreakable)
{
definitionItem.Add(new BlankLineBlock());
}
@@ -179,7 +179,7 @@ namespace Markdig.Extensions.DefinitionLists
}
var paragraphBlock = definitionItem.LastChild as ParagraphBlock;
if (lastBlankLine == null && paragraphBlock != null)
if (lastBlankLine is null && paragraphBlock != null)
{
return BlockState.Continue;
}
@@ -190,7 +190,7 @@ namespace Markdig.Extensions.DefinitionLists
definitionItem.RemoveAt(definitionItem.Count - 1);
}
list.Span.End = list.LastChild.Span.End;
list.Span.End = list.LastChild!.Span.End;
return BlockState.Break;
}
}

View File

@@ -17,7 +17,7 @@ namespace Markdig.Extensions.DefinitionLists
protected override void Write(HtmlRenderer renderer, DefinitionList list)
{
renderer.EnsureLine();
renderer.Write("<dl").WriteAttributes(list).WriteLine(">");
renderer.Write("<dl").WriteAttributes(list).WriteLine('>');
foreach (var item in list)
{
bool hasOpendd = false;
@@ -41,7 +41,7 @@ namespace Markdig.Extensions.DefinitionLists
hasOpendd = false;
countdd = 0;
}
renderer.Write("<dt").WriteAttributes(definitionTerm).Write(">");
renderer.Write("<dt").WriteAttributes(definitionTerm).Write('>');
renderer.WriteLeafInline(definitionTerm);
renderer.WriteLine("</dt>");
}
@@ -49,13 +49,13 @@ namespace Markdig.Extensions.DefinitionLists
{
if (!hasOpendd)
{
renderer.Write("<dd").WriteAttributes(definitionItem).Write(">");
renderer.Write("<dd").WriteAttributes(definitionItem).Write('>');
countdd = 0;
hasOpendd = true;
}
var nextTerm = i + 1 < definitionItem.Count ? definitionItem[i + 1] : null;
bool isSimpleParagraph = (nextTerm == null || nextTerm is DefinitionItem) && countdd == 0 &&
bool isSimpleParagraph = (nextTerm is null || nextTerm is DefinitionItem) && countdd == 0 &&
definitionTermOrContent is ParagraphBlock;
var saveImplicitParagraph = renderer.ImplicitParagraph;

View File

@@ -21,7 +21,7 @@ namespace Markdig.Extensions.Diagrams
{
if (renderer is HtmlRenderer htmlRenderer)
{
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>()!;
// TODO: Add other well known diagram languages
codeRenderer.BlocksAsDiv.Add("mermaid");
codeRenderer.BlocksAsDiv.Add("nomnoml");

View File

@@ -34,6 +34,6 @@ namespace Markdig.Extensions.Emoji
/// <summary>
/// Gets or sets the original match string (either an emoji shortcode or a text smiley)
/// </summary>
public string Match { get; set; }
public string? Match { get; set; }
}
}

View File

@@ -1745,10 +1745,10 @@ namespace Markdig.Extensions.Emoji
/// </summary>
public EmojiMapping(IDictionary<string, string> shortcodeToUnicode, IDictionary<string, string> smileyToShortcode)
{
if (shortcodeToUnicode == null)
if (shortcodeToUnicode is null)
ThrowHelper.ArgumentNullException(nameof(shortcodeToUnicode));
if (smileyToShortcode == null)
if (smileyToShortcode is null)
ThrowHelper.ArgumentNullException(nameof(smileyToShortcode));
// Build emojis and smileys CompactPrefixTree
@@ -1778,7 +1778,7 @@ namespace Markdig.Extensions.Emoji
if (string.IsNullOrEmpty(smiley.Key) || string.IsNullOrEmpty(smiley.Value))
ThrowHelper.ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(smileyToShortcode));
if (!shortcodeToUnicode.TryGetValue(smiley.Value, out string unicode))
if (!shortcodeToUnicode.TryGetValue(smiley.Value, out string? unicode))
ThrowHelper.ArgumentException(string.Format("Invalid smiley target: {0} is not present in the emoji shortcodes dictionary", smiley.Value));
firstChars.Add(smiley.Key[0]);

View File

@@ -102,7 +102,7 @@ namespace Markdig.Extensions.EmphasisExtras
}
}
private string GetTag(EmphasisInline emphasisInline)
private string? GetTag(EmphasisInline emphasisInline)
{
var c = emphasisInline.DelimiterChar;
switch (c)

View File

@@ -61,7 +61,7 @@ namespace Markdig.Extensions.Figures
Column = column + line.Start - startPosition,
IsOpen = false
};
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition, processor.TrackTrivia);
figure.Add(caption);
}
processor.NewBlocks.Push(figure);
@@ -96,7 +96,7 @@ namespace Markdig.Extensions.Figures
Column = column + line.Start - startPosition,
IsOpen = false
};
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition, processor.TrackTrivia);
figure.Add(caption);
}

View File

@@ -16,7 +16,7 @@ namespace Markdig.Extensions.Figures
protected override void Write(HtmlRenderer renderer, FigureCaption obj)
{
renderer.EnsureLine();
renderer.Write("<figcaption").WriteAttributes(obj).Write(">");
renderer.Write("<figcaption").WriteAttributes(obj).Write('>');
renderer.WriteLeafInline(obj);
renderer.WriteLine("</figcaption>");
}

View File

@@ -16,14 +16,13 @@ namespace Markdig.Extensions.Footnotes
{
public Footnote(BlockParser parser) : base(parser)
{
Links = new List<FootnoteLink>();
Order = -1;
}
/// <summary>
/// Gets or sets the label used by this footnote.
/// </summary>
public string Label { get; set; }
public string? Label { get; set; }
/// <summary>
/// Gets or sets the order of this footnote (determined by the order of the <see cref="FootnoteLink"/> in the document)
@@ -33,7 +32,7 @@ namespace Markdig.Extensions.Footnotes
/// <summary>
/// Gets the links referencing this footnote.
/// </summary>
public List<FootnoteLink> Links { get; private set; }
public List<FootnoteLink> Links { get; } = new ();
/// <summary>
/// The label span

View File

@@ -12,6 +12,11 @@ namespace Markdig.Extensions.Footnotes
/// <seealso cref="Inline" />
public class FootnoteLink : Inline
{
public FootnoteLink(Footnote footnote)
{
Footnote = footnote;
}
/// <summary>
/// Gets or sets a value indicating whether this instance is back link (from a footnote to the link)
/// </summary>

View File

@@ -12,6 +12,11 @@ namespace Markdig.Extensions.Footnotes
/// <seealso cref="LinkReferenceDefinition" />
public class FootnoteLinkReferenceDefinition : LinkReferenceDefinition
{
public FootnoteLinkReferenceDefinition(Footnote footnote)
{
Footnote = footnote;
}
/// <summary>
/// Gets or sets the footnote related to this link reference definition.
/// </summary>

View File

@@ -41,7 +41,7 @@ namespace Markdig.Extensions.Footnotes
var saved = processor.Column;
int start = processor.Start;
if (!LinkHelper.TryParseLabel(ref processor.Line, false, out string label, out SourceSpan labelSpan) || !label.StartsWith("^") || processor.CurrentChar != ':')
if (!LinkHelper.TryParseLabel(ref processor.Line, false, out string? label, out SourceSpan labelSpan) || !label.StartsWith("^") || processor.CurrentChar != ':')
{
processor.GoToColumn(saved);
return BlockState.None;
@@ -70,16 +70,15 @@ namespace Markdig.Extensions.Footnotes
}
footnotes.Add(footnote);
var linkRef = new FootnoteLinkReferenceDefinition()
var linkRef = new FootnoteLinkReferenceDefinition(footnote)
{
Footnote = footnote,
CreateLinkInline = CreateLinkToFootnote,
Line = processor.LineIndex,
Span = new SourceSpan(start, processor.Start - 2), // account for ]:
LabelSpan = labelSpan,
Label = label
};
processor.Document.SetLinkReferenceDefinition(footnote.Label, linkRef);
processor.Document.SetLinkReferenceDefinition(footnote.Label, linkRef, true);
processor.NewBlocks.Push(footnote);
return BlockState.Continue;
}
@@ -130,12 +129,12 @@ namespace Markdig.Extensions.Footnotes
/// </summary>
/// <param name="state">The processor.</param>
/// <param name="inline">The inline.</param>
private void Document_ProcessInlinesEnd(InlineProcessor state, Inline inline)
private void Document_ProcessInlinesEnd(InlineProcessor state, Inline? inline)
{
// Unregister
state.Document.ProcessInlinesEnd -= Document_ProcessInlinesEnd;
var footnotes = ((FootnoteGroup)state.Document.GetData(DocumentKey));
var footnotes = (FootnoteGroup)state.Document.GetData(DocumentKey)!;
// Remove the footnotes from the document and readd them at the end
state.Document.Remove(footnotes);
state.Document.Add(footnotes);
@@ -166,7 +165,7 @@ namespace Markdig.Extensions.Footnotes
// Insert all footnote backlinks
var paragraphBlock = footnote.LastChild as ParagraphBlock;
if (paragraphBlock == null)
if (paragraphBlock is null)
{
paragraphBlock = new ParagraphBlock();
footnote.Add(paragraphBlock);
@@ -180,28 +179,27 @@ namespace Markdig.Extensions.Footnotes
{
linkIndex++;
link.Index = linkIndex;
var backLink = new FootnoteLink()
var backLink = new FootnoteLink(footnote)
{
Index = linkIndex,
IsBackLink = true,
Footnote = footnote
IsBackLink = true
};
paragraphBlock.Inline.AppendChild(backLink);
}
}
}
private static Inline CreateLinkToFootnote(InlineProcessor state, LinkReferenceDefinition linkRef, Inline child)
private static Inline CreateLinkToFootnote(InlineProcessor state, LinkReferenceDefinition linkRef, Inline? child)
{
var footnote = ((FootnoteLinkReferenceDefinition)linkRef).Footnote;
if (footnote.Order < 0)
{
var footnotes = (FootnoteGroup)state.Document.GetData(DocumentKey);
var footnotes = (FootnoteGroup)state.Document.GetData(DocumentKey)!;
footnotes.CurrentOrder++;
footnote.Order = footnotes.CurrentOrder;
}
var link = new FootnoteLink() {Footnote = footnote};
var link = new FootnoteLink(footnote);
footnote.Links.Add(link);
return link;

View File

@@ -51,7 +51,7 @@ namespace Markdig.Extensions.GenericAttributes
var copy = line;
copy.Start = indexOfAttributes;
var startOfAttributes = copy.Start;
if (GenericAttributesParser.TryParse(ref copy, out HtmlAttributes attributes))
if (GenericAttributesParser.TryParse(ref copy, out HtmlAttributes? attributes))
{
var htmlAttributes = block.GetAttributes();
attributes.CopyTo(htmlAttributes);

View File

@@ -3,6 +3,8 @@
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
@@ -28,7 +30,7 @@ namespace Markdig.Extensions.GenericAttributes
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
var startPosition = slice.Start;
if (TryParse(ref slice, out HtmlAttributes attributes))
if (TryParse(ref slice, out HtmlAttributes? attributes))
{
var inline = processor.Inline;
@@ -45,16 +47,16 @@ namespace Markdig.Extensions.GenericAttributes
}
}
}
var objectToAttach = inline == null || inline == processor.Root ? (MarkdownObject) processor.Block : inline;
var objectToAttach = inline is null || inline == processor.Root ? (MarkdownObject)processor.Block! : inline;
// If the current block is a Paragraph, but only the HtmlAttributes is used,
// Try to attach the attributes to the following block
if (objectToAttach is ParagraphBlock paragraph &&
paragraph.Inline.FirstChild == null &&
processor.Inline == null &&
paragraph.Inline!.FirstChild is null &&
processor.Inline is null &&
slice.IsEmptyOrWhitespace())
{
var parent = paragraph.Parent;
var parent = paragraph.Parent!;
var indexOfParagraph = parent.IndexOf(paragraph);
if (indexOfParagraph + 1 < parent.Count)
{
@@ -86,7 +88,7 @@ namespace Markdig.Extensions.GenericAttributes
/// <param name="slice">The slice to parse.</param>
/// <param name="attributes">The output attributes or null if not found or invalid</param>
/// <returns><c>true</c> if parsing the HTML attributes was successful</returns>
public static bool TryParse(ref StringSlice slice, out HtmlAttributes attributes)
public static bool TryParse(ref StringSlice slice, [NotNullWhen(true)] out HtmlAttributes? attributes)
{
attributes = null;
if (slice.PeekCharExtra(-1) == '{')
@@ -96,9 +98,9 @@ namespace Markdig.Extensions.GenericAttributes
var line = slice;
string id = null;
List<string> classes = null;
List<KeyValuePair<string, string>> properties = null;
string? id = null;
List<string>? classes = null;
List<KeyValuePair<string, string?>>? properties = null;
bool isValid = false;
var c = line.NextChar();
@@ -107,7 +109,7 @@ namespace Markdig.Extensions.GenericAttributes
if (c == '}')
{
isValid = true;
line.NextChar(); // skip }
line.SkipChar(); // skip }
break;
}
@@ -135,7 +137,7 @@ namespace Markdig.Extensions.GenericAttributes
var text = slice.Text.Substring(start, end - start + 1);
if (isClass)
{
if (classes == null)
if (classes is null)
{
classes = new List<string>();
}
@@ -175,12 +177,10 @@ namespace Markdig.Extensions.GenericAttributes
// Handle boolean properties that are not followed by =
if ((hasSpace && (c == '.' || c == '#' || IsStartAttributeName(c))) || c == '}')
{
if (properties == null)
{
properties = new List<KeyValuePair<string, string>>();
}
properties ??= new ();
// Add a null value for the property
properties.Add(new KeyValuePair<string, string>(name, null));
properties.Add(new KeyValuePair<string, string?>(name, null));
continue;
}
@@ -191,7 +191,7 @@ namespace Markdig.Extensions.GenericAttributes
}
// Go to next char, skip any spaces
line.NextChar();
line.SkipChar();
line.TrimStart();
int startValue = -1;
@@ -245,11 +245,8 @@ namespace Markdig.Extensions.GenericAttributes
var value = slice.Text.Substring(startValue, endValue - startValue + 1);
if (properties == null)
{
properties = new List<KeyValuePair<string, string>>();
}
properties.Add(new KeyValuePair<string, string>(name, value));
properties ??= new();
properties.Add(new KeyValuePair<string, string?>(name, value));
continue;
}

View File

@@ -10,6 +10,7 @@ using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Collections.Generic;
using System.Diagnostics;
namespace Markdig.Extensions.Globalization
{
@@ -51,7 +52,7 @@ namespace Markdig.Extensions.Globalization
}
private bool ShouldBeRightToLeft(MarkdownObject item)
private static bool ShouldBeRightToLeft(MarkdownObject item)
{
if (item is IEnumerable<MarkdownObject> container)
{
@@ -67,7 +68,7 @@ namespace Markdig.Extensions.Globalization
}
else if (item is LeafBlock leaf)
{
return ShouldBeRightToLeft(leaf.Inline);
return ShouldBeRightToLeft(leaf.Inline!);
}
else if (item is LiteralInline literal)
{
@@ -76,7 +77,7 @@ namespace Markdig.Extensions.Globalization
foreach (var paragraph in item.Descendants<ParagraphBlock>())
{
foreach (var inline in paragraph.Inline)
foreach (var inline in paragraph.Inline!)
{
if (inline is LiteralInline literal)
{
@@ -88,14 +89,30 @@ namespace Markdig.Extensions.Globalization
return false;
}
private bool StartsWithRtlCharacter(StringSlice slice)
private static bool StartsWithRtlCharacter(StringSlice slice)
{
foreach (var c in CharHelper.ToUtf32(slice))
for (int i = slice.Start; i <= slice.End; i++)
{
if (CharHelper.IsRightToLeft(c))
if (slice[i] < 128)
{
continue;
}
int rune;
if (CharHelper.IsHighSurrogate(slice[i]) && i < slice.End && CharHelper.IsLowSurrogate(slice[i + 1]))
{
Debug.Assert(char.IsSurrogatePair(slice[i], slice[i + 1]));
rune = char.ConvertToUtf32(slice[i], slice[i + 1]);
}
else
{
rune = slice[i];
}
if (CharHelper.IsRightToLeft(rune))
return true;
else if (CharHelper.IsLeftToRight(c))
if (CharHelper.IsLeftToRight(rune))
return false;
}

View File

@@ -40,8 +40,14 @@ namespace Markdig.Extensions.JiraLinks
var startKey = slice.Start;
var endKey = slice.Start;
//read as many uppercase characters as required - project key
while (current.IsAlphaUpper())
// the first character of the key can not be a digit.
if (current.IsDigit())
{
return false;
}
// read as many uppercase characters or digits as required - project key
while (current.IsAlphaUpper() || current.IsDigit())
{
endKey = slice.Start;
current = slice.NextChar();

View File

@@ -1,5 +1,5 @@
// 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 Markdig.Renderers;
@@ -16,11 +16,19 @@ namespace Markdig.Extensions.Mathematics
protected override void Write(HtmlRenderer renderer, MathBlock obj)
{
renderer.EnsureLine();
renderer.Write("<div").WriteAttributes(obj).WriteLine(">");
renderer.WriteLine("\\[");
renderer.WriteLeafRawLines(obj, true, true);
renderer.Write("\\]");
renderer.WriteLine("</div>");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<div").WriteAttributes(obj).WriteLine(">");
renderer.WriteLine("\\[");
}
renderer.WriteLeafRawLines(obj, true, renderer.EnableHtmlEscape);
if (renderer.EnableHtmlForBlock)
{
renderer.Write("\\]");
renderer.WriteLine("</div>");
}
}
}
}

View File

@@ -1,5 +1,5 @@
// 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 Markdig.Renderers;
@@ -15,9 +15,24 @@ namespace Markdig.Extensions.Mathematics
{
protected override void Write(HtmlRenderer renderer, MathInline obj)
{
renderer.Write("<span").WriteAttributes(obj).Write(">\\(");
renderer.WriteEscape(ref obj.Content);
renderer.Write("\\)</span>");
if (renderer.EnableHtmlForInline)
{
renderer.Write("<span").WriteAttributes(obj).Write(">\\(");
}
if (renderer.EnableHtmlEscape)
{
renderer.WriteEscape(ref obj.Content);
}
else
{
renderer.Write(ref obj.Content);
}
if (renderer.EnableHtmlForInline)
{
renderer.Write("\\)</span>");
}
}
}
}

View File

@@ -5,6 +5,7 @@
using Markdig.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Markdig.Extensions.MediaLinks
@@ -13,12 +14,23 @@ namespace Markdig.Extensions.MediaLinks
{
private sealed class DelegateProvider : IHostProvider
{
public string HostPrefix { get; set; }
public Func<Uri, string> Delegate { get; set; }
public bool AllowFullScreen { get; set; } = true;
public string Class { get; set; }
public DelegateProvider(string hostPrefix, Func<Uri, string?> handler, bool allowFullscreen = true, string? className = null)
{
HostPrefix = hostPrefix;
Delegate = handler;
AllowFullScreen = allowFullscreen;
Class = className;
}
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
public string HostPrefix { get; }
public Func<Uri, string?> Delegate { get; }
public bool AllowFullScreen { get; }
public string? Class { get; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, [NotNullWhen(true)] out string? iframeUrl)
{
if (!mediaUri.Host.StartsWith(HostPrefix, StringComparison.OrdinalIgnoreCase))
{
@@ -38,14 +50,14 @@ namespace Markdig.Extensions.MediaLinks
/// <param name="allowFullScreen">Should the generated iframe has allowfullscreen attribute.</param>
/// <param name="iframeClass">"class" attribute of generated iframe.</param>
/// <returns>A <see cref="IHostProvider"/> with delegate handler.</returns>
public static IHostProvider Create(string hostPrefix, Func<Uri, string> handler, bool allowFullScreen = true, string iframeClass = null)
public static IHostProvider Create(string hostPrefix, Func<Uri, string?> handler, bool allowFullScreen = true, string? iframeClass = null)
{
if (string.IsNullOrEmpty(hostPrefix))
ThrowHelper.ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
if (handler == null)
if (handler is null)
ThrowHelper.ArgumentNullException(nameof(handler));
return new DelegateProvider { HostPrefix = hostPrefix, Delegate = handler, AllowFullScreen = allowFullScreen, Class = iframeClass };
return new DelegateProvider(hostPrefix, handler, allowFullScreen, iframeClass);
}
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
@@ -67,7 +79,7 @@ namespace Markdig.Extensions.MediaLinks
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
}
private static string YouTube(Uri uri)
private static string? YouTube(Uri uri)
{
string uriPath = uri.AbsolutePath;
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
@@ -80,20 +92,20 @@ namespace Markdig.Extensions.MediaLinks
}
var queryParams = SplitQuery(uri);
return BuildYouTubeIframeUrl(
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
queryParams.FirstOrDefault(p => p.StartsWith("v=", StringComparison.Ordinal))?.Substring(2),
queryParams.FirstOrDefault(p => p.StartsWith("t=", StringComparison.Ordinal))?.Substring(2)
);
}
private static string YouTubeShortened(Uri uri)
private static string? YouTubeShortened(Uri uri)
{
return BuildYouTubeIframeUrl(
uri.AbsolutePath.Substring(1),
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t=", StringComparison.Ordinal))?.Substring(2)
);
}
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
private static string? BuildYouTubeIframeUrl(string? videoId, string? startTime)
{
if (string.IsNullOrEmpty(videoId))
{
@@ -103,31 +115,27 @@ namespace Markdig.Extensions.MediaLinks
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
}
private static string Vimeo(Uri uri)
private static string? Vimeo(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://player.vimeo.com/video/{ items[items.Length - 1] }" : null;
}
private static string Odnoklassniki(Uri uri)
private static string? Odnoklassniki(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://ok.ru/videoembed/{ items[items.Length - 1] }" : null;
}
private static string Yandex(Uri uri)
private static string? Yandex(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
var albumKeyword
= items.Skip(0).FirstOrDefault();
var albumId
= items.Skip(1).FirstOrDefault();
var trackKeyword
= items.Skip(2).FirstOrDefault();
var trackId
= items.Skip(3).FirstOrDefault();
string[] items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
string? albumKeyword = items.Skip(0).FirstOrDefault();
string? albumId = items.Skip(1).FirstOrDefault();
string? trackKeyword = items.Skip(2).FirstOrDefault();
string? trackId = items.Skip(3).FirstOrDefault();
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
if (albumKeyword != "album" || albumId is null || trackKeyword != "track" || trackId is null)
{
return null;
}

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
namespace Markdig.Extensions.MediaLinks
{
@@ -14,7 +15,7 @@ namespace Markdig.Extensions.MediaLinks
/// <summary>
/// "class" attribute of generated iframe.
/// </summary>
string Class { get; }
string? Class { get; }
/// <summary>
/// Generate url for iframe.
@@ -23,7 +24,7 @@ namespace Markdig.Extensions.MediaLinks
/// <param name="isSchemaRelative"><see langword="true"/> if <paramref name="mediaUri"/> is a schema relative uri, i.e. uri starts with "//".</param>
/// <param name="iframeUrl">Generated url for iframe.</param>
/// <seealso href="https://tools.ietf.org/html/rfc3986#section-4.2"/>
bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl);
bool TryHandle(Uri mediaUri, bool isSchemaRelative, [NotNullWhen(true)] out string? iframeUrl);
/// <summary>
/// Should the generated iframe has allowfullscreen attribute.

View File

@@ -20,7 +20,7 @@ namespace Markdig.Extensions.MediaLinks
{
}
public MediaLinkExtension(MediaOptions options)
public MediaLinkExtension(MediaOptions? options)
{
Options = options ?? new MediaOptions();
}
@@ -46,18 +46,18 @@ namespace Markdig.Extensions.MediaLinks
private bool TryLinkInlineRenderer(HtmlRenderer renderer, LinkInline linkInline)
{
if (!linkInline.IsImage || linkInline.Url == null)
if (!linkInline.IsImage || linkInline.Url is null)
{
return false;
}
bool isSchemaRelative = false;
// Only process absolute Uri
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out Uri uri) || !uri.IsAbsoluteUri)
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out Uri? uri) || !uri.IsAbsoluteUri)
{
// see https://tools.ietf.org/html/rfc3986#section-4.2
// since relative uri doesn't support many properties, "http" is used as a placeholder here.
if (linkInline.Url.StartsWith("//") && Uri.TryCreate("http:" + linkInline.Url, UriKind.Absolute, out uri))
if (linkInline.Url.StartsWith("//", StringComparison.Ordinal) && Uri.TryCreate("http:" + linkInline.Url, UriKind.Absolute, out uri))
{
isSchemaRelative = true;
}
@@ -98,10 +98,10 @@ namespace Markdig.Extensions.MediaLinks
// Otherwise try to detect if we have an audio/video from the file extension
var lastDot = path.LastIndexOf('.');
if (lastDot >= 0 &&
Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out string mimeType))
Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out string? mimeType))
{
var htmlAttributes = GetHtmlAttributes(linkInline);
var isAudio = mimeType.StartsWith("audio");
var isAudio = mimeType.StartsWith("audio", StringComparison.Ordinal);
var tagType = isAudio ? "audio" : "video";
renderer.Write($"<{tagType}");
@@ -126,9 +126,8 @@ namespace Markdig.Extensions.MediaLinks
private bool TryRenderIframeFromKnownProviders(Uri uri, bool isSchemaRelative, HtmlRenderer renderer, LinkInline linkInline)
{
IHostProvider foundProvider = null;
string iframeUrl = null;
IHostProvider? foundProvider = null;
string? iframeUrl = null;
foreach (var provider in Options.Hosts)
{
if (!provider.TryHandle(uri, isSchemaRelative, out iframeUrl))
@@ -137,7 +136,7 @@ namespace Markdig.Extensions.MediaLinks
break;
}
if (foundProvider == null)
if (foundProvider is null)
{
return false;
}
@@ -145,7 +144,7 @@ namespace Markdig.Extensions.MediaLinks
var htmlAttributes = GetHtmlAttributes(linkInline);
renderer.Write("<iframe src=\"");
renderer.WriteEscapeUrl(iframeUrl);
renderer.Write("\"");
renderer.Write('"');
if (!string.IsNullOrEmpty(Options.Width))
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
@@ -156,8 +155,8 @@ namespace Markdig.Extensions.MediaLinks
if (!string.IsNullOrEmpty(Options.Class))
htmlAttributes.AddClass(Options.Class);
if (!string.IsNullOrEmpty(foundProvider.Class))
htmlAttributes.AddClass(foundProvider.Class);
if (foundProvider.Class is { Length: > 0 } className)
htmlAttributes.AddClass(className);
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
if (foundProvider.AllowFullScreen)

View File

@@ -1,4 +1,4 @@
// 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.
@@ -36,7 +36,7 @@ namespace Markdig.Extensions.PragmaLines
{
var attribute = block.GetAttributes();
var pragmaId = GetPragmaId(block);
if ( attribute.Id == null)
if ( attribute.Id is null)
{
attribute.Id = pragmaId;
}
@@ -49,8 +49,7 @@ namespace Markdig.Extensions.PragmaLines
var tag = $"<a id=\"{pragmaId}\"></a>";
if (heading?.Inline?.FirstChild != null)
{
heading.Inline.FirstChild.InsertBefore(new HtmlInline() { Tag = tag });
heading.Inline.FirstChild.InsertBefore(new HtmlInline(tag));
}
else
{

View File

@@ -2,12 +2,11 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
namespace Markdig.Extensions.ReferralLinks
{
@@ -15,7 +14,7 @@ namespace Markdig.Extensions.ReferralLinks
{
public ReferralLinksExtension(string[] rels)
{
Rels = rels?.ToList();
Rels = rels?.ToList() ?? throw new ArgumentNullException(nameof(rels));
}
public List<string> Rels { get; }
@@ -26,8 +25,7 @@ namespace Markdig.Extensions.ReferralLinks
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
string relString = Rels == null? null :
string.Join(" ", Rels.Where(r => !string.IsNullOrEmpty(r)));
string relString = string.Join(" ", Rels.Where(r => !string.IsNullOrEmpty(r)));
var linkRenderer = renderer.ObjectRenderers.Find<LinkInlineRenderer>();
if (linkRenderer != null)

View File

@@ -24,7 +24,7 @@ namespace Markdig.Extensions.SelfPipeline
/// <param name="tag">The matching start tag.</param>
/// <param name="defaultExtensions">The default extensions.</param>
/// <exception cref="ArgumentException">Tag cannot contain angle brackets</exception>
public SelfPipelineExtension(string tag = null, string defaultExtensions = null)
public SelfPipelineExtension(string? tag = null, string? defaultExtensions = null)
{
tag = tag?.Trim();
tag = string.IsNullOrEmpty(tag) ? DefaultTag : tag;
@@ -46,7 +46,7 @@ namespace Markdig.Extensions.SelfPipeline
/// <summary>
/// Gets the default pipeline to configure if no tag was found in the input text. Default is <c>null</c> (core pipeline).
/// </summary>
public string DefaultExtensions { get; }
public string? DefaultExtensions { get; }
/// <summary>
/// Gets the self pipeline hint tag start that will be matched.
@@ -75,10 +75,10 @@ namespace Markdig.Extensions.SelfPipeline
/// <exception cref="ArgumentNullException"></exception>
public MarkdownPipeline CreatePipelineFromInput(string inputText)
{
if (inputText == null) ThrowHelper.ArgumentNullException(nameof(inputText));
if (inputText is null) ThrowHelper.ArgumentNullException(nameof(inputText));
var builder = new MarkdownPipelineBuilder();
string defaultConfig = DefaultExtensions;
string? defaultConfig = DefaultExtensions;
var indexOfSelfPipeline = inputText.IndexOf(SelfPipelineHintTagStart, StringComparison.OrdinalIgnoreCase);
if (indexOfSelfPipeline >= 0)
{
@@ -90,7 +90,7 @@ namespace Markdig.Extensions.SelfPipeline
}
}
if (!string.IsNullOrEmpty(defaultConfig))
if (defaultConfig is { Length: > 0 })
{
builder.Configure(defaultConfig);
}

View File

@@ -1,6 +1,7 @@
// 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 Markdig.Renderers;
using Markdig.Renderers.Html;
@@ -22,14 +23,14 @@ namespace Markdig.Extensions.SmartyPants
/// </summary>
/// <param name="options">The options.</param>
/// <exception cref="ArgumentNullException"></exception>
public HtmlSmartyPantRenderer(SmartyPantOptions options)
public HtmlSmartyPantRenderer(SmartyPantOptions? options)
{
this.options = options ?? throw new ArgumentNullException(nameof(options));
}
protected override void Write(HtmlRenderer renderer, SmartyPant obj)
{
if (!options.Mapping.TryGetValue(obj.Type, out string text))
if (!options.Mapping.TryGetValue(obj.Type, out string? text))
{
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
}

View File

@@ -16,7 +16,7 @@ namespace Markdig.Extensions.SmartyPants
/// Initializes a new instance of the <see cref="SmartyPantsExtension"/> class.
/// </summary>
/// <param name="options">The options.</param>
public SmartyPantsExtension(SmartyPantOptions options)
public SmartyPantsExtension(SmartyPantOptions? options)
{
Options = options ?? new SmartyPantOptions();
}

View File

@@ -52,7 +52,7 @@ namespace Markdig.Extensions.SmartyPants
type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines
if (slice.PeekChar() == '\'')
{
slice.NextChar();
slice.SkipChar();
type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines
}
break;
@@ -171,9 +171,9 @@ namespace Markdig.Extensions.SmartyPants
var quotePants = GetOrCreateState(processor);
// Register only if we don't have yet any quotes
if (quotePants.Count == 0)
if (quotePants.Count is 0)
{
processor.Block.ProcessInlinesEnd += BlockOnProcessInlinesEnd;
processor.Block!.ProcessInlinesEnd += BlockOnProcessInlinesEnd;
}
quotePants.Add(pant);
}
@@ -203,13 +203,13 @@ namespace Markdig.Extensions.SmartyPants
}
}
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline inline)
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline? inline)
{
processor.Block.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
processor.Block!.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
var pants = (ListSmartyPants) processor.ParserStates[Index];
Stack<Opener> openers = new Stack<Opener>(4);
var openers = new Stack<Opener>(4);
for (int i = 0; i < pants.Count; i++)
{
@@ -219,17 +219,17 @@ namespace Markdig.Extensions.SmartyPants
int type;
bool isLeft;
if (quoteType == SmartyPantType.LeftQuote || quoteType == SmartyPantType.RightQuote)
if (quoteType is SmartyPantType.LeftQuote or SmartyPantType.RightQuote)
{
type = 0;
isLeft = quoteType == SmartyPantType.LeftQuote;
}
else if (quoteType == SmartyPantType.LeftDoubleQuote || quoteType == SmartyPantType.RightDoubleQuote)
else if (quoteType is SmartyPantType.LeftDoubleQuote or SmartyPantType.RightDoubleQuote)
{
type = 1;
isLeft = quoteType == SmartyPantType.LeftDoubleQuote;
}
else if (quoteType == SmartyPantType.LeftAngleQuote || quoteType == SmartyPantType.RightAngleQuote)
else if (quoteType is SmartyPantType.LeftAngleQuote or SmartyPantType.RightAngleQuote)
{
type = 2;
isLeft = quoteType == SmartyPantType.LeftAngleQuote;
@@ -280,7 +280,11 @@ namespace Markdig.Extensions.SmartyPants
pants.Clear();
}
bool IPostInlineProcessor.PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex,
bool IPostInlineProcessor.PostProcess(
InlineProcessor state,
Inline? root,
Inline? lastChild,
int postInlineProcessorIndex,
bool isFinalProcessing)
{
// Don't try to process anything if there are no dash
@@ -339,9 +343,9 @@ namespace Markdig.Extensions.SmartyPants
next = postLiteral;
}
}
else if (child is ContainerInline)
else if (child is ContainerInline childContainer)
{
pendingContainers.Push(((ContainerInline)child).FirstChild);
pendingContainers.Push(childContainer.FirstChild!);
}
child = next;

View File

@@ -25,7 +25,7 @@ namespace Markdig.Extensions.Tables
}
var line = processor.Line;
GridTableState tableState = null;
GridTableState? tableState = null;
// Match the first row that should be of the minimal form: +---------------
var c = line.CurrentChar;
@@ -33,7 +33,7 @@ namespace Markdig.Extensions.Tables
while (c == '+')
{
var columnStart = line.Start;
line.NextChar();
line.SkipChar();
line.TrimStart();
// if we have reached the end of the line, exit
@@ -49,13 +49,13 @@ namespace Markdig.Extensions.Tables
return BlockState.None;
}
tableState ??= new GridTableState { Start = processor.Start, ExpectRow = true };
tableState ??= new GridTableState(start: processor.Start, expectRow: true);
tableState.AddColumn(columnStart - lineStart, line.Start - lineStart, columnAlign);
c = line.CurrentChar;
}
if (c != 0 || tableState == null)
if (c != 0 || tableState is null)
{
return BlockState.None;
}
@@ -66,7 +66,7 @@ namespace Markdig.Extensions.Tables
// Calculate the total width of all columns
int totalWidth = 0;
foreach (var columnSlice in tableState.ColumnSlices)
foreach (var columnSlice in tableState.ColumnSlices!)
{
totalWidth += columnSlice.End - columnSlice.Start - 1;
}
@@ -91,7 +91,7 @@ namespace Markdig.Extensions.Tables
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var gridTable = (Table)block;
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
var tableState = (GridTableState)block.GetData(typeof(GridTableState))!;
tableState.AddLine(ref processor.Line);
if (processor.CurrentChar == '+')
{
@@ -113,7 +113,7 @@ namespace Markdig.Extensions.Tables
private BlockState HandleNewRow(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
var columns = tableState.ColumnSlices;
var columns = tableState.ColumnSlices!;
SetRowSpanState(columns, processor.Line, out bool isHeaderRow, out bool hasRowSpan);
SetColumnSpanState(columns, processor.Line);
TerminateCurrentRow(processor, tableState, gridTable, false);
@@ -161,44 +161,43 @@ namespace Markdig.Extensions.Tables
private static bool IsRowSeperator(StringSlice slice)
{
while (slice.Length > 0)
char c = slice.CurrentChar;
do
{
if (slice.CurrentChar != '-' && slice.CurrentChar != '=' && slice.CurrentChar != ':')
if (c != '-' && c != '=' && c != ':')
{
return false;
return c == '\0';
}
slice.NextChar();
c = slice.NextChar();
}
return true;
while (true);
}
private static void TerminateCurrentRow(BlockProcessor processor, GridTableState tableState, Table gridTable, bool isLastRow)
{
var columns = tableState.ColumnSlices;
TableRow currentRow = null;
for (int i = 0; i < columns.Count; i++)
TableRow? currentRow = null;
for (int i = 0; i < columns!.Count; i++)
{
var columnSlice = columns[i];
if (columnSlice.CurrentCell != null)
{
if (currentRow == null)
{
currentRow = new TableRow();
}
currentRow ??= new TableRow();
// If this cell does not already belong to a row
if (columnSlice.CurrentCell.Parent == null)
if (columnSlice.CurrentCell.Parent is null)
{
currentRow.Add(columnSlice.CurrentCell);
}
// If the cell is not going to span through to the next row
if (columnSlice.CurrentCell.AllowClose)
{
columnSlice.BlockProcessor.Close(columnSlice.CurrentCell);
columnSlice.BlockProcessor!.Close(columnSlice.CurrentCell);
}
}
// Renew the block parser processor (or reset it for the last row)
if (columnSlice.BlockProcessor != null && (columnSlice.CurrentCell == null || columnSlice.CurrentCell.AllowClose))
if (columnSlice.BlockProcessor != null && (columnSlice.CurrentCell is null || columnSlice.CurrentCell.AllowClose))
{
columnSlice.BlockProcessor.ReleaseChild();
columnSlice.BlockProcessor = isLastRow ? null : processor.CreateChild();
@@ -214,7 +213,7 @@ namespace Markdig.Extensions.Tables
}
}
if (currentRow != null && currentRow.Count > 0)
if (currentRow is { Count: > 0 })
{
gridTable.Add(currentRow);
}
@@ -223,7 +222,7 @@ namespace Markdig.Extensions.Tables
private BlockState HandleContents(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
var isRowLine = processor.CurrentChar == '+';
var columns = tableState.ColumnSlices;
var columns = tableState.ColumnSlices!;
var line = processor.Line;
SetColumnSpanState(columns, line);
if (!isRowLine && !CanContinueRow(columns))
@@ -267,7 +266,7 @@ namespace Markdig.Extensions.Tables
if (!isRowLine || !IsRowSeperator(sliceForCell))
{
if (columnSlice.CurrentCell == null)
if (columnSlice.CurrentCell is null)
{
columnSlice.CurrentCell = new TableCell(this)
{
@@ -275,7 +274,7 @@ namespace Markdig.Extensions.Tables
ColumnIndex = i
};
if (columnSlice.BlockProcessor == null)
if (columnSlice.BlockProcessor is null)
{
columnSlice.BlockProcessor = processor.CreateChild();
}
@@ -284,7 +283,7 @@ namespace Markdig.Extensions.Tables
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
}
// Process the content of the cell
columnSlice.BlockProcessor.LineIndex = processor.LineIndex;
columnSlice.BlockProcessor!.LineIndex = processor.LineIndex;
columnSlice.BlockProcessor.ProcessLine(sliceForCell);
}
@@ -335,7 +334,7 @@ namespace Markdig.Extensions.Tables
{
var parser = processor.Parsers.FindExact<ParagraphBlockParser>();
// Discard the grid table
var parent = gridTable.Parent;
var parent = gridTable.Parent!;
processor.Discard(gridTable);
var paragraphBlock = new ParagraphBlock(parser)
{
@@ -351,7 +350,7 @@ namespace Markdig.Extensions.Tables
var gridTable = block as Table;
if (gridTable != null)
{
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
var tableState = (GridTableState)block.GetData(typeof(GridTableState))!;
TerminateCurrentRow(processor, tableState, gridTable, true);
if (!gridTable.IsValid())
{

View File

@@ -13,63 +13,65 @@ namespace Markdig.Extensions.Tables
/// </summary>
internal sealed class GridTableState
{
public int Start { get; set; }
public GridTableState(int start, bool expectRow)
{
Start = start;
ExpectRow = expectRow;
}
public int Start { get; }
public StringLineGroup Lines;
public List<ColumnSlice> ColumnSlices { get; private set; }
public List<ColumnSlice>? ColumnSlices { get; private set; }
public bool ExpectRow { get; set; }
public bool ExpectRow { get; }
public int StartRowGroup { get; set; }
public void AddLine(ref StringSlice line)
{
if (Lines.Lines == null)
if (Lines.Lines is null)
{
Lines = new StringLineGroup(4);
}
Lines.Add(line);
}
public void AddColumn(int start, int end, TableColumnAlign? align)
{
if (ColumnSlices == null)
{
ColumnSlices = new List<ColumnSlice>();
}
ColumnSlices.Add(new ColumnSlice()
{
Start = start,
End = end,
Align = align,
});
ColumnSlices ??= new List<ColumnSlice>();
ColumnSlices.Add(new ColumnSlice(start, end, align));
}
public class ColumnSlice
public sealed class ColumnSlice
{
public ColumnSlice()
public ColumnSlice(int start, int end, TableColumnAlign? align)
{
Start = start;
End = end;
Align = align;
CurrentColumnSpan = -1;
}
/// <summary>
/// Gets or sets the index position of this column (after the |)
/// </summary>
public int Start { get; set; }
public int Start { get; }
public int End { get; set; }
public int End { get; }
public TableColumnAlign? Align { get; set; }
public TableColumnAlign? Align { get; }
public int CurrentColumnSpan { get; set; }
public int PreviousColumnSpan { get; set; }
public BlockProcessor BlockProcessor { get; set; }
public BlockProcessor? BlockProcessor { get; set; }
public TableCell CurrentCell { get; set; }
public TableCell? CurrentCell { get; set; }
}
}
}

View File

@@ -18,7 +18,7 @@ namespace Markdig.Extensions.Tables
protected override void Write(HtmlRenderer renderer, Table table)
{
renderer.EnsureLine();
renderer.Write("<table").WriteAttributes(table).WriteLine(">");
renderer.Write("<table").WriteAttributes(table).WriteLine('>');
bool hasBody = false;
bool hasAlreadyHeader = false;
@@ -69,7 +69,7 @@ namespace Markdig.Extensions.Tables
renderer.WriteLine("<tbody>");
hasBody = true;
}
renderer.Write("<tr").WriteAttributes(row).WriteLine(">");
renderer.Write("<tr").WriteAttributes(row).WriteLine('>');
for (int i = 0; i < row.Count; i++)
{
var cellObj = row[i];
@@ -109,7 +109,7 @@ namespace Markdig.Extensions.Tables
}
}
renderer.WriteAttributes(cell);
renderer.Write(">");
renderer.Write('>');
var previousImplicitParagraph = renderer.ImplicitParagraph;
if (cell.Count == 1)

View File

@@ -1,4 +1,4 @@
// 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.
@@ -29,7 +29,7 @@ namespace Markdig.Extensions.Tables
{
// Only if we have already a paragraph
var paragraph = processor.CurrentBlock as ParagraphBlock;
if (processor.IsCodeIndent || paragraph == null)
if (processor.IsCodeIndent || paragraph is null)
{
return BlockState.None;
}
@@ -45,7 +45,7 @@ namespace Markdig.Extensions.Tables
if (countPipe > 0)
{
// Mark the paragraph as open (important, otherwise we would have an infinite loop)
paragraph.AppendLine(ref processor.Line, processor.Column, processor.LineIndex, processor.Line.Start);
paragraph.AppendLine(ref processor.Line, processor.Column, processor.LineIndex, processor.Line.Start, processor.TrackTrivia);
paragraph.IsOpen = true;
return BlockState.BreakDiscard;
}

View File

@@ -17,7 +17,7 @@ namespace Markdig.Extensions.Tables
/// Initializes a new instance of the <see cref="PipeTableExtension"/> class.
/// </summary>
/// <param name="options">The options.</param>
public PipeTableExtension(PipeTableOptions options = null)
public PipeTableExtension(PipeTableOptions? options = null)
{
Options = options ?? new PipeTableOptions();
}
@@ -38,7 +38,7 @@ namespace Markdig.Extensions.Tables
var lineBreakParser = pipeline.InlineParsers.FindExact<LineBreakInlineParser>();
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
{
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(lineBreakParser, Options));
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(lineBreakParser!, Options));
}
}

View File

@@ -28,10 +28,10 @@ namespace Markdig.Extensions.Tables
/// </summary>
/// <param name="lineBreakParser">The linebreak parser to use</param>
/// <param name="options">The options.</param>
public PipeTableParser(LineBreakInlineParser lineBreakParser, PipeTableOptions options = null)
public PipeTableParser(LineBreakInlineParser lineBreakParser, PipeTableOptions? options = null)
{
this.lineBreakParser = lineBreakParser ?? throw new ArgumentNullException(nameof(lineBreakParser));
OpeningCharacters = new[] { '|', '\n' };
OpeningCharacters = new[] { '|', '\n', '\r' };
Options = options ?? new PipeTableOptions();
}
@@ -59,7 +59,7 @@ namespace Markdig.Extensions.Tables
var position = processor.GetSourcePosition(slice.Start, out int globalLineIndex, out int column);
var localLineIndex = globalLineIndex - processor.LineIndex;
if (tableState == null)
if (tableState is null)
{
// A table could be preceded by an empty line or a line containing an inline
@@ -67,12 +67,12 @@ namespace Markdig.Extensions.Tables
// start for a table. Typically, with this, we can have an attributes {...}
// starting on the first line of a pipe table, even if the first line
// doesn't have a pipe
if (processor.Inline != null && (localLineIndex > 0 || c == '\n'))
if (processor.Inline != null && (localLineIndex > 0 || c == '\n' || c == '\r'))
{
return false;
}
if (processor.Inline == null)
if (processor.Inline is null)
{
isFirstLineEmpty = true;
}
@@ -81,7 +81,7 @@ namespace Markdig.Extensions.Tables
processor.ParserStates[Index] = tableState;
}
if (c == '\n')
if (c == '\n' || c == '\r')
{
if (!isFirstLineEmpty && !tableState.LineHasPipe)
{
@@ -92,8 +92,8 @@ namespace Markdig.Extensions.Tables
tableState.LineIndex++;
if (!isFirstLineEmpty)
{
tableState.ColumnAndLineDelimiters.Add(processor.Inline);
tableState.EndOfLines.Add(processor.Inline);
tableState.ColumnAndLineDelimiters.Add(processor.Inline!);
tableState.EndOfLines.Add(processor.Inline!);
}
}
else
@@ -112,16 +112,15 @@ namespace Markdig.Extensions.Tables
}
tableState.LineHasPipe = true;
tableState.LineIndex = localLineIndex;
slice.NextChar(); // Skip the `|` character
slice.SkipChar(); // Skip the `|` character
tableState.ColumnAndLineDelimiters.Add(processor.Inline);
}
return true;
}
public bool PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex, bool isFinalProcessing)
public bool PostProcess(InlineProcessor state, Inline? root, Inline? lastChild, int postInlineProcessorIndex, bool isFinalProcessing)
{
var container = root as ContainerInline;
var tableState = state.ParserStates[Index] as TableState;
@@ -129,23 +128,20 @@ namespace Markdig.Extensions.Tables
// If the delimiters are being processed by an image link, we need to transform them back to literals
if (!isFinalProcessing)
{
if (container == null || tableState == null)
if (container is null || tableState is null)
{
return true;
}
var child = container.LastChild;
List<PipeTableDelimiterInline> delimitersToRemove = null;
List<PipeTableDelimiterInline>? delimitersToRemove = null;
while (child != null)
{
var pipeDelimiter = child as PipeTableDelimiterInline;
if (pipeDelimiter != null)
if (child is PipeTableDelimiterInline pipeDelimiter)
{
if (delimitersToRemove == null)
{
delimitersToRemove = new List<PipeTableDelimiterInline>();
}
delimitersToRemove ??= new List<PipeTableDelimiterInline>();
delimitersToRemove.Add(pipeDelimiter);
}
@@ -196,10 +192,10 @@ namespace Markdig.Extensions.Tables
}
// Remove previous state
state.ParserStates[Index] = null;
state.ParserStates[Index] = null!;
// Continue
if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe ) //|| tableState.LineIndex != state.LocalLineIndex)
if (tableState is null || container is null || tableState.IsInvalidTable || !tableState.LineHasPipe ) //|| tableState.LineIndex != state.LocalLineIndex)
{
return true;
}
@@ -209,7 +205,7 @@ namespace Markdig.Extensions.Tables
// TODO: we could optimize this by merging FindHeaderRow and the cell loop
var aligns = FindHeaderRow(delimiters);
if (Options.RequireHeaderSeparator && aligns == null)
if (Options.RequireHeaderSeparator && aligns is null)
{
return true;
}
@@ -217,7 +213,7 @@ namespace Markdig.Extensions.Tables
var table = new Table();
// If the current paragraph block has any attributes attached, we can copy them
var attributes = state.Block.TryGetAttributes();
var attributes = state.Block!.TryGetAttributes();
if (attributes != null)
{
attributes.CopyTo(table.GetAttributes());
@@ -258,9 +254,9 @@ namespace Markdig.Extensions.Tables
{
while (true)
{
if (lastElement is ContainerInline)
if (lastElement is ContainerInline lastElementContainer)
{
var nextElement = ((ContainerInline) lastElement).LastChild;
var nextElement = lastElementContainer.LastChild;
if (nextElement != null)
{
lastElement = nextElement;
@@ -287,26 +283,24 @@ namespace Markdig.Extensions.Tables
// Cell loop
// Reconstruct the table from the delimiters
TableRow row = null;
TableRow firstRow = null;
TableRow? row = null;
TableRow? firstRow = null;
for (int i = 0; i < delimiters.Count; i++)
{
var delimiter = delimiters[i];
var pipeSeparator = delimiter as PipeTableDelimiterInline;
var isLine = delimiter is LineBreakInline;
if (row == null)
if (row is null)
{
row = new TableRow();
if (firstRow == null)
{
firstRow = row;
}
firstRow ??= row;
// If the first delimiter is a pipe and doesn't have any parent or previous sibling, for cases like:
// 0) | a | b | \n
// 1) | a | b \n
if (pipeSeparator != null && (delimiter.PreviousSibling == null || delimiter.PreviousSibling is LineBreakInline))
if (pipeSeparator != null && (delimiter.PreviousSibling is null || delimiter.PreviousSibling is LineBreakInline))
{
delimiter.Remove();
continue;
@@ -318,23 +312,23 @@ namespace Markdig.Extensions.Tables
// x
// 1) | a | b \n
// 2) a | b \n
Inline endOfCell = null;
Inline beginOfCell = null;
Inline? endOfCell = null;
Inline? beginOfCell = null;
var cellContentIt = delimiter;
while (true)
{
cellContentIt = cellContentIt.PreviousSibling ?? cellContentIt.Parent;
if (cellContentIt == null || cellContentIt is LineBreakInline)
if (cellContentIt is null || cellContentIt is LineBreakInline)
{
break;
}
// The cell begins at the first effective child after a | or the top ContainerInline (which is not necessary to bring into the tree + it contains an invalid span calculation)
if (cellContentIt is PipeTableDelimiterInline || (cellContentIt.GetType() == typeof(ContainerInline) && cellContentIt.Parent == null ))
if (cellContentIt is PipeTableDelimiterInline || (cellContentIt.GetType() == typeof(ContainerInline) && cellContentIt.Parent is null ))
{
beginOfCell = ((ContainerInline)cellContentIt).FirstChild;
if (endOfCell == null)
if (endOfCell is null)
{
endOfCell = beginOfCell;
}
@@ -342,20 +336,19 @@ namespace Markdig.Extensions.Tables
}
beginOfCell = cellContentIt;
if (endOfCell == null)
if (endOfCell is null)
{
endOfCell = beginOfCell;
}
}
// If the current deilimiter is a pipe `|` OR
// the beginOfCell/endOfCell are not null and
// either they are :
// - different
// - they contain a single element, but it is not a line break (\n) or an empty/whitespace Literal.
// Then we can add a cell to the current row
if (!isLine || (beginOfCell != null && endOfCell != null && ( beginOfCell != endOfCell || !(beginOfCell is LineBreakInline || (beginOfCell is LiteralInline && ((LiteralInline)beginOfCell).Content.IsEmptyOrWhitespace())))))
if (!isLine || (beginOfCell != null && endOfCell != null && ( beginOfCell != endOfCell || !(beginOfCell is LineBreakInline || (beginOfCell is LiteralInline beingOfCellLiteral && beingOfCellLiteral.Content.IsEmptyOrWhitespace())))))
{
if (!isLine)
{
@@ -420,11 +413,11 @@ namespace Markdig.Extensions.Tables
Debug.Assert(row != null);
if (table.Span.IsEmpty)
{
table.Span = row.Span;
table.Span = row!.Span;
table.Line = row.Line;
table.Column = row.Column;
}
table.Add(row);
table.Add(row!);
row = null;
}
}
@@ -470,11 +463,11 @@ namespace Markdig.Extensions.Tables
return false;
}
private static bool ParseHeaderString(Inline inline, out TableColumnAlign? align)
private static bool ParseHeaderString(Inline? inline, out TableColumnAlign? align)
{
align = 0;
var literal = inline as LiteralInline;
if (literal == null)
if (literal is null)
{
return false;
}
@@ -493,10 +486,10 @@ namespace Markdig.Extensions.Tables
return false;
}
private List<TableColumnDefinition> FindHeaderRow(List<Inline> delimiters)
private List<TableColumnDefinition>? FindHeaderRow(List<Inline> delimiters)
{
bool isValidRow = false;
List<TableColumnDefinition> aligns = null;
List<TableColumnDefinition>? aligns = null;
for (int i = 0; i < delimiters.Count; i++)
{
if (!IsLine(delimiters[i]))
@@ -518,20 +511,21 @@ namespace Markdig.Extensions.Tables
// Check the left side of a `|` delimiter
TableColumnAlign? align = null;
if (delimiter.PreviousSibling != null && !ParseHeaderString(delimiter.PreviousSibling, out align))
if (delimiter.PreviousSibling != null &&
!(delimiter.PreviousSibling is LiteralInline li && li.Content.IsEmptyOrWhitespace()) && // ignore parsed whitespace
!ParseHeaderString(delimiter.PreviousSibling, out align))
{
break;
}
// Create aligns until we may have a header row
if (aligns == null)
{
aligns = new List<TableColumnDefinition>();
}
aligns ??= new List<TableColumnDefinition>();
aligns.Add(new TableColumnDefinition() { Alignment = align });
// If this is the last delimiter, we need to check the right side of the `|` delimiter
if (nextDelimiter == null)
if (nextDelimiter is null)
{
var nextSibling = columnDelimiter != null
? columnDelimiter.FirstChild
@@ -572,20 +566,20 @@ namespace Markdig.Extensions.Tables
return inline is LineBreakInline;
}
private static bool IsStartOfLineColumnDelimiter(Inline inline)
private static bool IsStartOfLineColumnDelimiter(Inline? inline)
{
if (inline == null)
if (inline is null)
{
return false;
}
var previous = inline.PreviousSibling;
if (previous == null)
if (previous is null)
{
return true;
}
var literal = previous as LiteralInline;
if (literal != null)
if (previous is LiteralInline literal)
{
if (!literal.Content.IsEmptyOrWhitespace())
{
@@ -593,65 +587,57 @@ namespace Markdig.Extensions.Tables
}
previous = previous.PreviousSibling;
}
return previous == null || IsLine(previous);
return previous is null || IsLine(previous);
}
private static void TrimStart(Inline inline)
private static void TrimStart(Inline? inline)
{
while (inline is ContainerInline && !(inline is DelimiterInline))
{
inline = ((ContainerInline)inline).FirstChild;
}
var literal = inline as LiteralInline;
if (literal != null)
if (inline is LiteralInline literal)
{
literal.Content.TrimStart();
}
}
private static void TrimEnd(Inline inline)
private static void TrimEnd(Inline? inline)
{
var literal = inline as LiteralInline;
if (literal != null)
if (inline is LiteralInline literal)
{
literal.Content.TrimEnd();
}
}
private static bool IsNullOrSpace(Inline inline)
private static bool IsNullOrSpace(Inline? inline)
{
if (inline == null)
if (inline is null)
{
return true;
}
var literal = inline as LiteralInline;
if (literal != null)
if (inline is LiteralInline literal)
{
return literal.Content.IsEmptyOrWhitespace();
}
return false;
}
private class TableState
private sealed class TableState
{
public TableState()
{
ColumnAndLineDelimiters = new List<Inline>();
Cells = new List<TableCell>();
EndOfLines = new List<Inline>();
}
public bool IsInvalidTable { get; set; }
public bool LineHasPipe { get; set; }
public int LineIndex { get; set; }
public List<Inline> ColumnAndLineDelimiters { get; }
public List<Inline> ColumnAndLineDelimiters { get; } = new();
public List<TableCell> Cells { get; }
public List<TableCell> Cells { get; } = new();
public List<Inline> EndOfLines { get; }
public List<Inline> EndOfLines { get; } = new();
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Markdig.Extensions.Tables
/// <summary>
/// Initializes a new instance of the <see cref="Table"/> class.
/// </summary>
public Table() : this(null)
public Table() : base(null)
{
}
@@ -25,15 +25,14 @@ namespace Markdig.Extensions.Tables
/// Initializes a new instance of the <see cref="Table"/> class.
/// </summary>
/// <param name="parser">The parser used to create this block.</param>
public Table(BlockParser parser) : base(parser)
public Table(BlockParser? parser) : base(parser)
{
ColumnDefinitions = new List<TableColumnDefinition>();
}
/// <summary>
/// Gets or sets the column alignments. May be null.
/// </summary>
public List<TableColumnDefinition> ColumnDefinitions { get; }
public List<TableColumnDefinition> ColumnDefinitions { get; } = new();
/// <summary>
/// Checks if the table structure is valid.
@@ -83,8 +82,7 @@ namespace Markdig.Extensions.Tables
var maxColumn = 0;
for (int i = 0; i < this.Count; i++)
{
var row = this[i] as TableRow;
if (row != null && row.Count > maxColumn)
if (this[i] is TableRow row && row.Count > maxColumn)
{
maxColumn = row.Count;
}
@@ -92,8 +90,7 @@ namespace Markdig.Extensions.Tables
for (int i = 0; i < this.Count; i++)
{
var row = this[i] as TableRow;
if (row != null)
if (this[i] is TableRow row)
{
for (int j = row.Count; j < maxColumn; j++)
{
@@ -124,8 +121,7 @@ namespace Markdig.Extensions.Tables
for (int i = 0; i < this.Count; i++)
{
var row = this[i] as TableRow;
if (row != null)
if (this[i] is TableRow row)
{
for (int j = row.Count; j < maxColumn; j++)
{

View File

@@ -24,7 +24,7 @@ namespace Markdig.Extensions.Tables
/// Initializes a new instance of the <see cref="TableCell"/> class.
/// </summary>
/// <param name="parser">The parser used to create this block.</param>
public TableCell(BlockParser parser) : base(parser)
public TableCell(BlockParser? parser) : base(parser)
{
AllowClose = true;
ColumnSpan = 1;

View File

@@ -60,7 +60,7 @@ namespace Markdig.Extensions.Tables
if (c == ':')
{
hasLeft = true;
slice.NextChar();
slice.SkipChar();
}
slice.TrimStart();
@@ -91,7 +91,7 @@ namespace Markdig.Extensions.Tables
if (c == ':')
{
hasRight = true;
slice.NextChar();
slice.SkipChar();
}
slice.TrimStart();

View File

@@ -40,7 +40,7 @@ namespace Markdig.Extensions.TaskLists
// [ ]
// or [x] or [X]
if (!(processor.Block.Parent is ListItemBlock listItemBlock))
if (!(processor.Block!.Parent is ListItemBlock listItemBlock))
{
return false;
}
@@ -56,7 +56,7 @@ namespace Markdig.Extensions.TaskLists
return false;
}
// Skip last ]
slice.NextChar();
slice.SkipChar();
// Create the TaskList
var taskItem = new TaskList()
@@ -75,7 +75,7 @@ namespace Markdig.Extensions.TaskLists
listItemBlock.GetAttributes().AddClass(ListItemClass);
}
var listBlock = (ListBlock) listItemBlock.Parent;
var listBlock = (ListBlock) listItemBlock.Parent!;
if (!string.IsNullOrEmpty(ListClass))
{
listBlock.GetAttributes().AddClass(ListClass);

View File

@@ -221,9 +221,9 @@ namespace Markdig.Helpers
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNewLine(this char c)
public static bool IsNewLineOrLineFeed(this char c)
{
return c == '\n';
return c == '\n' || c == '\r';
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -355,22 +355,6 @@ namespace Markdig.Helpers
internal static bool IsInInclusiveRange(int value, uint min, uint max)
=> ((uint)value - min) <= (max - min);
public static IEnumerable<int> ToUtf32(StringSlice text)
{
for (int i = text.Start; i <= text.End; i++)
{
if (IsHighSurrogate(text[i]) && i < text.End && IsLowSurrogate(text[i + 1]))
{
Debug.Assert(char.IsSurrogatePair(text[i], text[i + 1]));
yield return char.ConvertToUtf32(text[i], text[i + 1]);
}
else
{
yield return text[i];
}
}
}
public static bool IsRightToLeft(int c)
{
// Generated from Table D.1 of RFC3454

File diff suppressed because it is too large Load Diff

View File

@@ -14,10 +14,10 @@ namespace Markdig.Helpers
/// Allows to associate characters to a data structures and query efficiently for them.
/// </summary>
/// <typeparam name="T"></typeparam>
public class CharacterMap<T> where T : class
public sealed class CharacterMap<T> where T : class
{
private readonly T[] asciiMap;
private readonly Dictionary<uint, T> nonAsciiMap;
private readonly Dictionary<uint, T>? nonAsciiMap;
private readonly BoolVector128 isOpeningCharacter;
/// <summary>
@@ -27,7 +27,7 @@ namespace Markdig.Helpers
/// <exception cref="ArgumentNullException"></exception>
public CharacterMap(IEnumerable<KeyValuePair<char, T>> maps)
{
if (maps == null) ThrowHelper.ArgumentNullException(nameof(maps));
if (maps is null) ThrowHelper.ArgumentNullException(nameof(maps));
var charSet = new HashSet<char>();
int maxChar = 0;
@@ -59,7 +59,7 @@ namespace Markdig.Helpers
asciiMap[openingChar] ??= state.Value;
isOpeningCharacter.Set(openingChar);
}
else if (!nonAsciiMap.ContainsKey(openingChar))
else if (!nonAsciiMap!.ContainsKey(openingChar))
{
nonAsciiMap[openingChar] = state.Value;
}
@@ -76,7 +76,7 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="openingChar">The opening character.</param>
/// <returns>A list of parsers valid for the specified opening character or null if no parsers registered.</returns>
public T this[uint openingChar]
public T? this[uint openingChar]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@@ -88,7 +88,7 @@ namespace Markdig.Helpers
}
else
{
T map = null;
T? map = null;
nonAsciiMap?.TryGetValue(openingChar, out map);
return map;
}
@@ -172,7 +172,7 @@ namespace Markdig.Helpers
for (int i = start; i <= end; i++)
{
char c = pText[i];
if (c < 128 ? isOpeningCharacter[c] : nonAsciiMap.ContainsKey(c))
if (c < 128 ? isOpeningCharacter[c] : nonAsciiMap!.ContainsKey(c))
{
return i;
}

View File

@@ -1,5 +1,8 @@
// Copyright (c) Miha Zupan. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
#nullable disable
using System;
using System.Collections;
using System.Collections.Generic;
@@ -277,7 +280,7 @@ namespace Markdig.Helpers
}
else
{
if (_unicodeRootMap == null)
if (_unicodeRootMap is null)
{
_unicodeRootMap = new Dictionary<char, int>();
}
@@ -312,7 +315,7 @@ namespace Markdig.Helpers
/// <param name="input">Matches to initialize the <see cref="CompactPrefixTree{TValue}"/> with. For best lookup performance, this collection should be sorted.</param>
public CompactPrefixTree(ICollection<KeyValuePair<string, TValue>> input)
{
if (input == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
if (input is null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
Init(input.Count, input.Count * 2, input.Count * 2);
@@ -426,7 +429,7 @@ namespace Markdig.Helpers
private bool TryInsert(in KeyValuePair<string, TValue> pair, InsertionBehavior behavior)
{
string key = pair.Key;
if (key == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (key is null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (key.Length == 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.key, ExceptionReason.String_Empty);
Debug.Assert(!string.IsNullOrEmpty(key));
@@ -516,7 +519,7 @@ namespace Markdig.Helpers
Debug.Assert(key.Length != previousKey.Length);
if (previousKey.Length < key.Length) // If the input was sorted, this should be hit
{
Debug.Assert(key.StartsWith(previousKey));
Debug.Assert(key.StartsWith(previousKey, StringComparison.Ordinal));
node.ChildChar = key[i];
node.MatchIndex = previousMatchIndex;
EnsureTreeCapacity(TreeSize + 1);
@@ -530,7 +533,7 @@ namespace Markdig.Helpers
else // if key.Length < previousKey.Length
{
Debug.Assert(key.Length < previousKey.Length);
Debug.Assert(previousKey.StartsWith(key));
Debug.Assert(previousKey.StartsWith(key, StringComparison.Ordinal));
node.ChildChar = previousKey[i];
node.MatchIndex = Count;
EnsureTreeCapacity(TreeSize + 1);
@@ -580,7 +583,7 @@ namespace Markdig.Helpers
else
{
// This node has a child char, therefore we either don't have a match attached or that match is simply a prefix of the current key
Debug.Assert(node.MatchIndex == -1 || key.StartsWith(_matches[node.MatchIndex].Key));
Debug.Assert(node.MatchIndex == -1 || key.StartsWith(_matches[node.MatchIndex].Key, StringComparison.Ordinal));
// Set this pair as the current node's first element in the Children list
node.Children = _childrenIndex;
@@ -638,7 +641,7 @@ namespace Markdig.Helpers
// It's not a duplicate but shares key.Length characters, therefore it's longer
// This will never occur if the input was sorted
Debug.Assert(previousMatch.Key.Length > key.Length);
Debug.Assert(previousMatch.Key.StartsWith(key));
Debug.Assert(previousMatch.Key.StartsWith(key, StringComparison.Ordinal));
Debug.Assert(node.ChildChar == 0 && node.Children == -1);
// It is a leaf node

View File

@@ -23,14 +23,14 @@ namespace Markdig.Helpers
public T[] Rent()
{
T[][] buffers = _buffers;
T[] buffer = null;
T[] buffer = null!;
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
{
int index = _index;
if ((uint)index < (uint)buffers.Length)
{
buffer = buffers[index];
buffers[index] = null;
buffers[index] = null!;
_index = index + 1;
}
Interlocked.Decrement(ref _lock);
@@ -64,17 +64,16 @@ namespace Markdig.Helpers
_bucket32 = new Bucket(size32);
}
private Bucket SelectBucket(int length)
private Bucket? SelectBucket(int length)
{
switch (length)
return length switch
{
case 4: return _bucket4;
case 8: return _bucket8;
case 16: return _bucket16;
case 32: return _bucket32;
default: return null;
}
4 => _bucket4,
8 => _bucket8,
16 => _bucket16,
32 => _bucket32,
_ => null
};
}
public T[] Rent(int length)

View File

@@ -47,7 +47,7 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="entity">The entity without <c>&amp;</c> and <c>;</c> symbols, for example, <c>copy</c>.</param>
/// <returns>The unicode character set or <c>null</c> if the entity was not recognized.</returns>
public static string DecodeEntity(ReadOnlySpan<char> entity)
public static string? DecodeEntity(ReadOnlySpan<char> entity)
{
if (EntityMap.TryMatchExact(entity, out KeyValuePair<string, string> result))
return result.Value;

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace Markdig.Helpers
@@ -30,28 +31,30 @@ namespace Markdig.Helpers
}
}
}
public static string EscapeUrlCharacter(char c)
public static string? EscapeUrlCharacter(char c)
{
return c < 128 ? EscapeUrlsForAscii[c] : null;
}
public static bool TryParseHtmlTag(StringSlice text, out string htmlTag)
{
return TryParseHtmlTag(ref text, out htmlTag);
}
public static bool TryParseHtmlTag(ref StringSlice text, out string htmlTag)
public static bool TryParseHtmlTag(ref StringSlice text, [NotNullWhen(true)] out string? htmlTag)
{
var builder = StringBuilderCache.Local();
var result = TryParseHtmlTag(ref text, builder);
htmlTag = builder.ToString();
builder.Length = 0;
return result;
if (TryParseHtmlTag(ref text, builder))
{
htmlTag = builder.GetStringAndReset();
return true;
}
else
{
htmlTag = null;
return false;
}
}
public static bool TryParseHtmlTag(ref StringSlice text, StringBuilder builder)
{
if (builder == null) ThrowHelper.ArgumentNullException(nameof(builder));
if (builder is null) ThrowHelper.ArgumentNullException(nameof(builder));
var c = text.CurrentChar;
if (c != '<')
{
@@ -127,7 +130,7 @@ namespace Markdig.Helpers
case '\0':
return false;
case '>':
text.NextChar();
text.SkipChar();
builder.Append(c);
return true;
case '/':
@@ -137,7 +140,7 @@ namespace Markdig.Helpers
{
return false;
}
text.NextChar();
text.SkipChar();
builder.Append('>');
return true;
case '=':
@@ -269,7 +272,7 @@ namespace Markdig.Helpers
if (c == '>')
{
text.NextChar();
text.SkipChar();
builder.Append('>');
return true;
}
@@ -278,16 +281,12 @@ namespace Markdig.Helpers
private static bool TryParseHtmlTagCData(ref StringSlice text, StringBuilder builder)
{
builder.Append('[');
var c = text.NextChar();
if (c == 'C' &&
text.NextChar() == 'D' &&
text.NextChar() == 'A' &&
text.NextChar() == 'T' &&
text.NextChar() == 'A' &&
(c = text.NextChar()) == '[')
if (text.Match("[CDATA["))
{
builder.Append("CDATA[");
builder.Append("[CDATA[");
text.Start += 6;
char c = '\0';
while (true)
{
var pc = c;
@@ -297,23 +296,15 @@ namespace Markdig.Helpers
return false;
}
if (c == ']' && pc == ']')
{
builder.Append(']');
c = text.NextChar();
if (c == '>')
{
builder.Append('>');
text.NextChar();
return true;
}
if (c == '\0')
{
return false;
}
}
builder.Append(c);
if (c == ']' && pc == ']' && text.PeekChar() == '>')
{
text.SkipChar();
text.SkipChar();
builder.Append('>');
return true;
}
}
}
return false;
@@ -337,7 +328,7 @@ namespace Markdig.Helpers
c = text.NextChar();
if (c == '>')
{
text.NextChar();
text.SkipChar();
builder.Append('>');
return true;
}
@@ -392,7 +383,7 @@ namespace Markdig.Helpers
if (c == '>')
{
builder.Append('>');
text.NextChar();
text.SkipChar();
return true;
}
return false;
@@ -417,7 +408,7 @@ namespace Markdig.Helpers
if (c == '>' && prevChar == '?')
{
builder.Append('>');
text.NextChar();
text.SkipChar();
return true;
}
prevChar = c;
@@ -431,7 +422,7 @@ namespace Markdig.Helpers
/// <param name="text">The string data that will be changed by unescaping any punctuation or symbol characters.</param>
/// <param name="removeBackSlash">if set to <c>true</c> [remove back slash].</param>
/// <returns></returns>
public static string Unescape(string text, bool removeBackSlash = true)
public static string Unescape(string? text, bool removeBackSlash = true)
{
// Credits: code from CommonMark.NET
// Copyright (c) 2014, Kārlis Gaņģis All rights reserved.
@@ -446,9 +437,9 @@ namespace Markdig.Helpers
int lastPos = 0;
char c;
char[] search = removeBackSlash ? SearchBackAndAmp : SearchAmp;
StringBuilder sb = null;
StringBuilder? sb = null;
while ((searchPos = text.IndexOfAny(search, searchPos)) != -1)
while ((searchPos = text!.IndexOfAny(search, searchPos)) != -1)
{
sb ??= StringBuilderCache.Local();
c = text[searchPos];
@@ -497,7 +488,7 @@ namespace Markdig.Helpers
}
}
if (sb == null || lastPos == 0)
if (sb is null || lastPos == 0)
return text;
sb.Append(text, lastPos, text.Length - lastPos);
@@ -530,29 +521,24 @@ namespace Markdig.Helpers
if (c == '#')
{
c = slice.PeekChar();
if (c == 'x' || c == 'X')
if ((c | 0x20) == 'x')
{
c = slice.NextChar(); // skip #
// expect 1-6 hex digits starting from pos+3
while (c != '\0')
{
c = slice.NextChar();
if (c >= '0' && c <= '9')
if (c.IsDigit())
{
if (++counter == 7) return 0;
numericEntity = numericEntity*16 + (c - '0');
numericEntity = numericEntity * 16 + (c - '0');
continue;
}
else if (c >= 'A' && c <= 'F')
else if ((uint)((c - 'A') & ~0x20) <= ('F' - 'A'))
{
if (++counter == 7) return 0;
numericEntity = numericEntity*16 + (c - 'A' + 10);
continue;
}
else if (c >= 'a' && c <= 'f')
{
if (++counter == 7) return 0;
numericEntity = numericEntity*16 + (c - 'a' + 10);
numericEntity = numericEntity * 16 + ((c | 0x20) - 'a' + 10);
continue;
}
@@ -569,10 +555,10 @@ namespace Markdig.Helpers
{
c = slice.NextChar();
if (c >= '0' && c <= '9')
if (c.IsDigit())
{
if (++counter == 8) return 0;
numericEntity = numericEntity*10 + (c - '0');
numericEntity = numericEntity * 10 + (c - '0');
continue;
}
@@ -586,7 +572,7 @@ namespace Markdig.Helpers
else
{
// expect a letter and 1-31 letters or digits
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
if (!c.IsAlpha())
return 0;
namedEntityStart = slice.Start;
@@ -595,7 +581,8 @@ namespace Markdig.Helpers
while (c != '\0')
{
c = slice.NextChar();
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
if (c.IsAlphaNumeric())
{
if (++counter == 32)
return 0;

View File

@@ -31,12 +31,23 @@ namespace Markdig.Helpers
/// <returns>The next character. `\0` is end of the iteration.</returns>
char NextChar();
/// <summary>
/// Goes to the next character, incrementing the <see cref="Start" /> position.
/// </summary>
void SkipChar();
/// <summary>
/// Peeks at the next character, without incrementing the <see cref="Start"/> position.
/// </summary>
/// <returns>The next character. `\0` is end of the iteration.</returns>
char PeekChar();
/// <summary>
/// Peeks at the next character, without incrementing the <see cref="Start"/> position.
/// </summary>
/// <param name="offset"></param>
/// <returns>The next character. `\0` is end of the iteration.</returns>
char PeekChar(int offset = 1);
char PeekChar(int offset);
/// <summary>
/// Gets a value indicating whether this instance is empty.

View File

@@ -45,13 +45,25 @@ namespace Markdig.Helpers
for (int i = sourcePosition; i < text.Length; i++)
{
char c = text[i];
if (c == '\r' || c == '\n')
if (c == '\r')
{
var slice = new StringSlice(text, sourcePosition, i - 1);
int length = 1;
var newLine = NewLine.CarriageReturn;
if (c == '\r' && (uint)(i + 1) < (uint)text.Length && text[i + 1] == '\n')
{
i++;
length = 2;
newLine = NewLine.CarriageReturnLineFeed;
}
var slice = new StringSlice(text, sourcePosition, i - length, newLine);
SourcePosition = i + 1;
return slice;
}
if (c == '\n')
{
var slice = new StringSlice(text, sourcePosition, i - 1, NewLine.LineFeed);
SourcePosition = i + 1;
return slice;
}

View File

@@ -2,8 +2,9 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using Markdig.Syntax;
namespace Markdig.Helpers
@@ -13,7 +14,7 @@ namespace Markdig.Helpers
/// </summary>
public static class LinkHelper
{
public static bool TryParseAutolink(StringSlice text, out string link, out bool isEmail)
public static bool TryParseAutolink(StringSlice text, [NotNullWhen(true)] out string? link, out bool isEmail)
{
return TryParseAutolink(ref text, out link, out isEmail);
}
@@ -117,7 +118,7 @@ namespace Markdig.Helpers
return c == '_' || c == '-' || c == '.';
}
public static bool TryParseAutolink(ref StringSlice text, out string link, out bool isEmail)
public static bool TryParseAutolink(ref StringSlice text, [NotNullWhen(true)] out string? link, out bool isEmail)
{
link = null;
isEmail = false;
@@ -246,7 +247,7 @@ namespace Markdig.Helpers
break;
}
text.NextChar();
text.SkipChar();
link = builder.ToString();
builder.Length = 0;
return true;
@@ -294,7 +295,7 @@ namespace Markdig.Helpers
if (c == '>')
{
text.NextChar();
text.SkipChar();
link = builder.ToString();
builder.Length = 0;
return true;
@@ -321,22 +322,22 @@ namespace Markdig.Helpers
return false;
}
public static bool TryParseInlineLink(StringSlice text, out string link, out string title)
public static bool TryParseInlineLink(StringSlice text, out string? link, out string? title)
{
return TryParseInlineLink(ref text, out link, out title, out _, out _);
}
public static bool TryParseInlineLink(StringSlice text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)
public static bool TryParseInlineLink(StringSlice text, out string? link, out string? title, out SourceSpan linkSpan, out SourceSpan titleSpan)
{
return TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan);
}
public static bool TryParseInlineLink(ref StringSlice text, out string link, out string title)
public static bool TryParseInlineLink(ref StringSlice text, out string? link, out string? title)
{
return TryParseInlineLink(ref text, out link, out title, out SourceSpan linkSpan, out SourceSpan titleSpan);
}
public static bool TryParseInlineLink(ref StringSlice text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)
public static bool TryParseInlineLink(ref StringSlice text, out string? link, out string? title, out SourceSpan linkSpan, out SourceSpan titleSpan)
{
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
// 2. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
@@ -355,11 +356,11 @@ namespace Markdig.Helpers
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
if (c == '(')
{
text.NextChar();
text.TrimStart();
text.SkipChar();
text.TrimStart(); // this breaks whitespace before an uri
var pos = text.Start;
if (TryParseUrl(ref text, out link))
if (TryParseUrl(ref text, out link, out _))
{
linkSpan.Start = pos;
linkSpan.End = text.Start - 1;
@@ -384,7 +385,7 @@ namespace Markdig.Helpers
{
isValid = true;
}
else if (TryParseTitle(ref text, out title))
else if (TryParseTitle(ref text, out title, out char enclosingCharacter))
{
titleSpan.Start = pos;
titleSpan.End = text.Start - 1;
@@ -407,28 +408,136 @@ namespace Markdig.Helpers
if (isValid)
{
// Skip ')'
text.NextChar();
text.SkipChar();
title ??= string.Empty;
}
return isValid;
}
public static bool TryParseTitle<T>(T text, out string title) where T : ICharIterator
public static bool TryParseInlineLinkTrivia(
ref StringSlice text,
[NotNullWhen(true)] out string? link,
out SourceSpan unescapedLink,
out string? title,
out SourceSpan unescapedTitle,
out char titleEnclosingCharacter,
out SourceSpan linkSpan,
out SourceSpan titleSpan,
out SourceSpan triviaBeforeLink,
out SourceSpan triviaAfterLink,
out SourceSpan triviaAfterTitle,
out bool urlHasPointyBrackets)
{
return TryParseTitle(ref text, out title);
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
// 2. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
// 3. an optional link destination,
// 4. an optional link title separated from the link destination by whitespace,
// 5. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
// 6. and a right parenthesis )
bool isValid = false;
var c = text.CurrentChar;
link = null;
unescapedLink = SourceSpan.Empty;
title = null;
unescapedTitle = SourceSpan.Empty;
linkSpan = SourceSpan.Empty;
titleSpan = SourceSpan.Empty;
triviaBeforeLink = SourceSpan.Empty;
triviaAfterLink = SourceSpan.Empty;
triviaAfterTitle = SourceSpan.Empty;
urlHasPointyBrackets = false;
titleEnclosingCharacter = '\0';
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
if (c == '(')
{
text.SkipChar();
var sourcePosition = text.Start;
text.TrimStart();
triviaBeforeLink = new SourceSpan(sourcePosition, text.Start - 1);
var pos = text.Start;
if (TryParseUrlTrivia(ref text, out link, out urlHasPointyBrackets))
{
linkSpan.Start = pos;
linkSpan.End = text.Start - 1;
unescapedLink.Start = pos + (urlHasPointyBrackets ? 1 : 0);
unescapedLink.End = text.Start - 1 - (urlHasPointyBrackets ? 1 : 0);
if (linkSpan.End < linkSpan.Start)
{
linkSpan = SourceSpan.Empty;
}
int triviaStart = text.Start;
text.TrimStart(out int spaceCount);
triviaAfterLink = new SourceSpan(triviaStart, text.Start - 1);
var hasWhiteSpaces = spaceCount > 0;
c = text.CurrentChar;
if (c == ')')
{
isValid = true;
}
else if (hasWhiteSpaces)
{
c = text.CurrentChar;
pos = text.Start;
if (c == ')')
{
isValid = true;
}
else if (TryParseTitleTrivia(ref text, out title, out titleEnclosingCharacter))
{
titleSpan.Start = pos;
titleSpan.End = text.Start - 1;
unescapedTitle.Start = pos + 1; // skip opening character
unescapedTitle.End = text.Start - 1 - 1; // skip closing character
if (titleSpan.End < titleSpan.Start)
{
titleSpan = SourceSpan.Empty;
}
var startTrivia = text.Start;
text.TrimStart();
triviaAfterTitle = new SourceSpan(startTrivia, text.Start - 1);
c = text.CurrentChar;
if (c == ')')
{
isValid = true;
}
}
}
}
}
if (isValid)
{
// Skip ')'
text.SkipChar();
title ??= string.Empty;
}
return isValid;
}
public static bool TryParseTitle<T>(ref T text, out string title) where T : ICharIterator
public static bool TryParseTitle<T>(T text, out string? title) where T : ICharIterator
{
return TryParseTitle(ref text, out title, out _);
}
public static bool TryParseTitle<T>(ref T text, out string? title, out char enclosingCharacter) where T : ICharIterator
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
enclosingCharacter = '\0';
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
// a sequence of zero or more characters between straight single-quote characters ('), including a ' character only if it is backslash-escaped, or
var c = text.CurrentChar;
if (c == '\'' || c == '"' || c == '(')
{
enclosingCharacter = c;
var closingQuote = c == '(' ? ')' : c;
bool hasEscape = false;
// -1: undefined
@@ -439,7 +548,7 @@ namespace Markdig.Helpers
{
c = text.NextChar();
if (c == '\n')
if (c == '\r' || c == '\n')
{
if (hasOnlyWhiteSpacesSinceLastLine >= 0)
{
@@ -449,6 +558,12 @@ namespace Markdig.Helpers
}
hasOnlyWhiteSpacesSinceLastLine = -1;
}
buffer.Append(c);
if (c == '\r' && text.PeekChar() == '\n')
{
buffer.Append('\n');
}
continue;
}
if (c == '\0')
@@ -466,7 +581,7 @@ namespace Markdig.Helpers
}
// Skip last quote
text.NextChar();
text.SkipChar();
isValid = true;
break;
}
@@ -491,7 +606,7 @@ namespace Markdig.Helpers
hasOnlyWhiteSpacesSinceLastLine = 1;
}
}
else if (c != '\n')
else if (c != '\n' && c != '\r' && (c != '\r' && text.PeekChar() != '\n'))
{
hasOnlyWhiteSpacesSinceLastLine = 0;
}
@@ -505,15 +620,110 @@ namespace Markdig.Helpers
return isValid;
}
public static bool TryParseUrl<T>(T text, out string link) where T : ICharIterator
{
return TryParseUrl(ref text, out link);
}
public static bool TryParseUrl<T>(ref T text, out string link, bool isAutoLink = false) where T : ICharIterator
public static bool TryParseTitleTrivia<T>(ref T text, out string? title, out char enclosingCharacter) where T : ICharIterator
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
enclosingCharacter = '\0';
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
// a sequence of zero or more characters between straight single-quote characters ('), including a ' character only if it is backslash-escaped, or
var c = text.CurrentChar;
if (c == '\'' || c == '"' || c == '(')
{
enclosingCharacter = c;
var closingQuote = c == '(' ? ')' : c;
bool hasEscape = false;
// -1: undefined
// 0: has only spaces
// 1: has other characters
int hasOnlyWhiteSpacesSinceLastLine = -1;
while (true)
{
c = text.NextChar();
if (c == '\r' || c == '\n')
{
if (hasOnlyWhiteSpacesSinceLastLine >= 0)
{
if (hasOnlyWhiteSpacesSinceLastLine == 1)
{
break;
}
hasOnlyWhiteSpacesSinceLastLine = -1;
}
buffer.Append(c);
if (c == '\r' && text.PeekChar() == '\n')
{
buffer.Append('\n');
}
continue;
}
if (c == '\0')
{
break;
}
if (c == closingQuote)
{
if (hasEscape)
{
buffer.Append(closingQuote);
hasEscape = false;
continue;
}
// Skip last quote
text.SkipChar();
isValid = true;
break;
}
if (hasEscape && !c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
if (c == '\\')
{
hasEscape = true;
continue;
}
hasEscape = false;
if (c.IsSpaceOrTab())
{
if (hasOnlyWhiteSpacesSinceLastLine < 0)
{
hasOnlyWhiteSpacesSinceLastLine = 1;
}
}
else if (c != '\n' && c != '\r' && (c != '\r' && text.PeekChar() != '\n'))
{
hasOnlyWhiteSpacesSinceLastLine = 0;
}
buffer.Append(c);
}
}
title = isValid ? buffer.ToString() : null;
buffer.Length = 0;
return isValid;
}
public static bool TryParseUrl<T>(T text, [NotNullWhen(true)] out string? link) where T : ICharIterator
{
return TryParseUrl(ref text, out link, out _);
}
public static bool TryParseUrl<T>(ref T text, [NotNullWhen(true)] out string? link, out bool hasPointyBrackets, bool isAutoLink = false) where T : ICharIterator
{
bool isValid = false;
hasPointyBrackets = false;
var buffer = StringBuilderCache.Local();
var c = text.CurrentChar;
@@ -527,7 +737,8 @@ namespace Markdig.Helpers
c = text.NextChar();
if (!hasEscape && c == '>')
{
text.NextChar();
text.SkipChar();
hasPointyBrackets = true;
isValid = true;
break;
}
@@ -548,7 +759,7 @@ namespace Markdig.Helpers
continue;
}
if (c.IsNewLine())
if (c.IsNewLineOrLineFeed())
{
break;
}
@@ -648,6 +859,152 @@ namespace Markdig.Helpers
return isValid;
}
public static bool TryParseUrlTrivia<T>(ref T text, out string? link, out bool hasPointyBrackets, bool isAutoLink = false) where T : ICharIterator
{
bool isValid = false;
hasPointyBrackets = false;
var buffer = StringBuilderCache.Local();
var unescaped = new StringBuilder();
var c = text.CurrentChar;
// a sequence of zero or more characters between an opening < and a closing >
// that contains no line breaks, or unescaped < or > characters, or
if (c == '<')
{
bool hasEscape = false;
do
{
c = text.NextChar();
if (!hasEscape && c == '>')
{
text.SkipChar();
hasPointyBrackets = true;
isValid = true;
break;
}
if (!hasEscape && c == '<')
{
break;
}
if (hasEscape && !c.IsAsciiPunctuation())
{
buffer.Append('\\');
unescaped.Append('\\');
}
if (c == '\\')
{
hasEscape = true;
unescaped.Append('\\');
continue;
}
if (c.IsNewLineOrLineFeed())
{
break;
}
hasEscape = false;
buffer.Append(c);
unescaped.Append(c);
} while (c != '\0');
}
else
{
// a nonempty sequence of characters that does not start with <, does not include ASCII space or control characters,
// and includes parentheses only if (a) they are backslash-escaped or (b) they are part of a
// balanced pair of unescaped parentheses that is not itself inside a balanced pair of unescaped
// parentheses.
bool hasEscape = false;
int openedParent = 0;
while (true)
{
// Match opening and closing parenthesis
if (c == '(')
{
if (!hasEscape)
{
openedParent++;
}
}
if (c == ')')
{
if (!hasEscape)
{
openedParent--;
if (openedParent < 0)
{
isValid = true;
break;
}
}
}
if (!isAutoLink)
{
if (hasEscape && !c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
// If we have an escape
if (c == '\\')
{
hasEscape = true;
c = text.NextChar();
unescaped.Append('\\');
continue;
}
hasEscape = false;
}
if (IsEndOfUri(c, isAutoLink))
{
isValid = true;
break;
}
if (isAutoLink)
{
if (c == '&')
{
if (HtmlHelper.ScanEntity(text, out _, out _, out _) > 0)
{
isValid = true;
break;
}
}
if (IsTrailingUrlStopCharacter(c) && IsEndOfUri(text.PeekChar(), true))
{
isValid = true;
break;
}
}
buffer.Append(c);
unescaped.Append(c);
c = text.NextChar();
}
if (openedParent > 0)
{
isValid = false;
}
}
link = isValid ? buffer.ToString() : null;
buffer.Length = 0;
return isValid;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsTrailingUrlStopCharacter(char c)
{
@@ -712,20 +1069,13 @@ namespace Markdig.Helpers
segmentCount - lastUnderscoreSegment >= 2; // No underscores are present in the last two segments of the domain
}
public static bool TryParseLinkReferenceDefinition<T>(T text, out string label, out string url,
out string title) where T : ICharIterator
{
return TryParseLinkReferenceDefinition(ref text, out label, out url, out title);
}
public static bool TryParseLinkReferenceDefinition<T>(ref T text, out string label, out string url, out string title)
where T : ICharIterator
{
return TryParseLinkReferenceDefinition(ref text, out label, out url, out title, out SourceSpan labelSpan, out SourceSpan urlSpan,
out SourceSpan titleSpan);
}
public static bool TryParseLinkReferenceDefinition<T>(ref T text, out string label, out string url, out string title, out SourceSpan labelSpan, out SourceSpan urlSpan, out SourceSpan titleSpan) where T : ICharIterator
public static bool TryParseLinkReferenceDefinition<T>(ref T text,
out string? label,
out string? url,
out string? title,
out SourceSpan labelSpan,
out SourceSpan urlSpan,
out SourceSpan titleSpan) where T : ICharIterator
{
url = null;
title = null;
@@ -743,14 +1093,14 @@ namespace Markdig.Helpers
label = null;
return false;
}
text.NextChar(); // Skip ':'
text.SkipChar(); // Skip ':'
// Skip any whitespace before the url
text.TrimStart();
urlSpan.Start = text.Start;
bool isAngleBracketsUrl = text.CurrentChar == '<';
if (!TryParseUrl(ref text, out url) || (!isAngleBracketsUrl && string.IsNullOrEmpty(url)))
if (!TryParseUrl(ref text, out url, out _) || (!isAngleBracketsUrl && string.IsNullOrEmpty(url)))
{
return false;
}
@@ -762,7 +1112,7 @@ namespace Markdig.Helpers
if (c == '\'' || c == '"' || c == '(')
{
titleSpan.Start = text.Start;
if (TryParseTitle(ref text, out title))
if (TryParseTitle(ref text, out title, out _))
{
titleSpan.End = text.Start - 1;
// If we have a title, it requires a whitespace after the url
@@ -778,7 +1128,7 @@ namespace Markdig.Helpers
}
else
{
if (text.CurrentChar == '\0' || newLineCount > 0)
if (text.IsEmpty || newLineCount > 0)
{
return true;
}
@@ -811,27 +1161,188 @@ namespace Markdig.Helpers
return true;
}
public static bool TryParseLabel<T>(T lines, out string label) where T : ICharIterator
public static bool TryParseLinkReferenceDefinitionTrivia<T>(
ref T text,
out SourceSpan triviaBeforeLabel,
out string? label,
out SourceSpan labelWithTrivia,
out SourceSpan triviaBeforeUrl, // can contain newline
out string? url,
out SourceSpan unescapedUrl,
out bool urlHasPointyBrackets,
out SourceSpan triviaBeforeTitle, // can contain newline
out string? title, // can contain non-consecutive newlines
out SourceSpan unescapedTitle,
out char titleEnclosingCharacter,
out NewLine newLine,
out SourceSpan triviaAfterTitle,
out SourceSpan labelSpan,
out SourceSpan urlSpan,
out SourceSpan titleSpan) where T : ICharIterator
{
labelWithTrivia = SourceSpan.Empty;
triviaBeforeUrl = SourceSpan.Empty;
url = null;
unescapedUrl = SourceSpan.Empty;
triviaBeforeTitle = SourceSpan.Empty;
title = null;
unescapedTitle = SourceSpan.Empty;
newLine = NewLine.None;
urlSpan = SourceSpan.Empty;
titleSpan = SourceSpan.Empty;
text.TrimStart();
triviaBeforeLabel = new SourceSpan(0, text.Start - 1);
triviaAfterTitle = SourceSpan.Empty;
urlHasPointyBrackets = false;
titleEnclosingCharacter = '\0';
labelWithTrivia.Start = text.Start + 1; // skip opening [
if (!TryParseLabelTrivia(ref text, out label, out labelSpan))
{
return false;
}
labelWithTrivia.End = text.Start - 2; // skip closing ] and subsequent :
if (text.CurrentChar != ':')
{
label = null;
return false;
}
text.SkipChar(); // Skip ':'
var triviaBeforeUrlStart = text.Start;
// Skip any whitespace before the url
text.TrimStart();
triviaBeforeUrl = new SourceSpan(triviaBeforeUrlStart, text.Start - 1);
urlSpan.Start = text.Start;
bool isAngleBracketsUrl = text.CurrentChar == '<';
unescapedUrl.Start = text.Start + (isAngleBracketsUrl ? 1 : 0);
if (!TryParseUrlTrivia(ref text, out url, out urlHasPointyBrackets) || (!isAngleBracketsUrl && string.IsNullOrEmpty(url)))
{
return false;
}
urlSpan.End = text.Start - 1;
unescapedUrl.End = text.Start - 1 - (isAngleBracketsUrl ? 1 : 0);
int triviaBeforeTitleStart = text.Start;
var saved = text;
var hasWhiteSpaces = CharIteratorHelper.TrimStartAndCountNewLines(ref text, out int newLineCount, out newLine);
// Remove the newline from the trivia (as it may have multiple lines)
var triviaBeforeTitleEnd = text.Start - 1;
triviaBeforeTitle = new SourceSpan(triviaBeforeTitleStart, triviaBeforeTitleEnd);
var c = text.CurrentChar;
if (c == '\'' || c == '"' || c == '(')
{
titleSpan.Start = text.Start;
unescapedTitle.Start = text.Start + 1; // + 1; // skip opening enclosing character
if (TryParseTitleTrivia(ref text, out title, out titleEnclosingCharacter))
{
titleSpan.End = text.Start - 1;
unescapedTitle.End = text.Start - 1 - 1; // skip closing enclosing character
// If we have a title, it requires a whitespace after the url
if (!hasWhiteSpaces)
{
return false;
}
// Discard the newline if we have a title
newLine = NewLine.None;
}
else
{
return false;
}
}
else
{
if (text.IsEmpty || newLineCount > 0)
{
// If we have an end of line, we need to remove it from the trivia
triviaBeforeTitle.End -= newLine.Length();
triviaAfterTitle = new SourceSpan(text.Start, text.Start - 1);
return true;
}
}
// Check that the current line has only trailing spaces
c = text.CurrentChar;
int triviaAfterTitleStart = text.Start;
while (c.IsSpaceOrTab())
{
c = text.NextChar();
}
if (c != '\0' && c != '\n' && c != '\r' && (c != '\r' && text.PeekChar() != '\n'))
{
// If we were able to parse the url but the title doesn't end with space,
// we are still returning a valid definition
if (newLineCount > 0 && title != null)
{
text = saved;
title = null;
newLine = NewLine.None;
unescapedTitle = SourceSpan.Empty;
triviaAfterTitle = SourceSpan.Empty;
return true;
}
label = null;
url = null;
unescapedUrl = SourceSpan.Empty;
title = null;
unescapedTitle = SourceSpan.Empty;
return false;
}
triviaAfterTitle = new SourceSpan(triviaAfterTitleStart, text.Start - 1);
if (c != '\0')
{
if (c == '\n')
{
newLine = NewLine.LineFeed;
}
else if (c == '\r' && text.PeekChar() == '\n')
{
newLine = NewLine.CarriageReturnLineFeed;
}
else if (c == '\r')
{
newLine = NewLine.CarriageReturn;
}
}
return true;
}
public static bool TryParseLabel<T>(T lines, [NotNullWhen(true)] out string? label) where T : ICharIterator
{
return TryParseLabel(ref lines, false, out label, out SourceSpan labelSpan);
}
public static bool TryParseLabel<T>(T lines, out string label, out SourceSpan labelSpan) where T : ICharIterator
public static bool TryParseLabel<T>(T lines, [NotNullWhen(true)] out string? label, out SourceSpan labelSpan) where T : ICharIterator
{
return TryParseLabel(ref lines, false, out label, out labelSpan);
}
public static bool TryParseLabel<T>(ref T lines, out string label) where T : ICharIterator
public static bool TryParseLabel<T>(ref T lines, [NotNullWhen(true)] out string? label) where T : ICharIterator
{
return TryParseLabel(ref lines, false, out label, out SourceSpan labelSpan);
}
public static bool TryParseLabel<T>(ref T lines, out string label, out SourceSpan labelSpan) where T : ICharIterator
public static bool TryParseLabel<T>(ref T lines, [NotNullWhen(true)] out string? label, out SourceSpan labelSpan) where T : ICharIterator
{
return TryParseLabel(ref lines, false, out label, out labelSpan);
}
public static bool TryParseLabel<T>(ref T lines, bool allowEmpty, out string label, out SourceSpan labelSpan) where T : ICharIterator
public static bool TryParseLabelTrivia<T>(ref T lines, [NotNullWhen(true)] out string? label, out SourceSpan labelSpan) where T : ICharIterator
{
return TryParseLabelTrivia(ref lines, false, out label, out labelSpan);
}
public static bool TryParseLabel<T>(ref T lines, bool allowEmpty, [NotNullWhen(true)] out string? label, out SourceSpan labelSpan) where T : ICharIterator
{
label = null;
char c = lines.CurrentChar;
@@ -873,7 +1384,7 @@ namespace Markdig.Helpers
if (c == ']')
{
lines.NextChar(); // Skip ]
lines.SkipChar(); // Skip ]
if (allowEmpty || hasNonWhiteSpace)
{
// Remove trailing spaces
@@ -945,5 +1456,125 @@ namespace Markdig.Helpers
return isValid;
}
public static bool TryParseLabelTrivia<T>(ref T lines, bool allowEmpty, out string? label, out SourceSpan labelSpan) where T : ICharIterator
{
label = null;
char c = lines.CurrentChar;
labelSpan = SourceSpan.Empty;
if (c != '[')
{
return false;
}
var buffer = StringBuilderCache.Local();
var startLabel = -1;
var endLabel = -1;
bool hasEscape = false;
bool previousWhitespace = true;
bool hasNonWhiteSpace = false;
bool isValid = false;
while (true)
{
c = lines.NextChar();
if (c == '\0')
{
break;
}
if (hasEscape)
{
if (c != '[' && c != ']' && c != '\\')
{
break;
}
}
else
{
if (c == '[')
{
break;
}
if (c == ']')
{
lines.SkipChar(); // Skip ]
if (allowEmpty || hasNonWhiteSpace)
{
// Remove trailing spaces
for (int i = buffer.Length - 1; i >= 0; i--)
{
if (!buffer[i].IsWhitespace())
{
break;
}
buffer.Length = i;
endLabel--;
}
// Only valid if buffer is less than 1000 characters
if (buffer.Length <= 999)
{
labelSpan.Start = startLabel;
labelSpan.End = endLabel;
if (labelSpan.Start > labelSpan.End)
{
labelSpan = SourceSpan.Empty;
}
label = buffer.ToString();
isValid = true;
}
}
break;
}
}
var isWhitespace = c.IsWhitespace();
if (!hasEscape && c == '\\')
{
if (startLabel < 0)
{
startLabel = lines.Start;
}
hasEscape = true;
}
else
{
hasEscape = false;
if (!previousWhitespace || !isWhitespace)
{
if (startLabel < 0)
{
startLabel = lines.Start;
}
endLabel = lines.Start;
if (isWhitespace)
{
// Replace any whitespace by a single ' '
buffer.Append(' ');
}
else
{
buffer.Append(c);
}
if (!isWhitespace)
{
hasNonWhiteSpace = true;
}
}
}
previousWhitespace = isWhitespace;
}
buffer.Length = 0;
return isValid;
}
}
}

View File

@@ -0,0 +1,35 @@
// 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;
namespace Markdig.Helpers
{
/// <summary>
/// Represents a character or set of characters that represent a separation
/// between two lines of text
/// </summary>
public enum NewLine : byte
{
// Values have the length encoded in last 2 bits
None = 0,
CarriageReturn = 4 | 1,
LineFeed = 8 | 1,
CarriageReturnLineFeed = 16 | 2
}
public static class NewLineExtensions
{
public static string AsString(this NewLine newLine) => newLine switch
{
NewLine.CarriageReturnLineFeed => "\r\n",
NewLine.LineFeed => "\n",
NewLine.CarriageReturn => "\r",
_ => string.Empty,
};
public static int Length(this NewLine newLine) => (int)newLine & 3;
}
}

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