Compare commits

...

127 Commits

Author SHA1 Message Date
Alexandre Mutel
751c79fce4 Bump to 0.22.0 2020-10-05 08:20:45 +02:00
Alexandre Mutel
4dc8cc3977 Bump packages in tests 2020-10-05 08:20:31 +02:00
Alexandre Mutel
a1891e2984 Fix coverlet 2020-10-05 08:10:53 +02:00
Alexandre Mutel
ff0637993c Merge branch 'pr/n471_master' 2020-10-05 07:59:19 +02:00
Alexandre Mutel
8b64ce456f Fix quoteblock with setexheading when no lazy continuations are involved 2020-10-05 07:58:48 +02:00
Alexandre Mutel
a781ae1e5b Change tests to exe 2020-10-05 07:57:16 +02:00
Alexandre Mutel
2e5007241d Delay parsing of all specs in the tests itself instead of using static to restore the debuggability of markdig 2020-10-05 07:55:24 +02:00
Alexandre Mutel
cf6d98b7f8 Update tests 2020-10-05 07:54:13 +02:00
Ham Vocke
ccb7e8edfa add failing test, partial fix for setext headings in blockquotes
attempts to fix #465
2020-10-03 07:32:03 +02:00
Alexandre Mutel
db25c1db43 Merge pull request #473 from aloisdg/patch-1
Add a link to try the example
2020-10-03 07:22:22 +02:00
Alexandre Mutel
5db90ede4b Merge pull request #470 from iskcal/iss469
Fix issue 469
2020-10-03 07:21:34 +02:00
Alexandre Mutel
fef1ad3563 Merge pull request #474 from Wurstfried/pubFirstParentOfType
Make Inline.FirstParentOfType public
2020-10-03 07:20:20 +02:00
Alexandre Mutel
e9302d93bd Merge branch 'master' into pubFirstParentOfType 2020-10-03 07:15:02 +02:00
Alexandre Mutel
4704c49fbf Merge pull request #478 from yufeih/fix-476
Fix tel: treated as autolink
2020-10-03 07:12:36 +02:00
Alexandre Mutel
aca70e5c9a Merge pull request #479 from yufeih/fix-ci
Fix NETSDK1045 CI build failure
2020-10-03 06:34:36 +02:00
Yufei Huang
da5eff075d Fix NETSDK1045 CI build failure 2020-09-30 16:32:37 +08:00
Yufei Huang
971207e942 Fix tel: treated as autolink 2020-09-30 16:23:46 +08:00
iskcal
fb942f9810 fix typo 2020-09-15 19:31:29 +08:00
Sebastian Raffel
25a227fffd CI: Use dotnet SDK 3.1.402 2020-09-13 10:19:54 +02:00
Sebastian Raffel
2be8cd4aa7 Make Inline.FirstParentOfType public 2020-09-13 08:05:34 +02:00
Alois
569b80befe Add a link to try the example 2020-09-09 18:10:36 +02:00
iskcal
0b8b14490e fix issue 469 2020-08-25 13:05:44 +08:00
Alexandre Mutel
c59fd5c651 Merge pull request #468 from yufeih/parentblock
Add ParentBlock property to ContainerInline
2020-08-18 09:59:18 +02:00
Alexandre Mutel
4893e2b177 Merge pull request #467 from yufeih/container-block-parent
Set Parent in ContainerBlock.set[index] method
2020-08-18 09:44:41 +02:00
Yufei Huang
b30b219237 Add ParentBlock property to ContainerInline 2020-08-18 15:42:29 +08:00
Yufei Huang
d6c627aa88 Address PR feedback 2020-08-18 15:40:27 +08:00
Yufei Huang
a26f4298a4 Set Parent in ContainerBlock.set[index] method 2020-08-18 14:55:15 +08:00
Alexandre Mutel
d5b80f6a7b Bump to 0.21.1 2020-08-17 12:38:37 +02:00
Alexandre Mutel
98d747c839 Fix Pack signed 2020-08-17 12:38:07 +02:00
Alexandre Mutel
7dd9321a97 Bump to 0.21.0 2020-08-17 12:13:19 +02:00
Alexandre Mutel
c22d681c20 Merge pull request #464 from 928PJY/public-property
Expose IndentCount of FencedCodeBlock
2020-08-16 15:51:26 +02:00
Alexandre Mutel
ffe7f56bc2 Merge pull request #466 from Kryptos-FR/feature/net45
Bring back net45 target
2020-08-16 15:46:14 +02:00
Nicolas Musset
16c0b6a3fa ArrayHelper is needed for the tests
Slightly different from the previous implementation in order to mimic `System.Array.Empty<T>()` closely.
2020-08-16 13:13:45 +09:00
Nicolas Musset
db867dee48 Bring back net45 target 2020-08-16 12:47:04 +09:00
JiayinPei
d35b1b82ea Expose ColumnWidth of ListBlockItem 2020-08-13 14:34:07 +08:00
JiayinPei
8b829a2dea Export IndentCount of FencedCodeBlock 2020-08-13 12:07:04 +08:00
Alexandre Mutel
4f7ef61303 Merge pull request #459 from iskcal/fix456
Write custom rel into links
2020-08-06 18:51:02 +02:00
iskcal
831a529338 fix456 2020-08-03 11:51:38 +08:00
Alexandre Mutel
4f5e9cd334 Merge pull request #455 from hamvocke/master
Pipe Tables: Normalize using header column count
2020-07-30 18:18:28 +02:00
Ham Vocke
cb0b3a6292 add 'gfm-pipetables' pipeline mode, introduce new gfm test spec 2020-07-30 17:50:53 +02:00
Ham Vocke
4390ff289e add option to PipeTableOptions, use for normalization 2020-07-30 16:16:20 +02:00
Ham Vocke
be53e778be remove extra cells, use header row to find maxColumn 2020-07-30 15:03:33 +02:00
Ham Vocke
0eab0a3107 set up example006 of pipe table spec according to new behavior 2020-07-30 15:02:15 +02:00
Ham Vocke
8fa47427ac change test for pipe table normalization 2020-07-30 14:14:52 +02:00
Alexandre Mutel
bca544575b Merge pull request #449 from MihaZupan/invalid-idn-fallback
Fallback to non-punycode encoding for invalid IDN urls
2020-07-22 22:26:10 +02:00
Alexandre Mutel
0017dbffc4 Merge pull request #444 from iskcal/master
add the missing fact attribute
2020-07-22 22:25:14 +02:00
Alexandre Mutel
569efa357a Merge pull request #453 from davidackroyd99/feature/autolink-tel-uri
Autolink tel: uri
2020-07-22 22:24:47 +02:00
Alexandre Mutel
7e9df72173 Merge pull request #452 from davidackroyd99/bug/parsing-math
Bug/parsing math
2020-07-22 22:16:54 +02:00
David Ackroyd
b2fcb1cc37 autolink tel: uri, with basic test 2020-07-21 23:10:55 +00:00
David Ackroyd
8758ba460f fix: parsing math blocks with no leading or trailing whitespace 2020-07-21 21:35:52 +00:00
David Ackroyd
477a290538 typo: fixed comment in ApiController 2020-07-21 21:17:34 +00:00
MihaZupan
0fc112a6b8 Fallback to non-punycode encoding for invalid IDN urls 2020-07-12 00:15:02 +02:00
iskcal
b7aea44502 add the missing fact attribute 2020-06-17 16:22:34 +08:00
Alexandre Mutel
0545faa9af Merge pull request #435 from RudeySH/patch-1
Fix media link classes not being added
2020-06-09 20:34:11 +02:00
Rudey
ea67a295b2 Test CSS classes using both Bootstrap and MediaLinks 2020-05-12 22:32:05 +02:00
Rudey
d53128d56d Reorder class attribute in spec tests 2020-05-12 22:02:01 +02:00
Rudey
ba192ce065 Remove redundancy, apply same fix to audio/video 2020-05-12 21:45:50 +02:00
Rudey
42462d71ac Reorder class attribute in test cases 2020-05-12 21:41:52 +02:00
Rudey
6e0363aa91 Fix media link classes not being added 2020-05-12 21:29:28 +02:00
Alexandre Mutel
e9be3725d6 Merge pull request #431 from mlaily/escape-scheme
Escape URLs scheme
2020-04-30 07:34:03 +02:00
Melvyn Laïly
38f4332de1 Escape URLs scheme (fix #365) 2020-04-27 09:35:44 +02:00
Alexandre Mutel
bcc123d240 Merge pull request #429 from KrisVandermotten/StringSliceCountAndSkipChar
Added StringSlice.CountAndSkipChar(char)
2020-04-27 09:12:15 +02:00
Alexandre Mutel
144e8ad156 Merge pull request #430 from mlaily/fix-relative-url-replacement
Fix relative uri detection to be cross-platform compatible
2020-04-26 18:26:57 +02:00
Melvyn Laïly
55a7eae4d4 Fix relative uri detection to be cross-platform compatible 2020-04-26 18:02:45 +02:00
Kris Vandermotten
d96862378d Added StringSlice.CountAndSkipChar(char) 2020-04-26 15:17:19 +02:00
Alexandre Mutel
8acf5a548e Merge pull request #427 from MihaZupan/cleanup
Cleanup
2020-04-23 07:12:28 +02:00
MihaZupan
222a99c7e4 Pattern matching, inline variable declarations 2020-04-23 01:32:22 +02:00
MihaZupan
ac4a75bdf9 Fix comment warnings 2020-04-23 00:38:16 +02:00
MihaZupan
fc2e1ab896 Cleanup license headers 2020-04-22 17:48:44 +02:00
MihaZupan
ef7f20482e Remove unused usings 2020-04-22 17:10:14 +02:00
MihaZupan
fc50e2e2e7 Simplify crefs 2020-04-22 17:08:52 +02:00
Alexandre Mutel
4d513acf6b Add rider/idea to gitignore 2020-04-22 07:03:34 +02:00
Alexandre Mutel
32b1ee7eaa Remove Scriban.Signed from solution, keep it in the CI. Remove a warning 2020-04-22 06:59:57 +02:00
Alexandre Mutel
3e2a498ae7 Merge pull request #426 from MihaZupan/renormalize
git add --renormalize .
2020-04-22 06:55:35 +02:00
Alexandre Mutel
e0ac9b7ddf Merge pull request #425 from lellid/ContainerReadonlyList
Add IReadOnlyList interface to ContainerBlock to unify and simplify e…
2020-04-22 06:54:00 +02:00
Alexandre Mutel
bbf10a7ad0 Merge pull request #420 from SebastianRaffel/patch-1
PipeTableSpecs.md: Fix duplicate Rule numbering + bold headlines
2020-04-22 06:53:41 +02:00
MihaZupan
62a0992e5e git add --renormalize . 2020-04-22 00:47:46 +02:00
MihaZupan
b2e49d174e Add .gitattributes 2020-04-22 00:46:40 +02:00
SebastianRaffel
5e218721e7 Merge branch 'master' into patch-1 2020-04-20 09:25:36 +02:00
Sebastian Raffel
c64cd53ac3 Exec SpecFileGen 2020-04-20 09:23:13 +02:00
Dirk Lellinger
920ab6cb60 Add IReadonlyList interface to ContainerBlock to unify and simplify enumeration 2020-04-19 22:43:42 +02:00
Alexandre Mutel
3ef1d735d5 Bump to 0.20.0 2020-04-18 18:31:44 +02:00
Alexandre Mutel
47c6c49f5c Merge pull request #417 from KrisVandermotten/StringBuilderCache
Cleanup StringBuilderCache
2020-04-18 18:21:39 +02:00
Kris Vandermotten
a19f78342f merged from master 2020-04-18 18:17:40 +02:00
Alexandre Mutel
edb4c6c3cb Merge pull request #422 from KrisVandermotten/ListBlockParser
Cleanup ListBlockParser
2020-04-18 17:58:49 +02:00
Alexandre Mutel
4b6d7c78f5 Merge pull request #421 from KrisVandermotten/ParagraphBlockParser
Cleanup ParagraphBlockParser
2020-04-18 17:57:51 +02:00
Alexandre Mutel
0386cfd617 Merge pull request #418 from KrisVandermotten/CodeInlineParser
Optimized away the expensive StringBuilder.Remove(0, 1) in CodeInlineParser
2020-04-18 17:55:53 +02:00
Alexandre Mutel
5c52a7249d Merge pull request #416 from MihaZupan/new
Random improvements
2020-04-18 17:54:22 +02:00
Kris Vandermotten
14fb550704 Merge branch 'master' into StringBuilderCache 2020-04-18 14:21:48 +02:00
Kris Vandermotten
741d09a4e8 Merge branch 'master' into CodeInlineParser 2020-04-18 14:21:09 +02:00
Kris Vandermotten
34d9fa2bcc Merge branch 'master' into ParagraphBlockParser 2020-04-18 14:20:24 +02:00
Kris Vandermotten
8ee50a265d Merge branch 'master' into ListBlockParser 2020-04-18 14:19:46 +02:00
MihaZupan
eb002db3c7 Cross-target netstandard2.1 2020-04-18 14:14:57 +02:00
MihaZupan
a15203a061 Don't count non-ascii chars into maxChar limit in CharacterMap 2020-04-18 14:10:01 +02:00
MihaZupan
b0a2f19ed7 Optimize CharacterMap further 2020-04-18 14:10:00 +02:00
MihaZupan
a9d78e04a1 Cross-target Core 3.1 2020-04-18 14:10:00 +02:00
MihaZupan
a8b6e2c2e4 Remove allocation in EntityHelper.DecodeEntity 2020-04-18 14:10:00 +02:00
MihaZupan
665f5f5e51 Cache string representations of numbers 0-26 2020-04-18 14:10:00 +02:00
MihaZupan
86a72c3582 Avoid delegate allocation in LinkInlineParser.MarkParentAsInactive 2020-04-18 14:10:00 +02:00
MihaZupan
784b999d6c Improve CodeGen by using ThrowHelper everywhere 2020-04-18 14:10:00 +02:00
MihaZupan
be3a893d3d Use foreach where appropriate 2020-04-18 14:10:00 +02:00
MihaZupan
2679c84788 Remove .Net Foundation license header 2020-04-18 14:10:00 +02:00
MihaZupan
3d005d6444 Avoid allocations in EntityHelper 2020-04-18 14:09:59 +02:00
MihaZupan
09593ff3da Remove unused Helper 2020-04-18 14:09:59 +02:00
MihaZupan
f0269fc61f Remove ArrayHelper 2020-04-18 14:09:59 +02:00
MihaZupan
2d97628cfd Optimize StringSlice helpers 2020-04-18 14:09:59 +02:00
MihaZupan
ee97d32ef0 Remove MethodImplOptionPortable compat helper 2020-04-18 14:09:59 +02:00
MihaZupan
9ca5cdcb31 Remove substring allocation in RomanToArabic 2020-04-18 14:09:59 +02:00
MihaZupan
5f453fbe92 Remove ExcludeFromCodeCoverage compat for net35 2020-04-18 14:09:59 +02:00
MihaZupan
8300a9cca2 Add System.Memory package for netstandard2.0 2020-04-18 14:09:59 +02:00
MihaZupan
f7d763230d Unseal MarkdownParserContext 2020-04-18 14:09:58 +02:00
MihaZupan
cce1b99edc Drop net35 and net40 targets 2020-04-18 14:07:48 +02:00
Alexandre Mutel
2dca2119fa Run coveralls.net upload only on push 2020-04-18 09:13:32 +02:00
Alexandre Mutel
faf0a20816 Fix coverlet and coverall.net upload 2020-04-18 09:02:02 +02:00
Alexandre Mutel
68196c1ce6 Fix specs 2020-04-18 08:48:49 +02:00
Kris Vandermotten
fdf125d05d Cleanup ListBlockParser 2020-04-16 20:09:00 +02:00
Kris Vandermotten
46133715ab Cleanup ParagraphBlockParser 2020-04-16 20:03:40 +02:00
SebastianRaffel
ae726b3796 Fix duplicate Rule numbering + bold headlines 2020-04-14 08:51:44 +02:00
Kris Vandermotten
4151ac2287 Optimized away the expensive StringBuilder.Remove(0, 1) in CodeInlineParser 2020-04-13 16:24:35 +02:00
Kris Vandermotten
305f722899 Cleanup StringBuilderCache 2020-04-13 16:02:04 +02:00
Alexandre Mutel
abc8aa25b7 Merge pull request #411 from WeihanLi/patch-1
make dotnet framework ReferenceAssemblies Private
2020-03-23 20:49:37 +01:00
Alexandre Mutel
e6bb83ecdf Merge pull request #410 from KrisVandermotten/RendererBase
Optimized RendererBase
2020-03-23 20:48:54 +01:00
Weihan Li
e6ea8ad274 make dotnet framework ReferenceAssemblies Private 2020-03-19 13:33:18 +08:00
Kris Vandermotten
1f3cf5962f Optimized RendererBase 2020-03-09 15:32:29 +01:00
Alexandre Mutel
83b3427805 Fix Microsoft.SourceLink.GitHub 2020-03-08 10:45:00 +01:00
Alexandre Mutel
3ff3f1ccb9 Bump to 0.18.3 2020-03-08 10:33:57 +01:00
Alexandre Mutel
5d91f0e26f Fix NuGet symbol packages 2020-03-08 10:33:13 +01:00
320 changed files with 7372 additions and 5697 deletions

3
.gitattributes vendored Normal file
View File

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

View File

@@ -22,11 +22,6 @@ jobs:
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.0.100
- name: Add coveralls.net
run: dotnet tool install coveralls.net --version 1.0.0 --tool-path tools
- name: Build (Release)
run: dotnet build src -c Release
@@ -40,18 +35,22 @@ jobs:
- name: Build & Test (Debug)
run: dotnet test src -c Debug
- name: Coveralls
run: dotnet test src -c Release -f netcoreapp2.1 /p:Include=\"[${{env.PROJECT_NAME}}]*\" /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./coverage.info
- name: Coverlet
run: dotnet test src -c Release -f netcoreapp3.1 /p:Include=\"[${{env.PROJECT_NAME}}]*\" /p:CollectCoverage=true /p:CoverletOutputFormat=lcov
- name: Coveralls Upload
uses: coverallsapp/github-action@v1.0.1
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: src/${{env.PROJECT_NAME}}.Tests/coverage.info
path-to-lcov: src/${{env.PROJECT_NAME}}.Tests/coverage.netcoreapp3.1.info
- name: Pack
run: dotnet pack src -c Release
- name: Pack Signed
run: dotnet pack -c Release src/${{env.PROJECT_NAME}}.Signed/${{env.PROJECT_NAME}}.Signed.csproj
- name: Publish
if: github.event_name == 'push'
run: |

25
.gitignore vendored
View File

@@ -138,7 +138,7 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
@@ -216,3 +216,26 @@ FakesAssemblies/
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Common IntelliJ Platform excludes
# User specific
**/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
**/.idea/httpRequests/
# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml
# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml

View File

@@ -1,201 +1,235 @@
# Changelog
## 0.18.2 (8 Mar 2020)
- Optimize LineReader.ReadLine in [PR #393](https://github.com/lunet-io/markdig/pull/393)
- Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T> in [PR #394](https://github.com/lunet-io/markdig/pull/394)
- Use BitVector128 in CharacterMap<T> in [PR #396](https://github.com/lunet-io/markdig/pull/396)
- Optimizations in StringLineGroup in [PR #399](https://github.com/lunet-io/markdig/pull/399)
- Fixed a bug in HeadingRenderer in [PR #402](https://github.com/lunet-io/markdig/pull/402)
- Fixes issue #303 in [PR #404](https://github.com/lunet-io/markdig/pull/404)
- Make output of HtmlTableRenderer XML wellformed in [PR #406](https://github.com/lunet-io/markdig/pull/406)
## 0.18.1 (21 Jan 2020)
- Re-allow emojis and smileys customization, that was broken in [PR #308](https://github.com/lunet-io/markdig/pull/308) ([PR #386](https://github.com/lunet-io/markdig/pull/386))
- Add `IHostProvider` for medialink customization (#337), support protocol-less url (#135) ([(PR #341)](https://github.com/lunet-io/markdig/pull/341))
- Add missing Descendants<T> overload ([(PR #387)](https://github.com/lunet-io/markdig/pull/387))
## 0.18.0 (24 Oct 2019)
- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357))
- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360))
- Fix generic attributes with values of length 1 ([(PR #361)](https://github.com/lunet-io/markdig/pull/361))
- Fix link text balanced bracket matching ([(PR #375)](https://github.com/lunet-io/markdig/pull/375))
- Improve overall performance and substantially reduce allocations ([(PR #377)](https://github.com/lunet-io/markdig/pull/377))
## 0.17.1 (04 July 2019)
- Fix regression when escaping HTML characters ([(PR #340)](https://github.com/lunet-io/markdig/pull/340))
- Update Emoji Dictionary ([(PR #346)](https://github.com/lunet-io/markdig/pull/346))
## 0.17.0 (10 May 2019)
- Update to latest CommonMark specs 0.29 ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
- Add `AutoLinkOptions` with `OpenInNewWindow`, `UseHttpsForWWWLinks` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
- Add `DisableHeadings` extension method to `MarkdownPipelineBuilder` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
- Drop support for netstandard1.1 and Portable Class Libraries ([(PR #319)](https://github.com/lunet-io/markdig/pull/319))
- Allow non-ASCII characters in url domain names ([(PR #319)](https://github.com/lunet-io/markdig/pull/319))
- Add better support for youtu.be link ([(PR #336)](https://github.com/lunet-io/markdig/pull/336))
- Fix backsticks in Markdown.Normalize ([(PR #334)](https://github.com/lunet-io/markdig/pull/334))
## 0.16.0 (25 Feb 2019)
- Improve performance of emoji-abbreviation parser ([(PR #305)](https://github.com/lunet-io/markdig/pull/305))
- Change output for math extension to use a rendering more compatible with existing Math JS libraries ([(PR #311)](https://github.com/lunet-io/markdig/pull/311))
- Improve emphasis parser to allow to match more than 2 characters ([(PR #301)](https://github.com/lunet-io/markdig/pull/301))
- Output attached attributes to a `<tr>` from a table row ([(PR #300)](https://github.com/lunet-io/markdig/pull/300))
- Improve MarkdownObject.Descendants() search ([(PR #288)](https://github.com/lunet-io/markdig/pull/288))
- Allow to pass a `MarkdownParserContext` ([(PR #285)](https://github.com/lunet-io/markdig/pull/285))
## 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 #253)](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 `&lt;/iframe&gt;` 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 Abbreviation 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
# Changelog
## 0.22.0 (05 Oct 2020)
- Fix Setext headings in block quotes.
- Fix tel: treated as autolink ([PR #478](https://github.com/lunet-io/markdig/pull/478)
- Make Inline.FirstParentOfType public ([PR #474](https://github.com/lunet-io/markdig/pull/474)
- Fix `&` to be parsed as a punctuation while it was detected as a html entity in certain cases ([PR #471](https://github.com/lunet-io/markdig/pull/471)
- Add ParentBlock property to ContainerInline ([PR #468](https://github.com/lunet-io/markdig/pull/468)
## 0.21.1 (17 Aug 2020)
- Fix Markdig.Signed on GitHub Actions
## 0.21.0 (17 Aug 2020)
- Restore support for .NET 4.5 (#)
- Add IReadonlyList interface to ContainerBlock to unify and simplify enumeration (#425)
- Fix relative uri detection to be cross-platform compatible (#430)
- Escape URLs scheme (#431)
- Fix media links (#435)
- Fix parsing math blocks with no leading or trailing whitespace (#452)
- Add support for autolink `tel:` uri (#453)
- Fallback to non-punycode encoding for invalid IDN urls (#449)
- Pipe Tables: Normalize using header column count (#455)
- Expose IndentCount of FencedCodeBlock (#464)
## 0.20.0 (18 Apr 2020)
- Markdig is now compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`.
- Many performance improvements from [PR #416](https://github.com/lunet-io/markdig/pull/416)
[PR #417](https://github.com/lunet-io/markdig/pull/417)
[PR #418](https://github.com/lunet-io/markdig/pull/418)
[PR #421](https://github.com/lunet-io/markdig/pull/421)
[PR #422](https://github.com/lunet-io/markdig/pull/422)
[PR #410](https://github.com/lunet-io/markdig/pull/410)
## 0.18.3 (8 Mar 2020)
- Publish NuGet Symbol packages
## 0.18.2 (8 Mar 2020)
- Optimize LineReader.ReadLine in [PR #393](https://github.com/lunet-io/markdig/pull/393)
- Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T> in [PR #394](https://github.com/lunet-io/markdig/pull/394)
- Use BitVector128 in CharacterMap<T> in [PR #396](https://github.com/lunet-io/markdig/pull/396)
- Optimizations in StringLineGroup in [PR #399](https://github.com/lunet-io/markdig/pull/399)
- Fixed a bug in HeadingRenderer in [PR #402](https://github.com/lunet-io/markdig/pull/402)
- Fixes issue #303 in [PR #404](https://github.com/lunet-io/markdig/pull/404)
- Make output of HtmlTableRenderer XML wellformed in [PR #406](https://github.com/lunet-io/markdig/pull/406)
## 0.18.1 (21 Jan 2020)
- Re-allow emojis and smileys customization, that was broken in [PR #308](https://github.com/lunet-io/markdig/pull/308) ([PR #386](https://github.com/lunet-io/markdig/pull/386))
- Add `IHostProvider` for medialink customization (#337), support protocol-less url (#135) ([(PR #341)](https://github.com/lunet-io/markdig/pull/341))
- Add missing Descendants<T> overload ([(PR #387)](https://github.com/lunet-io/markdig/pull/387))
## 0.18.0 (24 Oct 2019)
- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357))
- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360))
- Fix generic attributes with values of length 1 ([(PR #361)](https://github.com/lunet-io/markdig/pull/361))
- Fix link text balanced bracket matching ([(PR #375)](https://github.com/lunet-io/markdig/pull/375))
- Improve overall performance and substantially reduce allocations ([(PR #377)](https://github.com/lunet-io/markdig/pull/377))
## 0.17.1 (04 July 2019)
- Fix regression when escaping HTML characters ([(PR #340)](https://github.com/lunet-io/markdig/pull/340))
- Update Emoji Dictionary ([(PR #346)](https://github.com/lunet-io/markdig/pull/346))
## 0.17.0 (10 May 2019)
- Update to latest CommonMark specs 0.29 ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
- Add `AutoLinkOptions` with `OpenInNewWindow`, `UseHttpsForWWWLinks` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
- Add `DisableHeadings` extension method to `MarkdownPipelineBuilder` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
- Drop support for netstandard1.1 and Portable Class Libraries ([(PR #319)](https://github.com/lunet-io/markdig/pull/319))
- Allow non-ASCII characters in url domain names ([(PR #319)](https://github.com/lunet-io/markdig/pull/319))
- Add better support for youtu.be link ([(PR #336)](https://github.com/lunet-io/markdig/pull/336))
- Fix backsticks in Markdown.Normalize ([(PR #334)](https://github.com/lunet-io/markdig/pull/334))
## 0.16.0 (25 Feb 2019)
- Improve performance of emoji-abbreviation parser ([(PR #305)](https://github.com/lunet-io/markdig/pull/305))
- Change output for math extension to use a rendering more compatible with existing Math JS libraries ([(PR #311)](https://github.com/lunet-io/markdig/pull/311))
- Improve emphasis parser to allow to match more than 2 characters ([(PR #301)](https://github.com/lunet-io/markdig/pull/301))
- Output attached attributes to a `<tr>` from a table row ([(PR #300)](https://github.com/lunet-io/markdig/pull/300))
- Improve MarkdownObject.Descendants() search ([(PR #288)](https://github.com/lunet-io/markdig/pull/288))
- Allow to pass a `MarkdownParserContext` ([(PR #285)](https://github.com/lunet-io/markdig/pull/285))
## 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 #253)](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 `&lt;/iframe&gt;` 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 Abbreviation 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

@@ -50,7 +50,9 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**YAML Front Matter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the front matter and to discard it from the HTML output (typically used for previewing without the front matter in MarkdownEditor)
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
- Compatible with .NET 3.5, 4.0+ and .NET Core (`netstandard1.1+`)
- Starting with Markdig version `0.20.0+`, Markdig is compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`.
If you are looking for support for an old .NET Framework 3.5 or 4.0, you can download Markdig `0.18.3`.
### Third Party Extensions
@@ -92,6 +94,8 @@ var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
```
[Try it online!](https://dotnetfiddle.net/GoZXyI)
You can have a look at the [MarkdownExtensions](https://github.com/lunet-io/markdig/blob/master/src/Markdig/MarkdownExtensions.cs) that describes all actionable extensions (by modifying the MarkdownPipeline)
## Build

View File

@@ -5,14 +5,9 @@ extern alias newcmark;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnostics;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using newcmark::CommonMark.Extension;
using Markdig;
@@ -55,7 +50,7 @@ namespace Testamina.Markdig.Benchmarks
//CommonMark.CommonMarkConverter.Parse(reader);
//reader.Dispose();
//var writer = new StringWriter();
global::CommonMark.CommonMarkConverter.Convert(text);
CommonMark.CommonMarkConverter.Convert(text);
//writer.Flush();
//writer.ToString();
}

View File

@@ -2,13 +2,9 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using BenchmarkDotNet.Attributes;
using Markdig.Helpers;
namespace Testamina.Markdig.Benchmarks
{

View File

@@ -1,23 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net451;netcoreapp2.1</TargetFrameworks>
<OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="2.6.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
<StartupObject>Markdig.Tests.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="2.9.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
</Project>

View File

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

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Headings
@@ -79,15 +78,15 @@ namespace Markdig.Tests.Specs.Normalize.Headings
// Heading
// =======
//
// Text after two newlines
// Text after two newlines 1
//
// Should be rendered as:
// # Heading
//
// Text after two newlines
// Text after two newlines 1
Console.WriteLine("Example 3\nSection Headings\n");
TestNormalize.TestSpec("Heading\n=======\n\nText after two newlines", "# Heading\n\nText after two newlines", "");
TestNormalize.TestSpec("Heading\n=======\n\nText after two newlines 1", "# Heading\n\nText after two newlines 1", "");
}
}
}

View File

@@ -1,5 +1,5 @@
# Headings
# Headings
```````````````````````````````` example
# Heading 1
@@ -24,8 +24,8 @@
##### Heading 5
###### Heading 6
````````````````````````````````
````````````````````````````````
```````````````````````````````` example
###### Heading
@@ -34,15 +34,15 @@ Text after two newlines
###### Heading
Text after two newlines
````````````````````````````````
````````````````````````````````
```````````````````````````````` example
Heading
=======
Text after two newlines
Text after two newlines 1
.
# Heading
Text after two newlines
Text after two newlines 1
````````````````````````````````

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Sample

View File

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

View File

@@ -1,4 +1,3 @@
// Generated: 2019-05-15 02:46:55
// --------------------------------
// Abbreviations

View File

@@ -112,9 +112,9 @@ A
The longest matching abbreviation should be used
```````````````````````````````` example
*[Foo]: foo
*[Foo Bar]: foobar
*[Foo]: foo
*[Foo Bar]: foobar
Foo B
.
<p><abbr title="foo">Foo</abbr> B</p>

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Auto Identifiers

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Auto Links
@@ -23,6 +22,7 @@ namespace Markdig.Tests.Specs.AutoLinks
// - `http://` or `https://`
// - `ftp://`
// - `mailto:`
// - `tel:`
// - `www.`
[Test]
public void ExtensionsAutoLinks_Example001()
@@ -34,16 +34,18 @@ namespace Markdig.Tests.Specs.AutoLinks
// This is a http://www.google.com URL and https://www.google.com
// This is a ftp://test.com
// And a mailto:email@toto.com
// And a tel:+1555123456
// And a plain www.google.com
//
// Should be rendered as:
// <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">email@toto.com</a>
// And a <a href="tel:+1555123456">+1555123456</a>
// And a plain <a href="http://www.google.com">www.google.com</a></p>
Console.WriteLine("Example 1\nSection Extensions / AutoLinks\n");
TestParser.TestSpec("This is a http://www.google.com URL and https://www.google.com\nThis is a ftp://test.com\nAnd a mailto:email@toto.com\nAnd 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>\nThis is a <a href=\"ftp://test.com\">ftp://test.com</a>\nAnd a <a href=\"mailto:email@toto.com\">email@toto.com</a>\nAnd a plain <a href=\"http://www.google.com\">www.google.com</a></p>", "autolinks|advanced");
TestParser.TestSpec("This is a http://www.google.com URL and https://www.google.com\nThis is a ftp://test.com\nAnd a mailto:email@toto.com\nAnd a tel:+1555123456\nAnd 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>\nThis is a <a href=\"ftp://test.com\">ftp://test.com</a>\nAnd a <a href=\"mailto:email@toto.com\">email@toto.com</a>\nAnd a <a href=\"tel:+1555123456\">+1555123456</a>\nAnd a plain <a href=\"http://www.google.com\">www.google.com</a></p>", "autolinks|advanced");
}
// But incomplete links will not be matched:
@@ -58,15 +60,17 @@ namespace Markdig.Tests.Specs.AutoLinks
// This is not a ftp:/test.com
// And not a mailto:emailtoto.com
// And not a plain www. or a www.x
// And not a tel:
//
// Should be rendered as:
// <p>This is not a http:/www.google.com URL and https:/www.google.com
// This is not a ftp:/test.com
// And not a mailto:emailtoto.com
// And not a plain www. or a www.x</p>
// And not a plain www. or a www.x
// And not a tel:</p>
Console.WriteLine("Example 2\nSection Extensions / AutoLinks\n");
TestParser.TestSpec("This is not a http:/www.google.com URL and https:/www.google.com\nThis is not a ftp:/test.com\nAnd not a mailto:emailtoto.com\nAnd not a plain www. or a www.x ", "<p>This is not a http:/www.google.com URL and https:/www.google.com\nThis is not a ftp:/test.com\nAnd not a mailto:emailtoto.com\nAnd not a plain www. or a www.x</p>", "autolinks|advanced");
TestParser.TestSpec("This is not a http:/www.google.com URL and https:/www.google.com\nThis is not a ftp:/test.com\nAnd not a mailto:emailtoto.com\nAnd not a plain www. or a www.x \nAnd not a tel:", "<p>This is not a http:/www.google.com URL and https:/www.google.com\nThis is not a ftp:/test.com\nAnd not a mailto:emailtoto.com\nAnd not a plain www. or a www.x\nAnd not a tel:</p>", "autolinks|advanced");
}
// Previous character must be a punctuation or a valid space (tab, space, new line):

View File

@@ -9,17 +9,20 @@ Autolinks will format as a HTML link any string that starts by:
- `http://` or `https://`
- `ftp://`
- `mailto:`
- `tel:`
- `www.`
```````````````````````````````` example
This is a http://www.google.com URL and https://www.google.com
This is a ftp://test.com
And a mailto:email@toto.com
And a tel:+1555123456
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">email@toto.com</a>
And a <a href="tel:+1555123456">+1555123456</a>
And a plain <a href="http://www.google.com">www.google.com</a></p>
````````````````````````````````
@@ -30,11 +33,13 @@ This is not a http:/www.google.com URL and https:/www.google.com
This is not a ftp:/test.com
And not a mailto:emailtoto.com
And not a plain www. or a www.x
And not a tel:
.
<p>This is not a http:/www.google.com URL and https:/www.google.com
This is not a ftp:/test.com
And not a mailto:emailtoto.com
And not a plain www. or a www.x</p>
And not a plain www. or a www.x
And not a tel:</p>
````````````````````````````````
Previous character must be a punctuation or a valid space (tab, space, new line):
@@ -245,18 +250,18 @@ https://github.com:443
Links with unicode characters in the path / query / fragment are matched and url encoded
```````````````````````````````` example
http://abc.net/☃
http://abc.net?☃
http://abc.net#☃
```````````````````````````````` example
http://abc.net/☃
http://abc.net?☃
http://abc.net#☃
http://abc.net/foo#☃
.
<p><a href="http://abc.net/%E2%98%83">http://abc.net/☃</a></p>
<p><a href="http://abc.net?%E2%98%83">http://abc.net?☃</a></p>
<p><a href="http://abc.net#%E2%98%83">http://abc.net#☃</a></p>
.
<p><a href="http://abc.net/%E2%98%83">http://abc.net/☃</a></p>
<p><a href="http://abc.net?%E2%98%83">http://abc.net?☃</a></p>
<p><a href="http://abc.net#%E2%98%83">http://abc.net#☃</a></p>
<p><a href="http://abc.net/foo#%E2%98%83">http://abc.net/foo#☃</a></p>
````````````````````````````````
@@ -270,18 +275,18 @@ http://☃.net?☃
Same goes for regular autolinks
```````````````````````````````` example
<http://abc.net/☃>
<http://abc.net?☃>
<http://abc.net#☃>
```````````````````````````````` example
<http://abc.net/☃>
<http://abc.net?☃>
<http://abc.net#☃>
<http://abc.net/foo#☃>
.
<p><a href="http://abc.net/%E2%98%83">http://abc.net/☃</a></p>
<p><a href="http://abc.net?%E2%98%83">http://abc.net?☃</a></p>
<p><a href="http://abc.net#%E2%98%83">http://abc.net#☃</a></p>
.
<p><a href="http://abc.net/%E2%98%83">http://abc.net/☃</a></p>
<p><a href="http://abc.net?%E2%98%83">http://abc.net?☃</a></p>
<p><a href="http://abc.net#%E2%98%83">http://abc.net#☃</a></p>
<p><a href="http://abc.net/foo#%E2%98%83">http://abc.net/foo#☃</a></p>
````````````````````````````````

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-15 05:23:49
// --------------------------------
// Bootstrap

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-12 15:26:42
// --------------------------------
// CommonMark v. 0.29
@@ -14925,6 +14924,29 @@ namespace Markdig.Tests.Specs.CommonMarkV_0_29
Console.WriteLine("Example 649\nSection Inlines / Textual content\n");
TestParser.TestSpec("Multiple spaces", "<p>Multiple spaces</p>", "");
}
// Within a blockquote a setext heading takes precedence
// over a thematic break:
[Test]
public void InlinesTextualContent_Example650()
{
// Example 650
// Section: Inlines / Textual content
//
// The following Markdown:
// > Foo
// > ---
// > bar
//
// Should be rendered as:
// <blockquote>
// <h2>Foo</h2>
// <p>bar</p>
// </blockquote>
Console.WriteLine("Example 650\nSection Inlines / Textual content\n");
TestParser.TestSpec("> Foo\n> ---\n> bar", "<blockquote>\n<h2>Foo</h2>\n<p>bar</p>\n</blockquote>", "");
}
// <!-- END TESTS -->
//
// # Appendix: A parsing strategy

View File

@@ -9368,6 +9368,20 @@ Multiple spaces
<p>Multiple spaces</p>
````````````````````````````````
Within a blockquote a setext heading takes precedence
over a thematic break:
```````````````````````````````` example
> Foo
> ---
> bar
.
<blockquote>
<h2>Foo</h2>
<p>bar</p>
</blockquote>
````````````````````````````````
<!-- END TESTS -->

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Custom Containers

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-15 05:06:35
// --------------------------------
// Definition Lists

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Diagrams

View File

@@ -1,4 +1,3 @@
// Generated: 2020-01-13 21:08:58
// --------------------------------
// Emoji

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Emphasis Extra

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Figures, Footers and Cites

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-15 05:33:49
// --------------------------------
// Footnotes

View File

@@ -1,4 +1,3 @@
// Generated: 2019-08-01 13:57:17
// --------------------------------
// Generic Attributes

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-15 05:25:26
// --------------------------------
// Globalization

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Grid Tables
@@ -62,8 +61,8 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:50%">
// <col style="width:50%">
// <col style="width:50%" />
// <col style="width:50%" />
// <tbody>
// <tr>
// <td>This is</td>
@@ -73,7 +72,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 1\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+---------+---------+\n| This is | a table |", "<table>\n<col style=\"width:50%\">\n<col style=\"width:50%\">\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+---------+---------+\n| This is | a table |", "<table>\n<col style=\"width:50%\" />\n<col style=\"width:50%\" />\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// The following is not a valid row separator
@@ -112,9 +111,9 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <tbody>
// <tr>
// <td>Col1
@@ -135,7 +134,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 3\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+---------+---------+---------+\n| Col1 | Col2 | Col3 |\n| Col1a | Col2a | Col3a |\n| Col1b | Col3b |\n| Col1c |", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td>Col1\nCol1a</td>\n<td>Col2\nCol2a</td>\n<td>Col3\nCol3a</td>\n</tr>\n<tr>\n<td colspan=\"2\">Col1b</td>\n<td>Col3b</td>\n</tr>\n<tr>\n<td colspan=\"3\">Col1c</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+---------+---------+---------+\n| Col1 | Col2 | Col3 |\n| Col1a | Col2a | Col3a |\n| Col1b | Col3b |\n| Col1c |", "<table>\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<tbody>\n<tr>\n<td>Col1\nCol1a</td>\n<td>Col2\nCol2a</td>\n<td>Col3\nCol3a</td>\n</tr>\n<tr>\n<td colspan=\"2\">Col1b</td>\n<td>Col3b</td>\n</tr>\n<tr>\n<td colspan=\"3\">Col1c</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// A row header is separated using `+========+` instead of `+---------+`:
@@ -152,8 +151,8 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:50%">
// <col style="width:50%">
// <col style="width:50%" />
// <col style="width:50%" />
// <thead>
// <tr>
// <th>This is</th>
@@ -163,7 +162,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 4\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+---------+---------+\n| This is | a table |\n+=========+=========+", "<table>\n<col style=\"width:50%\">\n<col style=\"width:50%\">\n<thead>\n<tr>\n<th>This is</th>\n<th>a table</th>\n</tr>\n</thead>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+---------+---------+\n| This is | a table |\n+=========+=========+", "<table>\n<col style=\"width:50%\" />\n<col style=\"width:50%\" />\n<thead>\n<tr>\n<th>This is</th>\n<th>a table</th>\n</tr>\n</thead>\n</table>", "gridtables|advanced");
}
// The last column separator `|` may be omitted:
@@ -179,8 +178,8 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:50%">
// <col style="width:50%">
// <col style="width:50%" />
// <col style="width:50%" />
// <tbody>
// <tr>
// <td>This is</td>
@@ -190,7 +189,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 5\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+---------+---------+\n| This is | a table with a longer text in the second column", "<table>\n<col style=\"width:50%\">\n<col style=\"width:50%\">\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table with a longer text in the second column</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+---------+---------+\n| This is | a table with a longer text in the second column", "<table>\n<col style=\"width:50%\" />\n<col style=\"width:50%\" />\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table with a longer text in the second column</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
@@ -215,9 +214,9 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:25%">
// <col style="width:50%">
// <col style="width:25%">
// <col style="width:25%" />
// <col style="width:50%" />
// <col style="width:25%" />
// <tbody>
// <tr>
// <td>A</td>
@@ -228,7 +227,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 6\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+----+--------+----+\n| A | B C D | E |\n+----+--------+----+", "<table>\n<col style=\"width:25%\">\n<col style=\"width:50%\">\n<col style=\"width:25%\">\n<tbody>\n<tr>\n<td>A</td>\n<td>B C D</td>\n<td>E</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+----+--------+----+\n| A | B C D | E |\n+----+--------+----+", "<table>\n<col style=\"width:25%\" />\n<col style=\"width:50%\" />\n<col style=\"width:25%\" />\n<tbody>\n<tr>\n<td>A</td>\n<td>B C D</td>\n<td>E</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// Alignment might be specified on the first row using the character `:`:
@@ -245,9 +244,9 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <tbody>
// <tr>
// <td>A</td>
@@ -258,7 +257,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 7\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+-----+:---:+-----+\n| A | B | C |\n+-----+-----+-----+", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td>A</td>\n<td style=\"text-align: center;\">B</td>\n<td>C</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+-----+:---:+-----+\n| A | B | C |\n+-----+-----+-----+", "<table>\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<tbody>\n<tr>\n<td>A</td>\n<td style=\"text-align: center;\">B</td>\n<td>C</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// A grid table may have cells spanning both columns and rows:
@@ -279,9 +278,9 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <tbody>
// <tr>
// <td colspan="2">AAAAA</td>
@@ -302,7 +301,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 8\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+---+---+ B +\n| D | E | B |\n+ D +---+---+\n| D | CCCCC |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td colspan=\"2\">AAAAA</td>\n<td rowspan=\"2\">B\nB\nB</td>\n</tr>\n<tr>\n<td rowspan=\"2\">D\nD\nD</td>\n<td>E</td>\n</tr>\n<tr>\n<td colspan=\"2\">CCCCC</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+---+---+ B +\n| D | E | B |\n+ D +---+---+\n| D | CCCCC |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<tbody>\n<tr>\n<td colspan=\"2\">AAAAA</td>\n<td rowspan=\"2\">B\nB\nB</td>\n</tr>\n<tr>\n<td rowspan=\"2\">D\nD\nD</td>\n<td>E</td>\n</tr>\n<tr>\n<td colspan=\"2\">CCCCC</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// A grid table may have cells with both colspan and rowspan:
@@ -323,9 +322,9 @@ namespace Markdig.Tests.Specs.GridTables
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <col style="width:33.33%" />
// <tbody>
// <tr>
// <td colspan="2" rowspan="2">AAAAA
@@ -345,7 +344,7 @@ namespace Markdig.Tests.Specs.GridTables
// </table>
Console.WriteLine("Example 9\nSection Extensions / Grid Table\n");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+ AAAAA +---+\n| AAAAA | C |\n+---+---+---+\n| D | E | F |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td colspan=\"2\" rowspan=\"2\">AAAAA\nAAAAA\nAAAAA</td>\n<td>B</td>\n</tr>\n<tr>\n<td>C</td>\n</tr>\n<tr>\n<td>D</td>\n<td>E</td>\n<td>F</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+ AAAAA +---+\n| AAAAA | C |\n+---+---+---+\n| D | E | F |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<col style=\"width:33.33%\" />\n<tbody>\n<tr>\n<td colspan=\"2\" rowspan=\"2\">AAAAA\nAAAAA\nAAAAA</td>\n<td>B</td>\n</tr>\n<tr>\n<td>C</td>\n</tr>\n<tr>\n<td>D</td>\n<td>E</td>\n<td>F</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
// A grid table may not have irregularly shaped cells:

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Hardline Breaks

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-15 05:30:00
// --------------------------------
// Jira Links

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// List Extras

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Math

View File

@@ -1,4 +1,3 @@
// Generated: 2019-05-15 02:46:20
// --------------------------------
// Media
@@ -17,7 +16,7 @@ namespace Markdig.Tests.Specs.Media
// Adds support for media links:
//
// ## Media links
//
//
// Allows to embed audio/video links to popular website:
[Test]
public void ExtensionsMediaLinks_Example001()
@@ -35,7 +34,7 @@ namespace Markdig.Tests.Specs.Media
// ![youtu.be with t](https://youtu.be/mswPy5bt3TQ?t=100)
//
// ![youtube.com/embed 1](https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0)
//
//
// ![youtube.com/embed 2](https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6)
//
// ![vimeo](https://vimeo.com/8607834)
@@ -47,19 +46,19 @@ namespace Markdig.Tests.Specs.Media
// ![ok.ru](https://ok.ru/video/26870090463)
//
// Should be rendered as:
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://player.vimeo.com/video/8607834" class="vimeo" 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" class="yandex" frameborder="0"></iframe></p>
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" class="yandex" width="500" height="281" frameborder="0"></iframe></p>
// <p><iframe src="https://ok.ru/videoembed/26870090463" class="odnoklassniki" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
Console.WriteLine("Example 1\nSection Extensions / Media links\n");
TestParser.TestSpec("![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)\n\n![youtube.com with t](https://www.youtube.com/watch?v=mswPy5bt3TQ&t=100)\n\n![youtu.be](https://youtu.be/mswPy5bt3TQ)\n\n![youtu.be with t](https://youtu.be/mswPy5bt3TQ?t=100)\n\n![youtube.com/embed 1](https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0)\n \n![youtube.com/embed 2](https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6)\n\n![vimeo](https://vimeo.com/8607834)\n\n![static mp4](https://sample.com/video.mp4)\n\n![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)\n\n![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
TestParser.TestSpec("![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)\n\n![youtube.com with t](https://www.youtube.com/watch?v=mswPy5bt3TQ&t=100)\n\n![youtu.be](https://youtu.be/mswPy5bt3TQ)\n\n![youtu.be with t](https://youtu.be/mswPy5bt3TQ?t=100)\n\n![youtube.com/embed 1](https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0)\n\n![youtube.com/embed 2](https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6)\n\n![vimeo](https://vimeo.com/8607834)\n\n![static mp4](https://sample.com/video.mp4)\n\n![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)\n\n![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" class=\"vimeo\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" class=\"yandex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
}
}
}

View File

@@ -3,7 +3,7 @@
Adds support for media links:
## Media links
Allows to embed audio/video links to popular website:
```````````````````````````````` example
@@ -16,7 +16,7 @@ Allows to embed audio/video links to popular website:
![youtu.be with t](https://youtu.be/mswPy5bt3TQ?t=100)
![youtube.com/embed 1](https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0)
![youtube.com/embed 2](https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6)
![vimeo](https://vimeo.com/8607834)
@@ -27,14 +27,14 @@ Allows to embed audio/video links to popular website:
![ok.ru](https://ok.ru/video/26870090463)
.
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" class="youtube" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" class="vimeo" 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" class="yandex" frameborder="0"></iframe></p>
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" class="yandex" width="500" height="281" frameborder="0"></iframe></p>
<p><iframe src="https://ok.ru/videoembed/26870090463" class="odnoklassniki" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
````````````````````````````````

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// No Html

View File

@@ -0,0 +1,856 @@
// --------------------------------
// GFM Pipe Tables
// --------------------------------
using System;
using NUnit.Framework;
namespace Markdig.Tests.Specs.GFMPipeTables
{
[TestFixture]
public class TestExtensionsGfmPipeTable
{
// # Extensions
//
// This section describes the different extensions supported:
//
// ## Gfm Pipe Table
//
// This groups a certain set of behaviors that makes pipe tables adhere more strictly to the GitHub-flavored Markdown specification.
//
// A pipe table is detected when:
//
// **Rule #1**
// - Each line of a paragraph block have to contain at least a **column delimiter** `|` that is not embedded by either a code inline (backtick \`) or a HTML inline.
// - The second row must separate the first header row from sub-sequent rows by containing a **header column separator** for each column separated by a column delimiter. A header column separator is:
// - starting by optional spaces
// - followed by an optional `:` to specify left align
// - followed by a sequence of at least one `-` character
// - followed by an optional `:` to specify right align (or center align if left align is also defined)
// - ending by optional spaces
//
// Because a list has a higher precedence than a pipe table, a table header row separator requires at least 2 dashes `--` to start a header row:
[Test]
public void ExtensionsGfmPipeTable_Example001()
{
// Example 1
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | -
// 0 | 1
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 1\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n-- | -\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// The following is also considered as a table, even if the second line starts like a list:
[Test]
public void ExtensionsGfmPipeTable_Example002()
{
// Example 2
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// - | -
// 0 | 1
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 2\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n- | -\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// A pipe table with only one header row is allowed:
[Test]
public void ExtensionsGfmPipeTable_Example003()
{
// Example 3
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | --
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// </table>
Console.WriteLine("Example 3\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n-- | --", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n</table>", "gfm-pipetables");
}
// After a row separator header, they will be interpreted as plain column:
[Test]
public void ExtensionsGfmPipeTable_Example004()
{
// Example 4
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | --
// -- | --
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>--</td>
// <td>--</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 4\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n-- | --\n-- | --", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>--</td>\n<td>--</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// But if a table doesn't start with a column delimiter, it is not interpreted as a table, even if following lines have a column delimiter
[Test]
public void ExtensionsGfmPipeTable_Example005()
{
// Example 5
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a b
// c | d
// e | f
//
// Should be rendered as:
// <p>a b
// c | d
// e | f</p>
Console.WriteLine("Example 5\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a b\nc | d\ne | f", "<p>a b\nc | d\ne | f</p>", "gfm-pipetables");
}
// If a line doesn't have a column delimiter `|` the table is not detected
[Test]
public void ExtensionsGfmPipeTable_Example006()
{
// Example 6
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// c no d
//
// Should be rendered as:
// <p>a | b
// c no d</p>
Console.WriteLine("Example 6\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\nc no d", "<p>a | b\nc no d</p>", "gfm-pipetables");
}
// If a row contains more columns than the header row, the extra columns will be ignored:
[Test]
public void ExtensionsGfmPipeTable_Example007()
{
// Example 7
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | --
// 0 | 1 | 2
// 3 | 4
// 5 |
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// <tr>
// <td>3</td>
// <td>4</td>
// </tr>
// <tr>
// <td>5</td>
// <td></td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 7\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b \n-- | --\n0 | 1 | 2\n3 | 4\n5 |", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>3</td>\n<td>4</td>\n</tr>\n<tr>\n<td>5</td>\n<td></td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Rule #2**
// A pipe table ends after a blank line or the end of the file.
//
// **Rule #3**
// A cell content is trimmed (start and end) from white-spaces.
[Test]
public void ExtensionsGfmPipeTable_Example008()
{
// Example 8
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b |
// -- | --
// 0 | 1 |
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 8\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b |\n-- | --\n0 | 1 |", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Rule #4**
// Column delimiters `|` at the very beginning of a line or just before a line ending with only spaces and/or terminated by a newline can be omitted
[Test]
public void ExtensionsGfmPipeTable_Example009()
{
// Example 9
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b |
// -- | --
// | 0 | 1
// | 2 | 3 |
// 4 | 5
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// <tr>
// <td>2</td>
// <td>3</td>
// </tr>
// <tr>
// <td>4</td>
// <td>5</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 9\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec(" a | b |\n-- | --\n| 0 | 1\n| 2 | 3 |\n 4 | 5 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>2</td>\n<td>3</td>\n</tr>\n<tr>\n<td>4</td>\n<td>5</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// A pipe may be present at both the beginning/ending of each line:
[Test]
public void ExtensionsGfmPipeTable_Example010()
{
// Example 10
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// |a|b|
// |-|-|
// |0|1|
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 10\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("|a|b|\n|-|-|\n|0|1|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// Or may be omitted on one side:
[Test]
public void ExtensionsGfmPipeTable_Example011()
{
// Example 11
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a|b|
// -|-|
// 0|1|
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 11\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a|b|\n-|-|\n0|1|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
[Test]
public void ExtensionsGfmPipeTable_Example012()
{
// Example 12
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// |a|b
// |-|-
// |0|1
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 12\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("|a|b\n|-|-\n|0|1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// Single column table can be declared with lines starting only by a column delimiter:
[Test]
public void ExtensionsGfmPipeTable_Example013()
{
// Example 13
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// | a
// | --
// | b
// | c
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>b</td>
// </tr>
// <tr>
// <td>c</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 13\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("| a\n| --\n| b\n| c ", "<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n<tr>\n<td>c</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Rule #5**
//
// The first row is considered as a **header row** if it is separated from the regular rows by a row containing a **header column separator** for each column. A header column separator is:
//
// - starting by optional spaces
// - followed by an optional `:` to specify left align
// - followed by a sequence of at least one `-` character
// - followed by an optional `:` to specify right align (or center align if left align is also defined)
// - ending by optional spaces
[Test]
public void ExtensionsGfmPipeTable_Example014()
{
// Example 14
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -------|-------
// 0 | 1
// 2 | 3
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// <tr>
// <td>2</td>
// <td>3</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 14\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec(" a | b \n-------|-------\n 0 | 1 \n 2 | 3 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>2</td>\n<td>3</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// The text alignment is defined by default to be center for header and left for cells. If the left alignment is applied, it will force the column heading to be left aligned.
// There is no way to define a different alignment for heading and cells (apart from the default).
// The text alignment can be changed by using the character `:` with the header column separator:
[Test]
public void ExtensionsGfmPipeTable_Example015()
{
// Example 15
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b | c
// :------|:-------:| ----:
// 0 | 1 | 2
// 3 | 4 | 5
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th style="text-align: left;">a</th>
// <th style="text-align: center;">b</th>
// <th style="text-align: right;">c</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td style="text-align: left;">0</td>
// <td style="text-align: center;">1</td>
// <td style="text-align: right;">2</td>
// </tr>
// <tr>
// <td style="text-align: left;">3</td>
// <td style="text-align: center;">4</td>
// <td style="text-align: right;">5</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 15\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec(" a | b | c \n:------|:-------:| ----:\n 0 | 1 | 2 \n 3 | 4 | 5 ", "<table>\n<thead>\n<tr>\n<th style=\"text-align: left;\">a</th>\n<th style=\"text-align: center;\">b</th>\n<th style=\"text-align: right;\">c</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: left;\">0</td>\n<td style=\"text-align: center;\">1</td>\n<td style=\"text-align: right;\">2</td>\n</tr>\n<tr>\n<td style=\"text-align: left;\">3</td>\n<td style=\"text-align: center;\">4</td>\n<td style=\"text-align: right;\">5</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// Test alignment with starting and ending pipes:
[Test]
public void ExtensionsGfmPipeTable_Example016()
{
// Example 16
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// | abc | def | ghi |
// |:---:|-----|----:|
// | 1 | 2 | 3 |
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th style="text-align: center;">abc</th>
// <th>def</th>
// <th style="text-align: right;">ghi</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td style="text-align: center;">1</td>
// <td>2</td>
// <td style="text-align: right;">3</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 16\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("| abc | def | ghi |\n|:---:|-----|----:|\n| 1 | 2 | 3 |", "<table>\n<thead>\n<tr>\n<th style=\"text-align: center;\">abc</th>\n<th>def</th>\n<th style=\"text-align: right;\">ghi</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align: center;\">1</td>\n<td>2</td>\n<td style=\"text-align: right;\">3</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// The following example shows a non matching header column separator:
[Test]
public void ExtensionsGfmPipeTable_Example017()
{
// Example 17
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -------|---x---
// 0 | 1
// 2 | 3
//
// Should be rendered as:
// <p>a | b
// -------|---x---
// 0 | 1
// 2 | 3</p>
Console.WriteLine("Example 17\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec(" a | b\n-------|---x---\n 0 | 1\n 2 | 3 ", "<p>a | b\n-------|---x---\n0 | 1\n2 | 3</p> ", "gfm-pipetables");
}
// **Rule #6**
//
// A column delimiter has a higher priority than emphasis delimiter
[Test]
public void ExtensionsGfmPipeTable_Example018()
{
// Example 18
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// *a* | b
// ----- |-----
// 0 | _1_
// _2 | 3*
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th><em>a</em></th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td><em>1</em></td>
// </tr>
// <tr>
// <td>_2</td>
// <td>3*</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 18\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec(" *a* | b\n----- |-----\n 0 | _1_\n _2 | 3* ", "<table>\n<thead>\n<tr>\n<th><em>a</em></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td><em>1</em></td>\n</tr>\n<tr>\n<td>_2</td>\n<td>3*</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Rule #7**
//
// A backtick/code delimiter has a higher precedence than a column delimiter `|`:
[Test]
public void ExtensionsGfmPipeTable_Example019()
{
// Example 19
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b `
// 0 | `
//
// Should be rendered as:
// <p>a | b <code>0 |</code></p>
Console.WriteLine("Example 19\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b `\n0 | ` ", "<p>a | b <code>0 |</code></p> ", "gfm-pipetables");
}
// **Rule #8**
//
// A HTML inline has a higher precedence than a column delimiter `|`:
[Test]
public void ExtensionsGfmPipeTable_Example020()
{
// Example 20
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a <a href="" title="|"></a> | b
// -- | --
// 0 | 1
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a <a href="" title="|"></a></th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 20\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a <a href=\"\" title=\"|\"></a> | b\n-- | --\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a <a href=\"\" title=\"|\"></a></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Rule #9**
//
// Links have a higher precedence than the column delimiter character `|`:
[Test]
public void ExtensionsGfmPipeTable_Example021()
{
// Example 21
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | --
// [This is a link with a | inside the label](http://google.com) | 1
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td><a href="http://google.com">This is a link with a | inside the label</a></td>
// <td>1</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 21\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n-- | --\n[This is a link with a | inside the label](http://google.com) | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"http://google.com\">This is a link with a | inside the label</a></td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Rule #10**
//
// It is possible to have a single row header only:
[Test]
public void ExtensionsGfmPipeTable_Example022()
{
// Example 22
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | --
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// </table>
Console.WriteLine("Example 22\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n-- | --", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n</table>", "gfm-pipetables");
}
[Test]
public void ExtensionsGfmPipeTable_Example023()
{
// Example 23
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// |a|b|c
// |---|---|---|
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// <th>c</th>
// </tr>
// </thead>
// </table>
Console.WriteLine("Example 23\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("|a|b|c\n|---|---|---|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n</table>", "gfm-pipetables");
}
// **Tests**
//
// Tests trailing spaces after pipes
[Test]
public void ExtensionsGfmPipeTable_Example024()
{
// Example 24
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// | abc | def |
// |---|---|
// | cde| ddd|
// | eee| fff|
// | fff | fffff |
// |gggg | ffff |
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>abc</th>
// <th>def</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>cde</td>
// <td>ddd</td>
// </tr>
// <tr>
// <td>eee</td>
// <td>fff</td>
// </tr>
// <tr>
// <td>fff</td>
// <td>fffff</td>
// </tr>
// <tr>
// <td>gggg</td>
// <td>ffff</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 24\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("| abc | def | \n|---|---|\n| cde| ddd| \n| eee| fff|\n| fff | fffff | \n|gggg | ffff | ", "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>cde</td>\n<td>ddd</td>\n</tr>\n<tr>\n<td>eee</td>\n<td>fff</td>\n</tr>\n<tr>\n<td>fff</td>\n<td>fffff</td>\n</tr>\n<tr>\n<td>gggg</td>\n<td>ffff</td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
// **Normalized columns count**
//
// The tables are normalized to the number of columns found in the table header.
// Extra columns will be ignored, missing columns will be inserted.
[Test]
public void ExtensionsGfmPipeTable_Example025()
{
// Example 25
// Section: Extensions / Gfm Pipe Table
//
// The following Markdown:
// a | b
// -- | -
// 0 | 1 | 2
// 3 |
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>a</th>
// <th>b</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>0</td>
// <td>1</td>
// </tr>
// <tr>
// <td>3</td>
// <td></td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example 25\nSection Extensions / Gfm Pipe Table\n");
TestParser.TestSpec("a | b\n-- | - \n0 | 1 | 2\n3 |", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>3</td>\n<td></td>\n</tr>\n</tbody>\n</table>", "gfm-pipetables");
}
}
}

View File

@@ -0,0 +1,617 @@
# Extensions
This section describes the different extensions supported:
## Gfm Pipe Table
This groups a certain set of behaviors that makes pipe tables adhere more strictly to the GitHub-flavored Markdown specification.
A pipe table is detected when:
**Rule #1**
- Each line of a paragraph block have to contain at least a **column delimiter** `|` that is not embedded by either a code inline (backtick \`) or a HTML inline.
- The second row must separate the first header row from sub-sequent rows by containing a **header column separator** for each column separated by a column delimiter. A header column separator is:
- starting by optional spaces
- followed by an optional `:` to specify left align
- followed by a sequence of at least one `-` character
- followed by an optional `:` to specify right align (or center align if left align is also defined)
- ending by optional spaces
Because a list has a higher precedence than a pipe table, a table header row separator requires at least 2 dashes `--` to start a header row:
```````````````````````````````` example
a | b
-- | -
0 | 1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
The following is also considered as a table, even if the second line starts like a list:
```````````````````````````````` example
a | b
- | -
0 | 1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
A pipe table with only one header row is allowed:
```````````````````````````````` example
a | b
-- | --
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
</table>
````````````````````````````````
After a row separator header, they will be interpreted as plain column:
```````````````````````````````` example
a | b
-- | --
-- | --
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>--</td>
<td>--</td>
</tr>
</tbody>
</table>
````````````````````````````````
But if a table doesn't start with a column delimiter, it is not interpreted as a table, even if following lines have a column delimiter
```````````````````````````````` example
a b
c | d
e | f
.
<p>a b
c | d
e | f</p>
````````````````````````````````
If a line doesn't have a column delimiter `|` the table is not detected
```````````````````````````````` example
a | b
c no d
.
<p>a | b
c no d</p>
````````````````````````````````
If a row contains more columns than the header row, the extra columns will be ignored:
```````````````````````````````` example
a | b
-- | --
0 | 1 | 2
3 | 4
5 |
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td></td>
</tr>
</tbody>
</table>
````````````````````````````````
**Rule #2**
A pipe table ends after a blank line or the end of the file.
**Rule #3**
A cell content is trimmed (start and end) from white-spaces.
```````````````````````````````` example
a | b |
-- | --
0 | 1 |
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
**Rule #4**
Column delimiters `|` at the very beginning of a line or just before a line ending with only spaces and/or terminated by a newline can be omitted
```````````````````````````````` example
a | b |
-- | --
| 0 | 1
| 2 | 3 |
4 | 5
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>4</td>
<td>5</td>
</tr>
</tbody>
</table>
````````````````````````````````
A pipe may be present at both the beginning/ending of each line:
```````````````````````````````` example
|a|b|
|-|-|
|0|1|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Or may be omitted on one side:
```````````````````````````````` example
a|b|
-|-|
0|1|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
```````````````````````````````` example
|a|b
|-|-
|0|1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
Single column table can be declared with lines starting only by a column delimiter:
```````````````````````````````` example
| a
| --
| b
| c
.
<table>
<thead>
<tr>
<th>a</th>
</tr>
</thead>
<tbody>
<tr>
<td>b</td>
</tr>
<tr>
<td>c</td>
</tr>
</tbody>
</table>
````````````````````````````````
**Rule #5**
The first row is considered as a **header row** if it is separated from the regular rows by a row containing a **header column separator** for each column. A header column separator is:
- starting by optional spaces
- followed by an optional `:` to specify left align
- followed by a sequence of at least one `-` character
- followed by an optional `:` to specify right align (or center align if left align is also defined)
- ending by optional spaces
```````````````````````````````` example
a | b
-------|-------
0 | 1
2 | 3
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>
````````````````````````````````
The text alignment is defined by default to be center for header and left for cells. If the left alignment is applied, it will force the column heading to be left aligned.
There is no way to define a different alignment for heading and cells (apart from the default).
The text alignment can be changed by using the character `:` with the header column separator:
```````````````````````````````` example
a | b | c
:------|:-------:| ----:
0 | 1 | 2
3 | 4 | 5
.
<table>
<thead>
<tr>
<th style="text-align: left;">a</th>
<th style="text-align: center;">b</th>
<th style="text-align: right;">c</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">0</td>
<td style="text-align: center;">1</td>
<td style="text-align: right;">2</td>
</tr>
<tr>
<td style="text-align: left;">3</td>
<td style="text-align: center;">4</td>
<td style="text-align: right;">5</td>
</tr>
</tbody>
</table>
````````````````````````````````
Test alignment with starting and ending pipes:
```````````````````````````````` example
| abc | def | ghi |
|:---:|-----|----:|
| 1 | 2 | 3 |
.
<table>
<thead>
<tr>
<th style="text-align: center;">abc</th>
<th>def</th>
<th style="text-align: right;">ghi</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">1</td>
<td>2</td>
<td style="text-align: right;">3</td>
</tr>
</tbody>
</table>
````````````````````````````````
The following example shows a non matching header column separator:
```````````````````````````````` example
a | b
-------|---x---
0 | 1
2 | 3
.
<p>a | b
-------|---x---
0 | 1
2 | 3</p>
````````````````````````````````
**Rule #6**
A column delimiter has a higher priority than emphasis delimiter
```````````````````````````````` example
*a* | b
----- |-----
0 | _1_
_2 | 3*
.
<table>
<thead>
<tr>
<th><em>a</em></th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td><em>1</em></td>
</tr>
<tr>
<td>_2</td>
<td>3*</td>
</tr>
</tbody>
</table>
````````````````````````````````
**Rule #7**
A backtick/code delimiter has a higher precedence than a column delimiter `|`:
```````````````````````````````` example
a | b `
0 | `
.
<p>a | b <code>0 |</code></p>
````````````````````````````````
**Rule #8**
A HTML inline has a higher precedence than a column delimiter `|`:
```````````````````````````````` example
a <a href="" title="|"></a> | b
-- | --
0 | 1
.
<table>
<thead>
<tr>
<th>a <a href="" title="|"></a></th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
**Rule #9**
Links have a higher precedence than the column delimiter character `|`:
```````````````````````````````` example
a | b
-- | --
[This is a link with a | inside the label](http://google.com) | 1
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://google.com">This is a link with a | inside the label</a></td>
<td>1</td>
</tr>
</tbody>
</table>
````````````````````````````````
**Rule #10**
It is possible to have a single row header only:
```````````````````````````````` example
a | b
-- | --
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
</table>
````````````````````````````````
```````````````````````````````` example
|a|b|c
|---|---|---|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
</table>
````````````````````````````````
**Tests**
Tests trailing spaces after pipes
```````````````````````````````` example
| abc | def |
|---|---|
| cde| ddd|
| eee| fff|
| fff | fffff |
|gggg | ffff |
.
<table>
<thead>
<tr>
<th>abc</th>
<th>def</th>
</tr>
</thead>
<tbody>
<tr>
<td>cde</td>
<td>ddd</td>
</tr>
<tr>
<td>eee</td>
<td>fff</td>
</tr>
<tr>
<td>fff</td>
<td>fffff</td>
</tr>
<tr>
<td>gggg</td>
<td>ffff</td>
</tr>
</tbody>
</table>
````````````````````````````````
**Normalized columns count**
The tables are normalized to the number of columns found in the table header.
Extra columns will be ignored, missing columns will be inserted.
```````````````````````````````` example
a | b
-- | -
0 | 1 | 2
3 |
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>3</td>
<td></td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-15 05:54:35
// --------------------------------
// Pipe Tables
@@ -647,7 +646,7 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("a | b `\n0 | ` ", "<p>a | b <code>0 |</code></p> ", "pipetables|advanced");
}
// **Rule #7**
// **Rule #8**
//
// A HTML inline has a higher precedence than a column delimiter `|`:
[Test]
@@ -681,7 +680,7 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("a <a href=\"\" title=\"|\"></a> | b\n-- | --\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a <a href=\"\" title=\"|\"></a></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
// **Rule #8**
// **Rule #9**
//
// Links have a higher precedence than the column delimiter character `|`:
[Test]
@@ -715,7 +714,7 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("a | b\n-- | --\n[This is a link with a | inside the label](http://google.com) | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"http://google.com\">This is a link with a | inside the label</a></td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
// ** Rule #9**
// **Rule #10**
//
// It is possible to have a single row header only:
[Test]
@@ -767,7 +766,7 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("|a|b|c\n|---|---|---|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n<th>c</th>\n</tr>\n</thead>\n</table>", "pipetables|advanced");
}
// ** Tests **
// **Tests**
//
// Tests trailing spaces after pipes
[Test]
@@ -816,7 +815,7 @@ namespace Markdig.Tests.Specs.PipeTables
TestParser.TestSpec("| abc | def | \n|---|---|\n| cde| ddd| \n| eee| fff|\n| fff | fffff | \n|gggg | ffff | ", "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>cde</td>\n<td>ddd</td>\n</tr>\n<tr>\n<td>eee</td>\n<td>fff</td>\n</tr>\n<tr>\n<td>fff</td>\n<td>fffff</td>\n</tr>\n<tr>\n<td>gggg</td>\n<td>ffff</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
// ** Normalized columns count **
// **Normalized columns count**
//
// The tables are normalized to the maximum number of columns found in a table
[Test]

View File

@@ -463,7 +463,7 @@ a | b `
<p>a | b <code>0 |</code></p>
````````````````````````````````
**Rule #7**
**Rule #8**
A HTML inline has a higher precedence than a column delimiter `|`:
@@ -488,7 +488,7 @@ a <a href="" title="|"></a> | b
</table>
````````````````````````````````
**Rule #8**
**Rule #9**
Links have a higher precedence than the column delimiter character `|`:
@@ -513,7 +513,7 @@ a | b
</table>
````````````````````````````````
** Rule #9**
**Rule #10**
It is possible to have a single row header only:
@@ -546,7 +546,7 @@ a | b
</table>
````````````````````````````````
** Tests **
**Tests**
Tests trailing spaces after pipes
@@ -586,7 +586,7 @@ Tests trailing spaces after pipes
</table>
````````````````````````````````
** Normalized columns count **
**Normalized columns count**
The tables are normalized to the maximum number of columns found in a table

View File

@@ -1,4 +1,3 @@
// Generated: 2019-08-01 12:33:23
// --------------------------------
// Smarty Pants

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Task Lists

View File

@@ -1,4 +1,3 @@
// Generated: 2019-04-05 16:06:14
// --------------------------------
// Yaml

View File

@@ -47,12 +47,14 @@ namespace Markdig.Tests
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container, one.Parent);
var two = new ParagraphBlock();
container.Insert(1, two);
Assert.AreEqual(2, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container[1], two);
Assert.AreSame(container, two.Parent);
var three = new ParagraphBlock();
container.Insert(0, three);
@@ -60,11 +62,31 @@ namespace Markdig.Tests
Assert.AreSame(container[0], three);
Assert.AreSame(container[1], one);
Assert.AreSame(container[2], two);
Assert.AreSame(container, three.Parent);
Assert.Throws<ArgumentNullException>(() => container.Insert(0, null));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(4, new ParagraphBlock()));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(-1, new ParagraphBlock()));
Assert.Throws<ArgumentException>(() => container.Insert(0, one)); // one already has a parent
}
[Test]
public void CanBeSet()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container, one.Parent);
var two = new ParagraphBlock();
container[0] = two;
Assert.AreSame(container, two.Parent);
Assert.Null(one.Parent);
Assert.Throws<ArgumentException>(() => container[0] = two); // two already has a parent
}
}
}

View File

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

View File

@@ -1,14 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Markdig.Extensions.Emoji;
using System;
using System.Collections.Generic;
using Markdig.Extensions.Emoji;
using NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public class TestCustomEmojis
{
{
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
@@ -28,13 +27,13 @@ namespace Markdig.Tests
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomSmiley(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
@@ -55,8 +54,8 @@ namespace Markdig.Tests
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":)", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":)", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
{
@@ -73,8 +72,8 @@ namespace Markdig.Tests
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
@@ -99,31 +98,31 @@ namespace Markdig.Tests
}
[Test]
public void TestCustomEmojiValidation()
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(null, smileyToEmoji));
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(emojiToUnicode, null));
emojiToUnicode.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
emojiToUnicode.Clear();
smileyToEmoji.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
smileyToEmoji.Add("foo", "something-that-does-not-exist-in-emojiToUnicode");
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
emojiToUnicode.Add("a", "aaa");
emojiToUnicode.Add("b", "bbb");
emojiToUnicode.Add("c", "ccc");
smileyToEmoji.Add("a", "c"); // "a" already exists in emojiToUnicode
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
public void TestCustomEmojiValidation()
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(null, smileyToEmoji));
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(emojiToUnicode, null));
emojiToUnicode.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
emojiToUnicode.Clear();
smileyToEmoji.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
smileyToEmoji.Add("foo", "something-that-does-not-exist-in-emojiToUnicode");
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
emojiToUnicode.Add("a", "aaa");
emojiToUnicode.Add("b", "bbb");
emojiToUnicode.Add("c", "ccc");
smileyToEmoji.Add("a", "c"); // "a" already exists in emojiToUnicode
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,25 +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);
}
}
}
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

@@ -2,7 +2,6 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.IO;
using NUnit.Framework;
using Markdig.Helpers;

View File

@@ -2,7 +2,6 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Security;
using NUnit.Framework;
using Markdig.Helpers;
using Markdig.Syntax;
@@ -16,8 +15,7 @@ namespace Markdig.Tests
public void TestUrlSimple()
{
var text = new StringSlice("toto tutu");
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.AreEqual("toto", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -26,8 +24,7 @@ namespace Markdig.Tests
public void TestUrlUrl()
{
var text = new StringSlice("http://google.com)");
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual(')', text.CurrentChar);
}
@@ -38,8 +35,7 @@ namespace Markdig.Tests
public void TestUrlTrailingFullStop(string uri)
{
var text = new StringSlice(uri);
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link, true));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link, true));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}
@@ -48,8 +44,7 @@ namespace Markdig.Tests
public void TestUrlNestedParenthesis()
{
var text = new StringSlice("(toto)tutu(tata) nooo");
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.AreEqual("(toto)tutu(tata)", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -58,8 +53,7 @@ namespace Markdig.Tests
public void TestUrlAlternate()
{
var text = new StringSlice("<toto_tata_tutu> nooo");
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.True(LinkHelper.TryParseUrl(ref text, out string link));
Assert.AreEqual("toto_tata_tutu", link);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -68,16 +62,14 @@ namespace Markdig.Tests
public void TestUrlAlternateInvalid()
{
var text = new StringSlice("<toto_tata_tutu");
string link;
Assert.False(LinkHelper.TryParseUrl(ref text, out link));
Assert.False(LinkHelper.TryParseUrl(ref text, out string link));
}
[Test]
public void TestTitleSimple()
{
var text = new StringSlice(@"'tata\tutu\''");
string title;
Assert.True(LinkHelper.TryParseTitle(ref text, out title));
Assert.True(LinkHelper.TryParseTitle(ref text, out string title));
Assert.AreEqual(@"tata\tutu'", title);
}
@@ -85,8 +77,7 @@ namespace Markdig.Tests
public void TestTitleSimpleAlternate()
{
var text = new StringSlice(@"""tata\tutu\"""" ");
string title;
Assert.True(LinkHelper.TryParseTitle(ref text, out title));
Assert.True(LinkHelper.TryParseTitle(ref text, out string title));
Assert.AreEqual(@"tata\tutu""", title);
Assert.AreEqual(' ', text.CurrentChar);
}
@@ -97,11 +88,7 @@ namespace Markdig.Tests
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
string link;
string title;
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("this is a title", title);
Assert.AreEqual(new SourceSpan(1, 17), linkSpan);
@@ -114,11 +101,7 @@ namespace Markdig.Tests
{
// 01234
var text = new StringSlice(@"(<>)A");
string link;
string title;
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(1, 2), linkSpan);
@@ -131,11 +114,7 @@ namespace Markdig.Tests
{
// 012345
var text = new StringSlice(@"( <> )A");
string link;
string title;
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(new SourceSpan(2, 3), linkSpan);
@@ -150,11 +129,7 @@ namespace Markdig.Tests
// 0 1 2
// 0123456789012345678901234567
var text = new StringSlice(@"( <> 'toto' )A");
string link;
string title;
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(4, 5), linkSpan);
@@ -166,11 +141,7 @@ namespace Markdig.Tests
public void TestUrlEmpty()
{
var text = new StringSlice(@"()A");
string link;
string title;
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual(string.Empty, link);
Assert.AreEqual(string.Empty, title);
Assert.AreEqual(SourceSpan.Empty, linkSpan);
@@ -184,11 +155,7 @@ namespace Markdig.Tests
// 0 1 2 3
// 01 2345678901234567890 1234567890123456789
var text = new StringSlice("(\n<http://google.com>\n 'toto' )A");
string link;
string title;
SourceSpan linkSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
Assert.True(LinkHelper.TryParseInlineLink(ref text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual("toto", title);
Assert.AreEqual(new SourceSpan(2, 20), linkSpan);
@@ -201,9 +168,7 @@ namespace Markdig.Tests
{
// 01234
var text = new StringSlice("[foo]");
string label;
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
Assert.AreEqual("foo", label);
}
@@ -213,9 +178,7 @@ namespace Markdig.Tests
{
// 012345678
var text = new StringSlice(@"[fo\[\]o]");
string label;
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 7), labelSpan);
Assert.AreEqual(@"fo[]o", label);
}
@@ -225,9 +188,7 @@ namespace Markdig.Tests
{
// 0123
var text = new StringSlice(@"[\]]");
string label;
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(1, 2), labelSpan);
Assert.AreEqual(@"]", label);
}
@@ -235,8 +196,7 @@ namespace Markdig.Tests
[Test]
public void TestLabelInvalids()
{
string label;
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"a"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"a"), out string label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"["), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[\x]"), out label));
Assert.False(LinkHelper.TryParseLabel(new StringSlice(@"[[]"), out label));
@@ -250,9 +210,7 @@ namespace Markdig.Tests
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[ fo o z ]");
string label;
SourceSpan labelSpan;
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
Assert.True(LinkHelper.TryParseLabel(ref text, out string label, out SourceSpan labelSpan));
Assert.AreEqual(new SourceSpan(6, 17), labelSpan);
Assert.AreEqual(@"fo o z", label);
}
@@ -263,13 +221,7 @@ namespace Markdig.Tests
// 0 1 2 3
// 0123456789012345678901234567890123456789
var text = new StringSlice(@"[foo]: /toto 'title'");
string label;
string url;
string title;
SourceSpan labelSpan;
SourceSpan urlSpan;
SourceSpan titleSpan;
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title, out labelSpan, out urlSpan, out titleSpan));
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out string label, out string url, out string title, out SourceSpan labelSpan, out SourceSpan urlSpan, out SourceSpan titleSpan));
Assert.AreEqual(@"foo", label);
Assert.AreEqual(@"/toto", url);
Assert.AreEqual(@"title", title);
@@ -283,9 +235,7 @@ namespace Markdig.Tests
public void TestAutoLinkUrlSimple()
{
var text = new StringSlice(@"<http://google.com>");
string url;
bool isEmail;
Assert.True(LinkHelper.TryParseAutolink(ref text, out url, out isEmail));
Assert.True(LinkHelper.TryParseAutolink(ref text, out string url, out bool isEmail));
Assert.False(isEmail);
Assert.AreEqual("http://google.com", url);
}
@@ -294,9 +244,7 @@ namespace Markdig.Tests
public void TestAutoLinkEmailSimple()
{
var text = new StringSlice(@"<user@host.com>");
string email;
bool isEmail;
Assert.True(LinkHelper.TryParseAutolink(ref text, out email, out isEmail));
Assert.True(LinkHelper.TryParseAutolink(ref text, out string email, out bool isEmail));
Assert.True(isEmail);
Assert.AreEqual("user@host.com", email);
}
@@ -304,9 +252,7 @@ namespace Markdig.Tests
[Test]
public void TestAutolinkInvalid()
{
string text;
bool isEmail;
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@""), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@""), out string text, out bool isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
@@ -327,9 +273,9 @@ namespace Markdig.Tests
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "3-applications")]
[TestCase("33", "33")]
public void TestUrilizeGfm(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.UrilizeAsGfm(input));
public void TestUrilizeGfm(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.UrilizeAsGfm(input));
}
[TestCase("abc", "abc")]
@@ -421,9 +367,9 @@ namespace Markdig.Tests
}
[Test]
public void TestUnicodeInDomainNameOfLinkReferenceDefinition()
{
TestParser.TestSpec("[Foo]\n\n[Foo]: http://ünicode.com", "<p><a href=\"http://xn--nicode-2ya.com\">Foo</a></p>");
public void TestUnicodeInDomainNameOfLinkReferenceDefinition()
{
TestParser.TestSpec("[Foo]\n\n[Foo]: http://ünicode.com", "<p><a href=\"http://xn--nicode-2ya.com\">Foo</a></p>");
}
}
}

View File

@@ -119,6 +119,7 @@ namespace Markdig.Tests
Assert.AreEqual("# Heading", normalized);
}
[Test]
public void TestNormalizeWithWriter()
{
StringWriter writer = new StringWriter();

View File

@@ -15,14 +15,22 @@ namespace Markdig.Tests
.Build();
}
private MarkdownPipeline GetPipelineWithBootstrap(MediaOptions options = null)
{
return new MarkdownPipelineBuilder()
.UseBootstrap()
.UseMediaLinks(options)
.Build();
}
[Test]
[TestCase("![static mp4](https://sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n")]
[TestCase("![vimeo](https://vimeo.com/8607834)", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](//ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" class=\"yandex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n")]
[TestCase("![vimeo](https://vimeo.com/8607834)", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" class=\"vimeo\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
[TestCase("![ok.ru](//ok.ru/video/26870090463)", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" class=\"odnoklassniki\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
public void TestBuiltInHosts(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipeline());
@@ -55,9 +63,9 @@ namespace Markdig.Tests
}
[Test]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](//sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^//sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4?token=aaabbb\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1?token=aaabbb")]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](//sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^//sample.com/(.+)$", @"https://example.com/$1")]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4?token=aaabbb\" class=\"regex\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1?token=aaabbb")]
public void TestCustomHostProvider(string markdown, string expected, string provider, string replace)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
@@ -72,9 +80,9 @@ namespace Markdig.Tests
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\" class=\"k\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "k")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"k youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "k")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "")]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video class=\"k\" width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "k")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"k youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "k")]
public void TestCustomClass(string markdown, string expected, string klass)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
@@ -83,5 +91,14 @@ namespace Markdig.Tests
}));
Assert.AreEqual(html, expected);
}
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video class=\"img-fluid\" width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" class=\"img-fluid youtube\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
public void TestWithBootstrap(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipelineWithBootstrap());
Assert.AreEqual(html, expected);
}
}
}

View File

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

View File

@@ -35,9 +35,9 @@ namespace Markdig.Tests
TestParser.TestSpec(markdownText, expected, extensions, plainText: true);
}
public static void TestSpec(string markdownText, string expected, string extensions)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true);
public static void TestSpec(string markdownText, string expected, string extensions)
{
TestParser.TestSpec(markdownText, expected, extensions, plainText: true);
}
}
}

View File

@@ -2,7 +2,6 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Syntax;
using NUnit.Framework;

View File

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

View File

@@ -140,48 +140,48 @@ literal ( 0, 2) 2-3
emphasis ( 0, 4) 4-9
literal ( 0, 6) 6-7
");
}
}
[Test]
public void TestFootnoteLinkReferenceDefinition()
public void TestFootnoteLinkReferenceDefinition()
{
// 01 2 345678
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants<FootnoteLinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(footnote);
Assert.AreEqual(2, footnote.Line);
Assert.NotNull(footnote);
Assert.AreEqual(2, footnote.Line);
Assert.AreEqual(new SourceSpan(4, 7), footnote.Span);
Assert.AreEqual(new SourceSpan(5, 6), footnote.LabelSpan);
}
Assert.AreEqual(new SourceSpan(5, 6), footnote.LabelSpan);
}
[Test]
public void TestLinkReferenceDefinition1()
public void TestLinkReferenceDefinition1()
{
// 0 1
// 0123456789012345
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(0, link.Line);
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);
Assert.AreEqual(new SourceSpan(11, 14), link.TitleSpan);
}
[Test]
public void TestLinkReferenceDefinition2()
public void TestLinkReferenceDefinition2()
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(2, link.Line);
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);
Assert.AreEqual(new SourceSpan(15, 18), link.TitleSpan);
}
[Test]
@@ -395,42 +395,42 @@ listitem ( 1, 0) 4-6
paragraph ( 1, 2) 6-6
literal ( 1, 2) 6-6
");
}
}
[Test]
public void TestListBlock2()
public void TestListBlock2()
{
string test = @"
1. Foo
9. Bar
5. Foo
6. Bar
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<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.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);
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]

View File

@@ -1,8 +1,6 @@
using NUnit.Framework;
using System.Collections.Generic;
using System.Text;
using Markdig.Helpers;
using Markdig.Syntax;
using System;
namespace Markdig.Tests

View File

@@ -13,7 +13,7 @@ namespace Markdig.WebApp
return string.Empty;
}
// GET api/to_html?text=xxx&extensions=advanced
// GET api/to_html?text=xxx&extension=advanced
[Route("api/to_html")]
[HttpGet()]
public object Get([FromQuery] string text, [FromQuery] string extension)

View File

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

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;

View File

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

@@ -12,7 +12,7 @@ namespace Markdig.Extensions.Abbreviations
/// <summary>
/// An abbreviation object stored at the document level. See extension methods in <see cref="AbbreviationHelper"/>.
/// </summary>
/// <seealso cref="Markdig.Syntax.LeafBlock" />
/// <seealso cref="LeafBlock" />
[DebuggerDisplay("Abbr {Label} => {Text}")]
public class Abbreviation : LeafBlock
{

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.
@@ -9,7 +9,7 @@ namespace Markdig.Extensions.Abbreviations
/// <summary>
/// Extension to allow abbreviations.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class AbbreviationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -19,8 +19,7 @@ namespace Markdig.Extensions.Abbreviations
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
if (renderer is HtmlRenderer htmlRenderer && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
{
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlAbbreviationRenderer());

View File

@@ -1,8 +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 Markdig.Helpers;
using Markdig.Syntax;
namespace Markdig.Extensions.Abbreviations
@@ -21,9 +22,9 @@ namespace Markdig.Extensions.Abbreviations
public static void AddAbbreviation(this MarkdownDocument document, string label, Abbreviation abbr)
{
if (document == null) throw new ArgumentNullException(nameof(document));
if (label == null) throw new ArgumentNullException(nameof(label));
if (abbr == null) throw new ArgumentNullException(nameof(abbr));
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
if (label == null) ThrowHelper.ArgumentNullException_label();
if (abbr == null) ThrowHelper.ArgumentNullException(nameof(abbr));
var map = document.GetAbbreviations();
if (map == null)

View File

@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Abbreviations
/// <summary>
/// The inline abbreviation.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.LeafInline" />
/// <seealso cref="LeafInline" />
[DebuggerDisplay("{Abbreviation}")]
public class AbbreviationInline : LeafInline
{

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;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
@@ -39,8 +41,7 @@ namespace Markdig.Extensions.Abbreviations
return BlockState.None;
}
SourceSpan labelSpan;
if (!LinkHelper.TryParseLabel(ref slice, out string label, out labelSpan))
if (!LinkHelper.TryParseLabel(ref slice, out string label, out SourceSpan labelSpan))
{
return BlockState.None;
}
@@ -98,29 +99,29 @@ namespace Markdig.Extensions.Abbreviations
var text = content.Text;
for (int i = content.Start; i <= content.End; i++)
{
// Abbreviation must be a whole word == start at the start of a line or after a whitespace
if (i != 0)
{
for (i = i - 1; i <= content.End; i++)
{
if (text[i].IsWhitespace())
{
i++;
goto ValidAbbreviationStart;
}
}
break;
}
{
// Abbreviation must be a whole word == start at the start of a line or after a whitespace
if (i != 0)
{
for (i = i - 1; i <= content.End; i++)
{
if (text[i].IsWhitespace())
{
i++;
goto ValidAbbreviationStart;
}
}
break;
}
ValidAbbreviationStart:;
if (prefixTree.TryMatchLongest(text, i, content.End - i + 1, out KeyValuePair<string, Abbreviation> abbreviationMatch))
if (prefixTree.TryMatchLongest(text.AsSpan(i, content.End - i + 1), out KeyValuePair<string, Abbreviation> abbreviationMatch))
{
var match = abbreviationMatch.Key;
if (!IsValidAbbreviationEnding(match, content, i))
if (!IsValidAbbreviationEnding(match, content, i))
{
continue;
continue;
}
var indexAfterMatch = i + match.Length;
@@ -150,7 +151,7 @@ namespace Markdig.Extensions.Abbreviations
// Append the previous literal
if (i > content.Start && literal.Parent == null)
{
{
container.AppendChild(literal);
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html;
@@ -9,7 +10,7 @@ namespace Markdig.Extensions.Abbreviations
/// <summary>
/// A HTML renderer for a <see cref="AbbreviationInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{CustomContainer}" />
/// <seealso cref="HtmlObjectRenderer{CustomContainer}" />
public class HtmlAbbreviationRenderer : HtmlObjectRenderer<AbbreviationInline>
{
protected override void Write(HtmlRenderer renderer, AbbreviationInline obj)

View File

@@ -1,214 +1,212 @@
// 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;
private readonly StripRendererCache rendererCache = new StripRendererCache();
/// <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 around
doc.RemoveData(this);
}
/// <summary>
/// Callback when there is a reference to found to a heading.
/// Note that reference are only working if they are declared after.
/// </summary>
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline child)
{
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
return new LinkInline()
{
// Use GetDynamicUrl to allow late binding of the Url (as a link may occur before the heading is declared and
// the inlines of the heading are actually processed by HeadingBlock_ProcessInlinesEnd)
GetDynamicUrl = () => HtmlHelper.Unescape("#" + headingRef.Heading.GetAttributes().Id),
Title = HtmlHelper.Unescape(linkRef.Title),
};
}
/// <summary>
/// Process the inlines of the heading to create a unique identifier
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="inline">The inline.</param>
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline inline)
{
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
if (identifiers == 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 stripRenderer = rendererCache.Get();
stripRenderer.Render(headingBlock.Inline);
var headingText = stripRenderer.Writer.ToString();
rendererCache.Release(stripRenderer);
// Urilize the link
headingText = (options & AutoIdentifierOptions.GitHub) != 0
? LinkHelper.UrilizeAsGfm(headingText)
: LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
// If the heading is empty, use the word "section" instead
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
// Add a trailing -1, -2, -3...etc. in case of collision
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;
}
private sealed class StripRendererCache : ObjectCache<HtmlRenderer>
{
protected override HtmlRenderer NewInstance()
{
var headingWriter = new StringWriter();
var stripRenderer = new HtmlRenderer(headingWriter)
{
// Set to false both to avoid having any HTML tags in the output
EnableHtmlForInline = false,
EnableHtmlEscape = false
};
return stripRenderer;
}
protected override void Reset(HtmlRenderer instance)
{
instance.Reset();
}
}
}
// 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="IMarkdownExtension" />
public class AutoIdentifierExtension : IMarkdownExtension
{
private const string AutoIdentifierKey = "AutoIdentifier";
private readonly AutoIdentifierOptions options;
private readonly StripRendererCache rendererCache = new StripRendererCache();
/// <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
if (!(block is HeadingBlock headingBlock))
{
return;
}
// If the AutoLink options is set, we register a LinkReferenceDefinition at the document level
if ((options & AutoIdentifierOptions.AutoLink) != 0)
{
var headingLine = headingBlock.Lines.Lines[0];
var text = headingLine.ToString();
var linkRef = new HeadingLinkReferenceDefinition()
{
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 around
doc.RemoveData(this);
}
/// <summary>
/// Callback when there is a reference to found to a heading.
/// Note that reference are only working if they are declared after.
/// </summary>
private Inline CreateLinkInlineForHeading(InlineProcessor inlineState, LinkReferenceDefinition linkRef, Inline child)
{
var headingRef = (HeadingLinkReferenceDefinition) linkRef;
return new LinkInline()
{
// Use GetDynamicUrl to allow late binding of the Url (as a link may occur before the heading is declared and
// the inlines of the heading are actually processed by HeadingBlock_ProcessInlinesEnd)
GetDynamicUrl = () => HtmlHelper.Unescape("#" + headingRef.Heading.GetAttributes().Id),
Title = HtmlHelper.Unescape(linkRef.Title),
};
}
/// <summary>
/// Process the inlines of the heading to create a unique identifier
/// </summary>
/// <param name="processor">The processor.</param>
/// <param name="inline">The inline.</param>
private void HeadingBlock_ProcessInlinesEnd(InlineProcessor processor, Inline inline)
{
var identifiers = processor.Document.GetData(AutoIdentifierKey) as HashSet<string>;
if (identifiers == 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 stripRenderer = rendererCache.Get();
stripRenderer.Render(headingBlock.Inline);
var headingText = stripRenderer.Writer.ToString();
rendererCache.Release(stripRenderer);
// Urilize the link
headingText = (options & AutoIdentifierOptions.GitHub) != 0
? LinkHelper.UrilizeAsGfm(headingText)
: LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
// If the heading is empty, use the word "section" instead
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
// Add a trailing -1, -2, -3...etc. in case of collision
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;
}
private sealed class StripRendererCache : ObjectCache<HtmlRenderer>
{
protected override HtmlRenderer NewInstance()
{
var headingWriter = new StringWriter();
var stripRenderer = new HtmlRenderer(headingWriter)
{
// Set to false both to avoid having any HTML tags in the output
EnableHtmlForInline = false,
EnableHtmlEscape = false
};
return stripRenderer;
}
protected override void Reset(HtmlRenderer instance)
{
instance.Reset();
}
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
namespace Markdig.Extensions.AutoIdentifiers

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 Markdig.Syntax;
namespace Markdig.Extensions.AutoIdentifiers
@@ -8,7 +9,7 @@ namespace Markdig.Extensions.AutoIdentifiers
/// <summary>
/// A link reference definition to a <see cref="HeadingBlock"/> stored at the <see cref="MarkdownDocument"/> level.
/// </summary>
/// <seealso cref="Markdig.Syntax.LinkReferenceDefinition" />
/// <seealso cref="LinkReferenceDefinition" />
public class HeadingLinkReferenceDefinition : LinkReferenceDefinition
{
/// <summary>

View File

@@ -5,6 +5,7 @@
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
using Markdig.Renderers.Normalize.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks
{
@@ -16,9 +17,9 @@ namespace Markdig.Extensions.AutoLinks
{
public readonly AutoLinkOptions Options;
public AutoLinkExtension(AutoLinkOptions options)
{
Options = options ?? new AutoLinkOptions();
public AutoLinkExtension(AutoLinkOptions options)
{
Options = options ?? new AutoLinkOptions();
}
public void Setup(MarkdownPipelineBuilder pipeline)

View File

@@ -1,22 +1,26 @@
namespace Markdig.Extensions.AutoLinks
{
public class AutoLinkOptions
{
public AutoLinkOptions()
{
ValidPreviousCharacters = "*_~(";
}
public string ValidPreviousCharacters { get; set; }
/// <summary>
/// Should the link open in a new window when clicked (false by default)
/// </summary>
public bool OpenInNewWindow { get; set; }
/// <summary>
/// Should a www link be prefixed with https:// instead of http:// (false by default)
/// </summary>
public bool UseHttpsForWWWLinks { get; set; }
}
}
// 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.
namespace Markdig.Extensions.AutoLinks
{
public class AutoLinkOptions
{
public AutoLinkOptions()
{
ValidPreviousCharacters = "*_~(";
}
public string ValidPreviousCharacters { get; set; }
/// <summary>
/// Should the link open in a new window when clicked (false by default)
/// </summary>
public bool OpenInNewWindow { get; set; }
/// <summary>
/// Should a www link be prefixed with https:// instead of http:// (false by default)
/// </summary>
public bool UseHttpsForWWWLinks { get; set; }
}
}

View File

@@ -3,10 +3,10 @@
// 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.Renderers.Html;
using Markdig.Renderers.Html;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.AutoLinks
@@ -29,14 +29,15 @@ namespace Markdig.Extensions.AutoLinks
'h', // for http:// and https://
'f', // for ftp://
'm', // for mailto:
't', // for tel:
'w', // for www.
};
_listOfCharCache = new ListOfCharCache();
}
public readonly AutoLinkOptions Options;
};
_listOfCharCache = new ListOfCharCache();
}
public readonly AutoLinkOptions Options;
private readonly ListOfCharCache _listOfCharCache;
public override bool Match(InlineProcessor processor, ref StringSlice slice)
@@ -49,151 +50,164 @@ namespace Markdig.Extensions.AutoLinks
}
List<char> pendingEmphasis = _listOfCharCache.Get();
try
{
// Check that an autolink is possible in the current context
if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis))
{
return false;
}
var startPosition = slice.Start;
int domainOffset = 0;
var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = 4; // www.
break;
}
// Parse URL
if (!LinkHelper.TryParseUrl(ref slice, out string link, true))
{
return false;
}
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
if (pendingEmphasis.Count > 0)
{
for (int i = link.Length - 1; i >= 0; i--)
{
if (pendingEmphasis.Contains(link[i]))
{
slice.Start--;
}
else
{
if (i < link.Length - 1)
{
link = link.Substring(0, i + 1);
}
break;
}
}
}
// Post-check URL
switch (c)
{
case 'h':
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'f':
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'm':
int atIndex = link.IndexOf('@');
if (atIndex == -1 ||
atIndex == 7) // mailto:@ - no email part
{
return false;
}
domainOffset = atIndex + 1;
break;
}
if (!LinkHelper.IsValidDomain(link, domainOffset))
{
return false;
}
var inline = new LinkInline()
{
try
{
// Check that an autolink is possible in the current context
if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis))
{
return false;
}
var startPosition = slice.Start;
int domainOffset = 0;
var c = slice.CurrentChar;
// Precheck URL
switch (c)
{
case 'h':
if (slice.MatchLowercase("ttp://", 1))
{
domainOffset = 7; // http://
}
else if (slice.MatchLowercase("ttps://", 1))
{
domainOffset = 8; // https://
}
else return false;
break;
case 'f':
if (!slice.MatchLowercase("tp://", 1))
{
return false;
}
domainOffset = 6; // ftp://
break;
case 'm':
if (!slice.MatchLowercase("ailto:", 1))
{
return false;
}
break;
case 't':
if (!slice.MatchLowercase("el:", 1))
{
return false;
}
domainOffset = 4;
break;
case 'w':
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
{
return false;
}
domainOffset = 4; // www.
break;
}
if (!LinkHelper.TryParseUrl(ref slice, out string link, true))
{
return false;
}
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
if (pendingEmphasis.Count > 0)
{
for (int i = link.Length - 1; i >= 0; i--)
{
if (pendingEmphasis.Contains(link[i]))
{
slice.Start--;
}
else
{
if (i < link.Length - 1)
{
link = link.Substring(0, i + 1);
}
break;
}
}
}
// Post-check URL
switch (c)
{
case 'h':
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'f':
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 't':
if (string.Equals(link, "tel", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'm':
int atIndex = link.IndexOf('@');
if (atIndex == -1 ||
atIndex == 7) // mailto:@ - no email part
{
return false;
}
domainOffset = atIndex + 1;
break;
}
// Do not need to check if a telephone number is a valid domain
if (c != 't' && !LinkHelper.IsValidDomain(link, domainOffset))
{
return false;
}
var inline = new LinkInline()
{
Span =
{
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
},
Line = line,
Column = column,
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
IsClosed = true,
IsAutoLink = true,
};
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
inline.Span.End = inline.Span.Start + link.Length - 1;
inline.UrlSpan = inline.Span;
inline.AppendChild(new LiteralInline()
{
Span = inline.Span,
Line = line,
Column = column,
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
IsClosed = true
});
processor.Inline = inline;
if (Options.OpenInNewWindow)
{
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
}
return true;
},
Line = line,
Column = column,
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
IsClosed = true,
IsAutoLink = true,
};
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
skipFromBeginning = c == 't' ? 4 : skipFromBeginning; // See above but for tel:
inline.Span.End = inline.Span.Start + link.Length - 1;
inline.UrlSpan = inline.Span;
inline.AppendChild(new LiteralInline()
{
Span = inline.Span,
Line = line,
Column = column,
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
IsClosed = true
});
processor.Inline = inline;
if (Options.OpenInNewWindow)
{
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
}
return true;
}
finally
{
_listOfCharCache.Release(pendingEmphasis);
{
_listOfCharCache.Release(pendingEmphasis);
}
}
@@ -254,14 +268,14 @@ namespace Markdig.Extensions.AutoLinks
}
return countBrackets <= 0;
}
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
{
protected override void Reset(List<char> instance)
{
instance.Clear();
}
}
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
{
protected override void Reset(List<char> instance)
{
instance.Clear();
}
}
}
}

View File

@@ -1,3 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
using Markdig.Syntax;
@@ -11,10 +15,10 @@ namespace Markdig.Extensions.AutoLinks
{
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;
return renderer is NormalizeRenderer normalizeRenderer
&& obj is LinkInline link
&& !normalizeRenderer.Options.ExpandAutoLinks
&& link.IsAutoLink;
}
else
{

View File

@@ -12,7 +12,7 @@ namespace Markdig.Extensions.Bootstrap
/// <summary>
/// Extension for tagging some HTML elements with bootstrap classes.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class BootstrapExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)

View File

@@ -6,8 +6,8 @@ using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
using System.Diagnostics;
using System.Diagnostics;
namespace Markdig.Extensions.Citations
{
/// <summary>

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 Markdig.Parsers;
using Markdig.Syntax;
@@ -9,8 +10,8 @@ namespace Markdig.Extensions.CustomContainers
/// <summary>
/// A block custom container.
/// </summary>
/// <seealso cref="Markdig.Syntax.ContainerBlock" />
/// <seealso cref="Markdig.Syntax.IFencedBlock" />
/// <seealso cref="ContainerBlock" />
/// <seealso cref="IFencedBlock" />
public class CustomContainer : ContainerBlock, IFencedBlock
{
/// <summary>

View File

@@ -3,8 +3,8 @@
// See the license.txt file in the project root for more information.
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers;
namespace Markdig.Extensions.CustomContainers
{
/// <summary>
@@ -27,12 +27,12 @@ namespace Markdig.Extensions.CustomContainers
{
inlineParser.EmphasisDescriptors.Add(new EmphasisDescriptor(':', 2, 2, true));
inlineParser.TryCreateEmphasisInlineList.Add((emphasisChar, delimiterCount) =>
{
if (delimiterCount == 2 && emphasisChar == ':')
{
return new CustomContainerInline();
}
return null;
{
if (delimiterCount == 2 && emphasisChar == ':')
{
return new CustomContainerInline();
}
return null;
});
}
}

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 Markdig.Syntax.Inlines;
namespace Markdig.Extensions.CustomContainers
@@ -8,8 +9,8 @@ namespace Markdig.Extensions.CustomContainers
/// <summary>
/// An inline custom container
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.ContainerInline" />
/// <seealso cref="Markdig.Syntax.Inlines.EmphasisInline" />
/// <seealso cref="ContainerInline" />
/// <seealso cref="EmphasisInline" />
public class CustomContainerInline : EmphasisInline
{
}

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 Markdig.Parsers;
namespace Markdig.Extensions.CustomContainers
@@ -8,7 +9,7 @@ namespace Markdig.Extensions.CustomContainers
/// <summary>
/// The block parser for a <see cref="CustomContainer"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.FencedBlockParserBase{Markdig.Extensions.CustomContainers.CustomContainer}" />
/// <seealso cref="FencedBlockParserBase{CustomContainer}" />
public class CustomContainerParser : FencedBlockParserBase<CustomContainer>
{
/// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html;
@@ -9,7 +10,7 @@ namespace Markdig.Extensions.CustomContainers
/// <summary>
/// A HTML renderer for a <see cref="CustomContainerInline"/>.
/// </summary>
/// <seealso cref="Renderers.Html.HtmlObjectRenderer{CustomContainerInline}" />
/// <seealso cref="HtmlObjectRenderer{CustomContainerInline}" />
public class HtmlCustomContainerInlineRenderer : HtmlObjectRenderer<CustomContainerInline>
{
protected override void Write(HtmlRenderer renderer, CustomContainerInline obj)

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html;
@@ -9,7 +10,7 @@ namespace Markdig.Extensions.CustomContainers
/// <summary>
/// A HTML renderer for a <see cref="CustomContainer"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{CustomContainer}" />
/// <seealso cref="HtmlObjectRenderer{CustomContainer}" />
public class HtmlCustomContainerRenderer : HtmlObjectRenderer<CustomContainer>
{
protected override void Write(HtmlRenderer renderer, CustomContainer obj)

View File

@@ -1,3 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers;
using Markdig.Syntax;
@@ -7,7 +11,7 @@ namespace Markdig.Extensions.DefinitionLists
/// A definition item contains zero to multiple <see cref="DefinitionTerm"/>
/// and definitions (any <see cref="Block"/>)
/// </summary>
/// <seealso cref="Markdig.Syntax.ContainerBlock" />
/// <seealso cref="ContainerBlock" />
public class DefinitionItem : ContainerBlock
{
/// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers;
using Markdig.Syntax;
@@ -9,7 +10,7 @@ namespace Markdig.Extensions.DefinitionLists
/// <summary>
/// A definition list contains <see cref="DefinitionItem"/> children.
/// </summary>
/// <seealso cref="Markdig.Syntax.ContainerBlock" />
/// <seealso cref="ContainerBlock" />
public class DefinitionList : ContainerBlock
{
/// <summary>

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 Markdig.Renderers;
namespace Markdig.Extensions.DefinitionLists
@@ -8,7 +9,7 @@ namespace Markdig.Extensions.DefinitionLists
/// <summary>
/// Extension to allow definition lists
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class DefinitionListExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -22,8 +23,7 @@ namespace Markdig.Extensions.DefinitionLists
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
if (renderer is HtmlRenderer htmlRenderer)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlDefinitionListRenderer>())
{

View File

@@ -11,7 +11,7 @@ namespace Markdig.Extensions.DefinitionLists
/// <summary>
/// The block parser for a <see cref="DefinitionList"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.BlockParser" />
/// <seealso cref="BlockParser" />
public class DefinitionListParser : BlockParser
{
/// <summary>

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers;
using Markdig.Syntax;
@@ -9,7 +10,7 @@ namespace Markdig.Extensions.DefinitionLists
/// <summary>
/// A definition term contains a single line with the term to define.
/// </summary>
/// <seealso cref="Markdig.Syntax.LeafBlock" />
/// <seealso cref="LeafBlock" />
public class DefinitionTerm : LeafBlock
{
/// <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.
@@ -11,7 +11,7 @@ namespace Markdig.Extensions.DefinitionLists
/// <summary>
/// A HTML renderer for <see cref="DefinitionList"/>, <see cref="DefinitionItem"/> and <see cref="DefinitionTerm"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Extensions.DefinitionLists.DefinitionList}" />
/// <seealso cref="HtmlObjectRenderer{DefinitionList}" />
public class HtmlDefinitionListRenderer : HtmlObjectRenderer<DefinitionList>
{
protected override void Write(HtmlRenderer renderer, DefinitionList list)

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.
@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Diagrams
/// <summary>
/// Extension to allow diagrams.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class DiagramExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -19,8 +19,7 @@ namespace Markdig.Extensions.Diagrams
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
if (renderer is HtmlRenderer htmlRenderer)
{
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
// TODO: Add other well known diagram languages

View File

@@ -9,16 +9,16 @@ namespace Markdig.Extensions.Emoji
/// <summary>
/// Extension to allow emoji shortcodes and smileys replacement.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
public EmojiExtension(EmojiMapping emojiMapping)
{
EmojiMapping = emojiMapping;
{
EmojiMapping = emojiMapping;
}
public EmojiMapping EmojiMapping { get; }
public EmojiMapping EmojiMapping { get; }
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())

View File

@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Emoji
/// <summary>
/// An emoji inline.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.Inline" />
/// <seealso cref="Inline" />
public class EmojiInline : LiteralInline
{
// Inherit from LiteralInline so that rendering is already handled by default

View File

@@ -2,39 +2,38 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
namespace Markdig.Extensions.Emoji
{
/// <summary>
/// An emoji shortcodes and smileys mapping, to be used by <see cref="EmojiParser"/>.
/// </summary>
public class EmojiMapping
{
/// <summary>
/// An emoji shortcodes and smileys mapping, to be used by <see cref="EmojiParser"/>.
/// </summary>
public class EmojiMapping
{
/// <summary>
/// The default emoji shortcodes and smileys mapping.
/// <summary>
/// The default emoji shortcodes and smileys mapping.
/// </summary>
public static readonly EmojiMapping DefaultEmojisAndSmileysMapping = new EmojiMapping();
/// <summary>
/// The default emoji shortcodes mapping, without smileys.
public static readonly EmojiMapping DefaultEmojisAndSmileysMapping = new EmojiMapping();
/// <summary>
/// The default emoji shortcodes mapping, without smileys.
/// </summary>
public static readonly EmojiMapping DefaultEmojisOnlyMapping = new EmojiMapping(enableSmileys: false);
public static readonly EmojiMapping DefaultEmojisOnlyMapping = new EmojiMapping(enableSmileys: false);
internal CompactPrefixTree<string> PrefixTree { get; }
internal char[] OpeningCharacters { get; }
#region Emojis and Smileys
/// <summary>
/// Returns a new instance of the default emoji shortcode to emoji unicode dictionary.
/// It can be used to create a customized <see cref="EmojiMapping"/>.
/// </summary>
public static IDictionary<string, string> GetDefaultEmojiShortcodeToUnicode()
{
internal char[] OpeningCharacters { get; }
#region Emojis and Smileys
/// <summary>
/// Returns a new instance of the default emoji shortcode to emoji unicode dictionary.
/// It can be used to create a customized <see cref="EmojiMapping"/>.
/// </summary>
public static IDictionary<string, string> GetDefaultEmojiShortcodeToUnicode()
{
return new Dictionary<string, string>(1603)
{
{":100:", "💯"},
@@ -1643,15 +1642,15 @@ namespace Markdig.Extensions.Emoji
{":south_africa:","🇿🇦"},
{":zambia:","🇿🇲"},
{":zimbabwe:","🇿🇼"}
};
}
/// <summary>
/// Gets a new instance of the default smiley to emoji shortcode dictionary.
/// It can be used to create a customized <see cref="EmojiMapping"/>.
/// </summary>
public static IDictionary<string, string> GetDefaultSmileyToEmojiShortcode()
{
};
}
/// <summary>
/// Gets a new instance of the default smiley to emoji shortcode dictionary.
/// It can be used to create a customized <see cref="EmojiMapping"/>.
/// </summary>
public static IDictionary<string, string> GetDefaultSmileyToEmojiShortcode()
{
return new Dictionary<string, string>(71)
{
{">:(", ":angry:"},
@@ -1728,67 +1727,67 @@ namespace Markdig.Extensions.Emoji
{"<=", ":custom_arrow_left_strong:" },
{"=>", ":custom_arrow_right_strong:" },
{"<=>", ":custom_arrow_left_right_strong:" },
};
}
#endregion
/// <summary>
/// Constructs a mapping for the default emoji shortcodes and smileys.
/// </summary>
public EmojiMapping(bool enableSmileys = true)
: this(GetDefaultEmojiShortcodeToUnicode(),
enableSmileys ? GetDefaultSmileyToEmojiShortcode() : new Dictionary<string, string>())
{ }
/// <summary>
/// Constructs a mapping from a dictionary of emoji shortcodes to unicode, and a dictionary of smileys to emoji shortcodes.
/// </summary>
public EmojiMapping(IDictionary<string, string> shortcodeToUnicode, IDictionary<string, string> smileyToShortcode)
{
};
}
#endregion
/// <summary>
/// Constructs a mapping for the default emoji shortcodes and smileys.
/// </summary>
public EmojiMapping(bool enableSmileys = true)
: this(GetDefaultEmojiShortcodeToUnicode(),
enableSmileys ? GetDefaultSmileyToEmojiShortcode() : new Dictionary<string, string>())
{ }
/// <summary>
/// Constructs a mapping from a dictionary of emoji shortcodes to unicode, and a dictionary of smileys to emoji shortcodes.
/// </summary>
public EmojiMapping(IDictionary<string, string> shortcodeToUnicode, IDictionary<string, string> smileyToShortcode)
{
if (shortcodeToUnicode == null)
throw new ArgumentNullException(nameof(shortcodeToUnicode));
ThrowHelper.ArgumentNullException(nameof(shortcodeToUnicode));
if (smileyToShortcode == null)
throw new ArgumentNullException(nameof(smileyToShortcode));
if (smileyToShortcode == null)
ThrowHelper.ArgumentNullException(nameof(smileyToShortcode));
// Build emojis and smileys CompactPrefixTree
int jointCount = shortcodeToUnicode.Count + smileyToShortcode.Count;
// Count * 2 seems to be a good fit for the data set
PrefixTree = new CompactPrefixTree<string>(jointCount, jointCount * 2, jointCount * 2);
// This is not the best data set for the prefix tree as it will have to check the first character linearly
// A work-around would require a bunch of substrings / removing the leading ':' from emojis, neither one is pretty
// This way we sacrifice a few microseconds for not introducing breaking changes, emojis aren't all that common anyhow
// Build emojis and smileys CompactPrefixTree
int jointCount = shortcodeToUnicode.Count + smileyToShortcode.Count;
// Count * 2 seems to be a good fit for the data set
PrefixTree = new CompactPrefixTree<string>(jointCount, jointCount * 2, jointCount * 2);
// This is not the best data set for the prefix tree as it will have to check the first character linearly
// A work-around would require a bunch of substrings / removing the leading ':' from emojis, neither one is pretty
// This way we sacrifice a few microseconds for not introducing breaking changes, emojis aren't all that common anyhow
var firstChars = new HashSet<char>();
foreach (var shortcode in shortcodeToUnicode)
{
if (string.IsNullOrEmpty(shortcode.Key) || string.IsNullOrEmpty(shortcode.Value))
throw new ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(shortcodeToUnicode));
firstChars.Add(shortcode.Key[0]);
PrefixTree.Add(shortcode);
foreach (var shortcode in shortcodeToUnicode)
{
if (string.IsNullOrEmpty(shortcode.Key) || string.IsNullOrEmpty(shortcode.Value))
ThrowHelper.ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(shortcodeToUnicode));
firstChars.Add(shortcode.Key[0]);
PrefixTree.Add(shortcode);
}
foreach (var smiley in smileyToShortcode)
{
if (string.IsNullOrEmpty(smiley.Key) || string.IsNullOrEmpty(smiley.Value))
throw new ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(smileyToShortcode));
{
if (string.IsNullOrEmpty(smiley.Key) || string.IsNullOrEmpty(smiley.Value))
ThrowHelper.ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(smileyToShortcode));
if (!shortcodeToUnicode.TryGetValue(smiley.Value, out string unicode))
throw new ArgumentException(string.Format("Invalid smiley target: {0} is not present in the emoji shortcodes dictionary", smiley.Value));
ThrowHelper.ArgumentException(string.Format("Invalid smiley target: {0} is not present in the emoji shortcodes dictionary", smiley.Value));
firstChars.Add(smiley.Key[0]);
if (!PrefixTree.TryAdd(smiley.Key, unicode))
throw new ArgumentException(string.Format("Smiley {0} is already present in the emoji mapping", smiley.Key));
ThrowHelper.ArgumentException(string.Format("Smiley {0} is already present in the emoji mapping", smiley.Key));
}
OpeningCharacters = new List<char>(firstChars).ToArray();
}
OpeningCharacters = new List<char>(firstChars).ToArray();
}
}
}

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
@@ -14,14 +15,14 @@ namespace Markdig.Extensions.Emoji
/// <seealso cref="InlineParser" />
public class EmojiParser : InlineParser
{
private readonly EmojiMapping _emojiMapping;
private readonly EmojiMapping _emojiMapping;
/// <summary>
/// Initializes a new instance of the <see cref="EmojiParser"/> class.
/// </summary>
public EmojiParser(EmojiMapping emojiMapping)
{
_emojiMapping = emojiMapping;
{
_emojiMapping = emojiMapping;
OpeningCharacters = _emojiMapping.OpeningCharacters;
}
@@ -34,7 +35,7 @@ namespace Markdig.Extensions.Emoji
}
// Try to match an emoji shortcode or smiley
if (!_emojiMapping.PrefixTree.TryMatchLongest(slice.Text, slice.Start, slice.Length, out KeyValuePair<string, string> match))
if (!_emojiMapping.PrefixTree.TryMatchLongest(slice.Text.AsSpan(slice.Start, slice.Length), out KeyValuePair<string, string> match))
{
return false;
}

View File

@@ -6,8 +6,8 @@ using Markdig.Parsers.Inlines;
using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
using System.Diagnostics;
using System.Diagnostics;
namespace Markdig.Extensions.EmphasisExtras
{
/// <summary>
@@ -68,9 +68,9 @@ namespace Markdig.Extensions.EmphasisExtras
}
if (requireTilde && !hasTilde)
{
int minimumCount = (Options & EmphasisExtraOptions.Subscript) != 0 ? 1 : 2;
int maximumCount = (Options & EmphasisExtraOptions.Strikethrough) != 0 ? 2 : 1;
{
int minimumCount = (Options & EmphasisExtraOptions.Subscript) != 0 ? 1 : 2;
int maximumCount = (Options & EmphasisExtraOptions.Strikethrough) != 0 ? 2 : 1;
parser.EmphasisDescriptors.Add(new EmphasisDescriptor('~', minimumCount, maximumCount, true));
}
if (requireSup && !hasSup)
@@ -107,7 +107,7 @@ namespace Markdig.Extensions.EmphasisExtras
var c = emphasisInline.DelimiterChar;
switch (c)
{
case '~':
case '~':
Debug.Assert(emphasisInline.DelimiterCount <= 2);
return emphasisInline.DelimiterCount == 2 ? "del" : "sub";
case '^':

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers;
using Markdig.Syntax;
@@ -9,7 +10,7 @@ namespace Markdig.Extensions.Figures
/// <summary>
/// Defines a figure container.
/// </summary>
/// <seealso cref="Markdig.Syntax.ContainerBlock" />
/// <seealso cref="ContainerBlock" />
public class Figure : ContainerBlock
{
/// <summary>

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