Compare commits

...

297 Commits

Author SHA1 Message Date
Alexandre Mutel
9cc5856c1c Bump to 0.13.3 2017-08-30 15:47:54 +02:00
Alexandre Mutel
a4bb174a77 Add .editorconfig 2017-08-30 15:44:08 +02:00
Alexandre Mutel
82987fa879 Merge pull request #139 from kenmuse/enhance/yaml-frontmatter
Implement Pandoc compatible front matter parsing
2017-08-30 15:36:38 +02:00
Ken Muse
2df2ff17c5 Replace tabs with spaces 2017-08-30 09:29:27 -04:00
Ken Muse
4e4825cb3f Implement Pandoc compatible front matter parsing 2017-08-29 18:49:14 -04:00
Alexandre Mutel
ed2371beae Update MSBuild.Sdk.Extras to version 1.0.9 to fix compatibility with pack for uap10.0 2017-08-29 08:06:29 +02:00
Alexandre Mutel
2c2898769e Bump to 0.13.2 2017-08-29 07:48:27 +02:00
Alexandre Mutel
001a99ab77 Add support for UAP10.0 (#137) 2017-08-29 07:48:14 +02:00
Alexandre Mutel
9233ec220c Bump to 0.13.1 2017-08-21 16:23:31 +02:00
Alexandre Mutel
bb30dc21c5 Fix indenting issue after a double digit list block using a tab (#134) 2017-08-21 16:04:47 +02:00
Alexandre Mutel
c761fa2243 Bump to 0.13.0 2017-08-03 19:26:48 +02:00
Alexandre Mutel
9d172ffcc1 Update URL links to accept balanced parenthesis for CommonMark specs 0.28 2017-08-03 19:25:17 +02:00
Alexandre Mutel
1863e22328 Update to emphasis to CommonMark specs 0.28 (when *** prefer em, strong vs strong, em) 2017-08-03 19:20:58 +02:00
Alexandre Mutel
c591d1950f Update CommonMark test specs to 0.28 2017-08-03 19:20:23 +02:00
Alexandre Mutel
339fcc9152 Add signed package link to readme.md (#11) 2017-08-03 14:57:28 +02:00
Alexandre Mutel
3a54f06540 Fix appveyor.yml for multiline cmd 2017-08-03 14:47:07 +02:00
Alexandre Mutel
3f305a25a8 Add sign assembly to build 2017-08-03 14:41:52 +02:00
Alexandre Mutel
5a210223b6 Bump to 0.12.4 2017-07-31 11:17:13 +02:00
Alexandre Mutel
a7786d934d Make Profile328 (for sl50 compat) not using unsafe code to pass verifier (issue #131) 2017-07-31 11:17:01 +02:00
Alexandre Mutel
cb3c1f1505 Merge pull request #124 from joannaz/master
Fix abbreviation matching
2017-07-03 14:38:25 +02:00
Joanna Zhang
50b33b8512 Add more test cases for abbreviation 2017-07-03 13:12:23 +01:00
Joanna Zhang
80790b5038 Fix abbreviation parsing 2017-07-03 11:55:31 +01:00
Joanna Zhang
311c28ae60 Fix abbreviation matching 2017-06-29 15:00:56 +01:00
Alexandre Mutel
9906a0554f Merge pull request #123 from joannaz/master
Fix abbreviation at start of paragraphs
2017-06-28 13:59:11 +02:00
Joanna Zhang
7e92f1881d Fix Abbrevation at start 2017-06-28 12:25:14 +01:00
Alexandre Mutel
41911806a0 Bump to 0.12.3 2017-06-26 11:53:59 +02:00
Alexandre Mutel
610f1519b0 Fix issue with HTML blocks for heading h2,h3,h4,h5,h6 that were not correctly identified as HTML blocks as per CommonMark spec 2017-06-26 11:53:47 +02:00
Alexandre Mutel
29aa56b4f1 Bump to 0.12.2 2017-06-18 10:58:40 +02:00
Alexandre Mutel
5004ecedb7 Use TestParser.TestSpec instead to mitigate problem with \r 2017-06-18 10:56:44 +02:00
Alexandre Mutel
ba06a796dc Merge branch 'pr/n121_pearsonn' 2017-06-18 10:50:39 +02:00
Nick Pearson
b1d6f34976 Fix issue in GenericAttributesParser causing inline elements to be skipped 2017-06-14 15:20:16 -07:00
Alexandre Mutel
28f4236a57 Bump to 0.12.1 2017-05-29 12:26:17 +02:00
Alexandre Mutel
0739a82735 Merge pull request #119 from meziantou/master
MediaLinkExtension: Remove unexpected closing tag
2017-05-28 19:00:54 +02:00
Meziantou
3ac9f2e788 Fix issue 2017-05-28 12:18:59 +02:00
Meziantou
d9607cc687 Update tests 2017-05-28 12:18:50 +02:00
Alexandre Mutel
f0573ef9e2 Bump to 0.12.0 2017-05-28 08:24:50 +02:00
Alexandre Mutel
017a3bd7e2 Merge branch 'pr/n118_meziantou' 2017-05-28 08:17:46 +02:00
Alexandre Mutel
d6f9a1055c Remove unnecessary test in BlockProcessor (#115) 2017-05-28 08:14:58 +02:00
Alexandre Mutel
2612e9d565 Fix issue in TryParseAutolink in LinkHelper (#115) 2017-05-28 08:02:55 +02:00
Alexandre Mutel
1076d7af10 Fix issue in ScanEntity (#115) 2017-05-28 07:56:58 +02:00
Meziantou
850cecfa6c Fix issue 2017-05-27 16:16:46 +02:00
Meziantou
bfdb03bd78 Update tests 2017-05-27 16:16:33 +02:00
Alexandre Mutel
56b6e329d5 Add missing update to Tests.csproj 2017-05-18 11:31:51 +02:00
Alexandre Mutel
006e384d1a Update links 2017-05-18 11:31:24 +02:00
Alexandre Mutel
9cd9b683a5 Remove specialized renderer for JiraLinks now going through the standard LinkInline 2017-05-18 11:04:42 +02:00
Alexandre Mutel
e01e2c3d2b Add StringBuilder extensions to accept a StringSlice 2017-05-18 11:04:06 +02:00
Alexandre Mutel
97ce0da564 Update JiraLinks, allow ( and ), use StringSlice instead of strings for key/issue, add support for attributes, add tests 2017-05-18 10:33:16 +02:00
Alexandre Mutel
26c5ce59b5 Update Benchmarks to latest DotNetBenchmark. Remove nuget.targets 2017-05-18 09:33:26 +02:00
Alexandre Mutel
d2d94ecf39 Merge branch 'pr/n113_clarkd' 2017-05-18 09:18:50 +02:00
Dave Clarke
0ec82bb81d Fix comment 2017-05-17 11:55:30 +01:00
Dave Clarke
ad9e941cb0 Fix missing space before target attribute
Rename Key -> ProjectKey
2017-05-16 16:48:25 +01:00
Dave Clarke
27dab6ca6d Automatically link JIRA issue references 2017-05-16 12:02:44 +01:00
Alexandre Mutel
762196f03b Bump to 0.11.0 2017-05-08 12:05:15 +02:00
Alexandre Mutel
26a565fa45 Fix/improve parsing of math inline, improving rules for matching, disallow newline between $$/$ for inline parsing (#87) 2017-05-08 11:50:40 +02:00
Alexandre Mutel
4369db1a43 Fix issue with $$ block parsing that should be interpreted as inline parsing (#105) 2017-05-08 11:48:44 +02:00
Alexandre Mutel
d83f1f87cc Add custom arrows to emoji 2017-05-08 09:25:44 +02:00
Alexandre Mutel
14027f4be3 Migrate to VS2017 - dotnet new csproj 2017-05-06 15:41:07 +02:00
Alexandre Mutel
8bcfb53607 Merge pull request #109 from aKzenT/issue-108
Fix for issue #108
2017-04-29 11:11:05 +02:00
Thomas Krause
700bb21e03 Fix for issue #108 2017-04-28 21:47:41 +02:00
Alexandre Mutel
1bc9d9083c Merge pull request #105 from MatthewRichards/DotTerminatedUrls
Treat trailing full stop after a URL as not being part of the URL
2017-04-04 10:02:38 +02:00
Alexandre Mutel
1d7bb021a7 Bump version to 0.10.7 2017-04-01 10:20:18 +02:00
Matthew Richards
3305a74b06 Additional test case 2017-03-30 08:51:05 +01:00
Matthew Richards
6312bc0515 Treat trailing full stop after a URL as not being part of the URL 2017-03-29 16:47:41 +01:00
Alexandre Mutel
b595383281 Merge pull request #103 from Daniel15/replace
Add Replace method to replace an element in an OrderedList
2017-03-20 10:39:01 +01:00
Alexandre Mutel
bf85964543 Merge pull request #102 from Daniel15/lock-dotnet
Lock .NET Core SDK to 1.0.0-preview2
2017-03-20 10:37:16 +01:00
Daniel Lo Nigro
d5b020b784 Add Replace method to replace an element in an OrderedList 2017-03-19 14:39:24 -07:00
Daniel Lo Nigro
8ab0467e78 Lock .NET Core SDK to 1.0.0-preview2 2017-03-19 13:42:26 -07:00
Alexandre Mutel
74fe3be7c7 Bump version to 0.10.6 2017-02-24 07:52:29 +01:00
Alexandre Mutel
b47ae8d9ba Fix emphasis with HTML entities (#97) 2017-02-24 07:49:46 +01:00
Alexandre Mutel
a926e1a326 Bump version to 0.10.5 2017-02-14 17:36:54 +01:00
Alexandre Mutel
6048406f52 Merge branch 'pr/n89_johnsimons' 2017-01-31 13:01:17 +01:00
John Simons
1edf14c742 Remove requirement for space after emoji.
Fixes #88
2017-01-31 12:58:32 +01:00
Alexandre Mutel
d4bb7c0a51 Merge pull request #82 from christophano/bugfix/abbreviation_parser_fix
Fixes the IsValidAbbreviation method in AbbreviationParser
2016-12-19 14:37:32 +01:00
Chris Rodgers
ba4e463517 Adds the test I ought to have included to begin with. 2016-12-19 13:25:56 +00:00
Chris Rodgers
2d0dae4ebb Fixes the IsValidAbbreviation method in AbbreviationParser 2016-12-19 13:05:03 +00:00
Alexandre Mutel
1a9d923c44 Merge pull request #81 from volkanceylan/master
Use Char.ToUpperInvariant instead of Char.ToUpper
2016-12-18 22:20:20 +01:00
volkan.ceylan
013ec0bf80 Due to use of Char.ToUpper, ListExtraItemParser fails in cultures like Turkish where uppercase of 'i' is 'İ'. Char.ToUpperInvariant should be used instead. 2016-12-18 11:47:25 +03:00
Alexandre Mutel
f82e874cb7 Merge pull request #80 from christophano/feature/pipeline_builder_use_method
Adds fluent extension registration method to MarkdownPipelineBuilder.
2016-12-15 11:49:32 +01:00
Chris Rodgers
976fc569fa Moves new Use method to MarkdownExtensions and changes it to extension method. 2016-12-15 10:31:02 +00:00
Chris Rodgers
5aab21f0bf Adds fluent extension registration method to MarkdownPipelineBuilder. 2016-12-15 10:07:54 +00:00
Alexandre Mutel
1df8344576 Bump to 0.10.4 2016-12-12 11:03:48 +01:00
Alexandre Mutel
9eb84cf95e Normalize pipe tables so they contain the same number of columns for all rows 2016-12-11 19:01:49 +01:00
Alexandre Mutel
47d45b5577 Fix issue when parsing autolinks embraced by a pending emphasis (issue #78) 2016-12-11 18:08:51 +01:00
Alexandre Mutel
ebcc286df0 Bump version to 0.10.3 2016-11-29 18:02:39 +01:00
Alexandre Mutel
f11d8037ac Fix issue when the last character of a table is not a newline and there are spaces in-between (issue #73) 2016-11-29 17:43:28 +01:00
Alexandre Mutel
f668b3fe38 Bump version to 0.10.2 2016-11-26 11:20:49 +01:00
Alexandre Mutel
5ca4e31332 Fix exception when trying to Urilize an url with an unicode character outside the supported range by string.Normalize + NormD (issue #75) 2016-11-26 11:20:37 +01:00
Alexandre Mutel
a8fbe79e30 Bump to 0.10.1 2016-11-19 16:53:56 +01:00
Alexandre Mutel
bdb09b9820 Update to latest CommonMark specs 0.27 2016-11-19 16:53:44 +01:00
Alexandre Mutel
79348ebea8 Add span to LinkReferenceDefinition (issue #51) 2016-10-27 17:06:37 +02:00
Alexandre Mutel
b366c1a5e3 Bump to 0.10.0 2016-10-16 21:30:55 +02:00
Alexandre Mutel
a5e53b014f Add better support to discover active extensions when setup renderer (issue #66) 2016-10-16 21:30:38 +02:00
Alexandre Mutel
c2f21e21c8 Bump to 0.9.1 2016-10-11 14:45:03 +02:00
Alexandre Mutel
81b8dce7a8 Fix regression between autolink extension with html inline link a /regular or regular markdown links (issue #65) 2016-10-11 14:44:50 +02:00
Alexandre Mutel
e6223f86d8 Update readme with autolink extension 2016-10-11 13:15:10 +02:00
Alexandre Mutel
2054c16662 Bump to 0.9.0 2016-10-11 13:10:25 +02:00
Alexandre Mutel
95641e562f Add support for the autolink extension (issue #64) 2016-10-11 13:10:06 +02:00
Alexandre Mutel
e4953931c7 Update donation logo 2016-09-29 20:00:08 +02:00
Alexandre Mutel
93b5b7a091 Add donation button 2016-09-29 18:23:30 +02:00
Alexandre Mutel
4f52e893ee Disable warning for comments as it slows down build on appveyor 2016-09-23 14:01:01 +02:00
Alexandre Mutel
23ede077a1 Bump version to 0.8.5 2016-09-23 13:48:15 +02:00
Alexandre Mutel
d5e6f17683 Allow to force table alignment to left (issue #62) 2016-09-23 13:43:49 +02:00
Alexandre Mutel
4d5980a485 Bump version to 0.8.4 2016-09-22 21:48:58 +02:00
Alexandre Mutel
19dd902519 Fix issue when calculating the span of an indented code block within a list. Make sure to include first whitespace on the line 2016-09-22 21:48:15 +02:00
Alexandre Mutel
f046a46275 Bump version to 0.8.3 2016-09-22 16:26:54 +02:00
Alexandre Mutel
25e9eafa8b Allow to pass a manual pipeline to TestParser.TestSpec 2016-09-22 16:25:46 +02:00
Alexandre Mutel
f4ff981008 Fix NullReferenceException with GridTable extension when a single + is entered on a line 2016-09-22 16:25:13 +02:00
Alexandre Mutel
a1b48aff89 Bump version to 0.8.2 2016-09-22 12:44:52 +02:00
Alexandre Mutel
0aa34caa82 Merge pull request #61 from christophano/bugfix/literal-inline-parser
Fixes issue where LiteralInlineParser calls PostMatch while processor.Inline is type other than LiteralInline
2016-09-22 12:40:16 +02:00
Chris Rodgers
98ce9b1a06 Fixes issue where LiteralInlineParser calls PostMatch while processor.Inline is type other than LiteralInline 2016-09-22 09:28:52 +01:00
Alexandre Mutel
ab157b21ea Update the readme with recent extensions added 2016-09-22 09:31:43 +02:00
Alexandre Mutel
53c72d3031 Bump version to 0.8.1 2016-09-21 13:06:51 +02:00
Alexandre Mutel
165e2f97d0 Output attached html attributes for dd in definition lists (fix issue #59) 2016-09-21 13:06:37 +02:00
Alexandre Mutel
6c577059ad Add extension NonAsciiNoEscape for URI to workaround a bug/singular behavior of EDGE/IE for local file links with non US-ASCII characters 2016-09-21 12:57:16 +02:00
Alexandre Mutel
0ea4dc769b Merge pull request #60 from christophano/bugfix/abbreviation-preceded-by-punctuation
Fixes issue where punctuation forms part of word, so abbreviation is not valid
2016-09-20 16:37:21 +02:00
Chris Rodgers
a9b626e810 Fixes issue where punctuation forms part of word, so abbreviation is not valid. 2016-09-20 15:22:31 +01:00
Alexandre Mutel
b90d6f9769 Bump to 0.8.0 2016-09-19 10:36:35 +02:00
Alexandre Mutel
f0ea008c46 Add support for YAML frontmatter parsing/discard (issue #37) 2016-09-19 10:11:38 +02:00
Alexandre Mutel
c43d5ccd63 Don't create an empty LiteralInline when trimming spaces at the end of a line (issue #42) 2016-09-19 09:10:36 +02:00
Alexandre Mutel
d6d7b398e4 Update to latest CommonMark specs. Fix new corner cases when parsing inline links 2016-09-18 13:24:19 +02:00
Alexandre Mutel
e1032e5094 Bump version to 0.7.5 2016-09-18 11:21:17 +02:00
Alexandre Mutel
a32ac298c5 Merge branch 'pr/n58_christophano'
# Conflicts:
#	src/Markdig/Parsers/InlineProcessor.cs
2016-09-18 11:12:10 +02:00
Alexandre Mutel
2e6ab670cb Keep UrlSpan (and LabelSpan and TitleSpan) for a LinkReferenceDefinition (issue #51) 2016-09-18 11:07:56 +02:00
Alexandre Mutel
72b7fce48c Fix extension DisableHtml not working properly with inline HTML (issue #46) 2016-09-18 10:59:12 +02:00
Alexandre Mutel
fa281f1ca1 Fix pipetable parsing (issue #44) 2016-09-18 10:44:26 +02:00
Chris Rodgers
1b92311aeb Fixes issue when using colon (among others) on lines with abbreviations 2016-09-15 14:18:58 +01:00
Alexandre Mutel
a593212f03 Merge pull request #45 from christophano/feature/rowspan
Adds support for rowspans in grid tables
2016-09-15 09:03:44 +02:00
Alexandre Mutel
c4ec928953 Merge pull request #57 from christophano/bugfix/punctuation-exceptions
Adds exceptions to allow certain punctuation characters to behave as inline delimiters
2016-09-15 09:00:57 +02:00
Chris Rodgers
e7df7fabeb Adds exceptions to allow certain punctuation characters to behave as inline delimiters 2016-09-14 12:00:31 +01:00
Alexandre Mutel
1c187f2d81 Merge pull request #56 from christophano/bugfix/similar-abbreviations
Fixes problem with multiple, similar abbreviations not parsing correctly
2016-09-14 07:42:26 +02:00
Alexandre Mutel
da66cf90c3 Merge pull request #55 from christophano/bugfix/restart-list-after-paragraph
Checks if paragraph block is closed when deciding if continuation list is allowable with character other than '1.'
2016-09-14 07:40:33 +02:00
Alexandre Mutel
0e8bd7407f Merge pull request #49 from christophano/bugfix/nested-definition-lists
Fixes issue where multiple definition lists are created when they are nested in a list item.
2016-09-14 07:39:24 +02:00
Chris Rodgers
d70f14addb Fixes problem with multiple, similar abbreviations not parsing correctly. 2016-09-13 16:53:25 +01:00
Chris Rodgers
e7b9eea3a5 Checks if paragraph block is closed when deciding if continuation list is allowable with character other than '1.'
Fixes #54
2016-09-09 14:09:52 +01:00
Alexandre Mutel
97af9d822d Merge pull request #50 from christophano/bugfix/ordered-list-start-attribute
Changes List Extra to output valid integer for start attribute, instead of roman or latin character.
2016-08-27 09:07:46 +02:00
Alexandre Mutel
cf7a09ab76 Merge pull request #52 from christophano/bugfix/merge-auto-generated-id
fixes issue where using special attributes overwites element id, even when id is not specified
2016-08-27 09:05:22 +02:00
Chris Rodgers
e755627421 fixes issue where using special attributes overwites element id, even when id is not specified 2016-08-25 09:11:50 +01:00
Chris Rodgers
f9be64a988 Changes List Extra to output valid integer for start attribute, instead of roman or latin character. 2016-08-23 13:58:06 +01:00
Alexandre Mutel
d65431e6cc Merge pull request #47 from christophano/bugfix/inline-maths-with-trailing-text
Fixes issue where trailing text after an inline math block is both included in the math block and appended after the math block.
2016-08-23 08:19:01 +02:00
Chris Rodgers
86fb962fdb Removes BlankLineBlock if it is found after an active definition list. 2016-08-22 15:41:39 +01:00
Chris Rodgers
d003837b27 Fixes issue where multiple definition lists are created when they are nested in a list item. 2016-08-22 14:47:43 +01:00
Chris Rodgers
fd813e3c5a Fixes issue where trailing text after an inline math block is both included in the math block and appended after the math block. 2016-08-19 12:15:17 +01:00
Chris Rodgers
a3691c4423 Fixed issue when malformed tables can result in an unhandled exception being thrown. 2016-08-12 15:57:35 +01:00
Chris Rodgers
9506f22025 Adds support for rowspans in grid tables 2016-08-12 15:57:28 +01:00
Alexandre Mutel
06ae907949 Bump version to 0.7.4 2016-07-30 11:35:36 +02:00
Alexandre Mutel
555523b2af Add additional tests for emphasis (issue #43) 2016-07-30 11:33:45 +02:00
Alexandre Mutel
3b9772f772 Merge branch 'pr/n43_christophano' 2016-07-30 11:20:48 +02:00
Chris Rodgers
891c80f48c Fixes issue where sentences containing strong emphasis words did not correctly delimit the strong tag 2016-07-28 12:04:49 +01:00
Alexandre Mutel
105b09e1ec Bump version to 0.7.3 2016-07-23 12:47:04 +02:00
Alexandre Mutel
9fe7596a23 Update project.json for WebApp to .NET Core RTM 2016-07-23 12:46:48 +02:00
Alexandre Mutel
82af7cadc5 Fix issue with MarkdownPipeline that was not threadsafe and initialization was deferred at parsing time instead of pipeline build time (issue #40) 2016-07-23 12:46:26 +02:00
Alexandre Mutel
800c81bb9a Bump version to 0.7.2 2016-07-20 11:03:07 +02:00
Alexandre Mutel
5653a4f7ee Render table colspan with optional decimals for percentage 2016-07-20 10:51:05 +02:00
Alexandre Mutel
4f14ffe63b Merge commit 'fba96774f427ccdc08b5cb89a6dd02e84b2ef9d6' from capjan/markdig 2016-07-20 10:31:03 +02:00
Alexandre Mutel
d57acefe56 Merge pull request #39 from christophano/bugfix/cell-after-colspan
Bugfix colspan for grid tables
2016-07-19 10:06:25 +02:00
Alexandre Mutel
fb61e5a8da Update to latest CommonMark 0.26 specs. Two consecutive blank lines in a list no longer break it. Ordered list can break a paragraph if it starts at '1' 2016-07-19 09:41:59 +02:00
Chris Rodgers
264516bfdb Updated tests via md file and regenerated from t4 2016-07-19 08:40:34 +01:00
Chris Rodgers
6d8f8996d5 Fixes cell contents not displaying after colspan merged cell 2016-07-19 08:38:05 +01:00
Chris Rodgers
ba8557d3bf Corrects spec for TestExtensionsGridTable Example003 2016-07-19 08:38:05 +01:00
Alexandre Mutel
d18fd0b957 Link to a fixed version of the CommonMark specs to avoid unexpected upgrades to latest changes in the specs 2016-07-19 08:03:23 +02:00
Jan Ruhländer
fba96774f4 Fix: Table extension column width doesn't work on computers that use a comma as demimal point 2016-07-12 14:47:35 +02:00
Alexandre Mutel
0b2764ea62 Merge pull request #36 from synhershko/norel-links
Adding support for rel=nofollow in links
2016-07-10 08:14:52 +09:00
Itamar Syn-Hershko
64d81ed47b Removing config 2016-07-10 02:01:53 +03:00
Itamar Syn-Hershko
9a9742888b Fixing last bits 2016-07-10 01:47:38 +03:00
Itamar Syn-Hershko
5400b30a90 Fixing for renderer.EnableHtmlForInline case 2016-07-10 00:27:50 +03:00
Itamar Syn-Hershko
673f4a4beb Avoid adding rel to images 2016-07-10 00:13:51 +03:00
Itamar Syn-Hershko
05a27649aa Adding support for rel=nofollow in links 2016-07-09 23:56:58 +03:00
Alexandre Mutel
26c6b12dea Merge pull request #35 from ChrisMissal/readme-changes
Update readme.md, fix typos
2016-07-08 09:09:28 +09:00
Chris Missal
9a18ca222f Update readme.md
fixes typos
2016-07-07 18:30:09 -05:00
Alexandre Mutel
354f31b870 Fix calculation of span for HTML entities 2016-07-06 06:45:48 +09:00
Alexandre Mutel
c6c2f58ec0 Add support for diagrams extension. wip with mermaid (issue #34) 2016-07-05 23:24:37 +09:00
Alexandre Mutel
4a146f00f6 Bump to 0.7.1 2016-06-30 21:56:05 +09:00
Alexandre Mutel
2c10ac59d3 Update to latest specs 2016-06-30 21:54:18 +09:00
Alexandre Mutel
9a98fb9453 Fix issue InvalidCastException with SmartyPants extension when using both a mdash with quotes. 2016-06-30 21:53:53 +09:00
Alexandre Mutel
1c7f843a4c Update readme for building Markdig (issue #28) 2016-06-27 22:55:21 +09:00
Alexandre Mutel
fdaf301bd6 Update to latest dotnet Core RTM, NETStandard.Library 1.6.0. Bump to version 0.7.0 2016-06-27 21:44:31 +09:00
Alexandre Mutel
5a9f2f3afe Fix calculated span for indented code blocks that was starting after the first 4 chars and not before (issue https://github.com/madskristensen/MarkdownEditor/issues/8) 2016-06-27 20:32:24 +09:00
Alexandre Mutel
6a1b30761a Merge branch 'pr/n30_Jither' 2016-06-27 06:59:13 +09:00
Jimmi Thøgersen
8f8b08fad6 Include digits after first letter in Urilize (for Auto-Identifiers)
Added dependency: NUnit3TestAdapter (for VS Test Runner)
Disabled "Prefer 32-bit" on Markdig.Tests to accommodate NUnit.
2016-06-26 23:39:43 +02:00
Alexandre Mutel
312b63a4c8 Bump to 0.6.2 2016-06-25 18:27:06 +09:00
Alexandre Mutel
1598b538ab Update to latest CommonMark spec. Handle new corner cases for emphasis (see https://talk.commonmark.org/t/emphasis-strong-emphasis-corner-cases/2123/1) 2016-06-25 18:25:24 +09:00
Alexandre Mutel
16ce46a741 Bump to 0.6.1 2016-06-24 20:33:30 +09:00
Alexandre Mutel
4456598228 Double check all extensions with advanced profiles to make sure there are no conflicts between existing extensions 2016-06-24 17:44:57 +09:00
Alexandre Mutel
2a937c63b9 Fix issue with auto identifiers extension overriding manual ids for headings 2016-06-24 17:43:46 +09:00
Alexandre Mutel
191bc940c7 Bump version to 0.6.0 2016-06-24 16:10:58 +09:00
Alexandre Mutel
05673758e3 Add new SelfPipeline extension 2016-06-24 15:05:37 +09:00
Alexandre Mutel
cff2b9a8ca Fix conflict between SmartyPants extension and pipetables (issue #13) 2016-06-24 12:04:29 +09:00
Alexandre Mutel
586095a475 Refactor StringSlice.Search to StringSlice.IndexOf to make it more familiar and consistent 2016-06-24 12:03:16 +09:00
Alexandre Mutel
eae2082a1e Rename IDelimiterProcessor to IPostInlineProcessor as it can be used outside of a delimiter processing (for example SmartyPants mdash) 2016-06-24 12:02:25 +09:00
Alexandre Mutel
4576548df3 Add comment to Descendants() method that should be implemented without recursive 2016-06-24 12:01:03 +09:00
Alexandre Mutel
266e0c8bfd Fix consecutive footnotes without blanklines between (issue #26) 2016-06-24 06:01:53 +09:00
Alexandre Mutel
f5c07dbab5 Bump to 0.5.12 2016-06-24 05:43:17 +09:00
Alexandre Mutel
c8a28a1ad7 Fix an issue where a consecutive footnote is parsed as a paragraph (issue #26) 2016-06-24 05:42:36 +09:00
Alexandre Mutel
72cc454314 Remove backsticks in SmartyPantsInlineParser. Rename SmaryPantsInlineParser to SmartyPantsInlineParser 2016-06-24 05:27:12 +09:00
Alexandre Mutel
7fe2c1f939 Remove OrderedList.ReplacyBy method, as it is not used and might be not predictable (issue #25) 2016-06-24 05:25:54 +09:00
Alexandre Mutel
087e7a68b6 Fix emoji parsing and allow them only if they are surrounded by spaces (issue #15) 2016-06-23 17:28:33 +09:00
Alexandre Mutel
abeabf15a1 Remove non necessary check in HtmlAttributes.CopyTo 2016-06-23 11:49:01 +09:00
Alexandre Mutel
f9bfcaab7b Bump version to 0.5.11 2016-06-23 11:46:22 +09:00
Alexandre Mutel
afa0182f02 Fix ArgumentNullReference exception in HtmlAttributes.CopyTo (issue #23) 2016-06-23 11:46:07 +09:00
Alexandre Mutel
b83de5934d Bump version to 0.5.10 2016-06-23 05:52:06 +09:00
Alexandre Mutel
c294d3bfb4 Improve compact method for tests taking into account \r\n and \r 2016-06-23 05:52:06 +09:00
Alexandre Mutel
a7cdb2351a Fix regression for html block parsing (issue #21) 2016-06-23 05:52:05 +09:00
Alexandre Mutel
46ef21a3ed Merge pull request #19 from jorrit/patch-1
Fix typo in MarkdownExtensions.cs
2016-06-22 20:55:51 +09:00
Jorrit Schippers
18c8d0178c Fix typo in MarkdownExtensions.cs 2016-06-22 13:55:09 +02:00
Alexandre Mutel
ebc79dafbd Bump version to 0.5.9 2016-06-22 08:37:37 +09:00
Alexandre Mutel
a1d2467643 Fix bug for deep nested list items that were incorrectly detected (not CommonMark compliant. Check with CommonMark specs to add a test case) 2016-06-22 08:35:42 +09:00
Alexandre Mutel
8220f0fa56 Fix ArgumentOutOfRangeException in Block.FindClosestLine. Add tests for Pragmalines 2016-06-22 08:35:08 +09:00
Alexandre Mutel
ec385acc7f Bump version to 0.5.8 2016-06-21 23:45:48 +09:00
Alexandre Mutel
04c1cc62d4 Add block extension methods for looking for a block from a position or for the closest line (typically used for syntax highlighting by MarkdownEditor) 2016-06-21 23:45:40 +09:00
Alexandre Mutel
abdbd65f60 Fix calculation of spans for nested blocks 2016-06-21 23:45:28 +09:00
Alexandre Mutel
1f32a060da Update readme and link to MarkdownEditor 2016-06-20 15:05:39 +09:00
Alexandre Mutel
699d80c150 Add missing test for issue #16 2016-06-20 13:48:22 +09:00
Alexandre Mutel
56bcac7600 Bump version to 0.5.7 2016-06-20 13:42:32 +09:00
Alexandre Mutel
cab3365104 Fix issue while detecting generic attributes that was breaking parsing (issue #16) 2016-06-20 13:42:15 +09:00
Alexandre Mutel
3821bd00fe Bump version to 0.5.6 2016-06-20 12:30:25 +09:00
Alexandre Mutel
62701fd0f1 Improve pragma line output 2016-06-20 12:29:13 +09:00
Alexandre Mutel
1be5e60506 Make to MarkdownPipeline.Setup public. Make Markdown.ToHtml returning a MarkdownDocument 2016-06-20 12:27:50 +09:00
Alexandre Mutel
c9f1512358 Bump to 0.5.5 2016-06-20 09:06:29 +09:00
Alexandre Mutel
8f23aed6af Add pragma line extension 2016-06-20 09:04:33 +09:00
Alexandre Mutel
6a62ae9c69 Add github like class for taslk lists (issue #14) 2016-06-20 09:04:13 +09:00
Alexandre Mutel
2c3de5688b Don't add a class an HtmlAttributes that is already in the list 2016-06-20 09:00:54 +09:00
Alexandre Mutel
f3c08b4ec4 Output HtmlAttributes for unordered list 2016-06-20 09:00:31 +09:00
Alexandre Mutel
69e3baafe5 Add support for callbacks to RendererBase, IMarkdownRenderer 2016-06-20 09:00:10 +09:00
Alexandre Mutel
5844ccc395 Bump to 0.5.4 2016-06-20 06:58:55 +09:00
Alexandre Mutel
be9c6fa54b Fix bug for html block parsing in StringSlice.Search (issue #12) 2016-06-20 06:56:13 +09:00
Alexandre Mutel
d14f277c7b Bump version to 0.5.3 2016-06-19 11:37:26 +09:00
Alexandre Mutel
593bf08b92 Fix bug in pipetables with a trailing | on the header row separator (issue #10) 2016-06-19 11:37:12 +09:00
Alexandre Mutel
85f631f868 Bump version to 0.5.2 2016-06-17 13:58:31 +09:00
Alexandre Mutel
5ad964bcb6 Rename MarkdownObject.SourceSpan to Span 2016-06-17 13:56:09 +09:00
Alexandre Mutel
137a404bdc Add property handling of precise position for links 2016-06-17 13:45:43 +09:00
Alexandre Mutel
5204ec758a Add version used for benchmarks. Add docfx Microsoft.DocAsCode.MarkdownLite for comparison 2016-06-17 13:15:32 +09:00
Alexandre Mutel
6f4fb69c62 Add precise source location for title, span for inlines/footnote/abbreviations (#8) 2016-06-17 09:34:46 +09:00
Alexandre Mutel
0a1b37c965 Breaking change: Add SourceSpan and replace SourceStartPosition and SourceEndPosition 2016-06-16 23:45:18 +09:00
Alexandre Mutel
90bdafb05a Update link to babelmark3 2016-06-16 13:35:45 +09:00
Alexandre Mutel
8cc668ae6d Update link to babelmark3 https 2016-06-16 13:22:23 +09:00
Alexandre Mutel
1787dc4590 Update readme.md with link to article 2016-06-16 12:43:32 +09:00
Alexandre Mutel
0a9cc8fcd7 Fix typo 2016-06-16 12:31:31 +09:00
Alexandre Mutel
10c06daf5d Make benchmark results shorter 2016-06-16 12:26:28 +09:00
Alexandre Mutel
a262e42980 Update readme and benchmarks 2016-06-16 12:24:01 +09:00
Alexandre Mutel
4cd3d045d1 Add tests precise position with text containing tabs 2016-06-16 10:03:04 +09:00
Alexandre Mutel
92357576b1 Add test precise position for indented code 2016-06-16 09:54:07 +09:00
Alexandre Mutel
d45f67f8c2 Add protection against infinite loops if an extension was messing the parsing of blocks or inlines 2016-06-16 09:22:45 +09:00
Alexandre Mutel
2571cdffee Add tasklists to readme.md 2016-06-16 06:43:19 +09:00
Alexandre Mutel
c31cb6da27 Bump version to 0.5.1 2016-06-16 06:40:22 +09:00
Alexandre Mutel
5503929d15 Add support for task lists (issue #7) 2016-06-16 06:39:20 +09:00
Alexandre Mutel
ca32dda1fe Bump version to 0.5.0 2016-06-15 21:15:03 +09:00
Alexandre Mutel
12111e0b63 Update the readme with latest version 2016-06-15 21:13:19 +09:00
Alexandre Mutel
bdd46c0fc0 Merge branch 'precise_position' 2016-06-15 21:12:32 +09:00
Alexandre Mutel
daf4c8fe86 Activate only calculation for precise location when PreciseSourceLocation is setup on the pipeline 2016-06-15 21:11:34 +09:00
Alexandre Mutel
bd2c2aff9c Add credits to CommonMark.NET 2016-06-15 18:19:26 +09:00
Alexandre Mutel
921f75e1f3 Add precise position for pipetables 2016-06-15 17:02:50 +09:00
Alexandre Mutel
44a3b85f0b Add precise position for smarty pants 2016-06-15 16:36:43 +09:00
Alexandre Mutel
f9e827395b Add precise position for mathematics extension 2016-06-15 16:19:20 +09:00
Alexandre Mutel
64a9a80774 Add test precise position for HtmlAttributes 2016-06-15 16:08:46 +09:00
Alexandre Mutel
e10594391d Add test source position for footers 2016-06-15 15:46:41 +09:00
Alexandre Mutel
60eb03a221 Add test precise position for figure captions 2016-06-15 15:37:43 +09:00
Alexandre Mutel
c0a0f10af0 Add test precise location for figures 2016-06-15 15:27:08 +09:00
Alexandre Mutel
56e1ed0e25 Add test precise location for emphasis extras 2016-06-15 15:26:47 +09:00
Alexandre Mutel
a9f33cbca6 Add test precise position for emojis 2016-06-15 15:26:31 +09:00
Alexandre Mutel
6f1d39e1bb Add precise position for definition lists 2016-06-15 15:01:41 +09:00
Alexandre Mutel
838f7c5598 Fix eol for TestSourcePosition for abbreviations 2016-06-15 14:04:59 +09:00
Alexandre Mutel
0f54cc5927 Add support for precise positions for abbreviations 2016-06-15 13:54:37 +09:00
Alexandre Mutel
12745f70cf Add test position for HtmlEntity inline 2016-06-15 12:11:31 +09:00
Alexandre Mutel
442737767f Add test for escape inline 2016-06-15 12:03:03 +09:00
Alexandre Mutel
61ac46e467 Add test for ListBlock 2016-06-15 12:02:52 +09:00
Alexandre Mutel
85550580d5 Add tests for QuoteBlock position 2016-06-15 12:02:35 +09:00
Alexandre Mutel
ed69ac5fe0 Add test for thematic break position 2016-06-15 11:25:01 +09:00
Alexandre Mutel
0aec5a5783 Improve html block test 2016-06-15 11:20:48 +09:00
Alexandre Mutel
c1885fe31b Add test for HtmlBlock position 2016-06-15 11:16:57 +09:00
Alexandre Mutel
c2270a2b3a Add test for code span, link, html inline, autolink, fenced code block 2016-06-15 11:16:31 +09:00
Alexandre Mutel
3e60515bb3 Fix offset for lines within a StringLineGroup 2016-06-15 11:15:53 +09:00
Alexandre Mutel
8550c13688 Add precise position for heading 2016-06-15 10:05:08 +09:00
Alexandre Mutel
3aa65694aa Improve InlineProcessor.GetSourcePosition 2016-06-15 00:33:06 +09:00
Alexandre Mutel
52403687db Improve handling of position for core elements. Add Line and Column for inline elements. 2016-06-15 00:22:28 +09:00
Alexandre Mutel
3e83409cf4 Cleanup code with source position for BlockProcessor 2016-06-14 21:18:16 +09:00
Alexandre Mutel
9b051955bd Switch to string only parsing instead of TextReader to avoid allocations of line. Use StringSlice instead on the whole input for each line. 2016-06-14 21:00:18 +09:00
Alexandre Mutel
35c8126add Add MarkdownObject.SourceStartPosition and SourceEndPosition and start to fill this for all core syntax 2016-06-14 17:05:49 +09:00
Alexandre Mutel
67e1c8ce7f Rename images to img 2016-06-13 22:44:04 +09:00
Alexandre Mutel
6e5fbda8e5 Bump version to 0.4.0 2016-06-13 22:42:39 +09:00
Alexandre Mutel
90e7ccd80a Allow a pipe table header to be separated by a single -|- instead of at least -- | - 2016-06-13 22:40:25 +09:00
Alexandre Mutel
c09b3eedd2 Rename several extensions adding a 's' at the end to make them uniform and coherent 2016-06-13 15:56:44 +09:00
Alexandre Mutel
9441e8a04b Use Replace of whole IndexOf/StringBuilder code 2016-06-13 13:35:52 +09:00
Alexandre Mutel
301dc70ab4 Update readme 2016-06-10 22:24:57 +09:00
Alexandre Mutel
959d6db62f Update logo 2016-06-10 21:30:16 +09:00
Alexandre Mutel
38a7410e4b Update Markdown logo 2016-06-10 21:28:15 +09:00
Alexandre Mutel
b941a58ad0 Fix project.json 2016-06-10 17:16:37 +09:00
Alexandre Mutel
3fa20fa92f Update logo 2016-06-10 17:10:11 +09:00
Alexandre Mutel
5dcd4ea4aa Update logo for nuget. Bump version to 0.3.3 2016-06-10 17:03:49 +09:00
Alexandre Mutel
9c03683913 Add logo to readme 2016-06-10 17:01:17 +09:00
Alexandre Mutel
2fed1b3ebf Add markdig logo 2016-06-10 16:56:23 +09:00
Alexandre Mutel
9da3eef65f Bump to 0.3.2 2016-06-10 06:20:44 +09:00
Alexandre Mutel
0b4fe3f02f Modify tests to run default and advanced together on the core CommonMark specs to make sure the Advanced mode is compatible with the core specs (except for the AutoIdentifier that we are disabling) 2016-06-10 06:19:14 +09:00
Alexandre Mutel
96b39e1856 Fix exception when extension Media was active. Make sure that we only process absolute Uri for media 2016-06-10 06:18:19 +09:00
Alexandre Mutel
6d90f517cc Fix BlockProcessor.GoToColumn that was messing indent 2016-06-10 06:17:47 +09:00
Alexandre Mutel
c17630e3b6 Add link to babelmark3 2016-06-09 14:00:06 +09:00
Alexandre Mutel
6eecfe2edc Bump version to 0.3.1 2016-06-09 10:13:53 +09:00
Alexandre Mutel
9abeb97394 Remove Bootstrap and add ListExtra to the UseAdvancedExtensions 2016-06-09 10:13:39 +09:00
197 changed files with 15129 additions and 5942 deletions

38
.editorconfig Normal file
View File

@@ -0,0 +1,38 @@
# EditorConfig is awesome:http://EditorConfig.org
# top-most EditorConfig file
root = true
# All Files
[*]
charset = utf-8
end_of_line = crlf
indent_style = space
indent_size = 4
insert_final_newline = false
trim_trailing_whitespace = true
# Solution Files
[*.sln]
indent_style = tab
# XML Project Files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Configuration Files
[*.{json,xml,yml,config,props,targets,nuspec,resx,ruleset}]
indent_size = 2
# Markdown Files
[*.md]
trim_trailing_whitespace = false
# Web Files
[*.{htm,html,js,ts,css,scss,less}]
indent_size = 2
insert_final_newline = true
# Bash Files
[*.sh]
end_of_line = lf

46
appveyor.yml Normal file
View File

@@ -0,0 +1,46 @@
version: 10.0.{build}
image: Visual Studio 2017
configuration: Release
install:
- ps: >-
cd src
nuget restore Markdig.sln
$env:MARKDIG_BUILD_NUMBER = ([int]$env:APPVEYOR_BUILD_NUMBER).ToString("000")
$env:MARKDIG_VERSION_SUFFIX = ""
$env:appveyor_nuget_push = 'false'
if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
if($env:appveyor_repo_tag -eq 'True') {
if($env:appveyor_repo_tag_name -match '^v[0-9]') {
$env:appveyor_nuget_push = 'true'
$env:MARKDIG_VERSION_SUFFIX = ""
}
if($env:appveyor_repo_tag_name -eq 'latest') {
$env:appveyor_nuget_push = 'true'
$env:MARKDIG_VERSION_SUFFIX = "pre$env:MARKDIG_BUILD_NUMBER"
}
}
}
build:
project: src/Markdig.sln
verbosity: minimal
before_package:
- cmd: >-
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
msbuild /t:Clean Markdig/Markdig.csproj
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release;SignAssembly=true Markdig/Markdig.csproj
artifacts:
- path: src\Markdig\Bin\Release\*.nupkg
name: Markdig Nugets
deploy:
- provider: NuGet
api_key:
secure: 7cthHh+wYWZjhqxaxR6QObRaRnstvFkQOY7MkxIsC5kpQEBlKZXuinf0IybbYxJt
on:
appveyor_nuget_push: true

BIN
img/BenchmarkCPU.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
img/BenchmarkMemory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
img/markdig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

131
img/markdig.svg Normal file
View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="192"
height="192"
viewBox="0 0 192 192"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="markdig.svg"
inkscape:export-filename="C:\Code\lunet-io\markdig\markdig.png"
inkscape:export-xdpi="93.400002"
inkscape:export-ydpi="93.400002">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4.4374889"
inkscape:cx="174.14769"
inkscape:cy="93.189838"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
units="px" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
transform="translate(0,-860.36216)">
<path
style="fill:#000000"
d="M 75.009766 60.486328 L 34.648438 122.74414 C 33.793918 123.59769 37.647081 134.96384 37.052734 136.09766 L 4.0234375 177.69727 C 2.4142291 180.39677 3.2900484 182.21846 4.8730469 183.84766 C 5.9214414 184.93636 6.6591287 186.06887 8.3828125 185.67188 C 9.0750612 185.50987 10.104893 185.27338 10.875 184.76758 L 52.806641 151.81445 C 53.912466 151.23195 64.44071 154.77813 65.289062 153.92383 L 126.45312 111.46875 L 75.009766 60.486328 z M 89.632812 84.769531 L 103.77539 98.912109 L 79.027344 123.66016 L 86.238281 130.87109 L 48.621094 139.92383 L 57.435547 102.30859 L 64.884766 109.51758 L 89.632812 84.769531 z "
transform="translate(0,860.36216)"
id="path4140" />
<path
style="fill:#000000;fill-opacity:1"
d="m 111.18463,862.06984 c -1.98231,0 -3.96282,0.78454 -5.54759,2.38445 L 88.200894,881.94537 75.123368,868.82653 c -3.169466,-3.18017 -7.92567,-3.18017 -11.095213,0 -3.169466,3.18017 -3.169466,7.95108 0,11.13109 l 13.077526,13.11885 -11.095212,10.73223 82.031621,81.49227 11.09525,-11.13109 13.87084,13.51554 c 1.57915,1.59105 3.56724,2.38445 5.54759,2.38445 1.98231,0 3.96285,-0.78453 5.54762,-2.38445 3.16947,-3.18017 3.16947,-7.95111 0,-11.13109 l -13.87083,-13.11884 17.43611,-17.09442 c 1.17983,-1.59105 1.98231,-3.5788 1.98231,-5.96329 0,-1.98351 -0.79863,-3.97554 -2.37816,-5.56446 l -70.54053,-70.35856 c -1.57914,-1.59105 -3.56724,-2.38446 -5.54758,-2.38446 z m 15.86949,20.75826 9.50139,9.5291 -4.04453,23.11372 23.04691,-4.05619 9.50138,9.5291 -36.8437,36.95052 -9.50135,-9.5291 21.13082,-21.19197 -23.04672,4.05619 4.04453,-23.11396 -21.131009,21.19198 -9.501383,-9.5291 36.843662,-36.95048 z"
id="path4142"
inkscape:connector-curvature="0" />
<rect
id="rect4168"
mask="url(#a)"
ry="0"
height="0"
width="0"
x="141.51523"
y="364.10403" />
<rect
id="rect4184"
mask="url(#a)"
ry="0"
height="0"
width="0"
x="96.108383"
y="352.01443" />
<rect
id="rect4200"
mask="url(#a)"
ry="2.1886277"
height="0.14590852"
width="0.17464182"
x="87.014519"
y="276.38696" />
<flowRoot
xml:space="preserve"
id="flowRoot4797"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
transform="matrix(0.1746417,0,0,0.1459084,499.69318,366.39614)"><flowRegion
id="flowRegion4799"><rect
id="rect4801"
width="972.27185"
height="618.71844"
x="959.01355"
y="-976.15039" /></flowRegion><flowPara
id="flowPara4803" /></flowRoot> <g
id="g4833"
transform="matrix(0.09510056,0,0,0.09061765,496.09965,368.83934)">
<rect
id="rect4823"
height="1"
width="1"
x="0"
y="0"
style="fill:#ffffff" />
</g>
<path
inkscape:connector-curvature="0"
id="path4886"
d="m 111.18463,862.06984 c -1.98231,0 -3.96282,0.78454 -5.54759,2.38445 L 88.200894,881.94537 75.123368,868.82653 c -3.169466,-3.18017 -7.92567,-3.18017 -11.095213,0 -3.169466,3.18017 -3.169466,7.95108 0,11.13109 l 13.077526,13.11885 -11.095212,10.73223 82.031621,81.49227 11.09525,-11.13109 13.87084,13.51554 c 1.57915,1.59105 3.56724,2.38445 5.54759,2.38445 1.98231,0 3.96285,-0.78453 5.54762,-2.38445 3.16947,-3.18017 3.16947,-7.95111 0,-11.13109 l -13.87083,-13.11884 17.43611,-17.09442 c 1.17983,-1.59105 1.98231,-3.5788 1.98231,-5.96329 0,-1.98351 -0.79863,-3.97554 -2.37816,-5.56446 l -70.54053,-70.35856 c -1.57914,-1.59105 -3.56724,-2.38446 -5.54758,-2.38446 z m 15.86949,20.75826 9.50139,9.5291 -4.04453,23.11372 23.04691,-4.05619 9.50138,9.5291 -36.8437,36.95052 -9.50135,-9.5291 21.13082,-21.19197 -23.04672,4.05619 4.04453,-23.11396 -21.131009,21.19198 -9.501383,-9.5291 36.843662,-36.95048 z"
style="fill:#000000;fill-opacity:1" />
<g
transform="translate(234.63786,787.55486)"
id="g4170" />
<path
id="path4225"
transform="translate(0,860.36216)"
d="M 75.009766 60.486328 L 34.648438 122.74414 C 33.793918 123.59769 37.647081 134.96384 37.052734 136.09766 L 4.0234375 177.69727 C 2.4142291 180.39677 3.2900484 182.21846 4.8730469 183.84766 C 5.9214414 184.93636 6.6591287 186.06887 8.3828125 185.67188 C 9.0750612 185.50987 10.104893 185.27338 10.875 184.76758 L 52.806641 151.81445 C 53.912466 151.23195 64.44071 154.77813 65.289062 153.92383 L 126.45312 111.46875 L 75.009766 60.486328 z M 89.632812 84.769531 L 103.77539 98.912109 L 79.027344 123.66016 L 86.238281 130.87109 L 48.621094 139.92383 L 57.435547 102.30859 L 64.884766 109.51758 L 89.632812 84.769531 z "
style="fill:#000000" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
img/markdig128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
img/markdig64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

177
readme.md
View File

@@ -1,51 +1,71 @@
# Markdig [![Build status](https://ci.appveyor.com/api/projects/status/hk391x8jcskxt1u8?svg=true)](https://ci.appveyor.com/project/xoofx/markdig) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/)
# Markdig [![Build status](https://ci.appveyor.com/api/projects/status/hk391x8jcskxt1u8?svg=true)](https://ci.appveyor.com/project/xoofx/markdig) [![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 is a fast, powerfull, [CommonMark](http://commonmark.org/) compliant, extensible Markdown processor for .NET.
<img align="right" width="160px" height="160px" src="img/markdig.png">
Markdig is a fast, powerful, [CommonMark](http://commonmark.org/) compliant, extensible Markdown processor for .NET.
> NOTE: The repository is under construction. There will be a dedicated website and proper documentation at some point!
You can **try Markdig online** and compare it to other implementations on [babelmark3](https://babelmark.github.io/?text=Hello+**Markdig**!)
## Features
- **Very fast parser** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
- **Abstract Syntax Tree**
- **Very fast parser and html renderer** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
- Converter to **HTML**
- Passing more than **600+ tests** from the latest [CommonMark specs](http://spec.commonmark.org/)
- Passing more than **600+ tests** from the latest [CommonMark specs (0.28)](http://spec.commonmark.org/)
- Includes all the core elements of CommonMark:
- including GFM fenced code blocks.
- 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 `@`)
- Built-in with **18+ extensions**, including:
- Built-in with **20+ extensions**, including:
- 2 kind of tables:
- **Pipe tables** (inspired from Github tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- **Grid tables** (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
- **Extra emphasis** (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from Github tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
- [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- strike through `~~`,
- Subscript `~`
- Superscript `^`
- Inserted `++`
- Marked `==`
- **Special attributes** or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- **Definition lists** (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- **Footnotes** (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- **Auto-identifiers** for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
- **Extra bullet lists**, supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
- **Media support** for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
- **Abbreviations** (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
- **Citation** text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
- **Custom containers** similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
- **Figures** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
- **Footers** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
- **Mathematics**/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- **Soft lines as hard lines**
- **Emoji** support (inspired from [Markdown-it](https://markdown-it.github.io/))
- **SmartyPants** (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- **Bootstrap** class (to output bootstrap class)
- [**Special attributes**](src/Markdig.Tests/Specs/GenericAttributesSpecs.md) or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- [**Definition lists**](src/Markdig.Tests/Specs/DefinitionListSpecs.md) (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- [**Footnotes**](src/Markdig.Tests/Specs/FootnotesSpecs.md) (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
- [**Auto-links**](src/Markdig.Tests/Specs/AutoLinks.md) generates links if a text starts with `http://` or `https://` or `ftp://` or `mailto:` or `www.xxx.yyy`
- [**Task Lists**](src/Markdig.Tests/Specs/TaskListSpecs.md) inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments).
- [**Extra bullet lists**](src/Markdig.Tests/Specs/ListExtraSpecs.md), supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
- [**Media support**](src/Markdig.Tests/Specs/MediaSpecs.md) for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
- [**Abbreviations**](src/Markdig.Tests/Specs/AbbreviationSpecs.md) (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
- [**Citation**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
- [**Custom containers**](src/Markdig.Tests/Specs/CustomContainerSpecs.md) similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
- [**Figures**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
- [**Footers**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
- [**Mathematics**](src/Markdig.Tests/Specs/MathSpecs.md)/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- [**Soft lines as hard lines**](src/Markdig.Tests/Specs/HardlineBreakSpecs.md)
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports only for [`mermaid` diagrams](https://knsv.github.io/mermaid/))
- [**YAML frontmatter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the frontmatter and to discard it from the HTML output (typically used for previewing without the frontmatter in MarkdownEditor)
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
- Compatible with .NET 3.5, 4.0+ and .NET Core (`netstandard1.1+`)
## Documentation
> The repository is under construction. There will be a dedicated website and proper documentation at some point!
While there is not yet a dedicated documentation, you can find from the [specs documentation](src/Markdig.Tests/Specs/readme.md) how to use these extensions.
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
## Download
Markdig is available as a NuGet package: [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/)
Also [Markdig.Signed](https://www.nuget.org/packages/Markdig.Signed/) NuGet package provides signed assemblies.
## Usage
The main entry point for the API is the `Markdig.Markdown` class:
@@ -57,7 +77,7 @@ var result = Markdown.ToHtml("This is a text with some *emphasis*");
Console.WriteLine(result); // prints: <p>This is a text with some <em>emphasis</em></p>
```
In order to activate most of all advanced extensions (except Emoji, SoftLine as HarLine and SmartyPants)
In order to activate most of all advanced extensions (except Emoji, SoftLine as HardLine, JiraLinks and SmartyPants)
```csharp
// Configure the pipeline with all advanced extensions active
@@ -67,6 +87,10 @@ var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
You can have a look at the [MarkdownExtensions](https://github.com/lunet-io/markdig/blob/master/src/Markdig/MarkdownExtensions.cs) that describes all actionable extensions (by modifying the MarkdownPipeline)
## Build
In order to build Markdig, you need to install [.NET Core RTM](https://www.microsoft.com/net/core)
## License
This software is released under the [BSD-Clause 2 license](https://github.com/lunet-io/markdig/blob/master/license.txt).
@@ -76,24 +100,44 @@ This software is released under the [BSD-Clause 2 license](https://github.com/lu
This is an early preview of the benchmarking against various implementations:
- Markdig: itself
- CommonMarkCpp: [cmark](https://github.com/jgm/cmark), Reference C implementation of CommonMark, no support for extensions
- [CommonMark.NET](https://github.com/Knagis/CommonMark.NET): CommonMark implementation for .NET, no support for extensions, port of cmark
- [CommonMarkNet (devel)](https://github.com/AMDL/CommonMark.NET/tree/pipe-tables): An evolution of CommonMark.NET, supports extensions, not released yet
- [MarkdownDeep](https://github.com/toptensoftware/markdowndeep) another .NET implementation
- [MarkdownSharp](https://github.com/Kiri-rin/markdownsharp): Open source C# implementation of Markdown processor, as featured on Stack Overflow, regexp based.
- [Moonshine](https://github.com/brandonc/moonshine): popular C Markdown processor
**C implementations**:
Markdig is roughly x100 times faster than MarkdownSharp and extremelly competitive to other implems (that are not feature wise comparable)
- [cmark](https://github.com/jgm/cmark) (version: 0.25.0): Reference C implementation of CommonMark, no support for extensions
- [Moonshine](https://github.com/brandonc/moonshine) (version: : popular C Markdown processor
Performance in x86:
**.NET implementations**:
- [Markdig](https://github.com/lunet-io/markdig) (version: 0.5.x): itself
- [CommonMark.NET(master)](https://github.com/Knagis/CommonMark.NET) (version: 0.11.0): CommonMark implementation for .NET, no support for extensions, port of cmark
- [CommonMark.NET(pipe_tables)](https://github.com/AMDL/CommonMark.NET/tree/pipe-tables): An evolution of CommonMark.NET, supports extensions, not released yet
- [MarkdownDeep](https://github.com/toptensoftware/markdowndeep) (version: 1.5.0): another .NET implementation
- [MarkdownSharp](https://github.com/Kiri-rin/markdownsharp) (version: 1.13.0): Open source C# implementation of Markdown processor, as featured on Stack Overflow, regexp based.
- [Marked.NET](https://github.com/T-Alex/MarkedNet) (version: 1.0.5) port of original [marked.js](https://github.com/chjj/marked) project
- [Microsoft.DocAsCode.MarkdownLite](https://github.com/dotnet/docfx/tree/dev/src/Microsoft.DocAsCode.MarkdownLite) (version: 2.0.1) used by the [docfx](https://github.com/dotnet/docfx) project
**JavaScript/V8 implementations**:
- [Strike.V8](https://github.com/SimonCropp/Strike) (version: 1.5.0) [marked.js](https://github.com/chjj/marked) running in Google V8 (not .NET based)
### Analysis of the results:
- Markdig is roughly **x100 times faster than MarkdownSharp**, **30x times faster than docfx**
- **Among the best in CPU**, Extremely competitive and often faster than other implementations (not feature wise equivalent)
- **15% to 30% less allocations** and GC pressure
Because Marked.NET, MarkdownSharp and DocAsCode.MarkdownLite are way too slow, they are not included in the following charts:
![BenchMark CPU Time](img/BenchmarkCPU.png)
![BenchMark Memory](img/BenchmarkMemory.png)
### Performance for x86:
```
// * Summary *
BenchmarkDotNet-Dev=v0.9.6.0+
BenchmarkDotNet-Dev=v0.9.7.0+
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, ProcessorCount=8
Processor=Intel(R) Core(TM) i7-4770 CPU 3.40GHz, ProcessorCount=8
Frequency=3319351 ticks, Resolution=301.2637 ns, Timer=TSC
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
JitModules=clrjit-v4.6.1080.0
@@ -101,26 +145,23 @@ JitModules=clrjit-v4.6.1080.0
Type=Program Mode=SingleRun LaunchCount=2
WarmupCount=2 TargetCount=10
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
--------------------- |---------- |---------- |------- |------ |------- |------------------- |
TestMarkdig | 5.4870 ms | 0.0158 ms | 193.00 | 12.00 | 84.00 | 1,425,192.72 |
TestCommonMarkCpp | 4.0134 ms | 0.1008 ms | - | - | 180.00 | 454,859.74 |
TestCommonMarkNet | 4.6139 ms | 0.0581 ms | 193.00 | 12.00 | 84.00 | 1,406,367.27 |
TestCommonMarkNetNew | 5.5327 ms | 0.0461 ms | 193.00 | 96.00 | 84.00 | 1,738,465.42 |
TestMarkdownDeep | 7.5910 ms | 0.1006 ms | 205.00 | 96.00 | 84.00 | 1,758,383.79 |
TestMoonshine | 5.8843 ms | 0.1758 ms | - | - | 215.00 | 565,000.73 |
// * Diagnostic Output - MemoryDiagnoser *
// ***** BenchmarkRunner: End *****
Method | Median | StdDev |Scaled | Gen 0 | Gen 1| Gen 2|Bytes Allocated/Op |
--------------------------- |------------ |---------- |------ | ------ |------|---------|------------------ |
Markdig | 5.5316 ms | 0.0372 ms | 0.71 | 56.00| 21.00| 49.00| 1,285,917.31 |
CommonMark.NET(master) | 4.7035 ms | 0.0422 ms | 0.60 | 113.00| 7.00| 49.00| 1,502,404.60 |
CommonMark.NET(pipe_tables) | 5.6164 ms | 0.0298 ms | 0.72 | 111.00| 56.00| 49.00| 1,863,128.13 |
MarkdownDeep | 7.8193 ms | 0.0334 ms | 1.00 | 120.00| 56.00| 49.00| 1,884,854.85 |
cmark | 4.2698 ms | 0.1526 ms | 0.55 | -| -| -| NA |
Moonshine | 6.0929 ms | 0.1053 ms | 1.28 | -| -| -| NA |
Strike.V8 | 10.5895 ms | 0.0492 ms | 1.35 | -| -| -| NA |
Marked.NET | 207.3169 ms | 5.2628 ms | 26.51 | 0.00| 0.00| 0.00| 303,125,228.65 |
MarkdownSharp | 675.0185 ms | 2.8447 ms | 86.32 | 40.00| 27.00| 41.00| 2,413,394.17 |
Microsoft DocfxMarkdownLite | 166.3357 ms | 0.4529 ms | 21.27 |4,452.00|948.00|11,167.00| 180,218,359.60 |
```
Performance for x64:
### Performance for x64:
```
// * Summary *
BenchmarkDotNet-Dev=v0.9.6.0+
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, ProcessorCount=8
@@ -133,25 +174,29 @@ WarmupCount=2 TargetCount=10
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
--------------------- |---------- |---------- |------- |------- |------ |------------------- |
TestMarkdig | 5.9539 ms | 0.0495 ms | 157.00 | 96.00 | 84.00 | 1,767,834.52 |
TestCommonMarkNet | 4.3158 ms | 0.0161 ms | 157.00 | 96.00 | 84.00 | 1,747,432.06 |
TestCommonMarkNetNew | 5.3421 ms | 0.0435 ms | 229.00 | 168.00 | 84.00 | 2,323,922.97 |
TestMarkdownDeep | 7.4750 ms | 0.0281 ms | 318.00 | 186.00 | 84.00 | 2,576,728.69 |
// * Diagnostic Output - MemoryDiagnoser *
// ***** BenchmarkRunner: End *****
TestMarkdig | 5.5276 ms | 0.0402 ms | 109.00 | 96.00 | 84.00 | 1,537,027.66 |
TestCommonMarkNet | 4.4661 ms | 0.1190 ms | 157.00 | 96.00 | 84.00 | 1,747,432.06 |
TestCommonMarkNetNew | 5.3151 ms | 0.0815 ms | 229.00 | 168.00 | 84.00 | 2,323,922.97 |
TestMarkdownDeep | 7.4076 ms | 0.0617 ms | 318.00 | 186.00 | 84.00 | 2,576,728.69 |
```
## Donate
If you are using this library and find it useful for your project, please consider a donation for it!
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
## Credits
Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard!
This project would not have been possible without this huge foundation.
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Helpers/EntityHelper.cs)) have been re-used from [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
## Author
Alexandre MUTEL aka [xoofx](http://xoofx.com)

View File

@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Markdig.Benchmarks</RootNamespace>
<AssemblyName>Markdig.Benchmarks</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(NuGetPackageRoot)' == ''">
<NuGetPackageRoot>$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
</PropertyGroup>
<ImportGroup>
<Import Project="$(NuGetPackageRoot)\Microsoft.Diagnostics.Tracing.TraceEvent\1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('$(NuGetPackageRoot)\Microsoft.Diagnostics.Tracing.TraceEvent\1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
</ImportGroup>
</Project>

View File

@@ -131,7 +131,7 @@ namespace Testamina.Markdig.Benchmarks
var config = ManualConfig.Create(DefaultConfig.Instance);
//var gcDiagnoser = new MemoryDiagnoser();
//config.Add(new Job { Mode = Mode.SingleRun, LaunchCount = 2, WarmupCount = 2, IterationTime = 1024, TargetCount = 10 });
config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
//config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
//config.Add(gcDiagnoser);
//var config = DefaultConfig.Instance;

View File

@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>

View File

@@ -4,7 +4,7 @@
"win-x64": {}
},
"frameworks": {
"net451": {
"net46": {
"compilationOptions": {
"define": [
"CLASSIC"
@@ -19,9 +19,9 @@
}
},
"dependencies": {
"BenchmarkDotNet": "0.9.6",
"BenchmarkDotNet.Diagnostics.Windows": "0.9.6",
"CommonMark.NET": "0.11.0",
"BenchmarkDotNet": "0.10.6",
"BenchmarkDotNet.Diagnostics.Windows": "0.10.6",
"CommonMark.NET": "0.15.1",
"MarkdownSharp": "1.13.0.0",
"Microsoft.Diagnostics.Runtime": "0.8.31-beta",
"Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41.0"

View File

@@ -24,6 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -38,10 +39,6 @@
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="Markdig">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Markdig\Bin\$(Configuration)\net40\Markdig.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@@ -59,8 +56,14 @@
<DesignTime>True</DesignTime>
<DependentUpon>Specs.tt</DependentUpon>
</Compile>
<Compile Include="Specs\TestEmphasisPlus.cs" />
<Compile Include="TestHtmlAttributes.cs" />
<Compile Include="TestHtmlHelper.cs" />
<Compile Include="TestLineReader.cs" />
<Compile Include="TestLinkHelper.cs" />
<Compile Include="TestOrderedList.cs" />
<Compile Include="TestPragmaLines.cs" />
<Compile Include="TestSourcePosition.cs" />
<Compile Include="TestStringSliceList.cs" />
<Compile Include="TestPlayParser.cs" />
<Compile Include="TextAssert.cs" />
@@ -69,6 +72,8 @@
<ItemGroup>
<None Include="App.config" />
<None Include="project.json" />
<None Include="Specs\JiraLinks.md" />
<None Include="Specs\AutoLinks.md" />
<None Include="Specs\AutoIdentifierSpecs.md" />
<None Include="Specs\AbbreviationSpecs.md" />
<None Include="Specs\FigureFooterAndCiteSpecs.md" />
@@ -81,6 +86,11 @@
<None Include="Specs\GridTableSpecs.md" />
<None Include="Specs\HardlineBreakSpecs.md" />
<None Include="Specs\BootstrapSpecs.md" />
<None Include="Specs\DiagramsSpecs.md" />
<None Include="Specs\NoHtmlSpecs.md" />
<None Include="Specs\readme.md" />
<None Include="Specs\YamlSpecs.md" />
<None Include="Specs\TaskListSpecs.md" />
<None Include="Specs\SmartyPantsSpecs.md" />
<None Include="Specs\MediaSpecs.md" />
<None Include="Specs\MathSpecs.md" />
@@ -95,6 +105,13 @@
</ItemGroup>
<ItemGroup>
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj">
<Project>{8a58a7e2-627c-4f41-933f-5ac92adfab48}</Project>
<Name>Markdig</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -45,3 +45,56 @@ This is a 😃 HTML document
.
<p>This is a <abbr title="Hypertext Markup Language">😃 HTML</abbr> document</p>
````````````````````````````````
Abbreviations may be similar:
```````````````````````````````` example
*[1A]: First
*[1A1]: Second
*[1A2]: Third
We can abbreviate 1A, 1A1 and 1A2!
.
<p>We can abbreviate <abbr title="First">1A</abbr>, <abbr title="Second">1A1</abbr> and <abbr title="Third">1A2</abbr>!</p>
````````````````````````````````
Abbreviations should match whole word only:
```````````````````````````````` example
*[1A]: First
We should not abbreviate 1.1A or 11A!
.
<p>We should not abbreviate 1.1A or 11A!</p>
````````````````````````````````
Abbreviations should match whole word only, even if the word is the entire content:
```````````````````````````````` example
*[1A]: First
1.1A
.
<p>1.1A</p>
````````````````````````````````
Abbreviations should match whole word only, even if there is another glossary term:
```````````````````````````````` example
*[SCO]: First
*[SCOM]: Second
SCOM
.
<p><abbr title="Second">SCOM</abbr></p>
````````````````````````````````
Abbreviations should only match when surrounded by whitespace:
```````````````````````````````` example
*[PR]: Pull Request
PRAA
.
<p>PRAA</p>
````````````````````````````````

View File

@@ -0,0 +1,78 @@
# Extensions
This section describes the different extensions supported:
## AutoLinks
Autolinks will format as a HTML link any string that starts by:
- `http://` or `https://`
- `ftp://`
- `mailto:`
- `www.`
```````````````````````````````` example
This is a http://www.google.com URL and https://www.google.com
This is a ftp://test.com
And a mailto:email@toto.com
And a plain www.google.com
.
<p>This is a <a href="http://www.google.com">http://www.google.com</a> URL and <a href="https://www.google.com">https://www.google.com</a>
This is a <a href="ftp://test.com">ftp://test.com</a>
And a <a href="mailto:email@toto.com">mailto:email@toto.com</a>
And a plain <a href="http://www.google.com">www.google.com</a></p>
````````````````````````````````
But incomplete links will not be matched:
```````````````````````````````` example
This is not a http:/www.google.com URL and https:/www.google.com
This is not a ftp:/test.com
And not a mailto:emailtoto.com
And not a plain www. or a www.x
.
<p>This is not a http:/www.google.com URL and https:/www.google.com
This is not a ftp:/test.com
And not a mailto:emailtoto.com
And not a plain www. or a www.x</p>
````````````````````````````````
Previous character must be a punctuation or a valid space (tab, space, new line):
```````````````````````````````` example
This is not a nhttp://www.google.com URL but this is (https://www.google.com)
.
<p>This is not a nhttp://www.google.com URL but this is (<a href="https://www.google.com">https://www.google.com</a>)</p>
````````````````````````````````
An autolink should not interfere with an `<a>` HTML inline:
```````````````````````````````` example
This is an HTML <a href="http://www.google.com">http://www.google.com</a> link
.
<p>This is an HTML <a href="http://www.google.com">http://www.google.com</a> link</p>
````````````````````````````````
or even within emphasis:
```````````````````````````````` example
This is an HTML <a href="http://www.google.com"> **http://www.google.com** </a> link
.
<p>This is an HTML <a href="http://www.google.com"> <strong>http://www.google.com</strong> </a> link</p>
````````````````````````````````
An autolink should not interfere with a markdown link:
```````````````````````````````` example
This is an HTML [http://www.google.com](http://www.google.com) link
.
<p>This is an HTML <a href="http://www.google.com">http://www.google.com</a> link</p>
````````````````````````````````
A link embraced by pending emphasis should let the emphasis takes precedence if characters are placed at the end of the matched link:
```````````````````````````````` example
Check **http://www.a.com** or __http://www.b.com__
.
<p>Check <strong><a href="http://www.a.com">http://www.a.com</a></strong> or <strong><a href="http://www.b.com">http://www.b.com</a></strong></p>
````````````````````````````````

View File

@@ -105,3 +105,28 @@ Term 1
<pre><code>: Not valid
</code></pre>
````````````````````````````````
Definition lists can be nested inside list items
```````````````````````````````` example
1. First
2. Second
Term 1
: Definition
Term 2
: Second Definition
.
<ol>
<li><p>First</p></li>
<li><p>Second</p>
<dl>
<dt>Term 1</dt>
<dd>Definition</dd>
<dt>Term 2</dt>
<dd>Second Definition</dd>
</dl></li>
</ol>
````````````````````````````````

View File

@@ -0,0 +1,26 @@
# Extensions
Adds support for diagrams extension:
## Mermaid diagrams
Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
```````````````````````````````` example
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
.
<div class="mermaid">graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
</div>
````````````````````````````````
TODO: Add other text diagram languages

View File

@@ -11,3 +11,29 @@ This is a test with a :) and a :angry: smiley
.
<p>This is a test with a 😃 and a 😠 smiley</p>
````````````````````````````````
An emoji needs to be preceded by a space:
```````````````````````````````` example
These are not:) an emoji with a:) x:angry:x
.
<p>These are not:) an emoji with a:) x:angry:x</p>
````````````````````````````````
Emoji can be followed by close ponctuation (or any other characters):
```````````````````````````````` example
We all need :), it makes us :muscle:. (and :ok_hand:).
.
<p>We all need 😃, it makes us 💪. (and 👌).</p>
````````````````````````````````
Sentences can end with Emoji:
```````````````````````````````` example
This is a sentance :ok_hand:
and keeps going to the next line :)
.
<p>This is a sentance 👌
and keeps going to the next line 😃</p>
````````````````````````````````

View File

@@ -21,6 +21,17 @@ H~2~O is a liquid. 2^10^ is 1024
.
<p>H<sub>2</sub>O is a liquid. 2<sup>10</sup> is 1024</p>
````````````````````````````````
Certain punctuation characters are exempted from the rule forbidding them within inline delimiters
```````````````````````````````` example
One quintillionth can be expressed as 10^-18^
Daggers^†^ and double-daggers^‡^ can be used to denote notes.
.
<p>One quintillionth can be expressed as 10<sup>-18</sup></p>
<p>Daggers<sup>†</sup> and double-daggers<sup>‡</sup> can be used to denote notes.</p>
````````````````````````````````
## Inserted
@@ -41,3 +52,16 @@ Marked text can be used to specify that a text has been marked in a document. T
.
<p><mark>Marked text</mark></p>
````````````````````````````````
## Emphasis on Html Entities
```````````````````````````````` example
This is text MyBrand ^&reg;^ and MyTrademark ^&trade;^
This is text MyBrand^&reg;^ and MyTrademark^&trade;^
This is text MyBrand~&reg;~ and MyCopyright^&copy;^
.
<p>This is text MyBrand <sup>®</sup> and MyTrademark <sup>TM</sup>
This is text MyBrand<sup>®</sup> and MyTrademark<sup>TM</sup>
This is text MyBrand<sub>®</sub> and MyCopyright<sup>©</sup></p>
````````````````````````````````

View File

@@ -61,3 +61,60 @@ multi-paragraph list items.<a href="#fnref:3" class="footnote-back-ref">&#8617;<
</div>
````````````````````````````````
Check with mulitple consecutive footnotes:
```````````````````````````````` example
Here is a footnote[^1]. And another one[^2]. And a third one[^3]. And a fourth[^4].
[^1]: Footnote 1 text
[^2]: Footnote 2 text
a
[^3]: Footnote 3 text
[^4]: Footnote 4 text
.
<p>Here is a footnote<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. And another one<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>. And a third one<a id="fnref:3" href="#fn:3" class="footnote-ref"><sup>3</sup></a>. And a fourth<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a>.</p>
<p>a</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Footnote 1 text<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p></li>
<li id="fn:2">
<p>Footnote 2 text<a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p></li>
<li id="fn:3">
<p>Footnote 3 text<a href="#fnref:3" class="footnote-back-ref">&#8617;</a></p></li>
<li id="fn:4">
<p>Footnote 4 text<a href="#fnref:4" class="footnote-back-ref">&#8617;</a></p></li>
</ol>
</div>
````````````````````````````````
Another test with consecutive footnotes without a blank line separator:
```````````````````````````````` example
Here is a footnote[^1]. And another one[^2]. And a third one[^3]. And a fourth[^4].
[^1]: Footnote 1 text
[^2]: Footnote 2 text
[^3]: Footnote 3 text
[^4]: Footnote 4 text
.
<p>Here is a footnote<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. And another one<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>. And a third one<a id="fnref:3" href="#fn:3" class="footnote-ref"><sup>3</sup></a>. And a fourth<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a>.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Footnote 1 text<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p></li>
<li id="fn:2">
<p>Footnote 2 text<a href="#fnref:2" class="footnote-back-ref">&#8617;</a></p></li>
<li id="fn:3">
<p>Footnote 3 text<a href="#fnref:3" class="footnote-back-ref">&#8617;</a></p></li>
<li id="fn:4">
<p>Footnote 4 text<a href="#fnref:4" class="footnote-back-ref">&#8617;</a></p></li>
</ol>
</div>
````````````````````````````````

View File

@@ -27,13 +27,13 @@ The following shows that attributes is attached to the current block or the prev
This is a heading{#heading-link2}
-----------------
This is a paragraph with an attached attributes {#myparagraph attached-bool-property}
This is a paragraph with an attached attributes {#myparagraph attached-bool-property attached-bool-property2}
.
<h1 id="heading-link">This is a heading with an an attribute</h1>
<h1 id="heading-link2">This is a heading</h1>
<p><a href="http://google.com" id="a-link" class="myclass" data-lang="fr" data-value="This is a value">This is a link</a></p>
<h2 id="heading-link2">This is a heading</h2>
<p id="myparagraph" attached-bool-property>This is a paragraph with an attached attributes </p>
<p id="myparagraph" attached-bool-property attached-bool-property2>This is a paragraph with an attached attributes </p>
````````````````````````````````
The following shows that attributes can be attached to the next block if they are used inside a single line just preceding the block (and preceded by a blank line or beginning of a block container):

View File

@@ -70,8 +70,8 @@ A regular row can continue a previous regular row when column separator `|` are
+---------+---------+---------+
| Col1 | Col2 | Col3 |
| Col1a | Col2a | Col3a |
| Col12 | Col3b |
| Col123 |
| Col1b | Col3b |
| Col1c |
.
<table>
<col style="width:33.33%">
@@ -87,11 +87,11 @@ Col2a</td>
Col3a</td>
</tr>
<tr>
<td colspan="2">Col12</td>
<td></td>
<td colspan="2">Col1b</td>
<td>Col3b</td>
</tr>
<tr>
<td colspan="3">Col123</td>
<td colspan="3">Col1c</td>
</tr>
</tbody>
</table>
@@ -184,3 +184,103 @@ Alignment might be specified on the first row using the character `:`:
</tbody>
</table>
````````````````````````````````
A grid table may have cells spanning both columns and rows:
```````````````````````````````` example
+---+---+---+
| AAAAA | B |
+---+---+ B +
| D | E | B |
+ D +---+---+
| D | CCCCC |
+---+---+---+
.
<table>
<col style="width:33.33%">
<col style="width:33.33%">
<col style="width:33.33%">
<tbody>
<tr>
<td colspan="2">AAAAA</td>
<td rowspan="2">B
B
B</td>
</tr>
<tr>
<td rowspan="2">D
D
D</td>
<td>E</td>
</tr>
<tr>
<td colspan="2">CCCCC</td>
</tr>
</tbody>
</table>
````````````````````````````````
A grid table may have cells with both colspan and rowspan:
```````````````````````````````` example
+---+---+---+
| AAAAA | B |
+ AAAAA +---+
| AAAAA | C |
+---+---+---+
| D | E | F |
+---+---+---+
.
<table>
<col style="width:33.33%">
<col style="width:33.33%">
<col style="width:33.33%">
<tbody>
<tr>
<td colspan="2" rowspan="2">AAAAA
AAAAA
AAAAA</td>
<td>B</td>
</tr>
<tr>
<td>C</td>
</tr>
<tr>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</tbody>
</table>
````````````````````````````````
A grid table may not have irregularly shaped cells:
```````````````````````````````` example
+---+---+---+
| AAAAA | B |
+ A +---+ B +
| A | C | B |
+---+---+---+
| DDDDD | E |
+---+---+---+
.
<p>+---+---+---+
| AAAAA | B |
+ A +---+ B +
| A | C | B |
+---+---+---+
| DDDDD | E |
+---+---+---+</p>
````````````````````````````````
An empty `+` on a line should result in a simple empty list output:
```````````````````````````````` example
+
.
<ul>
<li></li>
</ul>
````````````````````````````````

View File

@@ -0,0 +1,77 @@
## Jira Links
The JiraLinks extension will automatically add links to JIRA issue items within your markdown, e.g. XX-1234. For this to happen, you must configure the extension when adding to the pipeline, e.g.
```
var pipeline = new MarkdownPipelineBuilder()
.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"))
.Build();
```
The rules for detecting a link are:
- The project key must be composed of onre or more capitalised ASCII letter `[A-Z]+`
- A single hypen `-` must separate the project key and issue number.
- The issue number is composed of 1 or more digits `[0, 9]+`
- The reference must be preceeded by either `(` or whitespace or EOF.
- The reference must be followed by either `)` or whitespace or EOF.
The following are valid examples:
```````````````````````````````` example
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 KIRA-1 issue
.
<p>This is a <a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a> issue</p>
````````````````````````````````
```````````````````````````````` example
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>
````````````````````````````````
These are also valid links with `(` and `)`:
```````````````````````````````` example
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 (KIRA-1) issue
.
<p>This is a (<a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a>) issue</p>
````````````````````````````````
```````````````````````````````` example
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>
````````````````````````````````
These are not valid links:
```````````````````````````````` example
This is not aJIRA-123 issue
.
<p>This is not aJIRA-123 issue</p>
````````````````````````````````
```````````````````````````````` example
This is not JIRA-123a issue
.
<p>This is not JIRA-123a issue</p>
````````````````````````````````
```````````````````````````````` example
This is not JIRA- issue
.
<p>This is not JIRA- issue</p>
````````````````````````````````

View File

@@ -38,7 +38,7 @@ Like for numbered list, a list can start with a different letter
b. First item
c. Second item
.
<ol type="a" start="b">
<ol type="a" start="2">
<li>First item</li>
<li>Second item</li>
</ol>
@@ -100,8 +100,26 @@ Like for numbered list, a list can start with a different letter
ii. First item
iii. Second item
.
<ol type="i" start="ii">
<ol type="i" start="2">
<li>First item</li>
<li>Second item</li>
</ol>
````````````````````````````````
Lists can be restarted, specifying the start point.
```````````````````````````````` example
1. First item
Some text
2. Second item
.
<ol>
<li>First item</li>
</ol>
<p>Some text</p>
<ol start="2">
<li>Second item</li>
</ol>
````````````````````````````````

View File

@@ -20,12 +20,47 @@ This is a $$math block$$
<p>This is a <span class="math">math block</span></p>
````````````````````````````````
The opening `$` and closing `$` is following the rules of the emphasis delimiter `_`:
Newlines inside an inline math are not allowed:
```````````````````````````````` example
This is not a $ math block $
This is not a $$math
block$$ and? this is a $$math block$$
.
<p>This is not a $ math block $</p>
<p>This is not a $$math
block$$ and? this is a <span class="math">math block</span></p>
````````````````````````````````
```````````````````````````````` example
This is not a $math
block$ and? this is a $math block$
.
<p>This is not a $math
block$ and? this is a <span class="math">math block</span></p>
````````````````````````````````
An opening `$` can be followed by a space if the closing is also preceded by a space `$`:
```````````````````````````````` example
This is a $ math block $
.
<p>This is a <span class="math">math block</span></p>
````````````````````````````````
```````````````````````````````` example
This is a $ math block $ after
.
<p>This is a <span class="math">math block</span> after</p>
````````````````````````````````
```````````````````````````````` example
This is a $$ math block $$ after
.
<p>This is a <span class="math">math block</span> after</p>
````````````````````````````````
```````````````````````````````` example
This is a not $ math block$ because there is not a whitespace before the closing
.
<p>This is a not $ math block$ because there is not a whitespace before the closing</p>
````````````````````````````````
For the opening `$` it requires a space or a punctuation before (but cannot be used within a word):
@@ -68,6 +103,13 @@ This is a $$$math block$$$
<p>This is a <span class="math">$math block$</span></p>
````````````````````````````````
Regular text can come both before and after the math inline
```````````````````````````````` example
This is a $math block$ with text on both sides.
.
<p>This is a <span class="math">math block</span> with text on both sides.</p>
````````````````````````````````
A mathematic block takes precedence over standard emphasis `*` `_`:
```````````````````````````````` example
@@ -75,6 +117,13 @@ This is *a $math* block$
.
<p>This is *a <span class="math">math* block</span></p>
````````````````````````````````
An opening $$ at the beginning of a line should not be interpreted as a Math block:
```````````````````````````````` example
$$ math $$ starting at a line
.
<p><span class="math">math</span> starting at a line</p>
````````````````````````````````
## Math Block

View File

@@ -10,7 +10,10 @@ Allows to embed audio/video links to popular website:
![Video1](https://www.youtube.com/watch?v=mswPy5bt3TQ)
![Video2](https://vimeo.com/8607834)
![Video3](https://sample.com/video.mp4)
.
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
<p><video width="500" height="281" controls><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
````````````````````````````````

View File

@@ -0,0 +1,27 @@
# Extensions
## NoHTML
The extension DisableHtml allows to disable the parsing of HTML:
For inline HTML:
```````````````````````````````` example
this is some text</td></tr>
.
<p>this is some text&lt;/td&gt;&lt;/tr&gt;</p>
````````````````````````````````
For Block HTML:
```````````````````````````````` example
<div>
this is some text
</div>
.
<p>&lt;div&gt;
this is some text
&lt;/div&gt;</p>
````````````````````````````````

View File

@@ -38,18 +38,27 @@ a | b
</table>
````````````````````````````````
While the following would be considered as a plain paragraph with a list item:
The following is also considered as a table, even if the second line starts like a list:
```````````````````````````````` example
a | b
- | -
0 | 1
.
<p>a | b</p>
<ul>
<li>| -
0 | 1</li>
</ul>
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
A pipe table with only one header row is allowed:
@@ -113,7 +122,7 @@ c no d
c no d</p>
````````````````````````````````
The number of columns in the first row determine the number of columns for the whole table. Any extra columns delimiter `|` for sub-sequent lines are converted to literal strings instead:
If a row contains more column than the header row, it will still be added as a column:
```````````````````````````````` example
a | b
@@ -127,19 +136,24 @@ a | b
<tr>
<th>a</th>
<th>b</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1 | 2</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
<td></td>
</tr>
<tr>
<td>5</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
@@ -205,6 +219,76 @@ Column delimiters `|` at the very beginning of a line or just before a line endi
</tbody>
</table>
````````````````````````````````
A pipe may be present at both the beginning/ending of each line:
```````````````````````````````` example
|a|b|
|-|-|
|0|1|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Or may be ommitted on one side:
```````````````````````````````` example
a|b|
-|-|
0|1|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
```````````````````````````````` example
|a|b
|-|-
|0|1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Single column table can be declared with lines starting only by a column delimiter:
```````````````````````````````` example
@@ -266,7 +350,8 @@ The first row is considered as a **header row** if it is separated from the regu
</table>
````````````````````````````````
The text alignment is defined by default to be left.
The text alignment is defined by default to be center for header and left for cells. If the left alignment is applied, it will force the column heading to be left aligned.
There is no way to define a different alignment for heading and cells (apart from the default).
The text alignment can be changed by using the character `:` with the header column separator:
```````````````````````````````` example
@@ -278,19 +363,19 @@ The text alignment can be changed by using the character `:` with the header col
<table>
<thead>
<tr>
<th>a</th>
<th style="text-align: left;">a</th>
<th style="text-align: center;">b</th>
<th style="text-align: right;">c</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td style="text-align: left;">0</td>
<td style="text-align: center;">1</td>
<td style="text-align: right;">2</td>
</tr>
<tr>
<td>3</td>
<td style="text-align: left;">3</td>
<td style="text-align: center;">4</td>
<td style="text-align: right;">5</td>
</tr>
@@ -298,6 +383,31 @@ The text alignment can be changed by using the character `:` with the header col
</table>
````````````````````````````````
Test alignment with starting and ending pipes:
```````````````````````````````` example
| abc | def | ghi |
|:---:|-----|----:|
| 1 | 2 | 3 |
.
<table>
<thead>
<tr>
<th style="text-align: center;">abc</th>
<th>def</th>
<th style="text-align: right;">ghi</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">1</td>
<td>2</td>
<td style="text-align: right;">3</td>
</tr>
</tbody>
</table>
````````````````````````````````
The following example shows a non matching header column separator:
```````````````````````````````` example
@@ -403,3 +513,70 @@ a | b
</table>
````````````````````````````````
** Tests **
Tests trailing spaces after pipes
```````````````````````````````` example
| abc | def |
|---|---|
| cde| ddd|
| eee| fff|
| fff | fffff |
|gggg | ffff |
.
<table>
<thead>
<tr>
<th>abc</th>
<th>def</th>
</tr>
</thead>
<tbody>
<tr>
<td>cde</td>
<td>ddd</td>
</tr>
<tr>
<td>eee</td>
<td>fff</td>
</tr>
<tr>
<td>fff</td>
<td>fffff</td>
</tr>
<tr>
<td>gggg</td>
<td>ffff</td>
</tr>
</tbody>
</table>
````````````````````````````````
** Normalized columns count **
The tables are normalized to the maximum number of columns found in a table
```````````````````````````````` example
a | b
-- | -
0 | 1 | 2
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
<td>2</td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -83,29 +83,6 @@ They are' not matching 'quotes
.
<p>They are' not matching 'quotes</p>
````````````````````````````````
Double quotes using ``` `` ``` are working if they match another `''` pair, and there is no other double quotes on the line (otherwise they would be parsed as a code span):
```````````````````````````````` example
This is ``a double quote''
.
<p>This is &ldquo;a double quote&rdquo;</p>
````````````````````````````````
```````````````````````````````` example
This is ``a code span''``
.
<p>This is <code>a code span''</code></p>
````````````````````````````````
```````````````````````````````` example
hello ``there```
test
.
<p>hello &ldquo;there&rdquo;`
test</p>
````````````````````````````````
An emphasis starting inside left/right quotes will span over the right quote:
```````````````````````````````` example
@@ -133,3 +110,36 @@ This is a en ellipsis...
.
<p>This is a en ellipsis&hellip;</p>
````````````````````````````````
Check that a smartypants are not breaking pipetable parsing:
```````````````````````````````` example
a | b
-- | --
0 | 1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Check quotes and dash:
```````````````````````````````` example
A "quote" with a ---
.
<p>A &ldquo;quote&rdquo; with a &mdash;</p>
````````````````````````````````

File diff suppressed because it is too large Load Diff

View File

@@ -38,24 +38,30 @@ SOFTWARE.
<#@ import namespace="System.CodeDom.Compiler" #>
<#@ output extension=".cs" #><#
var specFiles = new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/master/spec.txt", string.Empty),
new KeyValuePair<string, string>(Host.ResolvePath("PipeTableSpecs.md"), "pipetables"),
new KeyValuePair<string, string>(Host.ResolvePath("FootnotesSpecs.md"), "footnotes"),
new KeyValuePair<string, string>(Host.ResolvePath("GenericAttributesSpecs.md"), "attributes"),
new KeyValuePair<string, string>(Host.ResolvePath("EmphasisExtraSpecs.md"), "emphasisextra"),
new KeyValuePair<string, string>(Host.ResolvePath("HardlineBreakSpecs.md"), "hardlinebreak"),
new KeyValuePair<string, string>(Host.ResolvePath("GridTableSpecs.md"), "gridtables"),
new KeyValuePair<string, string>(Host.ResolvePath("CustomContainerSpecs.md"), "customcontainers+attributes"),
new KeyValuePair<string, string>(Host.ResolvePath("DefinitionListSpecs.md"), "definitionlists+attributes"),
new KeyValuePair<string, string>(Host.ResolvePath("EmojiSpecs.md"), "emojis"),
new KeyValuePair<string, string>(Host.ResolvePath("AbbreviationSpecs.md"), "abbreviations"),
new KeyValuePair<string, string>(Host.ResolvePath("ListExtraSpecs.md"), "listextra"),
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+cites"),
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "math"),
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/4ec06917c3a3632be4a935ffa0973092bd2621be/spec.txt", string.Empty), // 0.28
new KeyValuePair<string, string>(Host.ResolvePath("PipeTableSpecs.md"), "pipetables|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("FootnotesSpecs.md"), "footnotes|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("GenericAttributesSpecs.md"), "attributes|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("EmphasisExtraSpecs.md"), "emphasisextras|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("HardlineBreakSpecs.md"), "hardlinebreak|advanced+hardlinebreak"),
new KeyValuePair<string, string>(Host.ResolvePath("GridTableSpecs.md"), "gridtables|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("CustomContainerSpecs.md"), "customcontainers+attributes|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("DefinitionListSpecs.md"), "definitionlists+attributes|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("EmojiSpecs.md"), "emojis|advanced+emojis"),
new KeyValuePair<string, string>(Host.ResolvePath("AbbreviationSpecs.md"), "abbreviations|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("ListExtraSpecs.md"), "listextras|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+citations|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "mathematics|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("BootstrapSpecs.md"), "bootstrap+pipetables+figures+attributes"),
new KeyValuePair<string, string>(Host.ResolvePath("MediaSpecs.md"), "medias"),
new KeyValuePair<string, string>(Host.ResolvePath("SmartyPantsSpecs.md"), "smartypants"),
new KeyValuePair<string, string>(Host.ResolvePath("AutoIdentifierSpecs.md"), "autoidentifiers"),
new KeyValuePair<string, string>(Host.ResolvePath("MediaSpecs.md"), "medialinks|advanced+medialinks"),
new KeyValuePair<string, string>(Host.ResolvePath("SmartyPantsSpecs.md"), "pipetables+smartypants|advanced+smartypants"), // Check with smartypants to make sure that it doesn't break pipetables
new KeyValuePair<string, string>(Host.ResolvePath("AutoIdentifierSpecs.md"), "autoidentifiers|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("TaskListSpecs.md"), "tasklists|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("DiagramsSpecs.md"), "diagrams|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("NoHtmlSpecs.md"), "nohtml"),
new KeyValuePair<string, string>(Host.ResolvePath("YamlSpecs.md"), "yaml"),
new KeyValuePair<string, string>(Host.ResolvePath("AutoLinks.md"), "autolinks|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("JiraLinks.md"), "jiralinks"),
};
var emptyLines = false;
var displayEmptyLines = false;

View File

@@ -0,0 +1,29 @@
# Extensions
Adds support for task lists:
## TaskLists
A task list item consist of `[ ]` or `[x]` or `[X]` inside a list item (ordered or unordered)
```````````````````````````````` example
- [ ] Item1
- [x] Item2
- [ ] Item3
- Item4
.
<ul class="contains-task-list">
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item1</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item3</li>
<li>Item4</li>
</ul>
````````````````````````````````
A task is not recognized outside a list item:
```````````````````````````````` example
[ ] This is not a task list
.
<p>[ ] This is not a task list</p>
````````````````````````````````

View File

@@ -0,0 +1,25 @@
// 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 NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public partial class TestEmphasisPlus
{
[Test]
public void StrongNormal()
{
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
}
[Test]
public void NormalStrongNormal()
{
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
}
}
}

View File

@@ -0,0 +1,103 @@
# Extensions
Adds support for YAML frontmatter parsing:
## YAML frontmatter discard
If a frontmatter is present, it will not be rendered:
```````````````````````````````` example
---
this: is a frontmatter
---
This is a text
.
<p>This is a text</p>
````````````````````````````````
But if a frontmatter doesn't happen on the first line, it will be parse as regular Markdown content
```````````````````````````````` example
This is a text1
---
this: is a frontmatter
---
This is a text2
.
<h2>This is a text1</h2>
<h2>this: is a frontmatter</h2>
<p>This is a text2</p>
````````````````````````````````
It expects an exact 3 dashes `---`:
```````````````````````````````` example
----
this: is a frontmatter
----
This is a text
.
<hr />
<h2>this: is a frontmatter</h2>
<p>This is a text</p>
````````````````````````````````
It can end with three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects exactly three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
....
This is a text
.
````````````````````````````````
Front matter ends with the first line containing three dots `...` or three dashes `...`:
```````````````````````````````` example
---
this: is a frontmatter
....
Hello
---
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects whitespace can exist after the leading characters
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects whitespace can exist after the trailing characters
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````

View File

@@ -0,0 +1,36 @@
# Documentation Tests/Specs
You will find from the following links the supported extensions in markdig and their usage:
- 2 kind of tables:
- [**Pipe tables**](PipeTableSpecs.md)
- [**Grid tables**](GridTableSpecs.md)
- [**Extra emphasis**](EmphasisExtraSpecs.md)
- strike through `~~`,
- Subscript `~`
- Superscript `^`
- Inserted `++`
- Marked `==`
- [**Special attributes**](GenericAttributesSpecs.md)
- [**Definition lists**](DefinitionListSpecs.md)
- [**Footnotes**](FootnotesSpecs.md)
- [**Auto-identifiers**](AutoIdentifierSpecs.md)
- [**Auto-links**](AutoLinks.md)
- [**Task Lists**](TaskListSpecs.md)
- [**Extra bullet lists**](ListExtraSpecs.md)
- [**Media support**](MediaSpecs.md)
- [**Abbreviations**](AbbreviationSpecs.md)
- [**Citation**](FigureFooterAndCiteSpecs.md)
- [**Custom containers**](CustomContainerSpecs.md)
- [**Figures**](FigureFooterAndCiteSpecs.md)
- [**Footers**](FigureFooterAndCiteSpecs.md)
- [**Mathematics**](MathSpecs.md)/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- [**Soft lines as hard lines**](HardlineBreakSpecs.md)
- [**Emoji**](EmojiSpecs.md)
- [**SmartyPants**](SmartyPantsSpecs.md)
- [**Bootstrap**](BootstrapSpecs.md)
- [**Diagrams**](DiagramsSpecs.md)
- [**YAML frontmatter**](YamlSpecs.md)
- [**JIRA links**](JiraLinks.md)
> Notice that the links above are not yet the final documentation but are "specification" files used for testing the correctness of markdig for each extension

View File

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

View File

@@ -0,0 +1,128 @@
// 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.IO;
using NUnit.Framework;
using Markdig.Helpers;
namespace Markdig.Tests
{
/// <summary>
/// Test for <see cref="LineReader"/>.
/// </summary>
[TestFixture]
public class TestLineReader
{
[Test]
public void TestEmpty()
{
var lineReader = new LineReader("");
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestLinesOnlyLf()
{
var lineReader = new LineReader("\n\n\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestLinesOnlyCr()
{
var lineReader = new LineReader("\r\r\r");
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestLinesOnlyCrLf()
{
var lineReader = new LineReader("\r\n\r\n\r\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestNoEndOfLine()
{
var lineReader = new LineReader("123");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestLf()
{
var lineReader = new LineReader("123\n");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestLf2()
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\n'
var lineReader = new LineReader("123\n456");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestCr()
{
var lineReader = new LineReader("123\r");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestCr2()
{
var lineReader = new LineReader("123\r456");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestCrLf()
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\r'
// and we check that we don't get a new line for `\n`
var lineReader = new LineReader("123\r\n");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine()?.ToString());
}
[Test]
public void TestCrLf2()
{
var lineReader = new LineReader("123\r\n456");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
}
}
}

View File

@@ -1,6 +1,8 @@
// 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.Security;
using NUnit.Framework;
using Markdig.Helpers;
using Markdig.Syntax;
@@ -30,6 +32,18 @@ namespace Markdig.Tests
Assert.AreEqual(')', text.CurrentChar);
}
[Test]
[TestCase("http://google.com.")]
[TestCase("http://google.com. ")]
public void TestUrlTrailingFullStop(string uri)
{
var text = new StringSlice(uri);
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}
[Test]
public void TestUrlNestedParenthesis()
{
@@ -80,36 +94,52 @@ namespace Markdig.Tests
[Test]
public void TestUrlAndTitle()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("this is a title", title);
Assert.AreEqual(new SourceSpan(1, 17), linkSpan);
Assert.AreEqual(new SourceSpan(19, 35), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitleEmpty()
{
// 01234
var text = new StringSlice(@"(<>)A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(1, 2), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestUrlAndTitleEmpty2()
{
// 012345
var text = new StringSlice(@"( <> )A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(2, 3), linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
@@ -117,12 +147,18 @@ namespace Markdig.Tests
[Test]
public void TestUrlEmptyWithTitleWithMultipleSpaces()
{
// 0 1 2
// 0123456789012345678901234567
var text = new StringSlice(@"( <> 'toto' )A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(4, 5), linkSpan);
Assert.AreEqual(new SourceSpan(12, 17), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
@@ -132,50 +168,67 @@ namespace Markdig.Tests
var text = new StringSlice(@"()A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(SourceSpan.Empty, linkSpan);
Assert.AreEqual(SourceSpan.Empty, titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestMultipleLines()
{
var text = new StringSlice(@"(
<http://google.com>
'toto' )A");
// 0 1 2 3
// 01 2345678901234567890 1234567890123456789
var text = new StringSlice("(\n<http://google.com>\n 'toto' )A");
string link;
string title;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(2, 20), linkSpan);
Assert.AreEqual(new SourceSpan(26, 31), titleSpan);
Assert.AreEqual('A', text.CurrentChar);
}
[Test]
public void TestLabelSimple()
{
// 01234
var text = new StringSlice("[foo]");
string label;
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual("foo", label);
}
[Test]
public void TestLabelEscape()
{
// 012345678
var text = new StringSlice(@"[fo\[\]o]");
string label;
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.AreEqual(new SourceSpan(1, 7), labelSpan);
Assert.AreEqual(@"fo[]o", label);
}
[Test]
public void TestLabelEscape2()
{
// 0123
var text = new StringSlice(@"[\]]");
string label;
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.AreEqual(new SourceSpan(1, 2), labelSpan);
Assert.AreEqual(@"]", label);
}
@@ -194,23 +247,36 @@ namespace Markdig.Tests
[Test]
public void TestLabelWhitespaceCollapsedAndTrim()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[ fo o z ]");
string label;
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.AreEqual(new SourceSpan(6, 17), labelSpan);
Assert.AreEqual(@"fo o z", label);
}
[Test]
public void TestlLinkReferenceDefinitionSimple()
{
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[foo]: /toto 'title'");
string label;
string url;
string title;
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title));
SourceSpan labelSpan;
SourceSpan urlSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title, out labelSpan, out urlSpan, out titleSpan));
Assert.AreEqual(@"foo", label);
Assert.AreEqual(@"/toto", url);
Assert.AreEqual(@"title", title);
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual(new SourceSpan(7, 11), urlSpan);
Assert.AreEqual(new SourceSpan(13, 19), titleSpan);
}
[Test]
@@ -245,5 +311,103 @@ namespace Markdig.Tests
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
}
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
[TestCase("* Dogs*?--in *my* house?", "dogs-in-my-house")] // Not Pandoc equivalent: dogs--in...
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "applications")]
[TestCase("33", "")]
public void TestUrilizeNonAscii_Pandoc(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("abc", "abc")]
[TestCase("a-c", "a-c")]
[TestCase("a c", "a-c")]
[TestCase("a_c", "a_c")]
[TestCase("a.c", "a.c")]
[TestCase("a,c", "ac")]
[TestCase("a--", "a")] // Not Pandoc-equivalent: a--
[TestCase("a__", "a")] // Not Pandoc-equivalent: a__
[TestCase("a..", "a")] // Not Pandoc-equivalent: a..
[TestCase("a??", "a")]
[TestCase("a ", "a")]
[TestCase("a--d", "a-d")]
[TestCase("a__d", "a_d")]
[TestCase("a??d", "ad")]
[TestCase("a d", "a-d")]
[TestCase("a..d", "a.d")]
[TestCase("-bc", "bc")]
[TestCase("_bc", "bc")]
[TestCase(" bc", "bc")]
[TestCase("?bc", "bc")]
[TestCase(".bc", "bc")]
[TestCase("a-.-", "a")] // Not Pandoc equivalent: a-.-
public void TestUrilizeOnlyAscii_Simple(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("bær", "br")]
[TestCase("bør", "br")]
[TestCase("bΘr", "br")]
[TestCase("四五", "")]
public void TestUrilizeOnlyAscii_NonAscii(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("bár", "bar")]
[TestCase("àrrivé", "arrive")]
public void TestUrilizeOnlyAscii_Normalization(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("123", "")]
[TestCase("1,-b", "b")]
[TestCase("b1,-", "b1")] // Not Pandoc equivalent: b1-
[TestCase("ab3", "ab3")]
[TestCase("ab3de", "ab3de")]
public void TestUrilizeOnlyAscii_Numeric(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
}
[TestCase("一二三四五", "一二三四五")]
[TestCase("一,-b", "一-b")]
public void TestUrilizeNonAscii_NonAsciiNumeric(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("bær", "bær")]
[TestCase("æ5el", "æ5el")]
[TestCase("-æ5el", "æ5el")]
[TestCase("-frø-", "frø")]
[TestCase("-fr-ø", "fr-ø")]
public void TestUrilizeNonAscii_Simple(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
// Just to be sure, test for characters expressly forbidden in URI fragments:
[TestCase("b#r", "br")]
[TestCase("b%r", "br")] // Invalid except as an escape character
[TestCase("b^r", "br")]
[TestCase("b[r", "br")]
[TestCase("b]r", "br")]
[TestCase("b{r", "br")]
[TestCase("b}r", "br")]
[TestCase("b<r", "br")]
[TestCase("b>r", "br")]
[TestCase(@"b\r", "br")]
[TestCase(@"b""r", "br")]
[TestCase(@"Requirement 😀", "requirement")]
public void TestUrilizeNonAscii_NonValidCharactersForFragments(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
}
}

View File

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

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
namespace Markdig.Tests
{
@@ -14,43 +15,76 @@ namespace Markdig.Tests
{
foreach (var pipeline in GetPipeline(extensions))
{
Console.WriteLine($"Pipeline configured with extensions: {extensions}");
// Uncomment this line to get more debug information for process inlines.
//pipeline.DebugLog = Console.Out;
var result = Markdown.ToHtml(inputText, pipeline);
result = Compact(result);
expectedOutputText = Compact(expectedOutputText);
Console.WriteLine("```````````````````Source");
Console.WriteLine(DisplaySpaceAndTabs(inputText));
Console.WriteLine("```````````````````Result");
Console.WriteLine(DisplaySpaceAndTabs(result));
Console.WriteLine("```````````````````Expected");
Console.WriteLine(DisplaySpaceAndTabs(expectedOutputText));
Console.WriteLine("```````````````````");
Console.WriteLine();
TextAssert.AreEqual(expectedOutputText, result);
Console.WriteLine($"Pipeline configured with extensions: {pipeline.Key}");
TestSpec(inputText, expectedOutputText, pipeline.Value);
}
}
private static IEnumerable<MarkdownPipeline> GetPipeline(string extensionsGroupText)
public static void TestSpec(string inputText, string expectedOutputText, MarkdownPipeline pipeline)
{
// Uncomment this line to get more debug information for process inlines.
//pipeline.DebugLog = Console.Out;
var result = Markdown.ToHtml(inputText, pipeline);
result = Compact(result);
expectedOutputText = Compact(expectedOutputText);
Console.WriteLine("```````````````````Source");
Console.WriteLine(DisplaySpaceAndTabs(inputText));
Console.WriteLine("```````````````````Result");
Console.WriteLine(DisplaySpaceAndTabs(result));
Console.WriteLine("```````````````````Expected");
Console.WriteLine(DisplaySpaceAndTabs(expectedOutputText));
Console.WriteLine("```````````````````");
Console.WriteLine();
TextAssert.AreEqual(expectedOutputText, result);
}
private static IEnumerable<KeyValuePair<string, MarkdownPipeline>> GetPipeline(string extensionsGroupText)
{
// For the standard case, we make sure that both the CommmonMark core and Extra/Advanced are CommonMark compliant!
if (string.IsNullOrEmpty(extensionsGroupText))
{
yield return new MarkdownPipelineBuilder().Build();
yield return new KeyValuePair<string, MarkdownPipeline>("default", new MarkdownPipelineBuilder().Build());
yield return new KeyValuePair<string, MarkdownPipeline>("advanced", new MarkdownPipelineBuilder() // Use similar to advanced extension without auto-identifier
.UseAbbreviations()
//.UseAutoIdentifiers()
.UseCitations()
.UseCustomContainers()
.UseDefinitionLists()
.UseEmphasisExtras()
.UseFigures()
.UseFooters()
.UseFootnotes()
.UseGridTables()
.UseMathematics()
.UseMediaLinks()
.UsePipeTables()
.UseListExtras()
.UseGenericAttributes().Build());
yield break;
}
var extensionGroups = extensionsGroupText.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var extensionsText in extensionGroups)
{
var pipeline = new MarkdownPipelineBuilder().Configure(extensionsText);
yield return pipeline.Build();
var builder = new MarkdownPipelineBuilder();
builder.DebugLog = Console.Out;
if (extensionsText == "jiralinks")
{
builder.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"));
}
else
{
builder = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
}
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, builder.Build());
}
}
private static string DisplaySpaceAndTabs(string text)
public static string DisplaySpaceAndTabs(string text)
{
// Output special characters to check correctly the results
return text.Replace('\t', '→').Replace(' ', '·');
@@ -59,7 +93,7 @@ namespace Markdig.Tests
private static string Compact(string html)
{
// Normalize the output to make it compatible with CommonMark specs
html = html.Replace("\r", "").Trim();
html = html.Replace("\r\n", "\n").Replace(@"\r", @"\n").Trim();
html = Regex.Replace(html, @"\s+</li>", "</li>");
html = Regex.Replace(html, @"<li>\s+", "<li>");
html = html.Normalize(NormalizationForm.FormKD);

View File

@@ -2,6 +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.Linq;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests
@@ -9,6 +12,18 @@ namespace Markdig.Tests
[TestFixture]
public class TestPlayParser
{
[Test]
public void TestListBug2()
{
TestParser.TestSpec("10.\t*test* test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
<li><p><em>test</em> test</p>
</li>
<li><p><strong>test</strong> test</p>
</li>
</ol>
");
}
[Test]
public void TestSimple()
{
@@ -21,12 +36,176 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
//> titi toto
//");
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtra());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviation().Build());
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtras());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviations().Build());
//File.WriteAllText("test.html", result, Encoding.UTF8);
Console.WriteLine(result);
}
[Test]
public void TestEmptyLiteral()
{
var text = @"> *some text*
> some other text";
var doc = Markdown.Parse(text);
Assert.True(doc.Descendants().OfType<LiteralInline>().All(x => !x.Content.IsEmpty),
"There should not have any empty literals");
}
[Test]
public void TestSelfPipeline1()
{
var text = @" <!--markdig:pipetables-->
a | b
- | -
0 | 1
";
TestParser.TestSpec(text, @"<!--markdig:pipetables-->
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
", "self");
}
[Test]
public void TestListBug()
{
// TODO: Add this test back to the CommonMark specs
var text = @"- item1
- item2
- item3
- item4";
TestParser.TestSpec(text, @"<ul>
<li>item1
<ul>
<li>item2
<ul>
<li>item3
<ul>
<li>item4</li>
</ul></li>
</ul></li>
</ul></li>
</ul>
");
}
[Test]
public void TestHtmlBug()
{
TestParser.TestSpec(@" # header1
<pre class='copy'>
blabla
</pre>
# header2
", @"<h1>header1</h1>
<pre class='copy'>
blabla
</pre>
<h1>header2</h1>");
}
[Test]
public void TestHtmlh4Bug()
{
TestParser.TestSpec(@"<h4>foobar</h4>", @"<h4>foobar</h4>");
}
[Test]
public void TestStandardUriEscape()
{
TestParser.TestSpec(@"![你好](你好.png)", "<p><img src=\"你好.png\" alt=\"你好\" /></p>", "nonascii-noescape");
}
[Test]
public void TestBugAdvanced()
{
TestParser.TestSpec(@"`https://{domain}/callbacks`
#### HEADING
Paragraph
", "<p><code>https://{domain}/callbacks</code></p>\n<h4 id=\"heading\">HEADING</h4>\n<p>Paragraph</p>", "advanced");
}
[Test]
public void TestBugEmphAttribute()
{
// https://github.com/lunet-io/markdig/issues/108
TestParser.TestSpec(@"*test*{name=value}", "<p><em name=\"value\">test</em></p>", "advanced");
}
[Test]
public void TestBugPipeTables()
{
// https://github.com/lunet-io/markdig/issues/73
TestParser.TestSpec(@"| abc | def |
| --- | --- |
| 1 | ~3 |
", @"<table>
<thead>
<tr>
<th>abc</th>
<th>def</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>~3</td>
</tr>
</tbody>
</table>", "advanced");
}
[Test]
public void TestGridTableWithCustomAttributes() {
var input = @"
{.table}
+---+---+
| a | b |
+===+===+
| 1 | 2 |
+---+---+
";
var expected = @"<table class=""table"">
<col style=""width:50%"">
<col style=""width:50%"">
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
</tbody>
</table>
";
TestParser.TestSpec(input, expected, "advanced");
}
[Test]
public void TestSamePipelineAllExtensions()
{

View File

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

View File

@@ -0,0 +1,791 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Markdig.Helpers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
namespace Markdig.Tests
{
/// <summary>
/// Test the precise source location of all Markdown elements, including extensions
/// </summary>
[TestFixture]
public class TestSourcePosition
{
[Test]
public void TestParagraph()
{
Check("0123456789", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-9
");
}
[Test]
public void TestParagraphAndNewLine()
{
Check("0123456789\n0123456789", @"
paragraph ( 0, 0) 0-20
literal ( 0, 0) 0-9
linebreak ( 0,10) 10-10
literal ( 1, 0) 11-20
");
Check("0123456789\r\n0123456789", @"
paragraph ( 0, 0) 0-21
literal ( 0, 0) 0-9
linebreak ( 0,10) 10-10
literal ( 1, 0) 12-21
");
}
[Test]
public void TestParagraphNewLineAndSpaces()
{
// 0123 45678
Check("012\n 345", @"
paragraph ( 0, 0) 0-8
literal ( 0, 0) 0-2
linebreak ( 0, 3) 3-3
literal ( 1, 2) 6-8
");
}
[Test]
public void TestParagraph2()
{
Check("0123456789\n\n0123456789", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-9
paragraph ( 2, 0) 12-21
literal ( 2, 0) 12-21
");
}
[Test]
public void TestEmphasis()
{
Check("012**3456789**", @"
paragraph ( 0, 0) 0-13
literal ( 0, 0) 0-2
emphasis ( 0, 3) 3-13
literal ( 0, 5) 5-11
");
}
[Test]
public void TestEmphasis2()
{
// 01234567
Check("01*2**3*", @"
paragraph ( 0, 0) 0-7
literal ( 0, 0) 0-1
emphasis ( 0, 2) 2-7
literal ( 0, 3) 3-3
literal ( 0, 4) 4-5
literal ( 0, 6) 6-6
");
}
[Test]
public void TestEmphasis3()
{
// 0123456789
Check("01**2***3*", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-1
emphasis ( 0, 2) 2-6
literal ( 0, 4) 4-4
emphasis ( 0, 7) 7-9
literal ( 0, 8) 8-8
");
}
[Test]
public void TestEmphasisFalse()
{
Check("0123456789**0123", @"
paragraph ( 0, 0) 0-15
literal ( 0, 0) 0-9
literal ( 0,10) 10-11
literal ( 0,12) 12-15
");
}
[Test]
public void TestHeading()
{
// 012345
Check("# 2345", @"
heading ( 0, 0) 0-5
literal ( 0, 2) 2-5
");
}
[Test]
public void TestHeadingWithEmphasis()
{
// 0123456789
Check("# 23**45**", @"
heading ( 0, 0) 0-9
literal ( 0, 2) 2-3
emphasis ( 0, 4) 4-9
literal ( 0, 6) 6-7
");
}
[Test]
public void TestCodeSpan()
{
// 012345678
Check("0123`456`", @"
paragraph ( 0, 0) 0-8
literal ( 0, 0) 0-3
code ( 0, 4) 4-8
");
}
[Test]
public void TestLink()
{
// 0123456789
Check("012[45](#)", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-2
link ( 0, 3) 3-9
literal ( 0, 4) 4-5
");
}
[Test]
public void TestLinkParts1()
{
// 0 1
// 01 2 3456789012345
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
Assert.AreEqual(new SourceSpan(12, 14), link.UrlSpan);
Assert.AreEqual(SourceSpan.Empty, link.TitleSpan);
}
[Test]
public void TestLinkParts2()
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
Assert.AreEqual(new SourceSpan(12, 14), link.UrlSpan);
Assert.AreEqual(new SourceSpan(16, 19), link.TitleSpan);
}
[Test]
public void TestLinkParts3()
{
// 0 1
// 01 2 3456789012345
var link = Markdown.Parse("0\n\n01![234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(new SourceSpan(5, 15), link.Span);
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
Assert.AreEqual(new SourceSpan(12, 14), link.UrlSpan);
Assert.AreEqual(SourceSpan.Empty, link.TitleSpan);
}
[Test]
public void TestAutolinkInline()
{
// 0123456789ABCD
Check("01<http://yes>", @"
paragraph ( 0, 0) 0-13
literal ( 0, 0) 0-1
autolink ( 0, 2) 2-13
");
}
[Test]
public void TestFencedCodeBlock()
{
// 012 3456 78 9ABC
Check("01\n```\n3\n```\n", @"
paragraph ( 0, 0) 0-1
literal ( 0, 0) 0-1
fencedcode ( 1, 0) 3-11
");
}
[Test]
public void TestHtmlBlock()
{
// 012345 67 89ABCDE F 0
Check("<div>\n0\n</div>\n\n1", @"
html ( 0, 0) 0-13
paragraph ( 4, 0) 16-16
literal ( 4, 0) 16-16
");
}
[Test]
public void TestHtmlBlock1()
{
// 0 1
// 01 2 345678901 23
Check("0\n\n<!--A-->\n1\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
html ( 2, 0) 3-10
paragraph ( 3, 0) 12-12
literal ( 3, 0) 12-12
");
}
[Test]
public void TestHtmlComment()
{
// 0 1 2
// 012345678901 234567890 1234
Check("# 012345678\n<!--0-->\n123\n", @"
heading ( 0, 0) 0-10
literal ( 0, 2) 2-10
html ( 1, 0) 12-19
paragraph ( 2, 0) 21-23
literal ( 2, 0) 21-23
");
}
[Test]
public void TestHtmlInline()
{
// 0123456789
Check("01<b>4</b>", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-1
html ( 0, 2) 2-4
literal ( 0, 5) 5-5
html ( 0, 6) 6-9
");
}
[Test]
public void TestHtmlInline1()
{
// 0
// 0123456789
Check("0<!--A-->1", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-0
html ( 0, 1) 1-8
literal ( 0, 9) 9-9
");
}
[Test]
public void TestThematicBreak()
{
// 0123 4567
Check("---\n---\n", @"
thematicbreak ( 0, 0) 0-2
thematicbreak ( 1, 0) 4-6
");
}
[Test]
public void TestQuoteBlock()
{
// 0123456
Check("> 2345\n", @"
quote ( 0, 0) 0-5
paragraph ( 0, 2) 2-5
literal ( 0, 2) 2-5
");
}
[Test]
public void TestQuoteBlockWithLines()
{
// 01234 56789A
Check("> 01\n> 23\n", @"
quote ( 0, 0) 0-9
paragraph ( 0, 2) 2-9
literal ( 0, 2) 2-3
linebreak ( 0, 4) 4-4
literal ( 1, 3) 8-9
");
}
[Test]
public void TestQuoteBlockWithLazyContinuation()
{
// 01234 56
Check("> 01\n23\n", @"
quote ( 0, 0) 0-6
paragraph ( 0, 2) 2-6
literal ( 0, 2) 2-3
linebreak ( 0, 4) 4-4
literal ( 1, 0) 5-6
");
}
[Test]
public void TestListBlock()
{
// 0123 4567
Check("- 0\n- 1\n", @"
list ( 0, 0) 0-6
listitem ( 0, 0) 0-2
paragraph ( 0, 2) 2-2
literal ( 0, 2) 2-2
listitem ( 1, 0) 4-6
paragraph ( 1, 2) 6-6
literal ( 1, 2) 6-6
");
}
[Test]
public void TestEscapeInline()
{
// 0123
Check(@"\-\)", @"
paragraph ( 0, 0) 0-3
literal ( 0, 0) 0-1
literal ( 0, 2) 2-3
");
}
[Test]
public void TestHtmlEntityInline()
{
// 01 23456789
Check("0\n&nbsp; 1", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-0
linebreak ( 0, 1) 1-1
htmlentity ( 1, 0) 2-7
literal ( 1, 6) 8-9
");
}
[Test]
public void TestAbbreviations()
{
Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML", @"
paragraph ( 2, 0) 38-102
container ( 2, 0) 38-102
literal ( 2, 0) 38-66
abbreviation ( 2,29) 67-70
literal ( 2,33) 71-98
abbreviation ( 2,61) 99-102
", "abbreviations");
}
[Test]
public void TestCitation()
{
// 0123 4 567 8
Check("01 \"\"23\"\"", @"
paragraph ( 0, 0) 0-8
literal ( 0, 0) 0-2
emphasis ( 0, 3) 3-8
literal ( 0, 5) 5-6
", "citations");
}
[Test]
public void TestCustomContainer()
{
// 01 2345 678 9ABC DEF
Check("0\n:::\n23\n:::\n45\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
customcontainer ( 1, 0) 2-11
paragraph ( 2, 0) 6-7
literal ( 2, 0) 6-7
paragraph ( 4, 0) 13-14
literal ( 4, 0) 13-14
", "customcontainers");
}
[Test]
public void TestDefinitionList()
{
// 012 3456789A
Check("a0\n: 1234", @"
definitionlist ( 0, 0) 0-10
definitionitem ( 1, 0) 3-10
definitionterm ( 0, 0) 0-1
literal ( 0, 0) 0-1
paragraph ( 1, 4) 7-10
literal ( 1, 4) 7-10
", "definitionlists");
}
[Test]
public void TestDefinitionList2()
{
// 012 3456789AB CDEF01234
Check("a0\n: 1234\n: 5678", @"
definitionlist ( 0, 0) 0-20
definitionitem ( 1, 0) 3-10
definitionterm ( 0, 0) 0-1
literal ( 0, 0) 0-1
paragraph ( 1, 4) 7-10
literal ( 1, 4) 7-10
definitionitem ( 2, 4) 12-20
paragraph ( 2, 5) 17-20
literal ( 2, 5) 17-20
", "definitionlists");
}
[Test]
public void TestEmoji()
{
// 01 2345
Check("0\n :)\n", @"
paragraph ( 0, 0) 0-4
literal ( 0, 0) 0-0
linebreak ( 0, 1) 1-1
emoji ( 1, 1) 3-4
", "emojis");
}
[Test]
public void TestEmphasisExtra()
{
// 0123456
Check("0 ~~1~~", @"
paragraph ( 0, 0) 0-6
literal ( 0, 0) 0-1
emphasis ( 0, 2) 2-6
literal ( 0, 4) 4-4
", "emphasisextras");
}
[Test]
public void TestFigures()
{
// 01 2345 67 89AB
Check("0\n^^^\n0\n^^^\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
figure ( 1, 0) 2-10
paragraph ( 2, 0) 6-6
literal ( 2, 0) 6-6
", "figures");
}
[Test]
public void TestFiguresCaption1()
{
// 01 234567 89 ABCD
Check("0\n^^^ab\n0\n^^^\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
figure ( 1, 0) 2-12
figurecaption ( 1, 3) 5-6
literal ( 1, 3) 5-6
paragraph ( 2, 0) 8-8
literal ( 2, 0) 8-8
", "figures");
}
[Test]
public void TestFiguresCaption2()
{
// 01 2345 67 89ABCD
Check("0\n^^^\n0\n^^^ab\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
figure ( 1, 0) 2-12
paragraph ( 2, 0) 6-6
literal ( 2, 0) 6-6
figurecaption ( 3, 3) 11-12
literal ( 3, 3) 11-12
", "figures");
}
[Test]
public void TestFooters()
{
// 01 234567 89ABCD
Check("0\n^^ 12\n^^ 34\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
footer ( 1, 0) 2-12
paragraph ( 1, 3) 5-12
literal ( 1, 3) 5-6
linebreak ( 1, 5) 7-7
literal ( 2, 3) 11-12
", "footers");
}
[Test]
public void TestAttributes()
{
// 0123456789
Check("0123{#456}", @"
paragraph ( 0, 0) 0-9
attributes ( 0, 4) 4-9
literal ( 0, 0) 0-3
", "attributes");
}
[Test]
public void TestAttributesForHeading()
{
// 0123456789ABC
Check("# 01 {#456}", @"
heading ( 0, 0) 0-4
attributes ( 0, 5) 5-10
literal ( 0, 2) 2-3
", "attributes");
}
[Test]
public void TestMathematicsInline()
{
// 01 23456789AB
Check("0\n012 $abcd$", @"
paragraph ( 0, 0) 0-11
literal ( 0, 0) 0-0
linebreak ( 0, 1) 1-1
literal ( 1, 0) 2-5
math ( 1, 4) 6-11
attributes ( 0, 0) 0--1
", "mathematics");
}
[Test]
public void TestSmartyPants()
{
// 01234567
// 01 23456789
Check("0\n2 <<45>>", @"
paragraph ( 0, 0) 0-9
literal ( 0, 0) 0-0
linebreak ( 0, 1) 1-1
literal ( 1, 0) 2-3
smartypant ( 1, 2) 4-5
literal ( 1, 4) 6-7
smartypant ( 1, 6) 8-9
", "smartypants");
}
[Test]
public void TestSmartyPantsUnbalanced()
{
// 012345
// 01 234567
Check("0\n2 <<45", @"
paragraph ( 0, 0) 0-7
literal ( 0, 0) 0-0
linebreak ( 0, 1) 1-1
literal ( 1, 0) 2-3
literal ( 1, 2) 4-5
literal ( 1, 4) 6-7
", "smartypants");
}
[Test]
public void TestPipeTable()
{
// 0123 4567 89AB
Check("a|b\n-|-\n0|1\n", @"
table ( 0, 0) 0-10
tablerow ( 0, 0) 0-2
tablecell ( 0, 0) 0-0
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
tablecell ( 0, 2) 2-2
paragraph ( 0, 2) 2-2
literal ( 0, 2) 2-2
tablerow ( 2, 0) 8-10
tablecell ( 2, 0) 8-8
paragraph ( 2, 0) 8-8
literal ( 2, 0) 8-8
tablecell ( 2, 2) 10-10
paragraph ( 2, 2) 10-10
literal ( 2, 2) 10-10
", "pipetables");
}
[Test]
public void TestPipeTable2()
{
// 01 2 3456 789A BCD
Check("0\n\na|b\n-|-\n0|1\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
table ( 2, 0) 3-13
tablerow ( 2, 0) 3-5
tablecell ( 2, 0) 3-3
paragraph ( 2, 0) 3-3
literal ( 2, 0) 3-3
tablecell ( 2, 2) 5-5
paragraph ( 2, 2) 5-5
literal ( 2, 2) 5-5
tablerow ( 4, 0) 11-13
tablecell ( 4, 0) 11-11
paragraph ( 4, 0) 11-11
literal ( 4, 0) 11-11
tablecell ( 4, 2) 13-13
paragraph ( 4, 2) 13-13
literal ( 4, 2) 13-13
", "pipetables");
}
[Test]
public void TestIndentedCode()
{
// 01 2 345678 9ABCDE
Check("0\n\n 0\n 1\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
code ( 2, 0) 3-13
");
}
[Test]
public void TestIndentedCodeAfterList()
{
// 0 1 2 3 4 5
// 012345678901234567 8 901234567890123456 789012345678901234 56789
Check("1) Some list item\n\n some code\n more code\n", @"
list ( 0, 0) 0-53
listitem ( 0, 0) 0-53
paragraph ( 0, 3) 3-16
literal ( 0, 3) 3-16
code ( 2, 0) 19-53
");
}
[Test]
public void TestIndentedCodeWithTabs()
{
// 01 2 3 45 6 78
Check("0\n\n\t0\n\t1\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
code ( 2, 0) 3-7
");
}
[Test]
public void TestIndentedCodeWithMixedTabs()
{
// 01 2 34 56 78 9
Check("0\n\n \t0\n \t1\n", @"
paragraph ( 0, 0) 0-0
literal ( 0, 0) 0-0
code ( 2, 0) 3-9
");
}
[Test]
public void TestTabsInList()
{
// 012 34 567 89
Check("- \t0\n- \t1\n", @"
list ( 0, 0) 0-8
listitem ( 0, 0) 0-3
paragraph ( 0, 4) 3-3
literal ( 0, 4) 3-3
listitem ( 1, 0) 5-8
paragraph ( 1, 4) 8-8
literal ( 1, 4) 8-8
");
}
[Test]
public void TestDocument()
{
// L0 L0 L1L2 L3 L4 L5L6 L7L8
// 0 10 20 30 40 50 60 70 80 90
// 012345678901234567890 1 2345678901 2345678901 2345678901 2 345678901234567890123 4 5678901234567890123
Check("# This is a document\n\n1) item 1\n2) item 2\n3) item 4\n\nWith an **emphasis**\n\n> and a blockquote\n", @"
heading ( 0, 0) 0-19
literal ( 0, 2) 2-19
list ( 2, 0) 22-51
listitem ( 2, 0) 22-30
paragraph ( 2, 3) 25-30
literal ( 2, 3) 25-30
listitem ( 3, 0) 32-40
paragraph ( 3, 3) 35-40
literal ( 3, 3) 35-40
listitem ( 4, 0) 42-51
paragraph ( 4, 3) 45-50
literal ( 4, 3) 45-50
paragraph ( 6, 0) 53-72
literal ( 6, 0) 53-60
emphasis ( 6, 8) 61-72
literal ( 6,10) 63-70
quote ( 8, 0) 75-92
paragraph ( 8, 2) 77-92
literal ( 8, 2) 77-92
");
}
private static void Check(string text, string expectedResult, string extensions = null)
{
var pipelineBuilder = new MarkdownPipelineBuilder().UsePreciseSourceLocation();
if (extensions != null)
{
pipelineBuilder.Configure(extensions);
}
var pipeline = pipelineBuilder.Build();
var document = Markdown.Parse(text, pipeline);
var build = new StringBuilder();
foreach (var val in document.Descendants())
{
var name = GetTypeName(val.GetType());
build.Append($"{name,-12} ({val.Line,2},{val.Column,2}) {val.Span.Start,2}-{val.Span.End}\n");
var attributes = val.TryGetAttributes();
if (attributes != null)
{
build.Append($"{"attributes",-12} ({attributes.Line,2},{attributes.Column,2}) {attributes.Span.Start,2}-{attributes.Span.End}\n");
}
}
var result = build.ToString().Trim();
expectedResult = expectedResult.Trim();
expectedResult = expectedResult.Replace("\r\n", "\n").Replace("\r", "\n");
if (expectedResult != result)
{
Console.WriteLine("```````````````````Source");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(text));
Console.WriteLine("```````````````````Result");
Console.WriteLine(result);
Console.WriteLine("```````````````````Expected");
Console.WriteLine(expectedResult);
Console.WriteLine("```````````````````");
Console.WriteLine();
}
TextAssert.AreEqual(expectedResult, result);
}
private static string GetTypeName(Type type)
{
return type.Name.ToLowerInvariant()
.Replace("block", string.Empty)
.Replace("inline", string.Empty);
}
}
}

View File

@@ -18,6 +18,7 @@
}
},
"dependencies": {
"NUnit": "3.2.0"
"NUnit": "3.2.0",
"NUnit3TestAdapter": "3.2.0"
}
}

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Markdig.WebApp</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Markdig.WebApp</PackageId>
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<None Update="Views">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
</ItemGroup>
</Project>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>3cad9801-9976-46be-baca-f6d0d21fdc00</ProjectGuid>
<RootNamespace>Markdig.WebApp</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -1,58 +0,0 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0-rc2-3002702",
"type": "platform"
},
"Microsoft.ApplicationInsights.AspNetCore": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
"Microsoft.Extensions.Logging": "1.0.0-rc2-final",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
"Markdig": "0.2.1"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
"version": "1.0.0-preview1-final",
"imports": "portable-net45+win8+dnxcore50"
}
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"gcServer": true
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"appsettings.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

View File

@@ -33,5 +33,10 @@ namespace Markdig.Extensions.Abbreviations
/// The text associated to this label.
/// </summary>
public StringSlice Text;
/// <summary>
/// The label span
/// </summary>
public SourceSpan LabelSpan;
}
}

View File

@@ -17,7 +17,7 @@ namespace Markdig.Extensions.Abbreviations
pipeline.BlockParsers.AddIfNotAlready<AbbreviationParser>();
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())

View File

@@ -4,6 +4,7 @@
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Abbreviations
@@ -19,7 +20,7 @@ namespace Markdig.Extensions.Abbreviations
/// </summary>
public AbbreviationParser()
{
OpeningCharacters = new[] {'*'};
OpeningCharacters = new[] { '*' };
}
public override BlockState TryOpen(BlockProcessor processor)
@@ -31,14 +32,16 @@ namespace Markdig.Extensions.Abbreviations
// A link must be of the form *[Some Text]: An abbreviation
var slice = processor.Line;
var startPosition = slice.Start;
var c = slice.NextChar();
if (c != '[')
{
return BlockState.None;
}
SourceSpan labelSpan;
string label;
if (!LinkHelper.TryParseLabel(ref slice, out label))
if (!LinkHelper.TryParseLabel(ref slice, out label, out labelSpan))
{
return BlockState.None;
}
@@ -55,7 +58,11 @@ namespace Markdig.Extensions.Abbreviations
var abbr = new Abbreviation(this)
{
Label = label,
Text = slice, Line = processor.LineIndex, Column = processor.Column
Text = slice,
Span = new SourceSpan(startPosition, slice.End),
Line = processor.LineIndex,
Column = processor.Column,
LabelSpan = labelSpan,
};
if (!processor.Document.HasAbbreviations())
{
@@ -83,7 +90,8 @@ 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;
@@ -93,20 +101,9 @@ namespace Markdig.Extensions.Abbreviations
for (int i = content.Start; i < content.End; i++)
{
string match;
if (matcher.TryMatch(text, i, content.End - i + 1, out match))
if (matcher.TryMatch(text, i, content.End - i + 1, out match) && IsValidAbbreviation(match, content, i))
{
// The word matched must be embraced by punctuation or whitespace or \0.
var c = content.PeekCharExtra(i - 1);
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
{
continue;
}
var indexAfterMatch = i + match.Length;
c = content.PeekCharExtra(indexAfterMatch);
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
{
continue;
}
// We should have a match, but in case...
Abbreviation abbr;
@@ -118,19 +115,43 @@ namespace Markdig.Extensions.Abbreviations
// If we don't have a container, create a new one
if (container == null)
{
container = new ContainerInline();
container = literal.Parent ??
new ContainerInline
{
Span = originalLiteral.Span,
Line = originalLiteral.Line,
Column = originalLiteral.Column,
};
}
var abbrInline = new AbbreviationInline(abbr);
int line;
int column;
var abbrInline = new AbbreviationInline(abbr)
{
Span =
{
Start = processor.GetSourcePosition(i, out line, out column),
},
Line = line,
Column = column
};
abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1;
// Append the previous literal
if (i > content.Start)
{
container.AppendChild(literal);
// Truncate it before the abbreviation
literal.Content.End = i - 1;
if (literal.Parent == null)
{
container.AppendChild(literal);
}
}
literal.Span.End = abbrInline.Span.Start - 1;
// Truncate it before the abbreviation
literal.Content.End = i - 1;
// Appned the abbreviation
container.AppendChild(abbrInline);
@@ -143,7 +164,12 @@ namespace Markdig.Extensions.Abbreviations
}
// Process the remaining literal
literal = new LiteralInline();
literal = new LiteralInline()
{
Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End),
Line = line,
Column = column + match.Length,
};
content.Start = indexAfterMatch;
literal.Content = content;
@@ -153,14 +179,63 @@ namespace Markdig.Extensions.Abbreviations
if (container != null)
{
processor.Inline = container;
// If we have a pending literal, we can add it
if (literal != null)
{
container.AppendChild(literal);
}
processor.Inline = container;
}
};
}
private static bool IsValidAbbreviation(string match, StringSlice content, int matchIndex)
{
// The word matched must be embraced by punctuation or whitespace or \0.
var index = matchIndex - 1;
while (index >= content.Start)
{
var c = content.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
{
return false;
}
if (c.IsAlphaNumeric())
{
return false;
}
if (!c.IsAsciiPunctuation() || c.IsWhitespace())
{
break;
}
index--;
}
// This will check if the next char at the end of the StringSlice is whitespace, punctuation or \0.
var contentNew = content;
contentNew.End = content.End + 1;
index = matchIndex + match.Length;
while (index <= contentNew.End)
{
var c = contentNew.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
{
return false;
}
if (c.IsAlphaNumeric())
{
return false;
}
if (c.IsWhitespace())
{
break;
}
index++;
}
return true;
}
}
}

View File

@@ -50,7 +50,7 @@ namespace Markdig.Extensions.AutoIdentifiers
headingBlockParser.Closed -= HeadingBlockParser_Closed;
headingBlockParser.Closed += HeadingBlockParser_Closed;
}
var paragraphBlockParser = pipeline.BlockParsers.Find<ParagraphBlockParser>();
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
if (paragraphBlockParser != null)
{
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
@@ -59,7 +59,7 @@ namespace Markdig.Extensions.AutoIdentifiers
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
@@ -132,6 +132,13 @@ namespace Markdig.Extensions.AutoIdentifiers
return;
}
// If id is already set, don't try to modify it
var attributes = processor.Block.GetAttributes();
if (attributes.Id != null)
{
return;
}
// Use a HtmlRenderer with
stripRenderer.Render(headingBlock.Inline);
var headingText = headingWriter.ToString();
@@ -152,7 +159,7 @@ namespace Markdig.Extensions.AutoIdentifiers
headingBuffer.Length = 0;
}
processor.Block.GetAttributes().Id = headingId;
attributes.Id = headingId;
}
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks
{
/// <summary>
/// Extension to automatically create <see cref="LinkInline"/> when a link url http: or mailto: is found.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class AutoLinkExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<AutoLinkParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new AutoLinkParser());
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
}
}

View File

@@ -0,0 +1,239 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks
{
/// <summary>
/// The inline parser used to for autolinks.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
public class AutoLinkParser : InlineParser
{
/// <summary>
/// Initializes a new instance of the <see cref="AutoLinkParser"/> class.
/// </summary>
public AutoLinkParser()
{
OpeningCharacters = new char[]
{
'h', // for http:// and https://
'f', // for ftp://
'm', // for mailto:
'w', // for www.
};
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Previous char must be a whitespace or a punctuation
var previousChar = slice.PeekCharExtra(-1);
if (!previousChar.IsAsciiPunctuation() && !previousChar.IsWhiteSpaceOrZero())
{
return false;
}
List<char> pendingEmphasis;
// Check that an autolink is possible in the current context
if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
{
return false;
}
var startPosition = slice.Start;
var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (!slice.MatchLowercase("ttp://", 1) && !slice.MatchLowercase("ttps://", 1))
{
return false;
}
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1) || previousChar == '/') // We won't match http:/www. or /www.xxx
{
return false;
}
break;
}
// Parse URL
string link;
if (!LinkHelper.TryParseUrl(ref slice, out link))
{
return false;
}
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
if (pendingEmphasis != null)
{
for (int i = link.Length - 1; i >= 0; i--)
{
if (pendingEmphasis.Contains(link[i]))
{
slice.Start--;
}
else
{
if (i < link.Length - 1)
{
link = link.Substring(0, i + 1);
}
break;
}
}
}
// Post-check URL
switch (c)
{
case 'h':
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'f':
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'm':
if (string.Equals(link, "mailto:", StringComparison.OrdinalIgnoreCase) || !link.Contains("@"))
{
return false;
}
break;
case 'w':
// We require at least two .
if (link.Length <= "www.x.y".Length || link.IndexOf(".", 4, StringComparison.Ordinal) < 0)
{
return false;
}
break;
}
int line;
int column;
var inline = new LinkInline()
{
Span =
{
Start = processor.GetSourcePosition(startPosition, out line, out column),
},
Line = line,
Column = column,
Url = c == 'w' ? "http://" + link : link,
IsClosed = true,
};
inline.Span.End = inline.Span.Start + link.Length - 1;
inline.UrlSpan = inline.Span;
inline.AppendChild(new LiteralInline()
{
Span = inline.Span,
Line = line,
Column = column,
Content = new StringSlice(slice.Text, startPosition, startPosition + link.Length - 1),
IsClosed = true
});
processor.Inline = inline;
return true;
}
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, out List<char> pendingEmphasis)
{
pendingEmphasis = null;
// Case where there is a pending HtmlInline <a>
var currentInline = processor.Inline;
while (currentInline != null)
{
var htmlInline = currentInline as HtmlInline;
if (htmlInline != null)
{
// If we have a </a> we don't expect nested <a>
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
{
break;
}
// If there is a pending <a>, we can't allow a link
if (htmlInline.Tag.StartsWith("<a", StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// Check previous sibling and parents in the tree
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
}
// Check that we don't have any pending brackets opened (where we could have a possible markdown link)
// NOTE: This assume that [ and ] are used for links, otherwise autolink will not work properly
currentInline = processor.Inline;
int countBrackets = 0;
while (currentInline != null)
{
var linkDelimiterInline = currentInline as LinkDelimiterInline;
if (linkDelimiterInline != null && linkDelimiterInline.IsActive)
{
if (linkDelimiterInline.Type == DelimiterType.Open)
{
countBrackets++;
}
else if (linkDelimiterInline.Type == DelimiterType.Close)
{
countBrackets--;
}
}
else
{
// Record all pending characters for emphasis
var emphasisDelimiter = currentInline as EmphasisDelimiterInline;
if (emphasisDelimiter != null)
{
if (pendingEmphasis == null)
{
// Not optimized for GC, but we don't expect this case much
pendingEmphasis = new List<char>();
}
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
{
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
}
}
}
currentInline = currentInline.Parent;
}
return countBrackets <= 0;
}
}
}

View File

@@ -22,7 +22,7 @@ namespace Markdig.Extensions.Bootstrap
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}

View File

@@ -7,13 +7,13 @@ using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Cites
namespace Markdig.Extensions.Citations
{
/// <summary>
/// Extension for cite ""...""
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class CiteExtension : IMarkdownExtension
public class CitationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
@@ -24,7 +24,7 @@ namespace Markdig.Extensions.Cites
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -38,7 +38,7 @@ namespace Markdig.Extensions.CustomContainers
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -20,7 +20,7 @@ namespace Markdig.Extensions.DefinitionLists
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -30,6 +30,8 @@ namespace Markdig.Extensions.DefinitionLists
return BlockState.None;
}
var startPosition = processor.Start;
var column = processor.ColumnBeforeIndent;
processor.NextChar();
processor.ParseIndent();
@@ -49,8 +51,7 @@ namespace Markdig.Extensions.DefinitionLists
}
var previousParent = paragraphBlock.Parent;
var indexOfParagraph = previousParent.IndexOf(paragraphBlock);
var currentDefinitionList = indexOfParagraph - 1 >= 0 ? previousParent[indexOfParagraph - 1] as DefinitionList : null;
var currentDefinitionList = GetCurrentDefinitionList(paragraphBlock, previousParent);
processor.Discard(paragraphBlock);
@@ -62,16 +63,22 @@ namespace Markdig.Extensions.DefinitionLists
if (currentDefinitionList == null)
{
currentDefinitionList = new DefinitionList(this);
currentDefinitionList = new DefinitionList(this)
{
Span = new SourceSpan(paragraphBlock.Span.Start, processor.Line.End),
Column = paragraphBlock.Column,
Line = paragraphBlock.Line,
};
previousParent.Add(currentDefinitionList);
}
var definitionItem = new DefinitionItem(this)
{
Column = processor.Column,
Line = processor.LineIndex,
Column = column,
Span = new SourceSpan(startPosition, processor.Line.End),
OpeningCharacter = processor.CurrentChar,
};
currentDefinitionList.Add(definitionItem);
for (int i = 0; i < paragraphBlock.Lines.Count; i++)
{
@@ -80,17 +87,34 @@ namespace Markdig.Extensions.DefinitionLists
{
Column = paragraphBlock.Column,
Line = line.Line,
Span = new SourceSpan(paragraphBlock.Span.Start, paragraphBlock.Span.End),
IsOpen = false
};
term.AppendLine(ref line.Slice, line.Column, line.Line);
term.AppendLine(ref line.Slice, line.Column, line.Line, line.Position);
definitionItem.Add(term);
}
currentDefinitionList.Add(definitionItem);
processor.Open(definitionItem);
// Update the end position
currentDefinitionList.UpdateSpanEnd(processor.Line.End);
return BlockState.Continue;
}
private static DefinitionList GetCurrentDefinitionList(ParagraphBlock paragraphBlock, ContainerBlock previousParent)
{
var index = previousParent.IndexOf(paragraphBlock) - 1;
if (index < 0) return null;
var lastBlock = previousParent[index];
if (lastBlock is BlankLineBlock)
{
lastBlock = previousParent[index - 1];
previousParent.RemoveAt(index);
}
return lastBlock as DefinitionList;
}
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var definitionItem = (DefinitionItem)block;
@@ -100,11 +124,13 @@ namespace Markdig.Extensions.DefinitionLists
return BlockState.Continue;
}
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)
{
var startPosition = processor.Start;
var column = processor.ColumnBeforeIndent;
processor.NextChar();
processor.ParseIndent();
@@ -118,6 +144,8 @@ namespace Markdig.Extensions.DefinitionLists
{
definitionItem.RemoveAt(definitionItem.Count - 1);
}
list.Span.End = list.LastChild.Span.End;
return BlockState.None;
}
@@ -126,10 +154,11 @@ namespace Markdig.Extensions.DefinitionLists
processor.GoToColumn(column + 4);
}
var list = (DefinitionList) definitionItem.Parent;
processor.Close(definitionItem);
var nextDefinitionItem = new DefinitionItem(this)
{
Span = new SourceSpan(startPosition, processor.Line.End),
Line = processor.LineIndex,
Column = processor.Column,
OpeningCharacter = processor.CurrentChar,
};
@@ -161,6 +190,7 @@ namespace Markdig.Extensions.DefinitionLists
definitionItem.RemoveAt(definitionItem.Count - 1);
}
list.Span.End = list.LastChild.Span.End;
return BlockState.Break;
}
}

View File

@@ -49,7 +49,7 @@ namespace Markdig.Extensions.DefinitionLists
{
if (!hasOpendd)
{
renderer.Write("<dd>");
renderer.Write("<dd").WriteAttributes(definitionItem).Write(">");
countdd = 0;
hasOpendd = true;
}

View File

@@ -0,0 +1,31 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html;
namespace Markdig.Extensions.Diagrams
{
/// <summary>
/// Extension to allow diagrams.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class DiagramExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
// TODO: Add other well known diagram languages
codeRenderer.BlocksAsDiv.Add("mermaid");
}
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Markdig.Extensions.Emoji
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
}

View File

@@ -41,7 +41,7 @@ namespace Markdig.Extensions.Emoji
/// </summary>
public Dictionary<string, string> SmileyToEmoji { get; }
public override void Initialize(InlineProcessor processor)
public override void Initialize()
{
var firstChars = new HashSet<char>();
var textToMatch = new HashSet<string>();
@@ -67,17 +67,28 @@ namespace Markdig.Extensions.Emoji
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
string match;
// Previous char must be a space
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
{
return false;
}
// Try to match an existing emoji
var startPosition = slice.Start;
if (!textMatchHelper.TryMatch(slice.Text, slice.Start, slice.Length, out match))
{
return false;
}
// If we have a smiley, we decode it to emoji
string emoji;
if (!SmileyToEmoji.TryGetValue(match, out emoji))
{
emoji = match;
}
// Decode the eomji to unicode
string unicode;
if (!EmojiToUnicode.TryGetValue(emoji, out unicode))
{
@@ -89,7 +100,19 @@ namespace Markdig.Extensions.Emoji
slice.Start += match.Length;
// Push the EmojiInline
processor.Inline = new EmojiInline(unicode) {Match = match};
int line;
int column;
processor.Inline = new EmojiInline(unicode)
{
Span =
{
Start = processor.GetSourcePosition(startPosition, out line, out column),
},
Line = line,
Column = column,
Match = match
};
processor.Inline.Span.End = processor.Inline.Span.Start + match.Length - 1;
return true;
}
@@ -968,7 +991,16 @@ namespace Markdig.Extensions.Emoji
{":large_orange_diamond:", "🔶"},
{":large_blue_diamond:", "🔷"},
{":small_orange_diamond:", "🔸"},
{":small_blue_diamond:", "🔹"}
{":small_blue_diamond:", "🔹"},
// Custom additions
{ ":custom_arrow_left:", "←"},
{ ":custom_arrow_right:", "→"},
{ ":custom_arrow_left_right:", "↔"},
{ ":custom_arrow_left_strong:", "⇐"},
{ ":custom_arrow_right_strong:", "⇒"},
{ ":custom_arrow_left_right_strong:", "⇔"},
};
SmileyToEmojiDefault = new Dictionary<string, string>()
@@ -1038,6 +1070,15 @@ namespace Markdig.Extensions.Emoji
{":-$", ":unamused:"},
{";)", ":wink:"},
{";-)", ":wink:"},
// Custom arrows
{"<-", ":custom_arrow_left:" },
{"->", ":custom_arrow_rigth:" },
{"<->", ":custom_arrow_left_rigth:" },
{"<=", ":custom_arrow_left_strong:" },
{"=>", ":custom_arrow_rigth_strong:" },
{"<=>", ":custom_arrow_left_rigth_strong:" },
};
}
#endregion

View File

@@ -7,7 +7,7 @@ using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.EmphasisExtra
namespace Markdig.Extensions.EmphasisExtras
{
/// <summary>
/// Extension for strikethrough, subscript, superscript, inserted and marked.
@@ -85,7 +85,7 @@ namespace Markdig.Extensions.EmphasisExtra
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -4,7 +4,7 @@
using System;
namespace Markdig.Extensions.EmphasisExtra
namespace Markdig.Extensions.EmphasisExtras
{
/// <summary>
/// Options for enabling support for extra emphasis.

View File

@@ -30,7 +30,9 @@ namespace Markdig.Extensions.Figures
// Match fenced char
int count = 0;
var column = processor.Column;
var line = processor.Line;
var startPosition = line.Start;
char c = line.CurrentChar;
var matchChar = c;
while (c != '\0')
@@ -51,6 +53,8 @@ namespace Markdig.Extensions.Figures
var figure = new Figure(this)
{
Span = new SourceSpan(startPosition, line.End),
Line = processor.LineIndex,
Column = processor.Column,
OpeningCharacter = matchChar,
OpeningCharacterCount = count
@@ -59,8 +63,14 @@ namespace Markdig.Extensions.Figures
line.TrimStart();
if (!line.IsEmpty)
{
var caption = new FigureCaption(this) {IsOpen = false};
caption.AppendLine(ref line, line.Start, processor.LineIndex);
var caption = new FigureCaption(this)
{
Span = new SourceSpan(line.Start, line.End),
Line = processor.LineIndex,
Column = column + line.Start - startPosition,
IsOpen = false
};
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
figure.Add(caption);
}
processor.NewBlocks.Push(figure);
@@ -76,8 +86,10 @@ namespace Markdig.Extensions.Figures
var matchChar = figure.OpeningCharacter;
var c = processor.CurrentChar;
var column = processor.Column;
// Match if we have a closing fence
var line = processor.Line;
var startPosition = line.Start;
while (c == matchChar)
{
c = line.NextChar();
@@ -91,11 +103,19 @@ namespace Markdig.Extensions.Figures
line.TrimStart();
if (!line.IsEmpty)
{
var caption = new FigureCaption(this) {IsOpen = false};
caption.AppendLine(ref line, line.Start, processor.LineIndex);
var caption = new FigureCaption(this)
{
Span = new SourceSpan(line.Start, line.End),
Line = processor.LineIndex,
Column = column + line.Start - startPosition,
IsOpen = false
};
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
figure.Add(caption);
}
figure.UpdateSpanEnd(line.End);
// Don't keep the last line
return BlockState.BreakDiscard;
}
@@ -103,6 +123,8 @@ namespace Markdig.Extensions.Figures
// Reset the indentation to the column before the indent
processor.GoToColumn(processor.ColumnBeforeIndent);
figure.UpdateSpanEnd(line.End);
return BlockState.Continue;
}
}

View File

@@ -2,7 +2,6 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Extensions.Cites;
using Markdig.Extensions.Footers;
using Markdig.Renderers;
@@ -30,7 +29,7 @@ namespace Markdig.Extensions.Figures
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -30,6 +30,7 @@ namespace Markdig.Extensions.Footers
}
var column = processor.Column;
var startPosition = processor.Start;
// A footer
// A Footer marker consists of 0-3 spaces of initial indent, plus (a) the characters ^^ together with a following space, or (b) a double character ^^ not followed by a space.
@@ -44,7 +45,13 @@ namespace Markdig.Extensions.Footers
{
processor.NextColumn();
}
processor.NewBlocks.Push(new FooterBlock(this) { OpeningCharacter = openingChar, Column = column});
processor.NewBlocks.Push(new FooterBlock(this)
{
Span = new SourceSpan(startPosition, processor.Line.End),
OpeningCharacter = openingChar,
Column = column,
Line = processor.LineIndex,
});
return BlockState.Continue;
}
@@ -60,19 +67,22 @@ namespace Markdig.Extensions.Footers
// A footer
// A Footer marker consists of 0-3 spaces of initial indent, plus (a) the characters ^^ together with a following space, or (b) a double character ^^ not followed by a space.
var c = processor.CurrentChar;
var result = BlockState.Continue;
if (c != quote.OpeningCharacter || processor.PeekChar(1) != c)
{
return processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
result = processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
}
processor.NextChar(); // Skip ^^ char (1st)
c = processor.NextChar(); // Skip ^^ char (2nd)
if (c.IsSpace())
else
{
processor.NextChar(); // Skip following space
processor.NextChar(); // Skip ^^ char (1st)
c = processor.NextChar(); // Skip ^^ char (2nd)
if (c.IsSpace())
{
processor.NextChar(); // Skip following space
}
block.UpdateSpanEnd(processor.Line.End);
}
return BlockState.Continue;
return result;
}
}
}

View File

@@ -29,7 +29,7 @@ namespace Markdig.Extensions.Footers
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -34,6 +34,11 @@ namespace Markdig.Extensions.Footnotes
/// </summary>
public List<FootnoteLink> Links { get; private set; }
/// <summary>
/// The label span
/// </summary>
public SourceSpan LabelSpan;
internal bool IsLastLineEmpty { get; set; }
}
}

View File

@@ -20,7 +20,7 @@ namespace Markdig.Extensions.Footnotes
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -26,9 +26,14 @@ namespace Markdig.Extensions.Footnotes
}
public override BlockState TryOpen(BlockProcessor processor)
{
return TryOpen(processor, false);
}
private BlockState TryOpen(BlockProcessor processor, bool isContinue)
{
// We expect footnote to appear only at document level and not indented more than a code indent block
if (processor.IsCodeIndent || processor.CurrentContainer.GetType() != typeof(MarkdownDocument) )
if (processor.IsCodeIndent || (!isContinue && processor.CurrentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(processor.CurrentContainer is Footnote)))
{
return BlockState.None;
}
@@ -36,19 +41,24 @@ namespace Markdig.Extensions.Footnotes
var saved = processor.Column;
string label;
int start = processor.Start;
if (!LinkHelper.TryParseLabel(ref processor.Line, false, out label) || !label.StartsWith("^") || processor.CurrentChar != ':')
SourceSpan labelSpan;
if (!LinkHelper.TryParseLabel(ref processor.Line, false, out label, out labelSpan) || !label.StartsWith("^") || processor.CurrentChar != ':')
{
processor.GoToColumn(saved);
return BlockState.None;
}
// Advance the column
int deltaColumn = processor.Start - start;
processor.Column = processor.Column + deltaColumn;
processor.NextChar(); // Skip ':'
var footnote = new Footnote(this) {Label = label};
var footnote = new Footnote(this)
{
Label = label,
LabelSpan = labelSpan,
};
// Maintain a list of all footnotes at document level
var footnotes = processor.Document.GetData(DocumentKey) as FootnoteGroup;
@@ -83,9 +93,23 @@ namespace Markdig.Extensions.Footnotes
return BlockState.ContinueDiscard;
}
if (footnote.IsLastLineEmpty && processor.Start == 0)
if (processor.Column == 0)
{
return BlockState.Break;
if (footnote.IsLastLineEmpty)
{
// Close the current footnote
processor.Close(footnote);
// Parse any opening footnote
return TryOpen(processor);
}
// Make sure that consecutive footnotes without a blanklines are parsed correctly
if (TryOpen(processor, true) == BlockState.Continue)
{
processor.Close(footnote);
return BlockState.Continue;
}
}
}
footnote.IsLastLineEmpty = false;

View File

@@ -37,7 +37,7 @@ namespace Markdig.Extensions.GenericAttributes
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
@@ -46,17 +46,25 @@ namespace Markdig.Extensions.GenericAttributes
// Try to find if there is any attributes { in the info string on the first line of a FencedCodeBlock
if (line.Start < line.End)
{
var indexOfAttributes = line.Text.LastIndexOf('{', line.End);
int indexOfAttributes = line.IndexOf('{');
if (indexOfAttributes >= 0)
{
// Work on a copy
var copy = line;
copy.Start = indexOfAttributes;
var startOfAttributes = copy.Start;
HtmlAttributes attributes;
if (GenericAttributesParser.TryParse(ref copy, out attributes))
{
var htmlAttributes = block.GetAttributes();
attributes.CopyTo(htmlAttributes);
// Update position for HtmlAttributes
htmlAttributes.Line = processor.LineIndex;
htmlAttributes.Column = startOfAttributes - processor.CurrentLineStartPosition; // This is not accurate with tabs!
htmlAttributes.Span.Start = startOfAttributes;
htmlAttributes.Span.End = copy.Start - 1;
line.End = indexOfAttributes - 1;
return true;
}

View File

@@ -28,6 +28,7 @@ namespace Markdig.Extensions.GenericAttributes
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
HtmlAttributes attributes;
var startPosition = slice.Start;
if (TryParse(ref slice, out attributes))
{
var inline = processor.Inline;
@@ -58,12 +59,20 @@ namespace Markdig.Extensions.GenericAttributes
{
objectToAttach = parent[indexOfParagraph + 1];
// We can remove the paragraph as it is empty
parent.RemoveAt(indexOfParagraph);
paragraph.RemoveAfterProcessInlines = true;
}
}
var currentHtmlAttributes = objectToAttach.GetAttributes();
attributes.CopyTo(currentHtmlAttributes);
attributes.CopyTo(currentHtmlAttributes, true, false);
// Update the position of the attributes
int line;
int column;
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out line, out column);
currentHtmlAttributes.Line = line;
currentHtmlAttributes.Column = column;
currentHtmlAttributes.Span.End = currentHtmlAttributes.Span.Start + slice.Start - startPosition - 1;
// We don't set the processor.Inline as we don't want to add attach attributes to a particular entity
return true;
@@ -162,9 +171,10 @@ namespace Markdig.Extensions.GenericAttributes
// Skip any whitespaces
line.TrimStart();
c = line.CurrentChar;
// Handle boolean properties that are not followed by =
if ((hasSpace && (line.CurrentChar == '.' || line.CurrentChar == '#' || IsStartAttributeName(line.CurrentChar))) || line.CurrentChar == '}')
if ((hasSpace && (c == '.' || c == '#' || IsStartAttributeName(c))) || c == '}')
{
if (properties == null)
{

View File

@@ -24,7 +24,7 @@ namespace Markdig.Extensions.Hardlines
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
}

View File

@@ -0,0 +1,31 @@
// 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.Diagnostics;
using Markdig.Helpers;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Model for a JIRA link item
/// </summary>
[DebuggerDisplay("{ProjectKey}-{Issue}")]
public class JiraLink : LinkInline
{
public JiraLink()
{
IsClosed = true;
}
/// <summary>
/// JIRA Project Key
/// </summary>
public StringSlice ProjectKey { get; set; }
/// <summary>
/// JIRA Issue Number
/// </summary>
public StringSlice Issue { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Simple inline parser extension for Markdig to find, and
/// automatically add links to JIRA issue numbers.
/// </summary>
public class JiraLinkExtension : IMarkdownExtension
{
private readonly JiraLinkOptions _options;
public JiraLinkExtension(JiraLinkOptions options)
{
_options = options;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<JiraLinkInlineParser>())
{
// Insert the parser before the link inline parser
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new JiraLinkInlineParser(_options));
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
// Nothing to setup, JiraLinks used a normal LinkInlineRenderer
}
}
}

View File

@@ -0,0 +1,115 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Text;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Finds and replaces JIRA links inline
/// </summary>
public class JiraLinkInlineParser : InlineParser
{
private readonly JiraLinkOptions _options;
private readonly string _baseUrl;
public JiraLinkInlineParser(JiraLinkOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_baseUrl = _options.GetUrl();
//look for uppercase chars at the start (for the project key)
OpeningCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Allow preceding whitespace or `(`
var pc = slice.PeekCharExtra(-1);
if (!pc.IsWhiteSpaceOrZero() && pc != '(')
{
return false;
}
var current = slice.CurrentChar;
var startKey = slice.Start;
var endKey = slice.Start;
//read as many uppercase characters as required - project key
while (current.IsAlphaUpper())
{
endKey = slice.Start;
current = slice.NextChar();
}
//require a '-' between key and issue number
if (!current.Equals('-'))
{
return false;
}
current = slice.NextChar(); // skip -
//read as many numbers as required - issue number
if (!current.IsDigit())
{
return false;
}
var startIssue = slice.Start;
var endIssue = slice.Start;
while (current.IsDigit())
{
endIssue = slice.Start;
current = slice.NextChar();
}
if (!current.IsWhiteSpaceOrZero() && current != ')') //can be followed only by a whitespace or `)`
{
return false;
}
int line;
int column;
var jiraLink = new JiraLink() //create the link at the relevant position
{
Span =
{
Start = processor.GetSourcePosition(slice.Start, out line, out column)
},
Line = line,
Column = column,
Issue = new StringSlice(slice.Text, startIssue, endIssue),
ProjectKey = new StringSlice(slice.Text, startKey, endKey),
};
jiraLink.Span.End = jiraLink.Span.Start + (endIssue - startKey);
// Builds the Url
var builder = new StringBuilder();
builder.Append(_baseUrl).Append('/').Append(jiraLink.ProjectKey).Append('-').Append(jiraLink.Issue);
jiraLink.Url = builder.ToString();
// Builds the Label
builder.Length = 0;
builder.Append(jiraLink.ProjectKey).Append('-').Append(jiraLink.Issue);
jiraLink.AppendChild(new LiteralInline(builder.ToString()));
if (_options.OpenInNewWindow)
{
jiraLink.GetAttributes().AddProperty("target", "blank");
}
processor.Inline = jiraLink;
return true;
}
}
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Text;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Available options for replacing JIRA links
/// </summary>
public class JiraLinkOptions
{
/// <summary>
/// The base Url (e.g. `https://mycompany.atlassian.net`)
/// </summary>
public string BaseUrl { get; set; }
/// <summary>
/// The base path after the base url (default is `/browse`)
/// </summary>
public string BasePath { get; set; }
/// <summary>
/// Should the link open in a new window when clicked
/// </summary>
public bool OpenInNewWindow { get; set; }
public JiraLinkOptions(string baseUrl)
{
OpenInNewWindow = true; //default
BaseUrl = baseUrl;
BasePath = "/browse";
}
/// <summary>
/// Gets the full url composed of the <see cref="BaseUrl"/> and <see cref="BasePath"/> with no trailing `/`
/// </summary>
public virtual string GetUrl()
{
var url = new StringBuilder();
var baseUrl = BaseUrl;
if (baseUrl != null)
{
url.Append(baseUrl.TrimEnd('/'));
}
url.Append("/");
if (BasePath != null)
{
url.Append(BasePath.Trim('/'));
}
return url.ToString();
}
}
}

View File

@@ -5,7 +5,7 @@
using Markdig.Parsers;
using Markdig.Renderers;
namespace Markdig.Extensions.ListExtra
namespace Markdig.Extensions.ListExtras
{
/// <summary>
/// Extension for adding new type of list items (a., A., i., I.)
@@ -22,7 +22,7 @@ namespace Markdig.Extensions.ListExtra
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
}

View File

@@ -5,8 +5,10 @@
using Markdig.Helpers;
using Markdig.Parsers;
namespace Markdig.Extensions.ListExtra
namespace Markdig.Extensions.ListExtras
{
using System;
/// <summary>
/// Parser that adds supports for parsing alpha/roman list items (e.g: `a)` or `a.` or `ii.` or `II.`)
/// </summary>
@@ -52,7 +54,7 @@ namespace Markdig.Extensions.ListExtra
c = state.NextChar();
}
result.OrderedStart = state.Line.Text.Substring(startChar, endChar - startChar + 1);
result.OrderedStart = CharHelper.RomanToArabic(state.Line.Text.Substring(startChar, endChar - startChar + 1)).ToString();
result.BulletType = isRomanLow ? 'i' : 'I';
result.DefaultOrderedStart = isRomanLow ? "i" : "I";
}
@@ -60,7 +62,7 @@ namespace Markdig.Extensions.ListExtra
{
// otherwise we expect a regular alpha lettered list with a single character.
var isUpper = c.IsAlphaUpper();
result.OrderedStart = c.ToString();
result.OrderedStart = (Char.ToUpperInvariant(c) - 64).ToString();
result.BulletType = isUpper ? 'A' : 'a';
result.DefaultOrderedStart = isUpper ? "A" : "a";
state.NextChar();

View File

@@ -1,15 +1,18 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
namespace Markdig.Extensions.Mathematics
{
/// <summary>
/// The block parser for a <see cref="MathBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.FencedBlockParserBase{Markdig.Extensions.Mathematics.MathBlock}" />
/// <seealso cref="MathBlock" />
public class MathBlockParser : FencedBlockParserBase<MathBlock>
{
/// <summary>
@@ -22,6 +25,8 @@ namespace Markdig.Extensions.Mathematics
MinimumMatchCount = 2;
MaximumMatchCount = 2;
InfoParser = NoInfoParser;
DefaultClass = "math";
// We don't need a prefix
@@ -39,5 +44,19 @@ namespace Markdig.Extensions.Mathematics
}
return block;
}
private static bool NoInfoParser(BlockProcessor state, ref StringSlice line, IFencedBlock fenced)
{
var c = line.CurrentChar;
for (int i = line.Start; i <= line.End; i++)
{
c = line.Text[i];
if (!c.IsSpaceOrTab())
{
return false;
}
}
return true;
}
}
}

View File

@@ -29,7 +29,7 @@ namespace Markdig.Extensions.Mathematics
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -5,6 +5,7 @@
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
namespace Markdig.Extensions.Mathematics
{
@@ -12,7 +13,7 @@ namespace Markdig.Extensions.Mathematics
/// An inline parser for <see cref="MathInline"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
/// <seealso cref="Markdig.Parsers.IDelimiterProcessor" />
/// <seealso cref="IPostInlineProcessor" />
public class MathInlineParser : InlineParser
{
/// <summary>
@@ -38,6 +39,8 @@ namespace Markdig.Extensions.Mathematics
return false;
}
var startPosition = slice.Start;
// Match the opened $ or $$
int openDollars = 1; // we have at least a $
var c = slice.NextChar();
@@ -47,11 +50,18 @@ namespace Markdig.Extensions.Mathematics
c = slice.NextChar();
}
bool canOpen;
bool canClose;
// Check that opening $/$$ is correct, using the same heuristics than for emphasis delimiters
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
if (!canOpen)
bool openPrevIsPunctuation;
bool openPrevIsWhiteSpace;
bool openNextIsPunctuation;
bool openNextIsWhiteSpace;
bool openNextIsDigit = c.IsDigit();
pc.CheckUnicodeCategory(out openPrevIsWhiteSpace, out openPrevIsPunctuation);
c.CheckUnicodeCategory(out openNextIsWhiteSpace, out openNextIsPunctuation);
// Check that opening $/$$ is correct, using the different heuristics than for emphasis delimiters
// If a $/$$ is not preceded by a whitespace or punctuation, or followed by a digit
// this is a not a math block
if ((!openPrevIsWhiteSpace && !openPrevIsPunctuation) || openNextIsDigit)
{
return false;
}
@@ -59,31 +69,60 @@ namespace Markdig.Extensions.Mathematics
bool isMatching = false;
int closeDollars = 0;
// Eat any leading spaces
while (c.IsSpaceOrTab())
{
c = slice.NextChar();
}
var start = slice.Start;
var end = 0;
pc = match;
var lastWhiteSpace = -1;
while (c != '\0')
{
// Count new '\n'
if (c == '\n')
// Don't allow newline in an inline math expression
if (c == '\r' || c == '\n')
{
processor.LocalLineIndex++;
processor.LineIndex++;
return false;
}
// Don't process sticks if we have a '\' as a previous char
if (pc != '\\' )
{
while (c == match)
// Record continous whitespaces at the end
if (c.IsSpaceOrTab())
{
closeDollars++;
c = slice.NextChar();
if (lastWhiteSpace < 0)
{
lastWhiteSpace = slice.Start;
}
}
else
{
bool hasClosingDollars = c == match;
if (hasClosingDollars)
{
while (c == match)
{
closeDollars++;
c = slice.NextChar();
}
}
if (closeDollars >= openDollars)
{
break;
if (closeDollars >= openDollars)
{
break;
}
lastWhiteSpace = -1;
if (hasClosingDollars)
{
pc = match;
continue;
}
}
pc = match;
}
if (closeDollars > 0)
@@ -99,23 +138,45 @@ namespace Markdig.Extensions.Mathematics
if (closeDollars >= openDollars)
{
// Check that closing $/$$ is correct
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
if (!canClose || c.IsDigit())
bool closePrevIsPunctuation;
bool closePrevIsWhiteSpace;
bool closeNextIsPunctuation;
bool closeNextIsWhiteSpace;
pc.CheckUnicodeCategory(out closePrevIsWhiteSpace, out closePrevIsPunctuation);
c.CheckUnicodeCategory(out closeNextIsWhiteSpace, out closeNextIsPunctuation);
// A closing $/$$ should be followed by at least a punctuation or a whitespace
// and if the character after an openning $/$$ was a whitespace, it should be
// a whitespace as well for the character preceding the closing of $/$$
if ((!closeNextIsPunctuation && !closeNextIsWhiteSpace) || (openNextIsWhiteSpace != closePrevIsWhiteSpace))
{
return false;
}
if (closePrevIsWhiteSpace && lastWhiteSpace > 0)
{
end = lastWhiteSpace + openDollars - 1;
}
else
{
end = slice.Start - 1;
}
// Create a new MathInline
int line;
int column;
var inline = new MathInline()
{
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.End)),
Line = line,
Column = column,
Delimiter = match,
DelimiterCount = openDollars,
Content = slice
};
inline.Content.Start = start;
// We substract the end to the number of opening $ to keep inside the block the additionals $
inline.Content.End = inline.Content.End - openDollars;
inline.Content.End = end - openDollars;
// Add the default class if necessary
if (DefaultClass != null)

View File

@@ -1,25 +1,26 @@
// 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;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Medias
namespace Markdig.Extensions.MediaLinks
{
/// <summary>
/// Extension for extending image Markdown links in case a video or an audio file is linked and output proper link.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class MediaExtension : IMarkdownExtension
public class MediaLinkExtension : IMarkdownExtension
{
public MediaExtension() : this(new MediaOptions())
public MediaLinkExtension() : this(new MediaOptions())
{
}
public MediaExtension(MediaOptions options)
public MediaLinkExtension(MediaOptions options)
{
Options = options ?? new MediaOptions();
}
@@ -30,7 +31,7 @@ namespace Markdig.Extensions.Medias
{
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
@@ -49,7 +50,8 @@ namespace Markdig.Extensions.Medias
if (linkInline.IsImage && linkInline.Url != null)
{
Uri uri;
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri))
// Only process absolute Uri
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) && uri.IsAbsoluteUri)
{
var htmlAttributes = new HtmlAttributes();
var fromAttributes = linkInline.TryGetAttributes();
@@ -113,7 +115,6 @@ namespace Markdig.Extensions.Medias
renderer.WriteAttributes(htmlAttributes);
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
renderer.Write("</iframe>");
return true;
}

View File

@@ -1,13 +1,14 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
namespace Markdig.Extensions.Medias
namespace Markdig.Extensions.MediaLinks
{
/// <summary>
/// Options for the <see cref="MediaExtension"/>.
/// Options for the <see cref="MediaLinkExtension"/>.
/// </summary>
public class MediaOptions
{

View File

@@ -0,0 +1,34 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
namespace Markdig.Extensions.NoRefLinks
{
/// <summary>
/// Extension to automatically render rel=nofollow to all links in an HTML output.
/// </summary>
public class NoFollowLinksExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var linkRenderer = renderer.ObjectRenderers.Find<LinkInlineRenderer>();
if (linkRenderer != null)
{
linkRenderer.AutoRelNoFollow = true;
}
var autolinkRenderer = renderer.ObjectRenderers.Find<AutolinkInlineRenderer>();
if (autolinkRenderer != null)
{
autolinkRenderer.AutoRelNoFollow = true;
}
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
namespace Markdig.Extensions.NonAsciiNoEscape
{
/// <summary>
/// Extension that will disable URI escape with % characters for non-US-ASCII characters in order to workaround a bug under IE/Edge with local file links containing non US-ASCII chars. DO NOT USE OTHERWISE.
/// </summary>
public class NonAsciiNoEscapeExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
htmlRenderer.UseNonAsciiNoEscape = true;
}
}
}
}

View File

@@ -0,0 +1,79 @@
// 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.Helpers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.PragmaLines
{
/// <summary>
/// Extension to a span for each line containing the original line id (using id = pragma-line#line_number_zero_based)
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class PragmaLineExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
int index = 0;
AddPragmas(document, ref index);
}
private static void AddPragmas(Block block, ref int index)
{
var attribute = block.GetAttributes();
var pragmaId = GetPragmaId(block);
if ( attribute.Id == null)
{
attribute.Id = pragmaId;
}
else if (block.Parent != null)
{
var heading = block as HeadingBlock;
// If we have a heading, we will try to add the tag inside it
// otherwise we will add it just before
var tag = $"<a id=\"{pragmaId}\"></a>";
if (heading?.Inline?.FirstChild != null)
{
heading.Inline.FirstChild.InsertBefore(new HtmlInline() { Tag = tag });
}
else
{
block.Parent.Insert(index, new HtmlBlock(null) { Lines = new StringLineGroup(tag) });
index++;
}
}
var container = block as ContainerBlock;
if (container != null)
{
for (int i = 0; i < container.Count; i++)
{
var subBlock = container[i];
AddPragmas(subBlock, ref i);
}
}
}
private static string GetPragmaId(Block block)
{
return $"pragma-line-{block.Line}";
}
}
}

View File

@@ -0,0 +1,99 @@
// 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;
namespace Markdig.Extensions.SelfPipeline
{
/// <summary>
/// Extension to enable SelfPipeline, to configure a Markdown parsing/convertion to HTML automatically
/// from an embedded special tag in the input text <code>&lt;!--markdig:extensions--&gt;</code> where extensions is a string
/// that specifies the extensions to use for the pipeline as exposed by <see cref="MarkdownExtensions.Configure"/> extension method
/// on the <see cref="MarkdownPipelineBuilder"/>. This extension will invalidate all other extensions and will override them.
/// </summary>
public sealed class SelfPipelineExtension : IMarkdownExtension
{
public const string DefaultTag = "markdig";
/// <summary>
/// Initializes a new instance of the <see cref="SelfPipelineExtension"/> class.
/// </summary>
/// <param name="tag">The matching start tag.</param>
/// <param name="defaultExtensions">The default extensions.</param>
/// <exception cref="System.ArgumentException">Tag cannot contain `<` or `>` characters</exception>
public SelfPipelineExtension(string tag = null, string defaultExtensions = null)
{
tag = tag?.Trim();
tag = string.IsNullOrEmpty(tag) ? DefaultTag : tag;
if (tag.IndexOfAny(new []{'<', '>'}) >= 0)
{
throw new ArgumentException("Tag cannot contain `<` or `>` characters", nameof(tag));
}
if (defaultExtensions != null)
{
// Check that this default pipeline is supported
// Will throw an ArgumentInvalidException if not
new MarkdownPipelineBuilder().Configure(defaultExtensions);
}
DefaultExtensions = defaultExtensions;
SelfPipelineHintTagStart = "<!--" + tag + ":";
}
/// <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; }
/// <summary>
/// Gets the self pipeline hint tag start that will be matched.
/// </summary>
public string SelfPipelineHintTagStart { get; }
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Make sure that this pipeline has only one extension (itself)
if (pipeline.Extensions.Count > 1)
{
throw new InvalidOperationException(
"The SelfPipeline extension cannot be configured with other extensions");
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
/// <summary>
/// Creates a pipeline automatically configured from an input markdown based on the presence of the configuration tag.
/// </summary>
/// <param name="inputText">The input text.</param>
/// <returns>The pipeline configured from the input</returns>
/// <exception cref="System.ArgumentNullException"></exception>
public MarkdownPipeline CreatePipelineFromInput(string inputText)
{
if (inputText == null) throw new ArgumentNullException(nameof(inputText));
var builder = new MarkdownPipelineBuilder();
string defaultConfig = DefaultExtensions;
var indexOfSelfPipeline = inputText.IndexOf(SelfPipelineHintTagStart, StringComparison.OrdinalIgnoreCase);
if (indexOfSelfPipeline >= 0)
{
var optionStart = indexOfSelfPipeline + SelfPipelineHintTagStart.Length;
var endOfTag = inputText.IndexOf("-->", optionStart, StringComparison.OrdinalIgnoreCase);
if (endOfTag >= 0)
{
defaultConfig = inputText.Substring(optionStart, endOfTag - optionStart).Trim();
}
}
if (!string.IsNullOrEmpty(defaultConfig))
{
builder.Configure(defaultConfig);
}
return builder.Build();
}
}
}

View File

@@ -28,14 +28,14 @@ namespace Markdig.Extensions.SmartyPants
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<SmaryPantsInlineParser>())
if (!pipeline.InlineParsers.Contains<SmartyPantsInlineParser>())
{
// Insert the parser after the code span parser
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(new SmaryPantsInlineParser());
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(new SmartyPantsInlineParser());
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)

View File

@@ -4,6 +4,7 @@
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.SmartyPants
@@ -11,14 +12,14 @@ namespace Markdig.Extensions.SmartyPants
/// <summary>
/// The inline parser for SmartyPants.
/// </summary>
public class SmaryPantsInlineParser : InlineParser
public class SmartyPantsInlineParser : InlineParser, IPostInlineProcessor
{
/// <summary>
/// Initializes a new instance of the <see cref="SmaryPantsInlineParser"/> class.
/// Initializes a new instance of the <see cref="SmartyPantsInlineParser"/> class.
/// </summary>
public SmaryPantsInlineParser()
public SmartyPantsInlineParser()
{
OpeningCharacters = new[] {'`', '\'', '"', '<', '>', '.', '-'};
OpeningCharacters = new[] {'\'', '"', '<', '>', '.', '-'};
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
@@ -30,6 +31,8 @@ namespace Markdig.Extensions.SmartyPants
// " “ ” &ldquo; &rdquo; 'left-double-quote', 'right-double-quote'
// << >> « » &laquo; &raquo; 'left-angle-quote', 'right-angle-quote'
// ... … &hellip; 'ellipsis'
// Special case: &ndash; and &mdash; are handle as a PostProcess step to avoid conflicts with pipetables header separator row
// -- &ndash; 'ndash'
// --- — &mdash; 'mdash'
@@ -37,18 +40,13 @@ namespace Markdig.Extensions.SmartyPants
var c = slice.CurrentChar;
var openingChar = c;
var startingPosition = slice.Start;
// undefined first
var type = (SmartyPantType) 0;
switch (c)
{
case '`':
if (slice.PeekChar(1) == '`')
{
slice.NextChar();
type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines
}
break;
case '\'':
type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines
if (slice.PeekChar(1) == '\'')
@@ -81,12 +79,9 @@ namespace Markdig.Extensions.SmartyPants
case '-':
if (slice.NextChar() == '-')
{
type = SmartyPantType.Dash2;
if (slice.PeekChar(1) == '-')
{
slice.NextChar();
type = SmartyPantType.Dash3;
}
var quotePants = GetOrCreateState(processor);
quotePants.HasDash = true;
return false;
}
break;
}
@@ -161,20 +156,23 @@ namespace Markdig.Extensions.SmartyPants
}
// Create the SmartyPant inline
int line;
int column;
var pant = new SmartyPant()
{
Span = {Start = processor.GetSourcePosition(startingPosition, out line, out column)},
Line = line,
Column = column,
OpeningCharacter = openingChar,
Type = type
};
pant.Span.End = pant.Span.Start + slice.Start - startingPosition - 1;
// We will check in a post-process step for balanaced open/close quotes
if (postProcess)
{
var quotePants = processor.ParserStates[Index] as List<SmartyPant>;
if (quotePants == null)
{
processor.ParserStates[Index] = quotePants = new List<SmartyPant>();
}
var quotePants = GetOrCreateState(processor);
// Register only if we don't have yet any quotes
if (quotePants.Count == 0)
{
@@ -187,11 +185,21 @@ namespace Markdig.Extensions.SmartyPants
return true;
}
private ListSmartyPants GetOrCreateState(InlineProcessor processor)
{
var quotePants = processor.ParserStates[Index] as ListSmartyPants;
if (quotePants == null)
{
processor.ParserStates[Index] = quotePants = new ListSmartyPants();
}
return quotePants;
}
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline inline)
{
processor.Block.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
var pants = (List<SmartyPant>) processor.ParserStates[Index];
var pants = (ListSmartyPants) processor.ParserStates[Index];
// We only change quote into left or right quotes if we find proper balancing
var previousIndices = new int[3] {-1, -1, -1};
@@ -241,14 +249,19 @@ namespace Markdig.Extensions.SmartyPants
{
if (quote.Type == expectedRightQuote)
{
// Replace all intermediate unmatched left or right SmartyPants to there literal equivalent
// Replace all intermediate unmatched left or right SmartyPants to their literal equivalent
pants.RemoveAt(i);
i--;
for (int j = i; j > previousIndex; j--)
{
var toReplace = pants[j];
pants.RemoveAt(j);
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString()));
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString())
{
Span = toReplace.Span,
Line = toReplace.Line,
Column = toReplace.Column,
});
i--;
}
@@ -266,10 +279,102 @@ namespace Markdig.Extensions.SmartyPants
// If we have any quotes lefts, replace them by there literal equivalent
foreach (var quote in pants)
{
quote.ReplaceBy(new LiteralInline(quote.ToString()));
quote.ReplaceBy(new LiteralInline(quote.ToString())
{
Span = quote.Span,
Line = quote.Line,
Column = quote.Column,
});
}
pants.Clear();
}
bool IPostInlineProcessor.PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex,
bool isFinalProcessing)
{
// Don't try to process anything if there are no dash
var quotePants = state.ParserStates[Index] as ListSmartyPants;
if (quotePants == null || !quotePants.HasDash)
{
return true;
}
var child = root;
var pendingContainers = new Stack<Inline>();
while (true)
{
while (child != null)
{
var next = child.NextSibling;
if (child is LiteralInline)
{
var literal = (LiteralInline) child;
var startIndex = 0;
var indexOfDash = literal.Content.IndexOf("--", startIndex);
if (indexOfDash >= 0)
{
var type = SmartyPantType.Dash2;
if (literal.Content.PeekCharAbsolute(indexOfDash + 2) == '-')
{
type = SmartyPantType.Dash3;
}
var nextContent = literal.Content;
var originalSpan = literal.Span;
literal.Span.End -= literal.Content.End - indexOfDash + 1;
literal.Content.End = indexOfDash - 1;
nextContent.Start = indexOfDash + (type == SmartyPantType.Dash2 ? 2 : 3);
var pant = new SmartyPant()
{
Span = new SourceSpan(literal.Content.End + 1, nextContent.Start - 1),
Line = literal.Line,
Column = literal.Column,
OpeningCharacter = '-',
Type = type
};
literal.InsertAfter(pant);
var postLiteral = new LiteralInline()
{
Span = new SourceSpan(pant.Span.End + 1, originalSpan.End),
Line = literal.Line,
Column = literal.Column,
Content = nextContent
};
pant.InsertAfter(postLiteral);
// Use the pending literal to proceed further
next = postLiteral;
}
}
else if (child is ContainerInline)
{
pendingContainers.Push(((ContainerInline)child).FirstChild);
}
child = next;
}
if (pendingContainers.Count > 0)
{
child = pendingContainers.Pop();
}
else
{
break;
}
}
return true;
}
private class ListSmartyPants : List<SmartyPant>
{
public bool HasDash { get; set; }
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Markdig.Extensions.Tables
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlTableRenderer>())

View File

@@ -1,6 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
@@ -10,7 +13,7 @@ namespace Markdig.Extensions.Tables
{
public GridTableParser()
{
OpeningCharacters = new[] {'+'};
OpeningCharacters = new[] { '+' };
}
public override BlockState TryOpen(BlockProcessor processor)
@@ -22,64 +25,51 @@ namespace Markdig.Extensions.Tables
}
var line = processor.Line;
// A grid table must start with a line like this:
// + ------------- + ------------ + ---------------------------------------- +
// Spaces are optional
GridTableState tableState = null;
// Match the first row that should be of the minimal form: +---------------
var c = line.CurrentChar;
while (true)
var lineStart = line.Start;
while (c == '+')
{
if (c == '+')
var columnStart = line.Start;
line.NextChar();
line.TrimStart();
// if we have reached the end of the line, exit
c = line.CurrentChar;
if (c == 0)
{
var startCharacter = line.Start;
line.NextChar();
if (line.IsEmptyOrWhitespace())
{
if (tableState == null)
{
return BlockState.None;
}
break;
}
TableColumnAlign align;
if (TableHelper.ParseColumnHeader(ref line, '-', out align))
{
if (tableState == null)
{
tableState = new GridTableState()
{
Start = processor.Column,
ExpectRow = true,
};
}
tableState.AddColumn(startCharacter, line.Start - 1, align);
c = line.CurrentChar;
continue;
}
break;
}
// If we have any other characters, this is an invalid line
return BlockState.None;
// Parse a column alignment
TableColumnAlign? columnAlign;
if (!TableHelper.ParseColumnHeader(ref line, '-', out columnAlign))
{
return BlockState.None;
}
tableState = tableState ?? new GridTableState { Start = processor.Start, ExpectRow = true };
tableState.AddColumn(columnStart - lineStart, line.Start - lineStart, columnAlign);
c = line.CurrentChar;
}
if (c != 0 || tableState == null)
{
return BlockState.None;
}
// Store the line (if we need later to build a ParagraphBlock because the GridTable was in fact invalid)
tableState.AddLine(ref processor.Line);
// Create the grid table
var table = new Table(this);
table.SetData(typeof(GridTableState), tableState);
// Calculate the total width of all columns
int totalWidth = 0;
foreach (var columnSlice in tableState.ColumnSlices)
{
totalWidth += columnSlice.End - columnSlice.Start;
totalWidth += columnSlice.End - columnSlice.Start - 1;
}
// Store the column width and alignment
@@ -88,8 +78,8 @@ namespace Markdig.Extensions.Tables
var columnDefinition = new TableColumnDefinition
{
// Column width proportional to the total width
Width = (float)(columnSlice.End - columnSlice.Start) * 100.0f / totalWidth,
Alignment = columnSlice.Align,
Width = (float)(columnSlice.End - columnSlice.Start - 1) * 100.0f / totalWidth,
Alignment = columnSlice.Align
};
table.ColumnDefinitions.Add(columnDefinition);
}
@@ -101,253 +91,276 @@ namespace Markdig.Extensions.Tables
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var gridTable = (Table) block;
var gridTable = (Table)block;
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
// We expect to start at the same
if (processor.Start == tableState.Start)
tableState.AddLine(ref processor.Line);
if (processor.CurrentChar == '+')
{
var columns = tableState.ColumnSlices;
foreach (var columnSlice in columns)
{
columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
columnSlice.CurrentColumnSpan = 0;
}
if (processor.CurrentChar == '+')
{
var result = ParseRowSeparator(processor, tableState, gridTable);
if (result != BlockState.None)
{
return result;
}
}
else if (processor.CurrentChar == '|')
{
var line = processor.Line;
// | ------------- | ------------ | ---------------------------------------- |
// Calculate the colspan for the new row
int columnIndex = -1;
foreach (var columnSlice in columns)
{
if (line.PeekCharExtra(columnSlice.Start) == '|')
{
columnIndex++;
}
if (columnIndex >= 0)
{
columns[columnIndex].CurrentColumnSpan++;
}
}
// Check if the colspan of the current row is the same than the previous row
bool continueRow = true;
foreach (var columnSlice in columns)
{
if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
{
continueRow = false;
break;
}
}
// If the current row doesn't continue the previous row (col span are different)
// Close the previous row
if (!continueRow)
{
TerminateLastRow(processor, tableState, gridTable, false);
}
for (int i = 0; i < columns.Count;)
{
var column = columns[i];
var nextColumnIndex = i + column.CurrentColumnSpan;
// If the span is 0, we exit
if (nextColumnIndex == i)
{
break;
}
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
var sliceForCell = line;
sliceForCell.Start = column.Start + 1;
if (nextColumn != null)
{
sliceForCell.End = nextColumn.Start - 1;
}
else
{
var columnEnd = columns[columns.Count - 1].End;
// If there is a `|` exactly at the expected end of the table row, we cut the line
// otherwise we allow to have the last cell of a row to be open for longer cell content
if (line.PeekCharExtra(columnEnd + 1) == '|')
{
sliceForCell.End = columnEnd;
}
}
sliceForCell.TrimEnd();
// Process the content of the cell
column.BlockProcessor.LineIndex = processor.LineIndex;
column.BlockProcessor.ProcessLine(sliceForCell);
// Go to next column
i = nextColumnIndex;
}
return BlockState.ContinueDiscard;
}
return HandleNewRow(processor, tableState, gridTable);
}
TerminateLastRow(processor, tableState, gridTable, true);
// If we don't have a row, it means that only the header was valid
// So we need to remove the grid table, and create a ParagraphBlock
// with the 2 slices
if (gridTable.Count == 0)
if (processor.CurrentChar == '|')
{
var parser = processor.Parsers.Find<ParagraphBlockParser>();
// Discard the grid table
var parent = gridTable.Parent;
processor.Discard(gridTable);
var paragraphBlock = new ParagraphBlock(parser)
{
Lines = tableState.Lines,
};
parent.Add(paragraphBlock);
processor.Open(paragraphBlock);
return HandleContents(processor, tableState, gridTable);
}
TerminateCurrentRow(processor, tableState, gridTable, true);
// If the table is not valid we need to remove the grid table,
// and create a ParagraphBlock with the slices
if (!gridTable.IsValid())
{
Undo(processor, tableState, gridTable);
}
return BlockState.Break;
}
private BlockState HandleNewRow(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
bool isHeaderRow, hasRowSpan;
var columns = tableState.ColumnSlices;
SetRowSpanState(columns, processor.Line, out isHeaderRow, out hasRowSpan);
SetColumnSpanState(columns, processor.Line);
TerminateCurrentRow(processor, tableState, gridTable, false);
if (isHeaderRow)
{
for (int i = 0; i < gridTable.Count; i++)
{
var row = (TableRow)gridTable[i];
row.IsHeader = true;
}
}
tableState.StartRowGroup = gridTable.Count;
if (hasRowSpan)
{
HandleContents(processor, tableState, gridTable);
}
return BlockState.ContinueDiscard;
}
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
{
var lineStart = line.Start;
isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
hasRowSpan = false;
foreach (var columnSlice in columns)
{
if (columnSlice.CurrentCell != null)
{
line.Start = lineStart + columnSlice.Start + 1;
line.End = lineStart + columnSlice.End - 1;
line.Trim();
if (line.IsEmptyOrWhitespace() || !IsRowSeperator(line))
{
hasRowSpan = true;
columnSlice.CurrentCell.RowSpan++;
columnSlice.CurrentCell.AllowClose = false;
}
else
{
columnSlice.CurrentCell.AllowClose = true;
}
}
}
}
private static bool IsRowSeperator(StringSlice slice)
{
while (slice.Length > 0)
{
if (slice.CurrentChar != '-' && slice.CurrentChar != '=' && slice.CurrentChar != ':')
{
return false;
}
slice.NextChar();
}
return 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++)
{
var columnSlice = columns[i];
if (columnSlice.CurrentCell != null)
{
if (currentRow == null)
{
currentRow = new TableRow();
}
// If this cell does not already belong to a row
if (columnSlice.CurrentCell.Parent == 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);
}
}
// Renew the block parser processor (or reset it for the last row)
if (columnSlice.BlockProcessor != null && (columnSlice.CurrentCell == null || columnSlice.CurrentCell.AllowClose))
{
columnSlice.BlockProcessor.ReleaseChild();
columnSlice.BlockProcessor = isLastRow ? null : processor.CreateChild();
}
// Create or erase the cell
if (isLastRow || columnSlice.CurrentColumnSpan == 0 || (columnSlice.CurrentCell != null && columnSlice.CurrentCell.AllowClose))
{
// We don't need the cell anymore if we have a last row
// Or the cell has a columnspan == 0
// And the cell does not have to be kept open to span rows
columnSlice.CurrentCell = null;
}
}
if (currentRow != null && currentRow.Count > 0)
{
gridTable.Add(currentRow);
}
}
private BlockState HandleContents(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
var isRowLine = processor.CurrentChar == '+';
var columns = tableState.ColumnSlices;
var line = processor.Line;
SetColumnSpanState(columns, line);
if (!isRowLine && !CanContinueRow(columns))
{
TerminateCurrentRow(processor, tableState, gridTable, false);
}
for (int i = 0; i < columns.Count;)
{
var columnSlice = columns[i];
var nextColumnIndex = i + columnSlice.CurrentColumnSpan;
// If the span is 0, we exit
if (nextColumnIndex == i)
{
break;
}
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
var sliceForCell = line;
sliceForCell.Start = line.Start + columnSlice.Start + 1;
if (nextColumn != null)
{
sliceForCell.End = line.Start + nextColumn.Start - 1;
}
else
{
var columnEnd = columns[columns.Count - 1].End;
var columnEndChar = line.PeekCharExtra(columnEnd);
// If there is a `|` (or a `+` in the case that we are dealing with a row line
// with spanned contents) exactly at the expected end of the table row, we cut the line
// otherwise we allow to have the last cell of a row to be open for longer cell content
if (columnEndChar == '|' || (isRowLine && columnEndChar == '+'))
{
sliceForCell.End = line.Start + columnEnd - 1;
}
else if (line.PeekCharExtra(line.End) == '|')
{
sliceForCell.End = line.End - 1;
}
}
sliceForCell.TrimEnd();
if (!isRowLine || !IsRowSeperator(sliceForCell))
{
if (columnSlice.CurrentCell == null)
{
columnSlice.CurrentCell = new TableCell(this)
{
ColumnSpan = columnSlice.CurrentColumnSpan,
ColumnIndex = i
};
if (columnSlice.BlockProcessor == null)
{
columnSlice.BlockProcessor = processor.CreateChild();
}
// Ensure that the BlockParser is aware that the TableCell is the top-level container
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
}
// Process the content of the cell
columnSlice.BlockProcessor.LineIndex = processor.LineIndex;
columnSlice.BlockProcessor.ProcessLine(sliceForCell);
}
// Go to next column
i = nextColumnIndex;
}
return BlockState.ContinueDiscard;
}
private static void SetColumnSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line)
{
foreach (var columnSlice in columns)
{
columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
columnSlice.CurrentColumnSpan = 0;
}
// | ------------- | ------------ | ---------------------------------------- |
// Calculate the colspan for the new row
int columnIndex = -1;
for (int i = 0; i < columns.Count; i++)
{
var columnSlice = columns[i];
var peek = line.PeekChar(columnSlice.Start);
if (peek == '|' || peek == '+')
{
columnIndex = i;
}
if (columnIndex >= 0)
{
columns[columnIndex].CurrentColumnSpan++;
}
}
}
private static bool CanContinueRow(List<GridTableState.ColumnSlice> columns)
{
foreach (var columnSlice in columns)
{
if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
{
return false;
}
}
return true;
}
private static void Undo(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
var parser = processor.Parsers.FindExact<ParagraphBlockParser>();
// Discard the grid table
var parent = gridTable.Parent;
processor.Discard(gridTable);
var paragraphBlock = new ParagraphBlock(parser)
{
Lines = tableState.Lines,
};
parent.Add(paragraphBlock);
processor.Open(paragraphBlock);
}
public override bool Close(BlockProcessor processor, Block block)
{
// Work only on Table, not on TableCell
var gridTable = block as Table;
if (gridTable != null)
{
var tableState = (GridTableState) block.GetData(typeof (GridTableState));
TerminateLastRow(processor, tableState, gridTable, true);
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
TerminateCurrentRow(processor, tableState, gridTable, true);
if (!gridTable.IsValid())
{
Undo(processor, tableState, gridTable);
}
}
return true;
}
private BlockState ParseRowSeparator(BlockProcessor state, GridTableState tableState, Table gridTable)
{
// A grid table must start with a line like this:
// + ------------- + ------------ + ---------------------------------------- +
// Spaces are optional
var line = state.Line;
var c = line.CurrentChar;
bool isFirst = true;
var delimiterChar = '\0';
while (true)
{
if (c == '+')
{
line.NextChar();
if (line.IsEmptyOrWhitespace())
{
if (isFirst)
{
return BlockState.None;
}
break;
}
TableColumnAlign align;
if (TableHelper.ParseColumnHeaderDetect(ref line, ref delimiterChar, out align))
{
isFirst = false;
c = line.CurrentChar;
continue;
}
}
// If we have any other characters, this is an invalid line
return BlockState.None;
}
// If we have an header row
var isHeader = delimiterChar == '=';
// Terminate the current row
TerminateLastRow(state, tableState, gridTable, false);
// If we had a header row separator, we can mark all rows since last row separator
// to be header rows
if (isHeader)
{
for (int i = tableState.StartRowGroup; i < gridTable.Count; i++)
{
var row = (TableRow) gridTable[i];
row.IsHeader = true;
}
}
// Makr the next start row group continue on the next row
tableState.StartRowGroup = gridTable.Count;
// We don't keep the line
return BlockState.ContinueDiscard;
}
private void TerminateLastRow(BlockProcessor state, GridTableState tableState, Table gridTable, bool isLastRow)
{
var columns = tableState.ColumnSlices;
TableRow currentRow = null;
foreach (var columnSlice in columns)
{
if (columnSlice.CurrentCell != null)
{
if (currentRow == null)
{
currentRow = new TableRow();
}
currentRow.Add(columnSlice.CurrentCell);
columnSlice.BlockProcessor.Close(columnSlice.CurrentCell);
}
// Renew the block parser processor (or reset it for the last row)
if (columnSlice.BlockProcessor != null)
{
columnSlice.BlockProcessor.ReleaseChild();
columnSlice.BlockProcessor = isLastRow ? null : state.CreateChild();
}
// Create or erase the cell
if (isLastRow || columnSlice.CurrentColumnSpan == 0)
{
// We don't need the cell anymore if we have a last row
// Or the cell has a columnspan == 0
columnSlice.CurrentCell = null;
}
else
{
// Else we can create a new cell
columnSlice.CurrentCell = new TableCell(this)
{
ColumnSpan = columnSlice.CurrentColumnSpan
};
if (columnSlice.BlockProcessor == null)
{
columnSlice.BlockProcessor = state.CreateChild();
}
// Ensure that the BlockParser is aware that the TableCell is the top-level container
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
}
}
if (currentRow != null)
{
gridTable.Add(currentRow);
}
}
}
}
}

View File

@@ -31,7 +31,7 @@ namespace Markdig.Extensions.Tables
Lines.Add(line);
}
public void AddColumn(int start, int end, TableColumnAlign align)
public void AddColumn(int start, int end, TableColumnAlign? align)
{
if (ColumnSlices == null)
{
@@ -60,7 +60,7 @@ namespace Markdig.Extensions.Tables
public int End { get; set; }
public TableColumnAlign Align { get; set; }
public TableColumnAlign? Align { get; set; }
public int CurrentColumnSpan { get; set; }

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using System;
using System.Globalization;
using Markdig.Renderers;
using Markdig.Renderers.Html;
@@ -38,7 +39,9 @@ namespace Markdig.Extensions.Tables
{
foreach (var tableColumnDefinition in table.ColumnDefinitions)
{
renderer.WriteLine($"<col style=\"width:{Math.Round(tableColumnDefinition.Width*100)/100}%\">");
var width = Math.Round(tableColumnDefinition.Width*100)/100;
var widthValue = string.Format(CultureInfo.InvariantCulture, "{0:0.##}", width);
renderer.WriteLine($"<col style=\"width:{widthValue}%\">");
}
}
@@ -78,17 +81,31 @@ namespace Markdig.Extensions.Tables
{
renderer.Write($" colspan=\"{cell.ColumnSpan}\"");
}
if (table.ColumnDefinitions != null && i < table.ColumnDefinitions.Count)
if (cell.RowSpan != 1)
{
switch (table.ColumnDefinitions[i].Alignment)
renderer.Write($" rowspan=\"{cell.RowSpan}\"");
}
if (table.ColumnDefinitions != null)
{
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i
: cell.ColumnIndex;
columnIndex = columnIndex >= table.ColumnDefinitions.Count ? table.ColumnDefinitions.Count - 1 : columnIndex;
var alignment = table.ColumnDefinitions[columnIndex].Alignment;
if (alignment.HasValue)
{
case TableColumnAlign.Center:
renderer.Write(" style=\"text-align: center;\"");
break;
case TableColumnAlign.Right:
renderer.Write(" style=\"text-align: right;\"");
break;
switch (alignment)
{
case TableColumnAlign.Center:
renderer.Write(" style=\"text-align: center;\"");
break;
case TableColumnAlign.Right:
renderer.Write(" style=\"text-align: right;\"");
break;
case TableColumnAlign.Left:
renderer.Write(" style=\"text-align: left;\"");
break;
}
}
}
renderer.WriteAttributes(cell);
@@ -101,7 +118,7 @@ namespace Markdig.Extensions.Tables
}
renderer.Write(cell);
renderer.ImplicitParagraph = previousImplicitParagraph;
renderer.WriteLine(row.IsHeader ? "</th>" : "</td>");
}
renderer.WriteLine("</tr>");

View File

@@ -0,0 +1,69 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
namespace Markdig.Extensions.Tables
{
/// <summary>
/// This block parsers for pipe tables is used to by-pass list items that could start by a single '-'
/// and would disallow to detect a pipe tables at inline parsing time, so we are basically forcing a line
/// that starts by a '-' and have at least a '|' (and have optional spaces) and is a continuation of a
/// paragraph.
/// </summary>
/// <seealso cref="Markdig.Parsers.BlockParser" />
public class PipeTableBlockParser : BlockParser
{
/// <summary>
/// Initializes a new instance of the <see cref="PipeTableBlockParser"/> class.
/// </summary>
public PipeTableBlockParser()
{
OpeningCharacters = new[] {'-'};
}
public override BlockState TryOpen(BlockProcessor processor)
{
// Only if we have already a paragraph
var paragraph = processor.CurrentBlock as ParagraphBlock;
if (processor.IsCodeIndent || paragraph == null)
{
return BlockState.None;
}
// We require at least a pipe (and we allow only : - | and space characters)
var line = processor.Line;
var countPipe = 0;
while (true)
{
var c = line.NextChar();
if (c == '\0')
{
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.IsOpen = true;
return BlockState.BreakDiscard;
}
return BlockState.None;
}
if (c.IsSpace() || c == '-' || c == '|' || c == ':')
{
if (c == '|')
{
countPipe++;
}
continue;
}
return BlockState.None;
}
}
}
}

View File

@@ -10,9 +10,9 @@ namespace Markdig.Extensions.Tables
/// The delimiter used to separate the columns of a pipe table.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.DelimiterInline" />
public class PiprTableDelimiterInline : DelimiterInline
public class PipeTableDelimiterInline : DelimiterInline
{
public PiprTableDelimiterInline(InlineParser parser) : base(parser)
public PipeTableDelimiterInline(InlineParser parser) : base(parser)
{
}

View File

@@ -28,13 +28,20 @@ namespace Markdig.Extensions.Tables
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Pipe tables require precise source location
pipeline.PreciseSourceLocation = true;
if (!pipeline.BlockParsers.Contains<PipeTableBlockParser>())
{
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());
}
var lineBreakParser = pipeline.InlineParsers.FindExact<LineBreakInlineParser>();
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
{
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(Options));
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(lineBreakParser, Options));
}
}
public void Setup(IMarkdownRenderer renderer)
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlTableRenderer>())

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