Compare commits

...

143 Commits

Author SHA1 Message Date
Alexandre Mutel
832f86cf5f Bump to 0.15.7 2019-01-11 15:52:56 +01:00
Alexandre Mutel
103434eb9b Merge pull request #283 from OsmondJiang/configurable_leading_#_count
make the max leading # count in heading block parser configurable
2019-01-07 10:50:15 +01:00
OsmondJiang
f9dd201563 move heading block extension outside 2019-01-07 17:18:52 +08:00
OsmondJiang
9b9243a61f add heading block parser to able to config max leading count of # 2019-01-07 17:03:02 +08:00
Alexandre Mutel
91dbfce18e Merge pull request #281 from FranklinWhale/bool-attr
Render XML well-formed boolean attribute
2019-01-04 13:47:45 +01:00
Franklin Tse
57692a557b controls attribute is also changed 2019-01-04 00:48:10 +08:00
Franklin Tse
02bd8149ac Use empty string instead of attribute name 2019-01-04 00:23:19 +08:00
Franklin Tse
0920001f45 Remove the unnecessary EOL at the end 2019-01-03 23:51:04 +08:00
Franklin Tse
cde939f79c Update spec and test cases 2019-01-03 23:49:38 +08:00
Franklin Tse
f932b3c87a Use attribute name as value if value is null 2019-01-03 23:43:54 +08:00
Alexandre Mutel
b2a7baa079 Bump to 0.15.6 2018-12-28 11:32:11 +01:00
Alexandre Mutel
1c37c996dc Try to fix loading files from CI 2018-12-28 11:19:37 +01:00
Alexandre Mutel
da99af68c6 Fix offset for StringLineGroup.Iterator.Remaining() 2018-12-28 10:59:34 +01:00
Alexandre Mutel
03858dc5c8 Add tests for checking that ArgumentOutOfRangeException doesn't occur on invalid input md string (#275) 2018-12-28 10:36:10 +01:00
Alexandre Mutel
ebedc6829d Fix IndexOutOfRangeException while parsing fenced code block with a single trailing space (#276) 2018-12-28 10:30:37 +01:00
Alexandre Mutel
28322c3645 Fix parsing of an invalid html entity (#277) 2018-12-28 10:23:25 +01:00
Alexandre Mutel
c3395e6779 Fix potential hang when parsing LinkReferenceDefinition (#278) 2018-12-28 10:12:17 +01:00
Alexandre Mutel
cc47d33c7d Bump version to 0.15.5 2018-12-11 13:40:22 +01:00
Alexandre Mutel
fedbe2adc5 Merge pull request #267 from redcrayonn/patch-1
AutoIdentifierOptions clarification
2018-12-11 13:34:02 +01:00
Alexandre Mutel
8efc082d1d Update typo in AutoIdentifierOptions 2018-12-04 10:02:56 +01:00
Alexandre Mutel
6c2702f9fd Merge pull request #270 from neilha/master
fixes #196 ensuring line breaks when renderer does not have html enabled
2018-11-22 15:46:33 +01:00
Neil Harrold
4824f460c0 fixes #196 ensuring line breaks when renderer does not have html enabled 2018-11-21 09:03:24 +00:00
Olaf de Mol
46fc840c35 AutoIdentifierOptions clarification
Clarified 'None' option in the options for the auto-identifiers extension.
2018-11-09 15:22:02 +01:00
Alexandre Mutel
ce3078dcf2 Merge pull request #264 from MihaZupan/autolink-prev-chars
Make AutoLink ValidPreviousCharacters configurable
2018-10-29 07:29:43 -07:00
Miha Zupan
b8255a87cf Make AutoLink ValidPreviousCharacters configurable 2018-10-29 15:10:11 +01:00
Alexandre Mutel
4f6ad47c6a Merge pull request #255 from dampee/patch-1
having three dots while it has to be three dashes
2018-10-27 07:27:49 -07:00
Alexandre Mutel
d6d6f9c5c1 Merge pull request #256 from SyntaxC4/patch-1
Fixed spelling mistake
2018-10-27 07:27:28 -07:00
Alexandre Mutel
92b06f0954 Merge pull request #260 from MihaZupan/fix-autolinks
Allow #, ? and : instead of only / as the domain separator for AutoLinks
2018-10-25 12:54:57 -07:00
Miha Zupan
ecd625d40b Test for links without a slash after the domain 2018-10-25 16:56:33 +02:00
Miha Zupan
7e81747662 AutoLink Match links without slash after domain 2018-10-25 16:54:51 +02:00
Cory Fowler
cffe5b2f4b Update FootnotesSpecs.md 2018-10-10 17:03:38 -07:00
Damiaan
c349ead32c having three dots while it has to be three dashes 2018-10-09 11:44:53 +02:00
Alexandre Mutel
07e6a13378 Merge pull request #254 from MihaZupan/issue_251
Empty image alt text for link reference definitions
2018-10-07 20:57:38 +02:00
Miha Zupan
1b54bb157c Added image alt text tests 2018-10-07 19:24:24 +02:00
Miha Zupan
e23fae26c8 Empty image alt text for link reference definitions 2018-10-07 16:27:50 +02:00
Alexandre Mutel
2501c6aaf6 Bump to 0.15.4 2018-10-07 09:23:53 +02:00
Alexandre Mutel
e390a870cc Merge branch 'pr/n253_MihaZupan' 2018-10-06 18:04:35 +02:00
Miha Zupan
e4892fc86d Update Specs.cs 2018-10-06 18:03:39 +02:00
Miha Zupan
dfc67021ee Add autolink domain validation tests 2018-10-06 17:29:14 +02:00
Miha Zupan
25db2774c3 Add autolink domain validation 2018-10-06 17:28:53 +02:00
Alexandre Mutel
22271db431 Bump to 0.15.3 2018-09-26 10:34:54 +02:00
Alexandre Mutel
446764873d Merge pull request #247 from MihaZupan/master
Expose line start indexes
2018-09-26 10:17:19 +02:00
Miha Zupan
bdb5119806 Update changelog 2018-09-25 01:03:06 +02:00
Miha Zupan
f3bb06531e Expose line start indexes 2018-09-25 00:56:36 +02:00
Alexandre Mutel
9a74f3708e Merge pull request #245 from dampee/patch-1
small typo
2018-09-18 13:58:27 +02:00
Damiaan
57033311dc small typo 2018-09-18 13:53:15 +02:00
Alexandre Mutel
bda2f8c9ad Merge pull request #244 from MihaZupan/list-item-order
Expose list item order
2018-09-17 20:13:20 +02:00
Miha Zupan
0d5d3ddb2a Expose List item order 2018-09-17 16:19:01 +02:00
Alexandre Mutel
3f8e50031b Update changelog 2018-09-15 20:58:49 +02:00
Alexandre Mutel
949d5c0c25 Merge pull request #243 from MihaZupan/all-link-definitions
Fix Link and Footnote reference definitions in syntax tree
2018-09-15 20:52:50 +02:00
Miha Zupan
31e163f9f7 Fix offset calculation 2018-09-15 15:58:35 +02:00
Miha Zupan
b1440ab788 Add a LinkReferenceDefinition source position test 2018-09-15 15:27:38 +02:00
Miha Zupan
6e55d84f01 Add a FootnoteDefinition source position test 2018-09-15 15:27:05 +02:00
Miha Zupan
595dbb9af4 Fix FootnoteLinkReferenceDefinition content and source positions 2018-09-15 15:25:33 +02:00
Miha Zupan
d1576fd2ee Fix LinkReferenceDefinition source positions 2018-09-15 15:25:16 +02:00
Alexandre Mutel
1fe05639c9 Merge pull request #241 from MihaZupan/document-lines
Expose the number of lines in a document
2018-09-04 12:01:16 +02:00
Miha Zupan
8afff09b3e Expose the number of lines in a document 2018-08-29 18:56:36 +02:00
Alexandre Mutel
694747471a Merge pull request #239 from encrypt0r/master
Globalization Extension
2018-08-28 16:06:57 +02:00
Muhammad Azeez
dfcd5cddfa Fix the specs to make sure that the requried extensions are added in the tests 2018-08-28 16:30:35 +03:00
Muhammad Azeez
a99b7f4572 update globalization specs, docs and tests 2018-08-28 16:18:25 +03:00
Muhammad Azeez
7d7598db54 fix copyright notices 2018-08-28 15:58:12 +03:00
Muhammad Azeez
8767a1d8ed take surrogate characters into account 2018-08-28 15:33:20 +03:00
Muhammad Azeez
16c0829d9e add globalization spec 2018-08-27 00:48:22 +03:00
Muhammad Azeez
fc5d832432 Add globalization case for Configure extension method 2018-08-27 00:34:50 +03:00
Muhammad Azeez
7292acc27a make sure TaskList's X doesn't affect whether a block is marked as RTL or not 2018-08-27 00:32:08 +03:00
Muhammad Azeez
d05bc1572d only take strong characters into consideration when deciding whether a block should be rtl or not 2018-08-27 00:30:47 +03:00
Muhammad Azeez
5aa138425f add globalization extension 2018-08-25 23:11:20 +03:00
Alexandre Mutel
c56041811b Bump to 0.15.2 2018-08-21 11:04:45 +02:00
Alexandre Mutel
790bff3baf Fix footnotes parsing when they are defined after a container that has been closed in the meantime (#223) 2018-08-21 11:01:07 +02:00
Alexandre Mutel
571e04fe28 Bump to 0.15.1 2018-07-10 11:02:38 +02:00
Alexandre Mutel
c847996146 Fix CRLF 2018-07-10 11:02:16 +02:00
Alexandre Mutel
2782d3b4c3 Remove usage of IDictionary 2018-07-10 10:31:34 +02:00
Alexandre Mutel
582a76f8f0 Change editorconfig to not force crlf 2018-07-10 10:31:34 +02:00
Alexandre Mutel
030d676497 Merge pull request #235 from yufeih/autolink
Make AutoIdentifierExtension thread safe
2018-07-10 10:09:17 +02:00
Yufei Huang
0794213eef Make AutoIdentifierExtension thread safe 2018-07-10 11:52:32 +08:00
Alexandre Mutel
427dc849f7 Merge pull request #234 from ForNeVeR/patch-1
Fix spelling in package description
2018-06-28 17:16:43 +02:00
Friedrich von Never
e598047964 Fix spelling in package description 2018-06-28 22:07:53 +07:00
Alexandre Mutel
15546732dd Merge pull request #228 from carbon/master
Cross target .NETSTANDARD 2.0
2018-05-22 18:40:29 +02:00
Jason Nelson
7bd238852d Fix formatting 2018-05-22 08:59:35 -07:00
Jason Nelson
0f406039a0 Enable FIXED_STRING & UNSAFE code for .NETSTANDARD 2.0 target 2018-05-22 08:55:03 -07:00
Jason Nelson
1cd9cdfca0 Cross target .NETSTANDARD 2.0 2018-05-22 08:45:46 -07:00
Alexandre Mutel
812c4fabe6 Merge pull request #222 from caioproiete/add-tests-for-ConfigureNewLine
Add tests for `ConfigureNewLineExtension`'s `ConfigureNewLine`
2018-04-13 10:27:15 +02:00
Caio Proiete
85f8f59786 Add tests for ConfigureNewLineExtension's ConfigureNewLine 2018-04-12 20:34:53 -03:00
Alexandre Mutel
b0839f114c Bump to 0.15.0 2018-04-04 11:37:41 +02:00
Alexandre Mutel
431fecb1c6 Add third party extensions (#206) 2018-04-04 11:37:24 +02:00
Alexandre Mutel
7620b2b760 Merge pull request #214 from caioproiete/add-configure-new-line-extension
Add `ConfigureNewLineExtension` markdown extension
2018-04-04 11:09:48 +02:00
Alexandre Mutel
ad18514824 Merge pull request #213 from caioproiete/add-new-use-extension-with-instance
Add alternative `Use` extension method to `MarkdownPipelineBuilder` that receives an object instance
2018-02-23 09:23:38 +01:00
Caio Proiete
c68a488717 Add ConfigureNewLine extension method to MarkdownPipelineBuilder 2018-02-20 19:53:36 -04:00
Caio Proiete
e2f0f00831 Add ConfigureNewLineExtension markdown extension 2018-02-20 19:46:14 -04:00
Caio Proiete
fea2ca5adf Add alternative Use extension method to MarkdownPipelineBuilder that receives an object instance 2018-02-20 19:32:47 -04:00
Alexandre Mutel
f77bd0b36d Merge pull request #208 from pascalberger/patch-1
Fix typo
2018-02-17 10:01:58 +01:00
Pascal Berger
20243a79a0 Fix typo 2018-02-08 22:03:06 +01:00
Alexandre Mutel
a097247272 Merge pull request #203 from matteofabbri/master
added class attribute to media link extension
2018-02-01 10:06:31 +01:00
Matteo Fabbri
f3cb0712ca added class attribute to media link extension 2018-01-31 20:23:30 +01:00
Alexandre Mutel
eedfc3cd9c Merge pull request #201 from markheath/relative-urls-2
Optional link rewriter func for HtmlRenderer
2018-01-25 07:16:10 +01:00
Mark Heath
5f4b049ce0 removing spurious nuget file 2018-01-24 14:17:01 +00:00
Mark Heath
20edf26c40 optional link rewriter func for HtmlRenderer #143 2018-01-24 14:00:28 +00:00
Alexandre Mutel
4192a00e20 Merge pull request #199 from leotsarev/fix-resharper-unit-tests
Upgrade NUnit3TestAdapter from 3.2 to 3.9
2018-01-20 17:21:59 +01:00
Leonid Tsarev
4346c52ef0 Upgrade NUnit3TestAdapter from 3.2 to 3.9 to address Resharper test runner problems 2018-01-20 18:46:34 +03:00
Alexandre Mutel
42683e043d Merge pull request #197 from markheath/relative-urls
Convert relative URLs to absolute
2018-01-17 16:23:51 +01:00
Mark Heath
96c469018e HTML renderer supports converting relative URLs on links and images to absolute #143 2018-01-17 14:59:02 +00:00
Alexandre Mutel
f64ac47841 Bump to 0.14.9 2018-01-15 10:12:42 +01:00
Alexandre Mutel
c29b7d2942 Merge pull request #195 from leotsarev/remove-mailto
AutoLinkParser should to remove mailto: in outputted text
2018-01-10 11:17:34 +01:00
Leonid Tsarev
0f7e3b8c52 AutoLinkParser should to remove mailto: in outputted text 2018-01-09 11:48:06 +03:00
Alexandre Mutel
6dff16a612 Merge pull request #191 from leotsarev/improve-test-names
Improve discoverability of test in VS test runner
2018-01-08 20:50:43 +01:00
Alexandre Mutel
8e15c8bc6a Merge pull request #193 from leotsarev/support-vk-and-yandex-music
Added yandex.music & ok.ru support to MediaLinkExtension
2018-01-08 18:06:32 +01:00
Leonid Tsarev
558db1fd70 Support odnoklassniki.ru (meaning: classmates, top 2 social network in Russia) 2018-01-08 19:30:23 +03:00
Leonid Tsarev
796b143316 Add support for yandex music 2018-01-08 19:14:01 +03:00
Leonid Tsarev
3402805ebb refactor MediaLinkExtension to make it pluggable 2018-01-08 18:48:09 +03:00
Leonid Tsarev
c3600c2ba5 Merge branch 'master' into improve-test-names 2018-01-08 18:09:54 +03:00
Leonid Tsarev
4ba98f594a Merge branch 'master' into improve-test-names 2018-01-08 18:08:47 +03:00
Leonid Tsarev
544a64e5c9 Improve discoverability of test in VS test runner 2018-01-08 18:05:19 +03:00
Alexandre Mutel
f17320702a Merge pull request #189 from markheath/patch-1
Media link extension to support MP3 by default
2018-01-03 09:08:51 +01:00
Alexandre Mutel
d883694ac4 Merge pull request #186 from tthiery/normalize-autolinks
Add Normalization Support for AutoLinks
2018-01-03 09:08:08 +01:00
Mark Heath
42fba26ea2 Media link extension to support MP3 by default
I presume MP3 wasn't intentionally left out this list?
2018-01-01 13:50:52 +00:00
T. Thiery
595dacf213 Add Normalization Support for AutoLinks (including Options) 2017-12-24 12:21:23 +01:00
T. Thiery
75adfa3fe8 Add Normalization Tests for AutoLinks 2017-12-24 12:20:26 +01:00
Alexandre Mutel
0bb8139450 Merge pull request #185 from tthiery/normalize-jiralinks
Add Normalization Support for JIRA Links
2017-12-17 20:25:59 +01:00
T. Thiery
60784901b2 Add Normalization Support for JIRA Links 2017-12-17 17:00:55 +01:00
Alexandre Mutel
5020fdd3b1 Bump to 0.14.8 2017-12-05 09:36:48 +01:00
Alexandre Mutel
bd4ea56d2a Fix potential StackOverflow exception when processing deep nested | delimiters (#179) 2017-12-05 09:34:59 +01:00
Alexandre Mutel
fbc8a4116c Bump to 0.14.7 2017-11-25 14:17:23 +01:00
Alexandre Mutel
9af318d334 Merge pull request #175 from terryjintry/fixautolink
fix autolink attribute
2017-11-24 09:29:44 +01:00
Terry Jin
88e561c3ae fix autolink attribute 2017-11-24 14:34:44 +08:00
Alexandre Mutel
1ca82eb058 Bump to 0.14.6 2017-11-21 07:36:14 +01:00
Alexandre Mutel
66007c6bbf Add TestLink example (#171) 2017-11-21 07:35:17 +01:00
Alexandre Mutel
14d4286fd7 Merge pull request #170 from yishengjin1413/master
fix yaml frontmatter issue when ending with a empty line
2017-11-20 08:26:47 +01:00
Terry Jin
58b1a48f5b fix yaml frontmatter issue when ending with a empty line 2017-11-20 15:11:16 +08:00
Alexandre Mutel
5e1eaf8590 Bump to 0.14.5 2017-11-18 17:08:21 +01:00
Alexandre Mutel
c946295d96 Fix link to changelog.md in Markdig.csproj 2017-11-18 17:08:11 +01:00
Alexandre Mutel
97cbf11f8b Bump to 0.14.4 2017-11-18 11:29:39 +01:00
Alexandre Mutel
8d4394e7c6 Fix link conflict between a link to an image definition and heading auto-identifiers (#159) 2017-11-18 11:17:06 +01:00
Alexandre Mutel
fbdb8cf063 Better handle YAML frontmatter in case the opening --- is never actually closed (#160) 2017-11-18 10:55:16 +01:00
Alexandre Mutel
964538ec79 Add support for GFM autolinks (#165, #169) 2017-11-17 21:28:27 +01:00
Alexandre Mutel
9a30883e2a Add support for compatible github auto-identifiers for headings 2017-11-10 20:42:53 +01:00
Alexandre Mutel
6408705f82 Return an empty string for / on markdig webapi 2017-11-10 10:59:27 +01:00
Alexandre Mutel
523582e588 Fix bug when a thematic break is inside a fenced code block inside a pending list (#164) 2017-11-09 09:08:51 +01:00
Alexandre Mutel
aaff022e7c Merge pull request #161 from tthiery/normalize-tasklists
Add Normalization Support for Task Lists
2017-11-04 23:09:14 +01:00
T. Thiery
37eb6aa529 Add Normalization Support for Task Lists 2017-11-04 23:01:01 +01:00
Alexandre Mutel
9b7356b05e Remove local project 2017-11-03 14:22:05 +01:00
Alexandre Mutel
33d3bc1330 Upgrade WebApp to .NET core app 2.0 and add ApplicationInsights 2017-11-03 14:19:32 +01:00
Alexandre Mutel
07a2980d5b Bump to 0.14.3 2017-11-01 07:44:41 +01:00
Alexandre Mutel
2d8872f2a1 Make EmojiExtension.EnableSmiley public 2017-11-01 07:44:30 +01:00
85 changed files with 24316 additions and 21611 deletions

View File

@@ -6,7 +6,6 @@ root = true
# All Files
[*]
charset = utf-8
end_of_line = crlf
indent_style = space
indent_size = 4
insert_final_newline = false

159
changelog.md Normal file
View File

@@ -0,0 +1,159 @@
# Changelog
## 0.15.7 (11 Jan 2019)
- Add configurable leading count for ATX headers ([(PR #282)](https://github.com/lunet-io/markdig/pull/282))
- Render XML well-formed boolean attribute ([(PR #281)](https://github.com/lunet-io/markdig/pull/281))
## 0.15.6 (28 Dec 2018)
- Fix potential hang when parsing LinkReferenceDefinition #278
- Fix parsing of an invalid html entity (#277)
- Fix IndexOutOfRangeException while parsing fenced code block with a single trailing space (#276)
- Add tests for checking that ArgumentOutOfRangeException doesn't occur on invalid input md string (#275)
## 0.15.5 (11 Dec 2018)
- Empty image alt text for link reference definitions ([(PR #254)](https://github.com/lunet-io/markdig/pull/254))
- Fix AutoLink Match links without slash after domain ([(PR #260)](https://github.com/lunet-io/markdig/pull/260))
- Make AutoLink ValidPreviousCharacters configurable ([(PR #264)](https://github.com/lunet-io/markdig/pull/264))
- Ensuring line breaks when renderer does not have html enabled ([(PR #270)](https://github.com/lunet-io/markdig/pull/270))
## 0.15.4 (07 Oct 2018)
- Add autolink domain GFM validation ([(PR #239)](https://github.com/lunet-io/markdig/pull/253))
## 0.15.3 (15 Sep 2018)
- Add support for RTL ([(PR #239)](https://github.com/lunet-io/markdig/pull/239))
- Add MarkdownDocument.LineCount ([(PR #241)](https://github.com/lunet-io/markdig/pull/241))
- Fix source positions for link definitions ([(PR #243)](https://github.com/lunet-io/markdig/pull/243))
- Add ListItemBlock.Order ([(PR #244)](https://github.com/lunet-io/markdig/pull/244))
- Add MarkdownDocument.LineStartIndexes ([(PR #247)](https://github.com/lunet-io/markdig/pull/247))
## 0.15.2 (21 Aug 2018)
- Fix footnotes parsing when they are defined after a container that has been closed in the meantime (#223)
## 0.15.1 (10 July 2018)
- Add support for `netstandard2.0`
- Make AutoIdentifierExtension thread safe
## 0.15.0 (4 Apr 2018)
- Add `ConfigureNewLine` extension method to `MarkdownPipelineBuilder` ([(PR #214)](https://github.com/lunet-io/markdig/pull/214))
- Add alternative `Use` extension method to `MarkdownPipelineBuilder` that receives an object instance ([(PR #213)](https://github.com/lunet-io/markdig/pull/213))
- Added class attribute to media link extension ([(PR #203)](https://github.com/lunet-io/markdig/pull/203))
- Optional link rewriter func for HtmlRenderer #143 ([(PR #201)](https://github.com/lunet-io/markdig/pull/201))
- Upgrade NUnit3TestAdapter from 3.2 to 3.9 to address Resharper test runner problems ([(PR #199)](https://github.com/lunet-io/markdig/pull/199))
- HTML renderer supports converting relative URLs on links and images to absolute #143 ([(PR #197)](https://github.com/lunet-io/markdig/pull/197))
## 0.14.9 (15 Jan 2018)
- AutoLinkParser should to remove mailto: in outputted text ([(PR #195)](https://github.com/lunet-io/markdig/pull/195))
- Add support for `music.yandex.ru` and `ok.ru` for MediaLinks extension ([(PR #193)](https://github.com/lunet-io/markdig/pull/193))
## 0.14.8 (05 Dec 2017)
- Fix potential StackOverflow exception when processing deep nested `|` delimiters (#179)
## 0.14.7 (25 Nov 2017)
- Fix autolink attached attributes not being displayed properly (#175)
## 0.14.6 (21 Nov 2017)
- Fix yaml frontmatter issue when ending with a empty line (#170)
## 0.14.5 (18 Nov 2017)
- Fix changelog link from nuget package
## 0.14.4 (18 Nov 2017)
- Add changelog.md
- Fix bug when a thematic break is inside a fenced code block inside a pending list (#164)
- Add support for GFM autolinks (#165, #169)
- Better handle YAML frontmatter in case the opening `---` is never actually closed (#160)
- Fix link conflict between a link to an image definition and heading auto-identifiers (#159)
## 0.14.3
- Make EmojiExtension.EnableSmiley public
## 0.14.2
- Fix issue with emphasis preceded/followed by an HTML entity (#157)
- Add support for link reference definitions for Normalize renderer (#155)
- Add option to disable smiley parsing in EmojiAndSmiley extension
## 0.14.1
- Fix crash in Markdown.Normalize to handle HtmlBlock correctly
- Add better handling of bullet character for lists in Markdown.Normalize
## 0.14.0
- Add Markdown.ToPlainText, Add option HtmlRenderer.EnableHtmlForBlock.
- Add Markdown.Normalize, to allow to normalize a markdown document. Add NormalizeRenderer, to render a MarkdownDocument back to markdown.
## 0.13.4
- Add support for single table header row without a table body rows (#141)
- ADd support for `nomnoml` diagrams
## 0.13.3
- Add support for Pandoc YAML frontmatter (#138)
## 0.13.2
- Add support for UAP10.0 (#137)
## 0.13.1
- Fix indenting issue after a double digit list block using a tab (#134)
## 0.13.0
- Update to latest CommonMark specs 0.28
## 0.12.3
- Fix issue with HTML blocks for heading h2,h3,h4,h5,h6 that were not correctly identified as HTML blocks as per CommonMark spec
## 0.12.2
- Fix issue with generic attributes used just before a pipe table (issue #121)
## 0.12.1
- Fix issue with media links extension when a URL to video is used, an unexpected closing `</iframe>` was inserted (issue #119)
## 0.12.0
- Add new extension JiraLink support (thanks to @clarkd)
- Fix issue in html attributes not parsing correctly properties (thanks to @meziantou)
- Fix issues detected by an automatic static code analysis tool
## 0.11.0
- Fix issue with math extension and $$ block parsing not handling correctly beginning of a $$ as a inline math instead (issue #107)
- Fix issue with custom attributes for emphasis
- Add support for new special custom arrows emoji (`->` `<-` `<->` `<=` `=>` `<==>`)
## 0.10.7
- Fix issue when an url ends by a dot `.`
## 0.10.6
- Fix emphasis with HTML entities
## 0.10.5
- Several minor fixes
## 0.10.4
- Fix issue with autolinks
- Normalize number of columns for tables
## 0.10.3
- Fix issue with pipetables shifting a cell to a new column (issue #73)
## 0.10.2
- Fix exception when trying to urlize an url with an unicode character outside the supported range by NormD (issue #75)
## 0.10.1
- Update to latest CommonMark specs
- Fix source span for LinkReferenceDefinition
## 0.10.0
- Breaking change of the IMarkdownExtension to allow to receive the MarkdownPipeline for the renderers setup
## 0.9.1
- Fix regression bug with conflicts between autolink extension and html inline/regular links
## 0.9.0
- Add new Autolink extension
## 0.8.5
- Allow to force table column alignment to left
## 0.8.4
- Fix issue when calculating the span of an indented code block within a list. Make sure to include first whitespace on the line
## 0.8.3
- fix NullReferenceException with Gridtables extension when a single `+` is entered on a line
## 0.8.2
- fix potential cast exception with Abreviation extension and empty literals
## 0.8.1
- new extension to disable URI escaping for non-US-ASCII characters to workaround a bug in Edge/IE
- Fix an issue with abbreviations with left/right multiple non-punctuation/space characters
## 0.8.0
- Update to latest CommonMark specs
- Fix empty literal
- Add YAML frontmatter extension
## 0.7.5
- several bug fixes (pipe tables, disable HTML, special attributes, inline maths, abbreviations...)
- add support for rowspan in grid tables
## 0.7.4
- Fix bug with strong emphasis starting at the beginning of a line
## 0.7.3
- Fix threading issue with pipeline
## 0.7.2
- Fix rendering of table colspan with non english locale
- Fix grid table colspan parsing
- Add nofollow extension for links
## 0.7.1
- Fix issue in smarty pants which could lead to an InvalidCastException
- Update parsers to latest CommonMark specs
## 0.7.0
- Update to latest NETStandard.Library 1.6.0
- Fix issue with digits in auto-identifier extension
- Fix incorrect start of span calculated for code indented blocks
## 0.6.2
- Handle latest CommonMark specs for corner cases for emphasis (See https://talk.commonmark.org/t/emphasis-strong-emphasis-corner-cases/2123/1 )
## 0.6.1:
- Fix issue with autoidentifier extension overriding manual HTML attributes id on headings
## 0.6.0
- Fix conflicts between PipeTables and SmartyPants extensions
- Add SelfPipeline extension

View File

@@ -1,4 +1,4 @@
Copyright (c) 2016, Alexandre Mutel
Copyright (c) 2018, Alexandre Mutel
All rights reserved.
Redistribution and use in source and binary forms, with or without modification

View File

@@ -52,6 +52,12 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**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+`)
### Third Party Extensions
- [**WPF/XAML Markdown Renderer**: `markdig.wpf`](https://github.com/Kryptos-FR/markdig.wpf)
- [**Syntax highlighting**: `Markdig.SyntaxHighlighting`](https://github.com/RichardSlater/Markdig.SyntaxHighlighting)
- [**Embedded C# scripting**: `Markdig.Extensions.ScriptCs`](https://github.com/macaba/Markdig.Extensions.ScriptCs)
## Documentation
> The repository is under construction. There will be a dedicated website and proper documentation at some point!

Binary file not shown.

View File

@@ -57,6 +57,7 @@
<DependentUpon>Specs.tt</DependentUpon>
</Compile>
<Compile Include="Specs\TestEmphasisPlus.cs" />
<Compile Include="TestConfigureNewLine.cs" />
<Compile Include="TestHtmlAttributes.cs" />
<Compile Include="TestHtmlHelper.cs" />
<Compile Include="TestLineReader.cs" />
@@ -65,6 +66,8 @@
<Compile Include="TestOrderedList.cs" />
<Compile Include="TestPlainText.cs" />
<Compile Include="TestPragmaLines.cs" />
<Compile Include="TestLinkRewriter.cs" />
<Compile Include="TestRelativeUrlReplacement.cs" />
<Compile Include="TestSourcePosition.cs" />
<Compile Include="TestStringSliceList.cs" />
<Compile Include="TestPlayParser.cs" />
@@ -73,7 +76,14 @@
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<Content Include="ArgumentOutOfRangeException.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="hang.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="project.json" />
<None Include="Specs\GlobalizationSpecs.md" />
<None Include="Specs\JiraLinks.md" />
<None Include="Specs\AutoLinks.md" />
<None Include="Specs\AutoIdentifierSpecs.md" />

View File

@@ -12,7 +12,7 @@ Allows to automatically creates an identifier for a heading:
<h1 id="this-is-a-heading">This is a heading</h1>
````````````````````````````````
Only punctuation `-`, `_` and `.` is kept, all over non letter characters are discarded.
Only punctuation `-`, `_` and `.` is kept, all other non letter characters are discarded.
Consecutive same character `-`, `_` or `.` are rendered into a single one
Characters `-`, `_` and `.` at the end of the string are also discarded.
@@ -96,3 +96,14 @@ The text of the link can be changed:
<p><a href="#this-is-a-heading">With a new text</a></p>
<h1 id="this-is-a-heading">This is a heading</h1>
````````````````````````````````
An autoidentifier should not conflict with an existing link:
```````````````````````````````` example
![scenario image][scenario]
## Scenario
[scenario]: ./scenario.png
.
<p><img src="./scenario.png" alt="scenario image" /></p>
<h2 id="scenario">Scenario</h2>
````````````````````````````````

View File

@@ -19,7 +19,7 @@ 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 <a href="mailto:email@toto.com">email@toto.com</a>
And a plain <a href="http://www.google.com">www.google.com</a></p>
````````````````````````````````
@@ -76,3 +76,167 @@ 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>
````````````````````````````````
It is not mentioned by the spec, but empty emails won't be matched (only a subset of [RFC2368](https://tools.ietf.org/html/rfc2368) is supported by auto links):
```````````````````````````````` example
mailto:email@test.com is okay, but mailto:@test.com is not
.
<p><a href="mailto:email@test.com">email@test.com</a> is okay, but mailto:@test.com is not</p>
````````````````````````````````
### GFM Support
Extract from [GFM Autolinks extensions specs](https://github.github.com/gfm/#autolinks-extension-)
```````````````````````````````` example
www.commonmark.org
.
<p><a href="http://www.commonmark.org">www.commonmark.org</a></p>
````````````````````````````````
```````````````````````````````` example
Visit www.commonmark.org/help for more information.
.
<p>Visit <a href="http://www.commonmark.org/help">www.commonmark.org/help</a> for more information.</p>
````````````````````````````````
```````````````````````````````` example
Visit www.commonmark.org.
Visit www.commonmark.org/a.b.
.
<p>Visit <a href="http://www.commonmark.org">www.commonmark.org</a>.</p>
<p>Visit <a href="http://www.commonmark.org/a.b">www.commonmark.org/a.b</a>.</p>
````````````````````````````````
```````````````````````````````` example
www.google.com/search?q=Markup+(business)
(www.google.com/search?q=Markup+(business))
.
<p><a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a></p>
<p>(<a href="http://www.google.com/search?q=Markup+(business)">www.google.com/search?q=Markup+(business)</a>)</p>
````````````````````````````````
```````````````````````````````` example
www.google.com/search?q=commonmark&hl=en
www.google.com/search?q=commonmark&hl;
.
<p><a href="http://www.google.com/search?q=commonmark&amp;hl=en">www.google.com/search?q=commonmark&amp;hl=en</a></p>
<p><a href="http://www.google.com/search?q=commonmark">www.google.com/search?q=commonmark</a>&amp;hl;</p>
````````````````````````````````
```````````````````````````````` example
www.commonmark.org/he<lp
.
<p><a href="http://www.commonmark.org/he">www.commonmark.org/he</a>&lt;lp</p>
````````````````````````````````
```````````````````````````````` example
http://commonmark.org
(Visit https://encrypted.google.com/search?q=Markup+(business))
Anonymous FTP is available at ftp://foo.bar.baz.
.
<p><a href="http://commonmark.org">http://commonmark.org</a></p>
<p>(Visit <a href="https://encrypted.google.com/search?q=Markup+(business)">https://encrypted.google.com/search?q=Markup+(business)</a>)</p>
<p>Anonymous FTP is available at <a href="ftp://foo.bar.baz">ftp://foo.bar.baz</a>.</p>
````````````````````````````````
### Valid Domain Tests
Domain names that have empty segments won't be matched
```````````````````````````````` example
www..
www..com
http://test.
http://.test
http://.
http://..
ftp://test.
ftp://.test
mailto:email@test.
mailto:email@.test
.
<p>www..
www..com
http://test.
http://.test
http://.
http://..
ftp://test.
ftp://.test
mailto:email@test.
mailto:email@.test</p>
````````````````````````````````
Domain names with too few segments won't be matched
```````````````````````````````` example
www
www.com
http://test
ftp://test
mailto:email@test
.
<p>www
www.com
http://test
ftp://test
mailto:email@test</p>
````````````````````````````````
Domain names that contain an underscores in the last two segments won't be matched
```````````````````````````````` example
www._test.foo.bar is okay, but www._test.foo is not
http://te_st.foo.bar is okay, as is http://test.foo_.bar.foo
But http://te_st.foo, http://test.foo_.bar and http://test._foo are not
ftp://test_.foo.bar is okay, but ftp://test.fo_o is not
mailto:email@_test.foo.bar is okay, but mailto:email@_test.foo is not
.
<p><a href="http://www._test.foo.bar">www._test.foo.bar</a> is okay, but www._test.foo is not</p>
<p><a href="http://te_st.foo.bar">http://te_st.foo.bar</a> is okay, as is <a href="http://test.foo_.bar.foo">http://test.foo_.bar.foo</a></p>
<p>But http://te_st.foo, http://test.foo_.bar and http://test._foo are not</p>
<p><a href="ftp://test_.foo.bar">ftp://test_.foo.bar</a> is okay, but ftp://test.fo_o is not</p>
<p><a href="mailto:email@_test.foo.bar">email@_test.foo.bar</a> is okay, but mailto:email@_test.foo is not</p>
````````````````````````````````
Domain names that contain invalid characters (not AlphaNumberic, -, _ or .) won't be matched
```````````````````````````````` example
https://[your-domain]/api
.
<p>https://[your-domain]/api</p>
````````````````````````````````
Domain names followed by ?, : or # instead of / are matched
```````````````````````````````` example
https://github.com?
https://github.com?a
https://github.com#a
https://github.com:
https://github.com:443
.
<p><a href="https://github.com">https://github.com</a>?</p>
<p><a href="https://github.com?a">https://github.com?a</a></p>
<p><a href="https://github.com#a">https://github.com#a</a></p>
<p><a href="https://github.com">https://github.com</a>:</p>
<p><a href="https://github.com:443">https://github.com:443</a></p>
````````````````````````````````

View File

@@ -4,7 +4,7 @@ This section describes the different extensions supported:
## Custom Container
A custom container is similar to a fenced code block, but it is using the character `:` to declare a block (with at least 3 characters), and instead of generating a `<pre><code>...</code></pre>` it will generate a `<div>...</dib>` block.
A custom container is similar to a fenced code block, but it is using the character `:` to declare a block (with at least 3 characters), and instead of generating a `<pre><code>...</code></pre>` it will generate a `<div>...</div>` block.
```````````````````````````````` example
:::spoiler
@@ -109,4 +109,4 @@ Attributes can be attached to a inline custom container:
This is a text ::with special emphasis::{#myId .myemphasis}
.
<p>This is a text <span id="myId" class="myemphasis">with special emphasis</span></p>
````````````````````````````````
````````````````````````````````

View File

@@ -2,7 +2,7 @@
This section describes the different extensions supported:
## Footontes
## Footnotes
Allows footnotes using the following syntax (taken from pandoc example):
@@ -118,3 +118,24 @@ Here is a footnote[^1]. And another one[^2]. And a third one[^3]. And a fourth[^
</ol>
</div>
````````````````````````````````
A footnote link inside a list should work as well:
```````````````````````````````` example
- abc
- def[^1]
[^1]: Here is the footnote.
.
<ul>
<li>abc</li>
<li>def<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a></li>
</ul>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Here is the footnote.<a href="#fnref:1" class="footnote-back-ref">&#8617;</a></p></li>
</ol>
</div>
````````````````````````````````

View File

@@ -0,0 +1,189 @@
# Extensions
This section describes the different extensions supported:
## Globalization
Adds support for RTL content by adding `dir="rtl"` and `align="right` attributes to the appropriate html elements. Left to right text is not affected by this extension.
Whether a markdown block is marked as RTL or not is determined by the [first strong character](https://en.wikipedia.org/wiki/Bi-directional_text#Strong_characters) of the block.
**Note**: You might need to add `<meta charset="UTF-8">` to the head of the html file to be able to see the result correctly.
Headings and block quotes:
```````````````````````````````` example
# Fruits
In botany, a [fruit](https://en.wikipedia.org/wiki/Fruit) is the seed-bearing structure in flowering plants (also known as angiosperms) formed from the ovary after flowering.
> Fruits are good for health
-- Anonymous
# میوە
[میوە](https://ckb.wikipedia.org/wiki/%D9%85%DB%8C%D9%88%DB%95) یان مێوە بەروبوومی ڕوەکیە کە ڕوەکەکان ھەڵیان ئەگرن وەک بەرگێک بۆ تۆوەکانیان، بە زۆری جیادەکرێتەوە بە شیرینی یان ترشی لە تامدا و بە بوونی بڕێکی زۆر ئاو
> میوە بۆ تەندروستی باشە
-- نەزانراو
.
<h1 id="fruits">Fruits</h1>
<p>In botany, a <a href="https://en.wikipedia.org/wiki/Fruit">fruit</a> is the seed-bearing structure in flowering plants (also known as angiosperms) formed from the ovary after flowering.</p>
<blockquote>
<p>Fruits are good for health
-- Anonymous</p>
</blockquote>
<h1 id="section" dir="rtl">میوە</h1>
<p dir="rtl"><a href="https://ckb.wikipedia.org/wiki/%D9%85%DB%8C%D9%88%DB%95" dir="rtl">میوە</a> یان مێوە بەروبوومی ڕوەکیە کە ڕوەکەکان ھەڵیان ئەگرن وەک بەرگێک بۆ تۆوەکانیان، بە زۆری جیادەکرێتەوە بە شیرینی یان ترشی لە تامدا و بە بوونی بڕێکی زۆر ئاو</p>
<blockquote dir="rtl">
<p dir="rtl">میوە بۆ تەندروستی باشە
-- نەزانراو</p>
</blockquote>
````````````````````````````````
Lists:
```````````````````````````````` example
## Types of fruits
- Berries
- Strawberry
- kiwifruit
- Citrus
- Orange
- Lemon
## Examples of fruits :yum:
1. Apple
2. Banana
3. Orange
## Grocery List
- [X] 􏿽 Watermelon
- [X] Apricot
- [ ] Fig
## نموونەی میوە :yum:
1. ? سێو
2. 5 مۆز
3. 􏿽 پرتەقاڵ
## جۆرەکانی میوە
- توو
- فڕاولە
- کیوی
- مزرەمەنی
- پڕتەقاڵ
- لیمۆ
## لیستی کڕین
- [X] شووتی
- [X] قەیسی
- [ ] هەنجیر
.
<h2 id="types-of-fruits">Types of fruits</h2>
<ul>
<li>Berries
<ul>
<li>Strawberry</li>
<li>kiwifruit</li>
</ul>
</li>
<li>Citrus
<ul>
<li>Orange</li>
<li>Lemon</li>
</ul>
</li>
</ul>
<h2 id="examples-of-fruits">Examples of fruits 😋</h2>
<ol>
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ol>
<h2 id="grocery-list">Grocery List</h2>
<ul class="contains-task-list">
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> 􏿽 Watermelon</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Apricot</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Fig</li>
</ul>
<h2 id="section" dir="rtl">نموونەی میوە 😋</h2>
<ol dir="rtl">
<li>? سێو</li>
<li>5 مۆز</li>
<li>􏿽 پرتەقاڵ</li>
</ol>
<h2 id="section-1" dir="rtl">جۆرەکانی میوە</h2>
<ul dir="rtl">
<li>توو
<ul dir="rtl">
<li>فڕاولە</li>
<li>کیوی</li>
</ul>
</li>
<li>مزرەمەنی
<ul dir="rtl">
<li>پڕتەقاڵ</li>
<li>لیمۆ</li>
</ul>
</li>
</ul>
<h2 id="section-2" dir="rtl">لیستی کڕین</h2>
<ul class="contains-task-list" dir="rtl">
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> شووتی</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> قەیسی</li>
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> هەنجیر</li>
</ul>
````````````````````````````````
Tables:
```````````````````````````````` example
Nuitrion |Apple | Oranges
--|-- | --
Calories|52|47
Sugar|10g|9g
پێکهاتە |سێو | پڕتەقاڵ
--|-- | --
کالۆری|٥٢|٤٧
شەکر| ١٠گ|٩گ
.
<table>
<thead>
<tr>
<th>Nuitrion</th>
<th>Apple</th>
<th>Oranges</th>
</tr>
</thead>
<tbody>
<tr>
<td>Calories</td>
<td>52</td>
<td>47</td>
</tr>
<tr>
<td>Sugar</td>
<td>10g</td>
<td>9g</td>
</tr>
</tbody>
</table>
<table dir="rtl" align="right">
<thead>
<tr>
<th>پێکهاتە</th>
<th>سێو</th>
<th>پڕتەقاڵ</th>
</tr>
</thead>
<tbody>
<tr>
<td>کالۆری</td>
<td>٥٢</td>
<td>٤٧</td>
</tr>
<tr>
<td>شەکر</td>
<td>١٠گ</td>
<td>٩گ</td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -12,8 +12,14 @@ Allows to embed audio/video links to popular website:
![Video2](https://vimeo.com/8607834)
![Video3](https://sample.com/video.mp4)
![Audio4](https://music.yandex.ru/album/411845/track/4402274)
![Video5](https://ok.ru/video/26870090463)
.
<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>
<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>
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
````````````````````````````````

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<#/*
Copyright (c) Alexandre Mutel. All rights reserved.
This file is licensed under the BSD-Clause 2 license.
This file is licensed under the BSD-Clause 2 license.
See the license.txt file in the project root for more information.The MIT License (MIT)
This is a modified version of code released previously with the following license:
@@ -63,10 +63,12 @@ SOFTWARE.
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"),
new KeyValuePair<string, string>(Host.ResolvePath("GlobalizationSpecs.md"), "globalization+advanced+emojis"),
};
var emptyLines = false;
var displayEmptyLines = false;
#>
// Generated the <#= DateTime.Now #>
using System;
using NUnit.Framework;
@@ -148,7 +150,7 @@ private class Spec
public string Name
{
get { return string.Format("Example{0:000}", Example); }
get { return string.Format("{0}_Example{1:000}", SecHeadingCompact, Example); }
}
}

View File

@@ -47,12 +47,25 @@ It can end with three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
If the end front matter marker (`...` or `---`) is not present, it will render the `---` has a `<hr>`:
```````````````````````````````` example
---
this: is a frontmatter
This is a text
.
<hr />
<p>this: is a frontmatter
This is a text</p>
````````````````````````````````
It expects exactly three dots `...`:
```````````````````````````````` example
@@ -61,9 +74,13 @@ this: is a frontmatter
....
This is a text
.
<hr />
<p>this: is a frontmatter
....
This is a text</p>
````````````````````````````````
Front matter ends with the first line containing three dots `...` or three dashes `...`:
Front matter ends with the first line containing three dots `...` or three dashes `---`:
```````````````````````````````` example
---

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -39,7 +39,7 @@ namespace Markdig.Tests
{
var text = new StringSlice(uri);
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out link, true));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}

View File

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

View File

@@ -396,6 +396,43 @@ This is a last line";
AssertNormalizeNoTrim(input);
}
[Test]
public void TaskLists()
{
AssertNormalizeNoTrim("- [X] This is done");
AssertNormalizeNoTrim("- [x] This is done",
"- [X] This is done");
AssertNormalizeNoTrim("- [ ] This is not done");
// ignore
AssertNormalizeNoTrim("[x] This is not a task list");
AssertNormalizeNoTrim("[ ] This is not a task list");
}
[Test]
public void JiraLinks()
{
AssertNormalizeNoTrim("FOO-1234");
AssertNormalizeNoTrim("AB-1");
AssertNormalizeNoTrim("**Hello World AB-1**");
}
[Test]
public void AutoLinks()
{
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from [http://example.com/foo](http://example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from [www.example.com/foo](http://www.example.com/foo)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from ftp://example.com", "Hello from [ftp://example.com](ftp://example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from [hello@example.com](mailto:hello@example.com)", new NormalizeOptions() { ExpandAutoLinks = true, });
AssertNormalizeNoTrim("Hello from http://example.com/foo", "Hello from http://example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
AssertNormalizeNoTrim("Hello from www.example.com/foo", "Hello from http://www.example.com/foo", new NormalizeOptions() { ExpandAutoLinks = false, });
AssertNormalizeNoTrim("Hello from mailto:hello@example.com", "Hello from mailto:hello@example.com", new NormalizeOptions() { ExpandAutoLinks = false, });
}
private static void AssertSyntax(string expected, MarkdownObject syntax)
{
var writer = new StringWriter();
@@ -425,7 +462,13 @@ This is a last line";
input = NormText(input, trim);
expected = NormText(expected, trim);
var result = Markdown.Normalize(input, options);
var pipeline = new MarkdownPipelineBuilder()
.UseAutoLinks()
.UseJiraLinks(new Extensions.JiraLinks.JiraLinkOptions("https://jira.example.com"))
.UseTaskLists()
.Build();
var result = Markdown.Normalize(input, options, pipeline: pipeline);
result = NormText(result, trim);
Console.WriteLine("```````````````````Source");

View File

@@ -1,8 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
@@ -12,6 +13,34 @@ namespace Markdig.Tests
{
public class TestParser
{
[Test]
public void TestFixHang()
{
var input = File.ReadAllText(Path.Combine(Path.GetDirectoryName(typeof(TestParser).Assembly.Location), "hang.md"));
var html = Markdown.ToHtml(input);
}
[Test]
public void TestInvalidHtmlEntity()
{
var input = "9&ddr;&*&ddr;&de<64><65>__";
TestSpec(input, "<p>9&amp;ddr;&amp;*&amp;ddr;&amp;de<64><65>__</p>");
}
[Test]
public void TestInvalidCharacterHandling()
{
var input = File.ReadAllText(Path.Combine(Path.GetDirectoryName(typeof(TestParser).Assembly.Location), "ArgumentOutOfRangeException.md"));
var html = Markdown.ToHtml(input);
}
[Test]
public void TestInvalidCodeEscape()
{
var input = "```**Header** ";
var html = Markdown.ToHtml(input);
}
[Test]
public void TestEmphasisAndHtmlEntity()
{
@@ -19,6 +48,25 @@ namespace Markdig.Tests
TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
}
[Test]
public void TestThematicInsideCodeBlockInsideList()
{
var input = @"1. In the :
```
Id DisplayName Description
-- ----------- -----------
62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ...
```";
TestSpec(input, @"<ol>
<li><p>In the :</p>
<pre><code>Id DisplayName Description
-- ----------- -----------
62375ab9-6b52-47ed-826b-58e47e0e304b Group.Unified ...
</code></pre></li>
</ol>");
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null)
{
foreach (var pipeline in GetPipeline(extensions))

View File

@@ -9,9 +9,30 @@ namespace Markdig.Tests
public void TestPlain()
{
var markdownText = "*Hello*, [world](http://example.com)!";
var expected = "Hello, world!";
var expected = "Hello, world!\n";
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(/* markdownText: */ "foo bar", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "foo\nbar", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "*foo\nbar*", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "[foo\nbar](http://example.com)", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "<http://foo.bar.baz>", /* expected: */ "http://foo.bar.baz\n")]
[TestCase(/* markdownText: */ "# foo bar", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "# foo\nbar", /* expected: */ "foo\nbar\n")]
[TestCase(/* markdownText: */ "> foo", /* expected: */ "foo\n")]
[TestCase(/* markdownText: */ "> foo\nbar\n> baz", /* expected: */ "foo\nbar\nbaz\n")]
[TestCase(/* markdownText: */ "`foo`", /* expected: */ "foo\n")]
[TestCase(/* markdownText: */ "`foo\nbar`", /* expected: */ "foo bar\n")] // new line within codespan is treated as whitespace (Example317)
[TestCase(/* markdownText: */ "```\nfoo bar\n```", /* expected: */ "foo bar\n")]
[TestCase(/* markdownText: */ "- foo\n- bar\n- baz", /* expected: */ "foo\nbar\nbaz\n")]
public void TestPlainEnsureNewLine(string markdownText, string expected)
{
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -12,6 +12,14 @@ namespace Markdig.Tests
[TestFixture]
public class TestPlayParser
{
[Test]
public void TestLink()
{
var doc = Markdown.Parse("There is a ![link](/yoyo)");
var link = doc.Descendants<ParagraphBlock>().SelectMany(x => x.Inline.Descendants<LinkInline>()).FirstOrDefault(l => l.IsImage);
Assert.AreEqual("/yoyo", link?.Url);
}
[Test]
public void TestListBug2()
{

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Markdig.Extensions.Footnotes;
using Markdig.Helpers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
@@ -140,6 +141,48 @@ literal ( 0, 2) 2-3
emphasis ( 0, 4) 4-9
literal ( 0, 6) 6-7
");
}
[Test]
public void TestFootnoteLinkReferenceDefinition()
{
// 01 2 345678
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants().OfType<FootnoteLinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(footnote);
Assert.AreEqual(2, footnote.Line);
Assert.AreEqual(new SourceSpan(4, 7), footnote.Span);
Assert.AreEqual(new SourceSpan(5, 6), footnote.LabelSpan);
}
[Test]
public void TestLinkReferenceDefinition1()
{
// 0 1
// 0123456789012345
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(0, link.Line);
Assert.AreEqual(new SourceSpan(0, 14), link.Span);
Assert.AreEqual(new SourceSpan(1, 3), link.LabelSpan);
Assert.AreEqual(new SourceSpan(7, 9), link.UrlSpan);
Assert.AreEqual(new SourceSpan(11, 14), link.TitleSpan);
}
[Test]
public void TestLinkReferenceDefinition2()
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(2, link.Line);
Assert.AreEqual(new SourceSpan(4, 18), link.Span);
Assert.AreEqual(new SourceSpan(5, 7), link.LabelSpan);
Assert.AreEqual(new SourceSpan(11, 13), link.UrlSpan);
Assert.AreEqual(new SourceSpan(15, 18), link.TitleSpan);
}
[Test]
@@ -353,6 +396,42 @@ listitem ( 1, 0) 4-6
paragraph ( 1, 2) 6-6
literal ( 1, 2) 6-6
");
}
[Test]
public void TestListBlock2()
{
string test = @"
1. Foo
9. Bar
5. Foo
6. Bar
987123. FooBar";
test = test.Replace("\r\n", "\n");
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<ListBlock>().FirstOrDefault();
Assert.NotNull(list);
Assert.AreEqual(1, list.Line);
Assert.True(list.IsOrdered);
List<ListItemBlock> items = list.Cast<ListItemBlock>().ToList();
Assert.AreEqual(5, items.Count);
// Test orders
Assert.AreEqual(1, items[0].Order);
Assert.AreEqual(9, items[1].Order);
Assert.AreEqual(5, items[2].Order);
Assert.AreEqual(6, items[3].Order);
Assert.AreEqual(987123, items[4].Order);
// Test positions
for (int i = 0; i < 4; i++)
{
Assert.AreEqual(i + 1, items[i].Line);
Assert.AreEqual(1 + (i * 7), items[i].Span.Start);
Assert.AreEqual(6, items[i].Span.Length);
}
Assert.AreEqual(5, items[4].Line);
Assert.AreEqual(new SourceSpan(29, 42), items[4].Span);
}
[Test]

BIN
src/Markdig.Tests/hang.md Normal file

Binary file not shown.

View File

@@ -19,6 +19,6 @@
},
"dependencies": {
"NUnit": "3.2.0",
"NUnit3TestAdapter": "3.2.0"
"NUnit3TestAdapter": "3.9.0"
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Text;
using Microsoft.AspNetCore.Mvc;
@@ -6,6 +6,13 @@ namespace Markdig.WebApp
{
public class ApiController : Controller
{
[HttpGet()]
[Route("")]
public string Empty()
{
return string.Empty;
}
// GET api/to_html?text=xxx&extensions=advanced
[Route("api/to_html")]
[HttpGet()]

View File

@@ -0,0 +1,7 @@
{
"ProviderId": "Microsoft.ApplicationInsights.ConnectedService.ConnectedServiceProvider",
"Version": "8.9.809.2",
"GettingStartedDocument": {
"Uri": "https://go.microsoft.com/fwlink/?LinkID=798432"
}
}

View File

@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netcoreapp2.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>
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<ApplicationInsightsResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsResourceId>
<ApplicationInsightsAnnotationResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsAnnotationResourceId>
</PropertyGroup>
<ItemGroup>
@@ -21,16 +22,20 @@
</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" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Connected Services" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -13,6 +13,7 @@ namespace Markdig.WebApp
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseApplicationInsights()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -46,10 +46,6 @@ namespace Markdig.WebApp
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseMvc();
}
}

View File

@@ -1,4 +1,4 @@
{
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
@@ -6,5 +6,8 @@
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationInsights": {
"InstrumentationKey": "5d12f113-76b2-41fe-a35a-db454b104bf9"
}
}
}

View File

@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-*" PrivateAssets="All"/>
</ItemGroup>
</Project>

View File

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

View File

@@ -12,7 +12,7 @@ namespace Markdig.Extensions.AutoIdentifiers
public enum AutoIdentifierOptions
{
/// <summary>
/// No options
/// No options: does not apply any additional formatting and/or transformations.
/// </summary>
None = 0,
@@ -21,11 +21,6 @@ namespace Markdig.Extensions.AutoIdentifiers
/// </summary>
Default = AutoLink | AllowOnlyAscii,
/// <summary>
/// Renders auto identifiers like GitHub.
/// </summary>
GitHub = Default | KeepOpeningDigits | DiscardDots,
/// <summary>
/// Allows to link to a header by using the same text as the header for the link label. Default is <c>true</c>
/// </summary>
@@ -37,13 +32,8 @@ namespace Markdig.Extensions.AutoIdentifiers
AllowOnlyAscii = 2,
/// <summary>
/// Allows to keep digits starting a heading (by default, it keeps only characters starting from the first letter)
/// Renders auto identifiers like GitHub.
/// </summary>
KeepOpeningDigits = 4,
/// <summary>
/// Discard dots when computing an identifier.
/// </summary>
DiscardDots = 8
GitHub = 4,
}
}
}

View File

@@ -1,9 +1,10 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Syntax.Inlines;
using Markdig.Renderers.Normalize;
using Markdig.Renderers.Normalize.Inlines;
namespace Markdig.Extensions.AutoLinks
{
@@ -13,17 +14,29 @@ namespace Markdig.Extensions.AutoLinks
/// <seealso cref="Markdig.IMarkdownExtension" />
public class AutoLinkExtension : IMarkdownExtension
{
public readonly string ValidPreviousCharacters;
public AutoLinkExtension(string validPreviousCharacters = AutoLinkParser.DefaultValidPreviousCharacters)
{
ValidPreviousCharacters = validPreviousCharacters;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<AutoLinkParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new AutoLinkParser());
pipeline.InlineParsers.Insert(0, new AutoLinkParser(ValidPreviousCharacters));
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var normalizeRenderer = renderer as NormalizeRenderer;
if (normalizeRenderer != null && !normalizeRenderer.ObjectRenderers.Contains<NormalizeAutoLinkRenderer>())
{
normalizeRenderer.ObjectRenderers.InsertBefore<LinkInlineRenderer>(new NormalizeAutoLinkRenderer());
}
}
}
}

View File

@@ -1,9 +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;
using System.Collections.Generic;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax.Inlines;
@@ -19,7 +19,7 @@ namespace Markdig.Extensions.AutoLinks
/// <summary>
/// Initializes a new instance of the <see cref="AutoLinkParser"/> class.
/// </summary>
public AutoLinkParser()
public AutoLinkParser(string validPreviousCharacters = DefaultValidPreviousCharacters)
{
OpeningCharacters = new char[]
{
@@ -28,13 +28,19 @@ namespace Markdig.Extensions.AutoLinks
'm', // for mailto:
'w', // for www.
};
ValidPreviousCharacters = validPreviousCharacters;
}
// All such recognized autolinks can only come at the beginning of a line, after whitespace, or any of the delimiting characters *, _, ~, and (.
public readonly string ValidPreviousCharacters;
public const string DefaultValidPreviousCharacters = "*_~(";
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())
if (!previousChar.IsWhiteSpaceOrZero() && ValidPreviousCharacters.IndexOf(previousChar) == -1)
{
return false;
}
@@ -47,22 +53,29 @@ namespace Markdig.Extensions.AutoLinks
}
var startPosition = slice.Start;
int domainOffset = 0;
var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (!slice.MatchLowercase("ttp://", 1) && !slice.MatchLowercase("ttps://", 1))
{
return false;
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
@@ -72,16 +85,17 @@ namespace Markdig.Extensions.AutoLinks
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1) || previousChar == '/') // We won't match http:/www. or /www.xxx
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = 4; // www.
break;
}
// Parse URL
string link;
if (!LinkHelper.TryParseUrl(ref slice, out link))
if (!LinkHelper.TryParseUrl(ref slice, out link, true))
{
return false;
}
@@ -124,19 +138,19 @@ namespace Markdig.Extensions.AutoLinks
}
break;
case 'm':
if (string.Equals(link, "mailto:", StringComparison.OrdinalIgnoreCase) || !link.Contains("@"))
int atIndex = link.IndexOf('@');
if (atIndex == -1 ||
atIndex == 7) // mailto:@ - no email part
{
return false;
return false;
}
domainOffset = atIndex + 1;
break;
}
case 'w':
// We require at least two .
if (link.Length <= "www.x.y".Length || link.IndexOf(".", 4, StringComparison.Ordinal) < 0)
{
return false;
}
break;
if (!LinkHelper.IsValidDomain(link, domainOffset))
{
return false;
}
int line;
@@ -151,7 +165,11 @@ namespace Markdig.Extensions.AutoLinks
Column = column,
Url = c == 'w' ? "http://" + link : link,
IsClosed = true,
IsAutoLink = true,
};
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
inline.Span.End = inline.Span.Start + link.Length - 1;
inline.UrlSpan = inline.Span;
inline.AppendChild(new LiteralInline()
@@ -159,7 +177,7 @@ namespace Markdig.Extensions.AutoLinks
Span = inline.Span,
Line = line,
Column = column,
Content = new StringSlice(slice.Text, startPosition, startPosition + link.Length - 1),
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
IsClosed = true
});
processor.Inline = inline;

View File

@@ -0,0 +1,29 @@
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks
{
public class NormalizeAutoLinkRenderer : NormalizeObjectRenderer<LinkInline>
{
public override bool Accept(RendererBase renderer, MarkdownObject obj)
{
if (base.Accept(renderer, obj))
{
var normalizeRenderer = renderer as NormalizeRenderer;
var link = obj as LinkInline;
return normalizeRenderer != null && link != null && !normalizeRenderer.Options.ExpandAutoLinks && link.IsAutoLink;
}
else
{
return false;
}
}
protected override void Write(NormalizeRenderer renderer, LinkInline obj)
{
renderer.Write(obj.Url);
}
}
}

View File

@@ -12,19 +12,19 @@ namespace Markdig.Extensions.Emoji
/// <seealso cref="Markdig.IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
private readonly bool _enableSmiley;
public EmojiExtension(bool enableSmiley = true)
{
_enableSmiley = enableSmiley;
EnableSmiley = enableSmiley;
}
public bool EnableSmiley { get; set; }
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new EmojiParser(_enableSmiley));
pipeline.InlineParsers.Insert(0, new EmojiParser(EnableSmiley));
}
}

View File

@@ -5,8 +5,8 @@
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Footnotes
{
/// <summary>
@@ -33,7 +33,8 @@ namespace Markdig.Extensions.Footnotes
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 || (!isContinue && processor.CurrentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(processor.CurrentContainer is Footnote)))
var currentContainer = processor.GetCurrentContainerOpened();
if (processor.IsCodeIndent || (!isContinue && currentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(currentContainer is FootnoteGroup)))
{
return BlockState.None;
}
@@ -74,7 +75,11 @@ namespace Markdig.Extensions.Footnotes
var linkRef = new FootnoteLinkReferenceDefinition()
{
Footnote = footnote,
CreateLinkInline = CreateLinkToFootnote
CreateLinkInline = CreateLinkToFootnote,
Line = processor.LineIndex,
Span = new SourceSpan(start, processor.Start - 2), // account for ]:
LabelSpan = labelSpan,
Label = label
};
processor.Document.SetLinkReferenceDefinition(footnote.Label, linkRef);
processor.NewBlocks.Push(footnote);

View File

@@ -0,0 +1,108 @@
// 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.Extensions.Tables;
using Markdig.Extensions.TaskLists;
using Markdig.Helpers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Collections.Generic;
namespace Markdig.Extensions.Globalization
{
/// <summary>
/// Extension to add support for RTL content.
/// </summary>
public class GlobalizationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Make sure we don't have a delegate twice
pipeline.DocumentProcessed -= Pipeline_DocumentProcessed;
pipeline.DocumentProcessed += Pipeline_DocumentProcessed;
}
private void Pipeline_DocumentProcessed(MarkdownDocument document)
{
foreach (var node in document.Descendants())
{
if (node is TableRow || node is TableCell || node is ListItemBlock)
continue;
if (ShouldBeRightToLeft(node))
{
var attributes = node.GetAttributes();
attributes.AddPropertyIfNotExist("dir", "rtl");
if (node is Table table)
{
attributes.AddPropertyIfNotExist("align", "right");
}
}
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}
private bool ShouldBeRightToLeft(MarkdownObject item)
{
if (item is IEnumerable<MarkdownObject> container)
{
foreach (var child in container)
{
// TaskList items contain an "X", which will cause
// the function to always return false.
if (child is TaskList)
continue;
return ShouldBeRightToLeft(child);
}
}
else if (item is LeafBlock leaf)
{
return ShouldBeRightToLeft(leaf.Inline);
}
else if (item is LiteralInline literal)
{
return StartsWithRtlCharacter(literal.ToString());
}
foreach (var descendant in item.Descendants())
{
if (descendant is ParagraphBlock p)
{
foreach (var i in p.Inline)
{
if (i is LiteralInline l)
{
return StartsWithRtlCharacter(l.ToString());
}
}
}
}
return false;
}
private bool StartsWithRtlCharacter(string text)
{
foreach (var c in CharHelper.ToUtf32(text))
{
if (CharHelper.IsRightToLeft(c))
return true;
else if (CharHelper.IsLeftToRight(c))
return false;
}
return false;
}
}
}

View File

@@ -1,8 +1,10 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Normalize.Inlines;
using Markdig.Renderers.Normalize;
namespace Markdig.Extensions.JiraLinks
{
@@ -30,7 +32,13 @@ namespace Markdig.Extensions.JiraLinks
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
// Nothing to setup, JiraLinks used a normal LinkInlineRenderer
// No HTML renderer required, since JiraLink type derives from InlineLink (which already has an HTML renderer)
var normalizeRenderer = renderer as NormalizeRenderer;
if (normalizeRenderer != null && !normalizeRenderer.ObjectRenderers.Contains<NormalizeJiraLinksRenderer>())
{
normalizeRenderer.ObjectRenderers.InsertBefore<LinkInlineRenderer>(new NormalizeJiraLinksRenderer());
}
}
}

View File

@@ -0,0 +1,18 @@
using Markdig.Renderers.Normalize;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Markdig.Extensions.JiraLinks
{
public class NormalizeJiraLinksRenderer : NormalizeObjectRenderer<JiraLink>
{
protected override void Write(NormalizeRenderer renderer, JiraLink obj)
{
renderer.Write(obj.ProjectKey);
renderer.Write("-");
renderer.Write(obj.Issue);
}
}
}

View File

@@ -1,8 +1,10 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Renderers.Html.Inlines;
@@ -47,88 +49,178 @@ namespace Markdig.Extensions.MediaLinks
private bool TryLinkInlineRenderer(HtmlRenderer renderer, LinkInline linkInline)
{
if (linkInline.IsImage && linkInline.Url != null)
if (!linkInline.IsImage || linkInline.Url == null)
{
Uri uri;
// Only process absolute Uri
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) && uri.IsAbsoluteUri)
return false;
}
Uri uri;
// Only process absolute Uri
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) || !uri.IsAbsoluteUri)
{
return false;
}
if (TryRenderIframeFromKnownProviders(uri, renderer, linkInline))
{
return true;
}
if (TryGuessAudioVideoFile(uri, renderer, linkInline))
{
return true;
}
return false;
}
private static HtmlAttributes GetHtmlAttributes(LinkInline linkInline)
{
var htmlAttributes = new HtmlAttributes();
var fromAttributes = linkInline.TryGetAttributes();
if (fromAttributes != null)
{
fromAttributes.CopyTo(htmlAttributes, false, false);
}
return htmlAttributes;
}
private bool TryGuessAudioVideoFile(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
{
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
// Otherwise try to detect if we have an audio/video from the file extension
string mimeType;
var lastDot = path.LastIndexOf('.');
if (lastDot >= 0 &&
Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out mimeType))
{
var htmlAttributes = GetHtmlAttributes(linkInline);
var isAudio = mimeType.StartsWith("audio");
var tagType = isAudio ? "audio" : "video";
renderer.Write($"<{tagType}");
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
if (!isAudio)
{
var htmlAttributes = new HtmlAttributes();
var fromAttributes = linkInline.TryGetAttributes();
if (fromAttributes != null)
{
fromAttributes.CopyTo(htmlAttributes, false, false);
}
// TODO: this code is not pluggable, so for now, we handle only the following web providers:
// - youtube
// - vimeo
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
string iFrameUrl = null;
if (uri.Host.StartsWith("www.youtube.com", StringComparison.OrdinalIgnoreCase))
{
var query = SplitQuery(uri);
if (query.Length > 0 && query[0].StartsWith("v="))
{
iFrameUrl = $"https://www.youtube.com/embed/{query[0].Substring(2)}";
}
}
else if (uri.Host.StartsWith("vimeo.com", StringComparison.OrdinalIgnoreCase))
{
var items = path.Split('/');
if (items.Length > 0)
{
iFrameUrl = $"https://player.vimeo.com/video/{items[items.Length - 1]}";
}
}
if (iFrameUrl != null)
{
renderer.Write($"<iframe src=\"{iFrameUrl}\"");
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
htmlAttributes.AddPropertyIfNotExist("allowfullscreen", null);
renderer.WriteAttributes(htmlAttributes);
renderer.Write("></iframe>");
return true;
}
else
{
// Otherwise try to detect if we have an audio/video from the file extension
string mimeType;
var lastDot = path.LastIndexOf('.');
if (lastDot >= 0 &&
Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out mimeType))
{
var isAudio = mimeType.StartsWith("audio");
var tagType = isAudio ? "audio" : "video";
renderer.Write($"<{tagType}");
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
if (!isAudio)
{
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
}
htmlAttributes.AddPropertyIfNotExist("controls", null);
renderer.WriteAttributes(htmlAttributes);
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
return true;
}
}
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
}
htmlAttributes.AddPropertyIfNotExist("controls", null);
renderer.WriteAttributes(htmlAttributes);
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
return true;
}
return false;
}
#region Known providers
private class KnownProvider
{
public string HostPrefix { get; set; }
public Func<Uri, string> Delegate { get; set; }
public bool AllowFullScreen { get; set; } = true; //Should be false for audio embedding
}
private static readonly List<KnownProvider> KnownHosts = new List<KnownProvider>()
{
new KnownProvider {HostPrefix = "www.youtube.com", Delegate = YouTube},
new KnownProvider {HostPrefix = "vimeo.com", Delegate = Vimeo},
new KnownProvider {HostPrefix = "music.yandex.ru", Delegate = Yandex, AllowFullScreen = false},
new KnownProvider {HostPrefix = "ok.ru", Delegate = Odnoklassniki},
};
private bool TryRenderIframeFromKnownProviders(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
{
var foundProvider =
KnownHosts
.Where(pair => uri.Host.StartsWith(pair.HostPrefix, StringComparison.OrdinalIgnoreCase)) // when host is match
.Select(provider =>
new
{
provider.AllowFullScreen,
Result = provider.Delegate(uri) // try to call delegate to get iframeUrl
}
)
.FirstOrDefault(provider => provider.Result != null); // use first success
if (foundProvider == null)
{
return false;
}
var htmlAttributes = GetHtmlAttributes(linkInline);
renderer.Write($"<iframe src=\"{foundProvider.Result}\"");
if(!string.IsNullOrEmpty(Options.Width))
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
if (!string.IsNullOrEmpty(Options.Height))
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
if (!string.IsNullOrEmpty(Options.Class))
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
if (foundProvider.AllowFullScreen)
{
htmlAttributes.AddPropertyIfNotExist("allowfullscreen", null);
}
renderer.WriteAttributes(htmlAttributes);
renderer.Write("></iframe>");
return true;
}
private static readonly string[] SplitAnd = {"&"};
private static string[] SplitQuery(Uri uri)
{
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
}
private static string YouTube(Uri uri)
{
var query = SplitQuery(uri);
return query.Length > 0 && query[0].StartsWith("v=")
? $"https://www.youtube.com/embed/{query[0].Substring(2)}"
: null;
}
private static string Vimeo(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://player.vimeo.com/video/{items[items.Length - 1]}" : null;
}
private static string Odnoklassniki(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://ok.ru/videoembed/{items[items.Length - 1]}" : null;
}
private static string Yandex(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
var albumKeyword
= items.Skip(0).FirstOrDefault();
var albumId
= items.Skip(1).FirstOrDefault();
var trackKeyword
= items.Skip(2).FirstOrDefault();
var trackId
= items.Skip(3).FirstOrDefault();
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
{
return null;
}
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
}
#endregion
}
}

View File

@@ -16,6 +16,7 @@ namespace Markdig.Extensions.MediaLinks
{
Width = "500";
Height = "281";
Class = "";
ExtensionToMimeType = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{".3gp", "video/3gpp"},
@@ -68,6 +69,7 @@ namespace Markdig.Extensions.MediaLinks
{".wma", "audio/x-ms-wma"},
{".wax", "audio/x-ms-wax"},
{".mid", "audio/midi"},
{".mp3", "audio/mpeg"},
{".mpga", "audio/mpeg"},
{".mp4a", "audio/mp4"},
{".ecelp4800", "audio/vnd.nuera.ecelp4800"},
@@ -86,6 +88,8 @@ namespace Markdig.Extensions.MediaLinks
public string Height { get; set; }
public string Class { get; set; }
public Dictionary<string, string> ExtensionToMimeType { get; }
}
}
}

View File

@@ -0,0 +1,14 @@
using Markdig.Renderers.Normalize;
namespace Markdig.Extensions.TaskLists
{
public class NormalizeTaskListRenderer : NormalizeObjectRenderer<TaskList>
{
protected override void Write(NormalizeRenderer renderer, TaskList obj)
{
renderer.Write("[");
renderer.Write(obj.Checked ? "X" : " ");
renderer.Write("]");
}
}
}

View File

@@ -1,9 +1,10 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
namespace Markdig.Extensions.TaskLists
{
@@ -28,6 +29,12 @@ namespace Markdig.Extensions.TaskLists
{
htmlRenderer.ObjectRenderers.AddIfNotAlready<HtmlTaskListRenderer>();
}
var normalizeRenderer = renderer as NormalizeRenderer;
if (normalizeRenderer != null)
{
normalizeRenderer.ObjectRenderers.AddIfNotAlready<NormalizeTaskListRenderer>();
}
}
}
}

View File

@@ -0,0 +1,34 @@
using Markdig.Renderers;
namespace Markdig.Extensions.TextRenderer
{
/// <summary>
/// Extension that allows setting line-endings for any IMarkdownRenderer
/// that inherits from <see cref="Markdig.Renderers.TextRendererBase"/>
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class ConfigureNewLineExtension : IMarkdownExtension
{
private readonly string newLine;
public ConfigureNewLineExtension(string newLine)
{
this.newLine = newLine;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var textRenderer = renderer as TextRendererBase;
if (textRenderer == null)
{
return;
}
textRenderer.Writer.NewLine = newLine;
}
}
}

View File

@@ -67,17 +67,55 @@ namespace Markdig.Extensions.Yaml
// this is a YAML front matter blcok
if (count == 3 && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
{
// Create a front matter block
var block = this.CreateFrontMatterBlock(processor);
block.Column = processor.Column;
block.Span.Start = 0;
block.Span.End = line.Start;
bool hasFullYamlFrontMatter = false;
// We make sure that there is a closing frontmatter somewhere in the document
// so here we work on the full document instead of just the line
var fullLine = new StringSlice(line.Text, line.Start, line.Text.Length - 1);
c = fullLine.CurrentChar;
while (c != '\0')
{
c = fullLine.NextChar();
if (c == '\n' || c == '\r')
{
var nc = fullLine.PeekChar();
if (c == '\r' && nc == '\n')
{
c = fullLine.NextChar();
}
nc = fullLine.PeekChar();
if (nc == '-')
{
if (fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
{
hasFullYamlFrontMatter = true;
break;
}
}
else if (nc == '.')
{
if (fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument()))
{
hasFullYamlFrontMatter = true;
break;
}
}
}
}
// Store the number of matched string into the context
processor.NewBlocks.Push(block);
if (hasFullYamlFrontMatter)
{
// Create a front matter block
var block = this.CreateFrontMatterBlock(processor);
block.Column = processor.Column;
block.Span.Start = 0;
block.Span.End = line.Start;
// Discard the current line as it is already parsed
return BlockState.ContinueDiscard;
// Store the number of matched string into the context
processor.NewBlocks.Push(block);
// Discard the current line as it is already parsed
return BlockState.ContinueDiscard;
}
}
return BlockState.None;

View File

@@ -1,6 +1,37 @@
// 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.
// The IsHighSurrogate, IsLowSurrogate and ConvertToUtf32 methods are copied from
// .Net Core source code which is under MIT license. They are copied here because
// they don't exist in `portable40-net40+sl5+win8+wp8+wpa81`, We probably should remove them
// once we dropped support for that target platform and use the official .Net methods.
//The MIT License(MIT)
//Copyright(c) .NET Foundation and Contributors
//All rights reserved.
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
@@ -20,8 +51,16 @@ namespace Markdig.Helpers
public const string ZeroSafeString = "\uFFFD";
private const char HighSurrogateStart = '\ud800';
private const char HighSurrogateEnd = '\udbff';
private const char LowSurrogateStart = '\udc00';
private const char LowSurrogateEnd = '\udfff';
// The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
private const int UnicodePlane01Start = 0x10000;
// We don't support LCDM
private static IDictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
private static readonly Dictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
private static readonly char[] punctuationExceptions = { '', '-', '†', '‡' };
@@ -306,5 +345,419 @@ namespace Markdig.Helpers
{
return ".!#$%&'*+/=?^_`{|}~-+.~".IndexOf(c) >= 0;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsHighSurrogate(char c)
{
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsLowSurrogate(char c)
{
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
}
public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
{
if (!IsHighSurrogate(highSurrogate))
{
throw new ArgumentOutOfRangeException(nameof(highSurrogate), "Invalid high surrogate");
}
if (!IsLowSurrogate(lowSurrogate))
{
throw new ArgumentOutOfRangeException(nameof(lowSurrogate), "Invalid low surrogate");
}
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
}
public static IEnumerable<int> ToUtf32(string text)
{
for (int i = 0; i < text.Length; i++)
{
if (IsHighSurrogate(text[i]) && i < text.Length - 1 && IsLowSurrogate(text[i + 1]))
{
yield return ConvertToUtf32(text[i], text[i + 1]);
}
else
{
yield return text[i];
}
}
}
public static bool IsRightToLeft(int c)
{
// Generated from Table D.1 of RFC3454
// http://www.ietf.org/rfc/rfc3454.txt
// Probably should use a binary search approach
return c >= 0x0005D0 && c <= 0x0005EA ||
c >= 0x0005F0 && c <= 0x0005F4 ||
c >= 0x000621 && c <= 0x00063A ||
c >= 0x000640 && c <= 0x00064A ||
c >= 0x00066D && c <= 0x00066F ||
c >= 0x000671 && c <= 0x0006D5 ||
c >= 0x0006E5 && c <= 0x0006E6 ||
c >= 0x0006FA && c <= 0x0006FE ||
c >= 0x000700 && c <= 0x00070D ||
c >= 0x000712 && c <= 0x00072C ||
c >= 0x000780 && c <= 0x0007A5 ||
c >= 0x00FB1F && c <= 0x00FB28 ||
c >= 0x00FB2A && c <= 0x00FB36 ||
c >= 0x00FB38 && c <= 0x00FB3C ||
c >= 0x00FB40 && c <= 0x00FB41 ||
c >= 0x00FB43 && c <= 0x00FB44 ||
c >= 0x00FB46 && c <= 0x00FBB1 ||
c >= 0x00FBD3 && c <= 0x00FD3D ||
c >= 0x00FD50 && c <= 0x00FD8F ||
c >= 0x00FD92 && c <= 0x00FDC7 ||
c >= 0x00FDF0 && c <= 0x00FDFC ||
c >= 0x00FE70 && c <= 0x00FE74 ||
c >= 0x00FE76 && c <= 0x00FEFC ||
c == 0x0005BE || c == 0x0005C0 ||
c == 0x0005C3 || c == 0x00061B ||
c == 0x00061F || c == 0x0006DD ||
c == 0x000710 || c == 0x0007B1 ||
c == 0x00200F || c == 0x00FB1D ||
c == 0x00FB3E;
}
public static bool IsLeftToRight(int c)
{
// Generated from Table D.2 of RFC3454
// http://www.ietf.org/rfc/rfc3454.txt
// Probably should use a binary search approach
return c >= 0x000041 && c <= 0x00005A ||
c >= 0x000061 && c <= 0x00007A ||
c >= 0x0000C0 && c <= 0x0000D6 ||
c >= 0x0000D8 && c <= 0x0000F6 ||
c >= 0x0000F8 && c <= 0x000220 ||
c >= 0x000222 && c <= 0x000233 ||
c >= 0x000250 && c <= 0x0002AD ||
c >= 0x0002B0 && c <= 0x0002B8 ||
c >= 0x0002BB && c <= 0x0002C1 ||
c >= 0x0002D0 && c <= 0x0002D1 ||
c >= 0x0002E0 && c <= 0x0002E4 ||
c >= 0x000388 && c <= 0x00038A ||
c >= 0x00038E && c <= 0x0003A1 ||
c >= 0x0003A3 && c <= 0x0003CE ||
c >= 0x0003D0 && c <= 0x0003F5 ||
c >= 0x000400 && c <= 0x000482 ||
c >= 0x00048A && c <= 0x0004CE ||
c >= 0x0004D0 && c <= 0x0004F5 ||
c >= 0x0004F8 && c <= 0x0004F9 ||
c >= 0x000500 && c <= 0x00050F ||
c >= 0x000531 && c <= 0x000556 ||
c >= 0x000559 && c <= 0x00055F ||
c >= 0x000561 && c <= 0x000587 ||
c >= 0x000905 && c <= 0x000939 ||
c >= 0x00093D && c <= 0x000940 ||
c >= 0x000949 && c <= 0x00094C ||
c >= 0x000958 && c <= 0x000961 ||
c >= 0x000964 && c <= 0x000970 ||
c >= 0x000982 && c <= 0x000983 ||
c >= 0x000985 && c <= 0x00098C ||
c >= 0x00098F && c <= 0x000990 ||
c >= 0x000993 && c <= 0x0009A8 ||
c >= 0x0009AA && c <= 0x0009B0 ||
c >= 0x0009B6 && c <= 0x0009B9 ||
c >= 0x0009BE && c <= 0x0009C0 ||
c >= 0x0009C7 && c <= 0x0009C8 ||
c >= 0x0009CB && c <= 0x0009CC ||
c >= 0x0009DC && c <= 0x0009DD ||
c >= 0x0009DF && c <= 0x0009E1 ||
c >= 0x0009E6 && c <= 0x0009F1 ||
c >= 0x0009F4 && c <= 0x0009FA ||
c >= 0x000A05 && c <= 0x000A0A ||
c >= 0x000A0F && c <= 0x000A10 ||
c >= 0x000A13 && c <= 0x000A28 ||
c >= 0x000A2A && c <= 0x000A30 ||
c >= 0x000A32 && c <= 0x000A33 ||
c >= 0x000A35 && c <= 0x000A36 ||
c >= 0x000A38 && c <= 0x000A39 ||
c >= 0x000A3E && c <= 0x000A40 ||
c >= 0x000A59 && c <= 0x000A5C ||
c >= 0x000A66 && c <= 0x000A6F ||
c >= 0x000A72 && c <= 0x000A74 ||
c >= 0x000A85 && c <= 0x000A8B ||
c >= 0x000A8F && c <= 0x000A91 ||
c >= 0x000A93 && c <= 0x000AA8 ||
c >= 0x000AAA && c <= 0x000AB0 ||
c >= 0x000AB2 && c <= 0x000AB3 ||
c >= 0x000AB5 && c <= 0x000AB9 ||
c >= 0x000ABD && c <= 0x000AC0 ||
c >= 0x000ACB && c <= 0x000ACC ||
c >= 0x000AE6 && c <= 0x000AEF ||
c >= 0x000B02 && c <= 0x000B03 ||
c >= 0x000B05 && c <= 0x000B0C ||
c >= 0x000B0F && c <= 0x000B10 ||
c >= 0x000B13 && c <= 0x000B28 ||
c >= 0x000B2A && c <= 0x000B30 ||
c >= 0x000B32 && c <= 0x000B33 ||
c >= 0x000B36 && c <= 0x000B39 ||
c >= 0x000B3D && c <= 0x000B3E ||
c >= 0x000B47 && c <= 0x000B48 ||
c >= 0x000B4B && c <= 0x000B4C ||
c >= 0x000B5C && c <= 0x000B5D ||
c >= 0x000B5F && c <= 0x000B61 ||
c >= 0x000B66 && c <= 0x000B70 ||
c >= 0x000B85 && c <= 0x000B8A ||
c >= 0x000B8E && c <= 0x000B90 ||
c >= 0x000B92 && c <= 0x000B95 ||
c >= 0x000B99 && c <= 0x000B9A ||
c >= 0x000B9E && c <= 0x000B9F ||
c >= 0x000BA3 && c <= 0x000BA4 ||
c >= 0x000BA8 && c <= 0x000BAA ||
c >= 0x000BAE && c <= 0x000BB5 ||
c >= 0x000BB7 && c <= 0x000BB9 ||
c >= 0x000BBE && c <= 0x000BBF ||
c >= 0x000BC1 && c <= 0x000BC2 ||
c >= 0x000BC6 && c <= 0x000BC8 ||
c >= 0x000BCA && c <= 0x000BCC ||
c >= 0x000BE7 && c <= 0x000BF2 ||
c >= 0x000C01 && c <= 0x000C03 ||
c >= 0x000C05 && c <= 0x000C0C ||
c >= 0x000C0E && c <= 0x000C10 ||
c >= 0x000C12 && c <= 0x000C28 ||
c >= 0x000C2A && c <= 0x000C33 ||
c >= 0x000C35 && c <= 0x000C39 ||
c >= 0x000C41 && c <= 0x000C44 ||
c >= 0x000C60 && c <= 0x000C61 ||
c >= 0x000C66 && c <= 0x000C6F ||
c >= 0x000C82 && c <= 0x000C83 ||
c >= 0x000C85 && c <= 0x000C8C ||
c >= 0x000C8E && c <= 0x000C90 ||
c >= 0x000C92 && c <= 0x000CA8 ||
c >= 0x000CAA && c <= 0x000CB3 ||
c >= 0x000CB5 && c <= 0x000CB9 ||
c >= 0x000CC0 && c <= 0x000CC4 ||
c >= 0x000CC7 && c <= 0x000CC8 ||
c >= 0x000CCA && c <= 0x000CCB ||
c >= 0x000CD5 && c <= 0x000CD6 ||
c >= 0x000CE0 && c <= 0x000CE1 ||
c >= 0x000CE6 && c <= 0x000CEF ||
c >= 0x000D02 && c <= 0x000D03 ||
c >= 0x000D05 && c <= 0x000D0C ||
c >= 0x000D0E && c <= 0x000D10 ||
c >= 0x000D12 && c <= 0x000D28 ||
c >= 0x000D2A && c <= 0x000D39 ||
c >= 0x000D3E && c <= 0x000D40 ||
c >= 0x000D46 && c <= 0x000D48 ||
c >= 0x000D4A && c <= 0x000D4C ||
c >= 0x000D60 && c <= 0x000D61 ||
c >= 0x000D66 && c <= 0x000D6F ||
c >= 0x000D82 && c <= 0x000D83 ||
c >= 0x000D85 && c <= 0x000D96 ||
c >= 0x000D9A && c <= 0x000DB1 ||
c >= 0x000DB3 && c <= 0x000DBB ||
c >= 0x000DC0 && c <= 0x000DC6 ||
c >= 0x000DCF && c <= 0x000DD1 ||
c >= 0x000DD8 && c <= 0x000DDF ||
c >= 0x000DF2 && c <= 0x000DF4 ||
c >= 0x000E01 && c <= 0x000E30 ||
c >= 0x000E32 && c <= 0x000E33 ||
c >= 0x000E40 && c <= 0x000E46 ||
c >= 0x000E4F && c <= 0x000E5B ||
c >= 0x000E81 && c <= 0x000E82 ||
c >= 0x000E87 && c <= 0x000E88 ||
c >= 0x000E94 && c <= 0x000E97 ||
c >= 0x000E99 && c <= 0x000E9F ||
c >= 0x000EA1 && c <= 0x000EA3 ||
c >= 0x000EAA && c <= 0x000EAB ||
c >= 0x000EAD && c <= 0x000EB0 ||
c >= 0x000EB2 && c <= 0x000EB3 ||
c >= 0x000EC0 && c <= 0x000EC4 ||
c >= 0x000ED0 && c <= 0x000ED9 ||
c >= 0x000EDC && c <= 0x000EDD ||
c >= 0x000F00 && c <= 0x000F17 ||
c >= 0x000F1A && c <= 0x000F34 ||
c >= 0x000F3E && c <= 0x000F47 ||
c >= 0x000F49 && c <= 0x000F6A ||
c >= 0x000F88 && c <= 0x000F8B ||
c >= 0x000FBE && c <= 0x000FC5 ||
c >= 0x000FC7 && c <= 0x000FCC ||
c >= 0x001000 && c <= 0x001021 ||
c >= 0x001023 && c <= 0x001027 ||
c >= 0x001029 && c <= 0x00102A ||
c >= 0x001040 && c <= 0x001057 ||
c >= 0x0010A0 && c <= 0x0010C5 ||
c >= 0x0010D0 && c <= 0x0010F8 ||
c >= 0x001100 && c <= 0x001159 ||
c >= 0x00115F && c <= 0x0011A2 ||
c >= 0x0011A8 && c <= 0x0011F9 ||
c >= 0x001200 && c <= 0x001206 ||
c >= 0x001208 && c <= 0x001246 ||
c >= 0x00124A && c <= 0x00124D ||
c >= 0x001250 && c <= 0x001256 ||
c >= 0x00125A && c <= 0x00125D ||
c >= 0x001260 && c <= 0x001286 ||
c >= 0x00128A && c <= 0x00128D ||
c >= 0x001290 && c <= 0x0012AE ||
c >= 0x0012B2 && c <= 0x0012B5 ||
c >= 0x0012B8 && c <= 0x0012BE ||
c >= 0x0012C2 && c <= 0x0012C5 ||
c >= 0x0012C8 && c <= 0x0012CE ||
c >= 0x0012D0 && c <= 0x0012D6 ||
c >= 0x0012D8 && c <= 0x0012EE ||
c >= 0x0012F0 && c <= 0x00130E ||
c >= 0x001312 && c <= 0x001315 ||
c >= 0x001318 && c <= 0x00131E ||
c >= 0x001320 && c <= 0x001346 ||
c >= 0x001348 && c <= 0x00135A ||
c >= 0x001361 && c <= 0x00137C ||
c >= 0x0013A0 && c <= 0x0013F4 ||
c >= 0x001401 && c <= 0x001676 ||
c >= 0x001681 && c <= 0x00169A ||
c >= 0x0016A0 && c <= 0x0016F0 ||
c >= 0x001700 && c <= 0x00170C ||
c >= 0x00170E && c <= 0x001711 ||
c >= 0x001720 && c <= 0x001731 ||
c >= 0x001735 && c <= 0x001736 ||
c >= 0x001740 && c <= 0x001751 ||
c >= 0x001760 && c <= 0x00176C ||
c >= 0x00176E && c <= 0x001770 ||
c >= 0x001780 && c <= 0x0017B6 ||
c >= 0x0017BE && c <= 0x0017C5 ||
c >= 0x0017C7 && c <= 0x0017C8 ||
c >= 0x0017D4 && c <= 0x0017DA ||
c >= 0x0017E0 && c <= 0x0017E9 ||
c >= 0x001810 && c <= 0x001819 ||
c >= 0x001820 && c <= 0x001877 ||
c >= 0x001880 && c <= 0x0018A8 ||
c >= 0x001E00 && c <= 0x001E9B ||
c >= 0x001EA0 && c <= 0x001EF9 ||
c >= 0x001F00 && c <= 0x001F15 ||
c >= 0x001F18 && c <= 0x001F1D ||
c >= 0x001F20 && c <= 0x001F45 ||
c >= 0x001F48 && c <= 0x001F4D ||
c >= 0x001F50 && c <= 0x001F57 ||
c >= 0x001F5F && c <= 0x001F7D ||
c >= 0x001F80 && c <= 0x001FB4 ||
c >= 0x001FB6 && c <= 0x001FBC ||
c >= 0x001FC2 && c <= 0x001FC4 ||
c >= 0x001FC6 && c <= 0x001FCC ||
c >= 0x001FD0 && c <= 0x001FD3 ||
c >= 0x001FD6 && c <= 0x001FDB ||
c >= 0x001FE0 && c <= 0x001FEC ||
c >= 0x001FF2 && c <= 0x001FF4 ||
c >= 0x001FF6 && c <= 0x001FFC ||
c >= 0x00210A && c <= 0x002113 ||
c >= 0x002119 && c <= 0x00211D ||
c >= 0x00212A && c <= 0x00212D ||
c >= 0x00212F && c <= 0x002131 ||
c >= 0x002133 && c <= 0x002139 ||
c >= 0x00213D && c <= 0x00213F ||
c >= 0x002145 && c <= 0x002149 ||
c >= 0x002160 && c <= 0x002183 ||
c >= 0x002336 && c <= 0x00237A ||
c >= 0x00249C && c <= 0x0024E9 ||
c >= 0x003005 && c <= 0x003007 ||
c >= 0x003021 && c <= 0x003029 ||
c >= 0x003031 && c <= 0x003035 ||
c >= 0x003038 && c <= 0x00303C ||
c >= 0x003041 && c <= 0x003096 ||
c >= 0x00309D && c <= 0x00309F ||
c >= 0x0030A1 && c <= 0x0030FA ||
c >= 0x0030FC && c <= 0x0030FF ||
c >= 0x003105 && c <= 0x00312C ||
c >= 0x003131 && c <= 0x00318E ||
c >= 0x003190 && c <= 0x0031B7 ||
c >= 0x0031F0 && c <= 0x00321C ||
c >= 0x003220 && c <= 0x003243 ||
c >= 0x003260 && c <= 0x00327B ||
c >= 0x00327F && c <= 0x0032B0 ||
c >= 0x0032C0 && c <= 0x0032CB ||
c >= 0x0032D0 && c <= 0x0032FE ||
c >= 0x003300 && c <= 0x003376 ||
c >= 0x00337B && c <= 0x0033DD ||
c >= 0x0033E0 && c <= 0x0033FE ||
c >= 0x003400 && c <= 0x004DB5 ||
c >= 0x004E00 && c <= 0x009FA5 ||
c >= 0x00A000 && c <= 0x00A48C ||
c >= 0x00AC00 && c <= 0x00D7A3 ||
c >= 0x00D800 && c <= 0x00FA2D ||
c >= 0x00FA30 && c <= 0x00FA6A ||
c >= 0x00FB00 && c <= 0x00FB06 ||
c >= 0x00FB13 && c <= 0x00FB17 ||
c >= 0x00FF21 && c <= 0x00FF3A ||
c >= 0x00FF41 && c <= 0x00FF5A ||
c >= 0x00FF66 && c <= 0x00FFBE ||
c >= 0x00FFC2 && c <= 0x00FFC7 ||
c >= 0x00FFCA && c <= 0x00FFCF ||
c >= 0x00FFD2 && c <= 0x00FFD7 ||
c >= 0x00FFDA && c <= 0x00FFDC ||
c >= 0x010300 && c <= 0x01031E ||
c >= 0x010320 && c <= 0x010323 ||
c >= 0x010330 && c <= 0x01034A ||
c >= 0x010400 && c <= 0x010425 ||
c >= 0x010428 && c <= 0x01044D ||
c >= 0x01D000 && c <= 0x01D0F5 ||
c >= 0x01D100 && c <= 0x01D126 ||
c >= 0x01D12A && c <= 0x01D166 ||
c >= 0x01D16A && c <= 0x01D172 ||
c >= 0x01D183 && c <= 0x01D184 ||
c >= 0x01D18C && c <= 0x01D1A9 ||
c >= 0x01D1AE && c <= 0x01D1DD ||
c >= 0x01D400 && c <= 0x01D454 ||
c >= 0x01D456 && c <= 0x01D49C ||
c >= 0x01D49E && c <= 0x01D49F ||
c >= 0x01D4A5 && c <= 0x01D4A6 ||
c >= 0x01D4A9 && c <= 0x01D4AC ||
c >= 0x01D4AE && c <= 0x01D4B9 ||
c >= 0x01D4BD && c <= 0x01D4C0 ||
c >= 0x01D4C2 && c <= 0x01D4C3 ||
c >= 0x01D4C5 && c <= 0x01D505 ||
c >= 0x01D507 && c <= 0x01D50A ||
c >= 0x01D50D && c <= 0x01D514 ||
c >= 0x01D516 && c <= 0x01D51C ||
c >= 0x01D51E && c <= 0x01D539 ||
c >= 0x01D53B && c <= 0x01D53E ||
c >= 0x01D540 && c <= 0x01D544 ||
c >= 0x01D54A && c <= 0x01D550 ||
c >= 0x01D552 && c <= 0x01D6A3 ||
c >= 0x01D6A8 && c <= 0x01D7C9 ||
c >= 0x020000 && c <= 0x02A6D6 ||
c >= 0x02F800 && c <= 0x02FA1D ||
c >= 0x0F0000 && c <= 0x0FFFFD ||
c >= 0x100000 && c <= 0x10FFFD ||
c == 0x0000AA || c == 0x0000B5 ||
c == 0x0000BA || c == 0x0002EE ||
c == 0x00037A || c == 0x000386 ||
c == 0x00038C || c == 0x000589 ||
c == 0x000903 || c == 0x000950 ||
c == 0x0009B2 || c == 0x0009D7 ||
c == 0x000A5E || c == 0x000A83 ||
c == 0x000A8D || c == 0x000AC9 ||
c == 0x000AD0 || c == 0x000AE0 ||
c == 0x000B40 || c == 0x000B57 ||
c == 0x000B83 || c == 0x000B9C ||
c == 0x000BD7 || c == 0x000CBE ||
c == 0x000CDE || c == 0x000D57 ||
c == 0x000DBD || c == 0x000E84 ||
c == 0x000E8A || c == 0x000E8D ||
c == 0x000EA5 || c == 0x000EA7 ||
c == 0x000EBD || c == 0x000EC6 ||
c == 0x000F36 || c == 0x000F38 ||
c == 0x000F7F || c == 0x000F85 ||
c == 0x000FCF || c == 0x00102C ||
c == 0x001031 || c == 0x001038 ||
c == 0x0010FB || c == 0x001248 ||
c == 0x001258 || c == 0x001288 ||
c == 0x0012B0 || c == 0x0012C0 ||
c == 0x001310 || c == 0x0017DC ||
c == 0x001F59 || c == 0x001F5B ||
c == 0x001F5D || c == 0x001FBE ||
c == 0x00200E || c == 0x002071 ||
c == 0x00207F || c == 0x002102 ||
c == 0x002107 || c == 0x002115 ||
c == 0x002124 || c == 0x002126 ||
c == 0x002128 || c == 0x002395 ||
c == 0x01D4A2 || c == 0x01D4BB ||
c == 0x01D546;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -472,10 +472,10 @@ namespace Markdig.Helpers
}
else if (c == '&')
{
string namedEntity;
int entityNameStart;
int entityNameLength;
int numericEntity;
var match = ScanEntity(text, searchPos, text.Length - searchPos, out namedEntity,
out numericEntity);
var match = ScanEntity(new StringSlice(text, searchPos, text.Length - 1), out numericEntity, out entityNameStart, out entityNameLength);
if (match == 0)
{
searchPos++;
@@ -484,9 +484,10 @@ namespace Markdig.Helpers
{
searchPos += match;
if (namedEntity != null)
if (entityNameLength > 0)
{
var decoded = EntityHelper.DecodeEntity(namedEntity);
var namedEntity = new StringSlice(text, entityNameStart, entityNameStart + entityNameLength - 1);
var decoded = EntityHelper.DecodeEntity(namedEntity.ToString());
if (decoded != null)
{
sb.Append(text, lastPos, searchPos - match - lastPos);
@@ -533,7 +534,7 @@ namespace Markdig.Helpers
/// Scans an entity.
/// Returns number of chars matched.
/// </summary>
public static int ScanEntity(string s, int pos, int length, out string namedEntity, out int numericEntity)
public static int ScanEntity<T>(T slice, out int numericEntity, out int namedEntityStart, out int namedEntityLength) where T : ICharIterator
{
// Credits: code from CommonMark.NET
// Copyright (c) 2014, Kārlis Gaņģis All rights reserved.
@@ -545,29 +546,29 @@ namespace Markdig.Helpers
.? { return 0; }
*/
var lastPos = pos + length;
namedEntity = null;
numericEntity = 0;
namedEntityStart = 0;
namedEntityLength = 0;
if (pos + 3 >= lastPos)
return 0;
if (s[pos] != '&')
return 0;
char c;
int i;
int counter = 0;
if (s[pos + 1] == '#')
if (slice.CurrentChar != '&' || slice.PeekChar(3) == '\0')
{
c = s[pos + 2];
return 0;
}
var start = slice.Start;
char c = slice.NextChar();
int counter = 0;
if (c == '#')
{
c = slice.PeekChar();
if (c == 'x' || c == 'X')
{
c = slice.NextChar(); // skip #
// expect 1-8 hex digits starting from pos+3
for (i = pos + 3; i < lastPos; i++)
while (c != '\0')
{
c = s[i];
c = slice.NextChar();
if (c >= '0' && c <= '9')
{
if (++counter == 9) return 0;
@@ -588,7 +589,7 @@ namespace Markdig.Helpers
}
if (c == ';')
return counter == 0 ? 0 : i - pos + 1;
return counter == 0 ? 0 : slice.Start - start + 1;
return 0;
}
@@ -596,9 +597,10 @@ namespace Markdig.Helpers
else
{
// expect 1-8 digits starting from pos+2
for (i = pos + 2; i < lastPos; i++)
while (c != '\0')
{
c = s[i];
c = slice.NextChar();
if (c >= '0' && c <= '9')
{
if (++counter == 9) return 0;
@@ -607,7 +609,7 @@ namespace Markdig.Helpers
}
if (c == ';')
return counter == 0 ? 0 : i - pos + 1;
return counter == 0 ? 0 : slice.Start - start + 1;
return 0;
}
@@ -616,25 +618,26 @@ namespace Markdig.Helpers
else
{
// expect a letter and 1-31 letters or digits
c = s[pos + 1];
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
return 0;
for (i = pos + 2; i < lastPos; i++)
namedEntityStart = slice.Start;
namedEntityLength++;
while (c != '\0')
{
c = s[i];
c = slice.NextChar();
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
if (++counter == 32)
return 0;
namedEntityLength++;
continue;
}
if (c == ';')
{
namedEntity = s.Substring(pos + 1, counter + 1);
return counter == 0 ? 0 : i - pos + 1;
return counter == 0 ? 0 : slice.Start - start + 1;
}
return 0;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -34,8 +34,9 @@ namespace Markdig.Helpers
/// <summary>
/// Peeks at the next character, without incrementing the <see cref="Start"/> position.
/// </summary>
/// <param name="offset"></param>
/// <returns>The next character. `\0` is end of the iteration.</returns>
char PeekChar();
char PeekChar(int offset = 1);
/// <summary>
/// Gets a value indicating whether this instance is empty.

View File

@@ -3,7 +3,6 @@
// See the license.txt file in the project root for more information.
using System;
using System.Runtime.CompilerServices;
using System.Text;
using Markdig.Syntax;
namespace Markdig.Helpers
@@ -18,7 +17,7 @@ namespace Markdig.Helpers
return TryParseAutolink(ref text, out link, out isEmail);
}
public static string Urilize(string headingText, bool allowOnlyAscii, bool keepOpeningDigits = false, bool discardDots = false)
public static string Urilize(string headingText, bool allowOnlyAscii, bool keepOpeningDigits = false)
{
var headingBuffer = StringBuilderCache.Local();
bool hasLetter = keepOpeningDigits && headingText.Length > 0 && char.IsLetterOrDigit(headingText[0]);
@@ -47,7 +46,7 @@ namespace Markdig.Helpers
}
else if (hasLetter)
{
if (IsReservedPunctuation(c, discardDots))
if (IsReservedPunctuation(c))
{
if (previousIsSpace)
{
@@ -67,7 +66,7 @@ namespace Markdig.Helpers
else if (!previousIsSpace && c.IsWhitespace())
{
var pc = headingBuffer[headingBuffer.Length - 1];
if (!IsReservedPunctuation(pc, discardDots))
if (!IsReservedPunctuation(pc))
{
headingBuffer.Append('-');
}
@@ -81,7 +80,7 @@ namespace Markdig.Helpers
while (headingBuffer.Length > 0)
{
var c = headingBuffer[headingBuffer.Length - 1];
if (IsReservedPunctuation(c, false))
if (IsReservedPunctuation(c))
{
headingBuffer.Length--;
}
@@ -96,10 +95,27 @@ namespace Markdig.Helpers
return text;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsReservedPunctuation(char c, bool discardDots)
public static string UrilizeAsGfm(string headingText)
{
return c == '_' || c == '-' || (!discardDots && c == '.');
// Following https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
var headingBuffer = StringBuilderCache.Local();
for (int i = 0; i < headingText.Length; i++)
{
var c = char.ToLowerInvariant(headingText[i]);
if (char.IsLetterOrDigit(c) || c == ' ' || c == '-' || c == '_')
{
headingBuffer.Append(c == ' ' ? '-' : c);
}
}
var result = headingBuffer.ToString();
headingBuffer.Length = 0;
return result;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsReservedPunctuation(char c)
{
return c == '_' || c == '-' || c == '.';
}
public static bool TryParseAutolink(ref StringSlice text, out string link, out bool isEmail)
@@ -496,7 +512,7 @@ namespace Markdig.Helpers
return TryParseUrl(ref text, out link);
}
public static bool TryParseUrl<T>(ref T text, out string link) where T : ICharIterator
public static bool TryParseUrl<T>(ref T text, out string link, bool isAutoLink = false) where T : ICharIterator
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
@@ -593,16 +609,30 @@ namespace Markdig.Helpers
hasEscape = false;
if (IsEndOfUri(c))
if (IsEndOfUri(c, isAutoLink))
{
isValid = true;
break;
}
if (c == '.' && IsEndOfUri(text.PeekChar()))
if (isAutoLink)
{
isValid = true;
break;
if (c == '&')
{
int entityNameStart;
int entityNameLength;
int entityValue;
if (HtmlHelper.ScanEntity(text, out entityValue, out entityNameStart, out entityNameLength) > 0)
{
isValid = true;
break;
}
}
if (IsTrailingUrlStopCharacter(c) && IsEndOfUri(text.PeekChar(), true))
{
isValid = true;
break;
}
}
buffer.Append(c);
@@ -621,9 +651,65 @@ namespace Markdig.Helpers
return isValid;
}
private static bool IsEndOfUri(char c)
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsTrailingUrlStopCharacter(char c)
{
return c == '\0' || c.IsSpaceOrTab() || c.IsControl(); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
// Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not be considered part of the autolink, though they may be included in the interior of the link:
return c == '?' || c == '!' || c == '.' || c == ',' || c == ':' || c == '*' || c == '*' || c == '_' || c == '~';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsEndOfUri(char c, bool isAutoLink)
{
return c == '\0' || c.IsSpaceOrTab() || c.IsControl() || (isAutoLink && c == '<'); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
}
public static bool IsValidDomain(string link, int prefixLength)
{
// https://github.github.com/gfm/#extended-www-autolink
// A valid domain consists of alphanumeric characters, underscores (_), hyphens (-) and periods (.).
// There must be at least one period, and no underscores may be present in the last two segments of the domain.
int segmentCount = 1;
bool segmentHasCharacters = false;
int lastUnderscoreSegment = -1;
for (int i = prefixLength; i < link.Length; i++)
{
char c = link[i];
if (c == '.') // New segment
{
if (!segmentHasCharacters)
return false;
segmentCount++;
segmentHasCharacters = false;
continue;
}
if (!c.IsAlphaNumeric())
{
if (c == '/' || c == '?' || c == '#' || c == ':') // End of domain name
break;
if (c == '_')
{
lastUnderscoreSegment = segmentCount;
}
else if (c != '-')
{
// An invalid character has been found
return false;
}
}
segmentHasCharacters = true;
}
return segmentCount != 1 && // At least one dot was present
segmentHasCharacters && // Last segment has valid characters
segmentCount - lastUnderscoreSegment >= 2; // No underscores are present in the last two segments of the domain
}
public static bool TryParseLinkReferenceDefinition<T>(T text, out string label, out string url,
@@ -745,7 +831,6 @@ namespace Markdig.Helpers
return TryParseLabel(ref lines, false, out label, out labelSpan);
}
public static bool TryParseLabel<T>(ref T lines, out string label, out SourceSpan labelSpan) where T : ICharIterator
{
return TryParseLabel(ref lines, false, out label, out labelSpan);

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -121,5 +121,20 @@ namespace Markdig.Helpers
}
return false;
}
/// <summary>
/// Replaces <typeparamref name="TElement"/> with <paramref name="newElement"/> or adds <paramref name="newElement"/>.
/// </summary>
/// <typeparam name="TElement">Element type to find in the list</typeparam>
/// <param name="newElement">Object to add/replace the found element with</param>
/// <returns><c>true</c> if a replacement was made; otherwise <c>false</c>.</returns>
public bool ReplaceOrAdd<TElement>(T newElement) where TElement : T
{
if (Replace<TElement>(newElement))
return true;
Add(newElement);
return false;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -197,14 +197,14 @@ namespace Markdig.Helpers
/// <seealso cref="ICharIterator" />
public struct Iterator : ICharIterator
{
private readonly StringLineGroup lines;
private int offset;
private readonly StringLineGroup _lines;
private int _offset;
public Iterator(StringLineGroup lines)
{
this.lines = lines;
this._lines = lines;
Start = -1;
offset = -1;
_offset = -1;
SliceIndex = 0;
CurrentChar = '\0';
End = -2;
@@ -225,48 +225,74 @@ namespace Markdig.Helpers
public int SliceIndex { get; private set; }
public StringLineGroup Remaining()
{
var lines = _lines;
if (CurrentChar == '\0')
{
lines.Clear();
}
else
{
for (int i = SliceIndex - 1; i >= 0; i--)
{
lines.RemoveAt(i);
}
if (lines.Count > 0 && _offset > 0)
{
lines.Lines[0].Column += _offset;
lines.Lines[0].Slice.Start += _offset;
}
}
return lines;
}
public char NextChar()
{
Start++;
offset++;
_offset++;
if (Start <= End)
{
var slice = (StringSlice)lines.Lines[SliceIndex];
if (offset < slice.Length)
var slice = (StringSlice)_lines.Lines[SliceIndex];
if (_offset < slice.Length)
{
CurrentChar = slice[slice.Start + offset];
CurrentChar = slice[slice.Start + _offset];
}
else
{
CurrentChar = '\n';
SliceIndex++;
offset = -1;
_offset = -1;
}
}
else
{
CurrentChar = '\0';
Start = End + 1;
SliceIndex = lines.Count;
offset--;
SliceIndex = _lines.Count;
_offset--;
}
return CurrentChar;
}
public char PeekChar()
public char PeekChar(int offset = 1)
{
if (Start + 1 > End)
if (offset < 0) throw new ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
if (Start + offset > End)
{
return '\0';
}
var slice = (StringSlice)lines.Lines[SliceIndex];
if (offset + 1 >= slice.Length)
var slice = (StringSlice)_lines.Lines[SliceIndex];
if (_offset + offset >= slice.Length)
{
return '\n';
}
return slice[slice.Start + offset + 1];
return slice[slice.Start + _offset + offset];
}
public bool TrimStart()

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -106,23 +106,12 @@ namespace Markdig.Helpers
/// <param name="offset">The offset.</param>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekChar(int offset)
public char PeekChar(int offset = 1)
{
var index = Start + offset;
return index >= Start && index <= End ? Text[index] : (char) 0;
}
/// <summary>
/// Peeks the character immediately after the current <see cref="Start"/> position
/// or returns `\0` if after the <see cref="End"/> position.
/// </summary>
/// <returns>The next character, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekChar()
{
return PeekChar(1);
}
/// <summary>
/// Peeks a character at the specified offset from the current beginning of the string, without taking into account <see cref="Start"/> and <see cref="End"/>
/// </summary>
@@ -179,6 +168,28 @@ namespace Markdig.Helpers
return i == text.Length;
}
/// <summary>
/// Expect spaces until a end of line. Return <c>false</c> otherwise.
/// </summary>
/// <returns><c>true</c> if whitespaces where matched until a end of line</returns>
public bool SkipSpacesToEndOfLineOrEndOfDocument()
{
for (int i = Start; i <= End; i++)
{
var c = Text[i];
if (c.IsWhitespace())
{
if (c == '\0' || c == '\n' || (c == '\r' && i + 1 <= End && Text[i + 1] != '\n'))
{
return true;
}
continue;
}
return false;
}
return true;
}
/// <summary>
/// Matches the specified text using lowercase comparison.
/// </summary>

View File

@@ -1,38 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
<Copyright>Alexandre Mutel</Copyright>
<AssemblyTitle>Markdig</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.14.2</VersionPrefix>
<VersionPrefix>0.15.7</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;uap10.0</TargetFrameworks>
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;netstandard2.0;uap10.0</TargetFrameworks>
<AssemblyName>Markdig</AssemblyName>
<PackageId>Markdig</PackageId>
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>
&gt; 0.14.2
- Fix issue with emphasis preceded/followed by an HTML entity (#157)
- Add support for link reference definitions for Normalize renderer (#155)
- Add option to disable smiley parsing in EmojiAndSmiley extension
&gt; 0.14.1
- Fix crash in Markdown.Normalize to handle HtmlBlock correctly
- Add better handling of bullet character for lists in Markdown.Normalize
&gt; 0.14.0
- Add Markdown.ToPlainText, Add option HtmlRenderer.EnableHtmlForBlock.
- Add Markdown.Normalize, to allow to normalize a markdown document. Add NormalizeRenderer, to render a MarkdownDocument back to markdown.
-
&gt; 0.13.4
- Add support for single table header row without a table body rows (#141)
- ADd support for `nomnoml` diagrams
</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/lunet-io/markdig/blob/master/license.txt</PackageLicenseUrl>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
@@ -65,21 +50,25 @@
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' ">
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.10240.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81'">
<TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile328</TargetFrameworkProfile>
<LanguageTargets>$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets</LanguageTargets>
</PropertyGroup>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@@ -29,9 +29,11 @@ using Markdig.Extensions.SmartyPants;
using Markdig.Extensions.NonAsciiNoEscape;
using Markdig.Extensions.Tables;
using Markdig.Extensions.TaskLists;
using Markdig.Extensions.TextRenderer;
using Markdig.Extensions.Yaml;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Extensions.Globalization;
namespace Markdig
{
@@ -51,6 +53,19 @@ namespace Markdig
return pipeline;
}
/// <summary>
/// Adds the specified extension instance to the extensions collection.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="extension">The instance of the extension to be added.</param>
/// <typeparam name="TExtension">The type of the extension.</typeparam>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder Use<TExtension>(this MarkdownPipelineBuilder pipeline, TExtension extension) where TExtension : class, IMarkdownExtension
{
pipeline.Extensions.AddIfNotAlready(extension);
return pipeline;
}
/// <summary>
/// Uses all extensions except the BootStrap, Emoji, SmartyPants and soft line as hard line breaks extensions.
/// </summary>
@@ -84,9 +99,9 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseAutoLinks(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseAutoLinks(this MarkdownPipelineBuilder pipeline, string validPreviousCharacters = AutoLinkParser.DefaultValidPreviousCharacters)
{
pipeline.Extensions.AddIfNotAlready<AutoLinkExtension>();
pipeline.Extensions.ReplaceOrAdd<AutoLinkExtension>(new AutoLinkExtension(validPreviousCharacters));
return pipeline;
}
@@ -446,6 +461,17 @@ namespace Markdig
return pipeline;
}
/// <summary>
/// Adds support for right-to-left content by adding appropriate html attribtues.
/// </summary>
/// <param name="pipeline">The pipeline</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseGlobalization(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<GlobalizationExtension>();
return pipeline;
}
/// <summary>
/// This will disable the HTML support in the markdown processor (for constraint/safe parsing).
/// </summary>
@@ -569,11 +595,26 @@ namespace Markdig
case "autolinks":
pipeline.UseAutoLinks();
break;
case "globalization":
pipeline.UseGlobalization();
break;
default:
throw new ArgumentException($"Invalid extension `{extension}` from `{extensions}`", nameof(extensions));
}
}
return pipeline;
}
/// <summary>
/// Configures the string to be used for line-endings, when writing.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="newLine">The string to be used for line-endings.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder ConfigureNewLine(this MarkdownPipelineBuilder pipeline, string newLine)
{
pipeline.Use(new ConfigureNewLineExtension(newLine));
return pipeline;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -116,9 +116,16 @@ namespace Markdig
extension.Setup(this);
}
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions),
new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog,
GetDocumentProcessed) {PreciseSourceLocation = PreciseSourceLocation};
pipeline = new MarkdownPipeline(
new OrderedList<IMarkdownExtension>(Extensions),
new BlockParserList(BlockParsers),
new InlineParserList(InlineParsers),
StringBuilderCache,
DebugLog,
GetDocumentProcessed)
{
PreciseSourceLocation = PreciseSourceLocation
};
return pipeline;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -162,6 +162,21 @@ namespace Markdig.Parsers
/// </summary>
private bool ContinueProcessingLine { get; set; }
/// <summary>
/// Get the current Container that is currently opened
/// </summary>
/// <returns>The current Container that is currently opened</returns>
public ContainerBlock GetCurrentContainerOpened()
{
var container = CurrentContainer;
while (container != null && !container.IsOpen)
{
container = container.Parent;
}
return container;
}
/// <summary>
/// Returns the next character in the line being processed. Update <see cref="Start"/> and <see cref="Column"/>.
/// </summary>
@@ -429,6 +444,8 @@ namespace Markdig.Parsers
{
CurrentLineStartPosition = newLine.Start;
Document.LineStartIndexes?.Add(CurrentLineStartPosition);
ContinueProcessingLine = true;
ResetLine(newLine);

View File

@@ -94,7 +94,7 @@ namespace Markdig.Parsers
// Skip any spaces after info string
firstSpace++;
while (true)
while (firstSpace <= line.End)
{
c = line[firstSpace];
if (c.IsSpaceOrTab())

View File

@@ -1,6 +1,7 @@
// 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.Diagnostics;
using Markdig.Helpers;
using Markdig.Syntax;
@@ -21,6 +22,11 @@ namespace Markdig.Parsers
OpeningCharacters = new[] {'#'};
}
/// <summary>
/// Gets or sets the max count of the leading unescaped # characters
/// </summary>
public int MaxLeadingCount { get; set; } = 6;
/// <summary>
/// A delegates that allows to process attached attributes after #
/// </summary>
@@ -36,7 +42,7 @@ namespace Markdig.Parsers
// 4.2 ATX headings
// An ATX heading consists of a string of characters, parsed as inline content,
// between an opening sequence of 16 unescaped # characters and an optional
// between an opening sequence of 16(configurable) unescaped # characters and an optional
// closing sequence of any number of unescaped # characters. The opening sequence
// of # characters must be followed by a space or by the end of line. The optional
// closing sequence of #s must be preceded by a space and may be followed by spaces
@@ -50,8 +56,9 @@ namespace Markdig.Parsers
var c = line.CurrentChar;
var matchingChar = c;
Debug.Assert(MaxLeadingCount > 0);
int leadingCount = 0;
while (c != '\0' && leadingCount <= 6)
while (c != '\0' && leadingCount <= MaxLeadingCount)
{
if (c != matchingChar)
{
@@ -62,7 +69,7 @@ namespace Markdig.Parsers
}
// A space is required after leading #
if (leadingCount > 0 && leadingCount <= 6 && (c.IsSpaceOrTab() || c == '\0'))
if (leadingCount > 0 && leadingCount <= MaxLeadingCount && (c.IsSpaceOrTab() || c == '\0'))
{
// Move to the content
var headingBlock = new HeadingBlock(this)

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.Text;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -25,23 +27,24 @@ namespace Markdig.Parsers.Inlines
public static bool TryParse(ref StringSlice slice, out string literal, out int match)
{
literal = null;
string entityName;
int entityNameStart;
int entityNameLength;
int entityValue;
match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
match = HtmlHelper.ScanEntity(slice, out entityValue, out entityNameStart, out entityNameLength);
if (match == 0)
{
return false;
}
if (entityName != null)
if (entityNameLength > 0)
{
literal = EntityHelper.DecodeEntity(entityName);
literal = EntityHelper.DecodeEntity(new StringSlice(slice.Text, entityNameStart, entityNameStart + entityNameLength - 1).ToString());
}
else if (entityValue >= 0)
{
literal = (entityValue == 0 ? null : EntityHelper.DecodeEntity(entityValue)) ?? CharHelper.ZeroSafeString;
}
return true;
return literal != null;
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)

View File

@@ -136,7 +136,7 @@ namespace Markdig.Parsers.Inlines
{
child = new LiteralInline()
{
Content = new StringSlice(label),
Content = StringSlice.Empty,
IsClosed = true,
// Not exact but we leave it like this
Span = parent.Span,

View File

@@ -86,7 +86,7 @@ namespace Markdig.Parsers
// interpretations of a line, the thematic break takes precedence
BlockState result;
var thematicParser = ThematicBreakParser.Default;
if (thematicParser.HasOpeningCharacter(processor.CurrentChar))
if (!(processor.LastBlock is FencedCodeBlock) && thematicParser.HasOpeningCharacter(processor.CurrentChar))
{
result = thematicParser.TryOpen(processor);
if (result.IsBreak())
@@ -262,10 +262,12 @@ namespace Markdig.Parsers
}
}
int.TryParse(listInfo.OrderedStart, out int order);
var newListItem = new ListItemBlock(this)
{
Column = initColumn,
ColumnWidth = columnWidth,
Order = order,
Span = new SourceSpan(sourcePosition, sourceEndPosition)
};
state.NewBlocks.Push(newListItem);

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -85,8 +85,14 @@ namespace Markdig.Parsers
/// <returns>A document instance</returns>
private MarkdownDocument Parse()
{
if (preciseSourceLocation)
document.LineStartIndexes = new List<int>();
ProcessBlocks();
ProcessInlines();
// At this point the LineIndex is the same as the number of lines in the document
document.LineCount = blockProcessor.LineIndex;
// Allow to call a hook after processing a document
documentProcessed?.Invoke(document);
@@ -146,8 +152,7 @@ namespace Markdig.Parsers
for (; item.Index < container.Count; item.Index++)
{
var block = container[item.Index];
var leafBlock = block as LeafBlock;
if (leafBlock != null)
if (block is LeafBlock leafBlock)
{
leafBlock.OnProcessInlinesBegin(inlineProcessor);
if (leafBlock.ProcessInlines)

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
@@ -9,7 +9,7 @@ namespace Markdig.Parsers
/// <summary>
/// Block parser for a <see cref="ParagraphBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.BlockParser" />
/// <seealso cref="BlockParser" />
public class ParagraphBlockParser : BlockParser
{
public override BlockState TryOpen(BlockProcessor processor)
@@ -152,27 +152,21 @@ namespace Markdig.Parsers
{
// If we have found a LinkReferenceDefinition, we can discard the previous paragraph
var iterator = lines.ToCharIterator();
LinkReferenceDefinition linkReferenceDefinition;
if (LinkReferenceDefinition.TryParse(ref iterator, out linkReferenceDefinition))
{
if (!state.Document.ContainsLinkReferenceDefinition(linkReferenceDefinition.Label))
{
state.Document.SetLinkReferenceDefinition(linkReferenceDefinition.Label, linkReferenceDefinition);
}
if (LinkReferenceDefinition.TryParse(ref iterator, out LinkReferenceDefinition linkReferenceDefinition))
{
state.Document.SetLinkReferenceDefinition(linkReferenceDefinition.Label, linkReferenceDefinition);
atLeastOneFound = true;
// Remove lines that have been matched
if (iterator.Start > iterator.End)
{
lines.Clear();
}
else
{
for (int i = iterator.SliceIndex - 1; i >= 0; i--)
{
lines.RemoveAt(i);
}
}
// Correct the locations of each field
linkReferenceDefinition.Line = lines.Lines[0].Line;
int startPosition = lines.Lines[0].Slice.Start;
linkReferenceDefinition.Span = linkReferenceDefinition.Span .MoveForward(startPosition);
linkReferenceDefinition.LabelSpan = linkReferenceDefinition.LabelSpan .MoveForward(startPosition);
linkReferenceDefinition.UrlSpan = linkReferenceDefinition.UrlSpan .MoveForward(startPosition);
linkReferenceDefinition.TitleSpan = linkReferenceDefinition.TitleSpan .MoveForward(startPosition);
lines = iterator.Remaining();
}
else
{

View File

@@ -87,6 +87,8 @@ namespace Markdig.Renderers.Html
renderer.WriteLine("</code></pre>");
}
}
renderer.EnsureLine();
}
}
}

View File

@@ -38,6 +38,8 @@ namespace Markdig.Renderers.Html
{
renderer.Write("</").Write(headingText).WriteLine(">");
}
renderer.EnsureLine();
}
}
}

View File

@@ -26,6 +26,7 @@ namespace Markdig.Renderers.Html.Inlines
renderer.Write("mailto:");
}
renderer.WriteEscapeUrl(obj.Url);
renderer.Write('"');
renderer.WriteAttributes(obj);
if (!obj.IsEmail && AutoRelNoFollow)
@@ -33,7 +34,7 @@ namespace Markdig.Renderers.Html.Inlines
renderer.Write(" rel=\"nofollow\"");
}
renderer.Write("\">");
renderer.Write(">");
}
renderer.WriteEscape(obj.Url);

View File

@@ -24,15 +24,9 @@ namespace Markdig.Renderers.Html.Inlines
{
renderer.WriteLine("<br />");
}
else
{
renderer.WriteLine();
}
}
else
{
renderer.Write(" ");
}
renderer.EnsureLine();
}
}
}

View File

@@ -59,6 +59,7 @@ namespace Markdig.Renderers.Html
renderer.WriteLine("</li>");
}
renderer.EnsureLine();
renderer.ImplicitParagraph = previousImplicit;
}
@@ -66,6 +67,8 @@ namespace Markdig.Renderers.Html
{
renderer.WriteLine(listBlock.IsOrdered ? "</ol>" : "</ul>");
}
renderer.EnsureLine();
}
}
}

View File

@@ -23,9 +23,14 @@ namespace Markdig.Renderers.Html
renderer.Write("<p").WriteAttributes(obj).Write(">");
}
renderer.WriteLeafInline(obj);
if (!renderer.ImplicitParagraph && renderer.EnableHtmlForBlock)
if (!renderer.ImplicitParagraph)
{
renderer.WriteLine("</p>");
if(renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</p>");
}
renderer.EnsureLine();
}
}
}

View File

@@ -26,6 +26,7 @@ namespace Markdig.Renderers.Html
{
renderer.WriteLine("</blockquote>");
}
renderer.EnsureLine();
}
}
}

View File

@@ -75,6 +75,16 @@ namespace Markdig.Renderers
public bool UseNonAsciiNoEscape { get; set; }
/// <summary>
/// Gets a value to use as the base url for all relative links
/// </summary>
public Uri BaseUrl { get; set; }
/// <summary>
/// Allows links to be rewritten
/// </summary>
public Func<string,string> LinkRewriter { get; set; }
/// <summary>
/// Writes the content escaped for HTML.
/// </summary>
@@ -192,6 +202,16 @@ namespace Markdig.Renderers
if (content == null)
return this;
if (BaseUrl != null && !Uri.TryCreate(content, UriKind.Absolute, out Uri _))
{
content = new Uri(BaseUrl, content).AbsoluteUri;
}
if (LinkRewriter != null)
{
content = LinkRewriter(content);
}
int previousPosition = 0;
int length = content.Length;
@@ -294,12 +314,9 @@ namespace Markdig.Renderers
foreach (var property in attributes.Properties)
{
Write(" ").Write(property.Key);
if (property.Value != null)
{
Write("=").Write("\"");
WriteEscape(property.Value);
Write("\"");
}
Write("=").Write("\"");
WriteEscape(property.Value ?? "");
Write("\"");
}
}

View File

@@ -17,6 +17,7 @@ namespace Markdig.Renderers.Normalize
EmptyLineAfterCodeBlock = true;
EmptyLineAfterHeading = true;
EmptyLineAfterThematicBreak = true;
ExpandAutoLinks = true;
ListItemCharacter = null;
}
@@ -44,5 +45,10 @@ namespace Markdig.Renderers.Normalize
/// The bullet character used for list items. Default is <c>null</c> leaving the original bullet character as-is.
/// </summary>
public char? ListItemCharacter { get; set; }
/// <summary>
/// Expands AutoLinks to the normal inline representation. Default is <c>true</c>
/// </summary>
public bool ExpandAutoLinks { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -192,17 +192,16 @@ namespace Markdig.Syntax.Inlines
/// <returns><c>true</c> if this instance contains a parent of the specified type; <c>false</c> otherwise</returns>
public bool ContainsParentOfType<T>() where T : Inline
{
var delimiter = this as T;
if (delimiter != null)
var inline = this;
while (inline != null)
{
return true;
var delimiter = inline as T;
if (delimiter != null)
{
return true;
}
inline = inline.Parent;
}
if (Parent != null)
{
return Parent.ContainsParentOfType<T>();
}
return false;
}
@@ -213,19 +212,15 @@ namespace Markdig.Syntax.Inlines
/// <returns>An enumeration on the parents of the specified type</returns>
public IEnumerable<T> FindParentOfType<T>() where T : Inline
{
var parent = Parent;
var delimiter = this as T;
if (delimiter != null)
var inline = this;
while (inline != null)
{
yield return delimiter;
}
if (parent != null)
{
foreach (var previous in parent.FindParentOfType<T>())
var delimiter = inline as T;
if (delimiter != null)
{
yield return previous;
yield return delimiter;
}
inline = inline.Parent;
}
}

View File

@@ -69,6 +69,11 @@ namespace Markdig.Syntax.Inlines
/// </summary>
public bool IsShortcut { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the inline link was parsed using markdown syntax or was automatic recognized.
/// </summary>
public bool IsAutoLink { get; set; }
/// <summary>
/// Gets or sets the reference this link is attached to. May be null.
/// </summary>

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -28,11 +28,14 @@ namespace Markdig.Syntax
public void Set(string label, LinkReferenceDefinition link)
{
if (link == null) throw new ArgumentNullException(nameof(link));
Links[label] = link;
if (!Contains(link))
{
Add(link);
if (link == null) throw new ArgumentNullException(nameof(link));
if (!Contains(link))
{
Add(link);
if (!Links.ContainsKey(label))
{
Links[label] = link;
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers;
@@ -20,5 +20,10 @@ namespace Markdig.Syntax
}
internal int ColumnWidth { get; set; }
/// <summary>
/// The number defined for this <see cref="ListItemBlock"/> in an ordered list
/// </summary>
public int Order { get; set; }
}
}

View File

@@ -1,6 +1,8 @@
// 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;
namespace Markdig.Syntax
{
/// <summary>
@@ -15,5 +17,16 @@ namespace Markdig.Syntax
public MarkdownDocument() : base(null)
{
}
/// <summary>
/// Gets the number of lines in this <see cref="MarkdownDocument"/>
/// </summary>
public int LineCount;
/// <summary>
/// Gets a list of zero-based indexes of line beginnings in the source span
/// <para>Available if <see cref="MarkdownPipelineBuilder.PreciseSourceLocation"/> is used, otherwise null</para>
/// </summary>
public List<int> LineStartIndexes;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -43,6 +43,11 @@ namespace Markdig.Syntax
public bool IsEmpty => Start > End;
public SourceSpan MoveForward(int count)
{
return new SourceSpan(Start + count, End + count);
}
public bool Equals(SourceSpan other)
{
return Start == other.Start && End == other.End;

View File

@@ -1,11 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
VisualStudioVersion = 15.0.27004.2005
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2-005C-4D13-A338-EA464BBEC60F}"
ProjectSection(SolutionItems) = preProject
..\appveyor.yml = ..\appveyor.yml
..\changelog.md = ..\changelog.md
..\license.txt = ..\license.txt
..\readme.md = ..\readme.md
EndProjectSection

View File

@@ -2,4 +2,5 @@
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) Alexandre Mutel. All rights reserved.&#xD;
This file is licensed under the BSD-Clause 2 license. &#xD;
See the license.txt file in the project root for more information.</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String></wpf:ResourceDictionary>