Compare commits

...

155 Commits

Author SHA1 Message Date
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
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
Alexandre Mutel
9af462e412 Bump to 0.18.2 2020-03-08 10:15:30 +01:00
Alexandre Mutel
e1ba52da20 Merge pull request #406 from FranklinWhale/patch-1
Make output of HtmlTableRenderer XML wellformed
2020-03-08 10:09:29 +01:00
Alexandre Mutel
70ee97d5c7 Merge pull request #404 from webmaster442/master
Fixes issue #303
2020-03-08 10:09:03 +01:00
Alexandre Mutel
da916a1700 Merge pull request #402 from KrisVandermotten/HeadingRendererBugFix
Fixed a bug in HeadingRenderer
2020-03-08 10:08:47 +01:00
Franklin Tse
5548246703 Update TestPlayParser.cs 2020-03-06 19:00:37 +08:00
Franklin Tse
097fc5c4e0 Update GridTableSpecs.md 2020-03-06 18:55:00 +08:00
Franklin Tse
168ad39ff6 Make output of HtmlTableRenderer XML wellformed 2020-03-06 18:49:22 +08:00
Alexandre Mutel
54e065aabe Merge pull request #405 from bbodenmiller/patch-1
Github -> GitHub
2020-03-06 08:56:56 +01:00
Ben Bodenmiller
98d58797d9 Github -> GitHub 2020-03-05 23:55:39 -08:00
Ruzsinszki Gábor
2f0dd2a6a7 Fixes issue #303 2020-03-03 11:10:17 +01:00
Kris Vandermotten
247209870e Fixed a bug in HeadingRenderer 2020-03-01 14:44:41 +01:00
Alexandre Mutel
ef5b958267 Merge pull request #399 from KrisVandermotten/StringLineGroup
Some optimizations in StringLineGroup
2020-02-18 21:01:55 +01:00
Kris Vandermotten
7d61df2c0c Some optimizations in StringLineGroup 2020-02-15 10:34:24 +01:00
Alexandre Mutel
51f9da1974 Merge pull request #396 from KrisVandermotten/CharacterMapBitVector128
Use BitVector128 in CharacterMap<T>
2020-02-06 06:56:32 +01:00
Kris Vandermotten
9321628b9c Use uint instead of ulong 2020-02-05 22:38:21 +01:00
Kris Vandermotten
525e2c7fb8 Use BitVector128 in CharacterMap<T> 2020-02-04 22:45:42 +01:00
Alexandre Mutel
1f9b70636f Merge pull request #394 from KrisVandermotten/charactermap
Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T>
2020-01-31 18:43:24 +01:00
Kris Vandermotten
d4f43f826f Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T> 2020-01-31 18:35:28 +01:00
Alexandre Mutel
62788b1101 Merge pull request #393 from MihaZupan/line-reader
Optimize LineReader.ReadLine
2020-01-31 08:01:42 +01:00
MihaZupan
c3c4d37b82 Optimize LineReader.ReadLine 2020-01-30 21:14:06 +01:00
Alexandre Mutel
333bf04274 Update ci.yml, fix publish 2020-01-29 08:14:26 +01:00
Alexandre Mutel
dab4b7176d Merge pull request #392 from RudeySH/patch-1
Add Bootstrap and YAML to exclusion list in readme
2020-01-27 19:00:14 +01:00
Rudey
25ec56e1be Change frontmatter to Front Matter 2020-01-27 18:50:25 +01:00
Rudey
3455fd12da Add Bootstrap and YAML to exclusion list in readme
Both the Bootstrap and YAML frontmatter aren't included in `UseAdvancedExtensions`, but the readme didn't specify that.
2020-01-27 18:30:53 +01:00
Alexandre Mutel
ae5d47a0ed Merge pull request #391 from MihaZupan/remove-uap-remains
Remove UAP specific code
2020-01-27 08:36:14 +01:00
MihaZupan
1ff721d512 Remove UAP specific code 2020-01-26 23:04:57 +01:00
Alexandre Mutel
2c130504e2 Merge pull request #389 from MihaZupan/code-coverage
Improve code coverage
2020-01-26 22:01:06 +01:00
Alexandre Mutel
6405b16692 Migrate to GitHub Actions 2020-01-26 19:42:23 +01:00
Alexandre Mutel
2e1912a23d Update projects to compile with dotnet build/test, remove uap10.0 2020-01-26 19:35:21 +01:00
MihaZupan
71c680388c Make ExcludeFromCodeCoverage compatibility stub internal 2020-01-22 02:27:50 +01:00
MihaZupan
3f50b3fd0b Add LinkHelper.UrilizeAsGfm tests 2020-01-22 02:18:24 +01:00
MihaZupan
99b250b4c9 Add ContainerBlock tests 2020-01-22 02:06:42 +01:00
MihaZupan
655bf13df0 Exclude CompactPrefixTree from code coverage 2020-01-22 01:43:12 +01:00
MihaZupan
d2c89a3a06 Add tests for core Markdown apis 2020-01-22 01:42:23 +01:00
MihaZupan
754d11fd44 Fix test execution inside VS Enterprise Live Unit Testing 2020-01-21 21:44:29 +01:00
MihaZupan
1252e49ba4 Add more custom emoji tests 2020-01-21 18:36:36 +01:00
MihaZupan
d18487ae53 Fix StringLineGroupIterator.PeekChar for cross-line peeks 2020-01-21 18:16:13 +01:00
MihaZupan
f401d5082e Add SmartyPants tests 2020-01-21 17:35:58 +01:00
Alexandre Mutel
dd6418d108 Bump to 0.18.1 2020-01-21 08:30:32 +01:00
Alexandre Mutel
7875e4bce9 Merge pull request #387 from MihaZupan/descendants-api
Add missing Descendants<T> api
2020-01-21 08:24:13 +01:00
Alexandre Mutel
2e1b1a1fdc Merge pull request #386 from mlaily/emoji-customization
Emojis and smileys customization
2020-01-21 08:23:48 +01:00
MihaZupan
fa2b157c1a Improve some CharHelper methods 2020-01-21 01:31:44 +01:00
MihaZupan
e4e6406546 Add missing Descendants<T> overload 2020-01-21 01:03:47 +01:00
Melvyn Laïly
1cff10270a Set the default emoji dictionaries capacity in their ctor 2020-01-16 09:12:05 +01:00
Melvyn Laïly
87023184cb Clarify emoji terminology (emoji "shortcode") 2020-01-16 09:12:04 +01:00
Melvyn Laïly
aecdf2192e Improve the emoji extension (code review remarks) 2020-01-16 09:12:04 +01:00
Melvyn Laïly
0a36382126 Update changelog 2020-01-16 09:12:04 +01:00
Melvyn Laïly
0057b368ec Add unit tests for custom emojis and smileys 2020-01-16 09:11:04 +01:00
Melvyn Laïly
3033284096 Re-allow emojis and smileys customization
(this feature was broken in #308)
2020-01-16 09:11:03 +01:00
Alexandre Mutel
9b1b791b18 Merge pull request #341 from OpportunityLiu/master
Add customizable media link; handle protocol-less media
2020-01-15 13:27:06 +01:00
Opportunity
2b7d205701 Merge branch 'master' into master 2020-01-15 19:35:17 +08:00
Opportunity
89b28659b1 Update src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs
Co-Authored-By: Alexandre Mutel <alexandre_mutel@live.com>
2020-01-15 19:32:37 +08:00
Alexandre Mutel
446b1bcc0d Bump to 0.18.0 2019-10-24 21:08:11 +02:00
Alexandre Mutel
ec7a4a6902 Create FUNDING.yml 2019-10-24 07:10:27 +02:00
Alexandre Mutel
07a77142f4 Merge pull request #377 from MihaZupan/allocation-reductions
10% time and 50% memory improvement
2019-10-15 23:06:31 +02:00
MihaZupan
3606f234b8 Update changelog 2019-10-15 15:09:34 +02:00
MihaZupan
616eed62bd Use try/finally instead of goto release in AutoLinkParser 2019-10-15 15:07:45 +02:00
MihaZupan
99f55e9ddc Resolve merge conflict 2019-10-15 14:58:03 +02:00
Alexandre Mutel
f8ab1cccc5 Merge pull request #375 from MihaZupan/balanced-link-brackets
Balanced link brackets
2019-10-15 13:46:31 +02:00
MihaZupan
f24067cd16 Update AppVeyor build image 2019-10-14 19:11:28 +02:00
MihaZupan
9af96ba2b4 Minor CharHelper optimizations 2019-10-14 19:03:15 +02:00
MihaZupan
c99f7dd96a Mark some struct methods as readonly 2019-10-14 18:39:40 +02:00
MihaZupan
bf28cbd33f Optimize ContainerBlock this[] accessor 2019-10-14 18:32:24 +02:00
MihaZupan
2040e23545 Correctly return IsEmpty for ICharIterator.TrimStart 2019-10-14 18:31:51 +02:00
MihaZupan
2761e36b6b Optimize StringSlice primitives 2019-10-14 18:28:32 +02:00
MihaZupan
891334134c Mark Readonly structs as Readonly 2019-10-14 13:38:21 +02:00
MihaZupan
0987fab6f2 Seal internal types 2019-10-14 13:33:24 +02:00
MihaZupan
ed5eea5e27 Cache List<char> in AutoLinkParser 2019-10-14 13:26:09 +02:00
MihaZupan
f73cbe4e76 Resize LineOffsets to sufficient Capacity before adding items 2019-10-14 13:11:07 +02:00
MihaZupan
aefad219cf Cache StringLine[]s in StringLineGroup with a custom ArrayPool 2019-10-14 13:04:27 +02:00
MihaZupan
76c3e88c58 Estimate LineCount from text Length to minimize List resizes 2019-10-14 12:58:03 +02:00
MihaZupan
afe4308e91 Cache HtmlRenderer on Pipeline for ToHtml(string, Pipeline) 2019-10-13 18:03:13 +02:00
MihaZupan
606556b692 Use Write(Span) on NetCore 2019-10-13 15:44:36 +02:00
MihaZupan
253be5c362 Cache HtmlRenderers in AutoIdentifier Extension 2019-10-13 15:17:33 +02:00
MihaZupan
33037d1034 Update changelog 2019-10-08 17:29:49 +02:00
MihaZupan
f16ee828db Fix link text balanced bracket matching 2019-10-08 17:27:50 +02:00
MihaZupan
a76305f39b Add tests for balanced brackets in link text
For issue #371
2019-10-08 17:26:16 +02:00
MihaZupan
f52a41e167 Cleanup LinkInline parsing code
No functional changes, readability only
Switch with a single case => if statement
Pattern matching, inline out variable declarations ...
2019-10-08 16:22:53 +02:00
Opportunity
4d172bf905 Merge branch 'master' into master 2019-10-01 21:31:02 +08:00
Opportunity
890b2cda2a Update MediaLinkExtension.cs 2019-09-30 21:09:44 +08:00
Alexandre Mutel
c818670919 Merge pull request #360 from MihaZupan/smarty-pants
Fix SmartyPants quote matching
2019-09-25 22:15:36 +02:00
Alexandre Mutel
a78a0b7016 Merge pull request #361 from MihaZupan/generic-attributes
Fix GenericAttributes matching of single-char values
2019-09-25 22:15:22 +02:00
Miha Zupan
6a0c9aeb47 Fix GenericAttributes matching of single-char values 2019-08-01 16:14:51 +02:00
Miha Zupan
b1cfcf2431 Add GenericAttributes test for #182 2019-08-01 16:13:37 +02:00
Miha Zupan
b411522a23 Increase EnsureSpecsAreUpToDate time leeway to 3min 2019-08-01 16:12:44 +02:00
Miha Zupan
1d2977d47b Fix SmartyPants quote matching 2019-08-01 15:28:20 +02:00
Miha Zupan
ee8c87c357 Add SmartyPants tests for #183 2019-08-01 15:26:04 +02:00
Alexandre Mutel
25959174d5 Merge pull request #357 from MihaZupan/master
Ignore backticks in GFM AutoLinks
2019-07-17 09:25:33 +02:00
Miha Zupan
033ddaf6a8 Ignore backticks in GFM AutoLinks 2019-07-16 10:21:52 +02:00
Alexandre Mutel
f3f7584c39 Bump to 0.17.1 2019-07-04 22:21:31 +02:00
Alexandre Mutel
d00ca4acc1 Add coverage badge to readme.readme.md 2019-07-03 09:00:45 +02:00
Alexandre Mutel
d9663ef2e6 Try to upload to coveralls.io 2019-07-03 08:39:34 +02:00
Alexandre Mutel
3106a49d02 Merge pull request #352 from MihaZupan/master
Add code coverage
2019-07-03 08:30:49 +02:00
Miha Zupan
a47a6890e7 Remove coverage report from source control 2019-07-02 15:53:32 +02:00
Miha Zupan
7d21f8b003 Add test code coverage 2019-06-29 21:04:55 +02:00
Miha Zupan
15f6205adc Ensure line ending diffs don't trigger spec regeneration 2019-06-29 19:28:45 +02:00
Miha Zupan
e6dd2cf3d4 Cleanup build configuration 2019-06-27 12:36:19 +02:00
Alexandre Mutel
ea6592b773 Merge pull request #346 from kelvindules/update-emoji-dictionary
Update Emoji Dictionary
2019-06-07 14:43:30 +02:00
Kelvin Dules
5cd20efe3e Removed duplicates 2019-06-06 20:19:41 -03:00
Kelvin Dules
31c7ba5862 Update Emoji Dictionary 2019-06-06 19:29:32 -03:00
Alexandre Mutel
0272840a62 Add Markdig.Signed project. Move Markdig.Benchmarks to new csproj 2019-05-25 22:46:27 +02:00
OpportunityLiu
8886b48634 add tests 2019-05-16 15:58:02 +08:00
OpportunityLiu
64cd8ec262 customize iframe class of each provider 2019-05-15 10:48:12 +08:00
OpportunityLiu
a1e19912a9 update changelog 2019-05-14 17:32:09 +08:00
OpportunityLiu
ca51967fb1 fix #135 2019-05-14 17:31:03 +08:00
OpportunityLiu
bb3a4f372c add change log 2019-05-14 16:07:14 +08:00
OpportunityLiu
1d6a464c5d add unit test 2019-05-14 15:59:05 +08:00
OpportunityLiu
0fe5c17a93 Add customizable media link 2019-05-14 15:30:15 +08:00
Alexandre Mutel
f879d55b4a Merge pull request #340 from MihaZupan/master
Fix regression from #315
2019-05-13 10:46:59 +02:00
Miha Zupan
6d3a3584ac Fix regression from #315 2019-05-12 11:28:04 +02:00
138 changed files with 5068 additions and 3604 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [xoofx]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

61
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: ci
env:
PROJECT_NAME: Markdig
on:
push:
paths-ignore:
- 'doc/**'
- 'img/**'
- 'changelog.md'
- 'readme.md'
pull_request:
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.0.100
- name: Build (Release)
run: dotnet build src -c Release
- name: SpecFileGen
run: dotnet src/SpecFileGen/bin/Release/netcoreapp2.1/SpecFileGen.dll
- name: Test (Release)
run: dotnet test src -c Release
- name: Build & Test (Debug)
run: dotnet test src -c Debug
- name: Coverlet
run: dotnet test src -c Release -f netcoreapp2.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'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: src/${{env.PROJECT_NAME}}.Tests/coverage.netcoreapp2.1.info
- name: Pack
run: dotnet pack src -c Release
- name: Publish
if: github.event_name == 'push'
run: |
if ( "${{github.ref}}" -match "^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$" ) {
dotnet nuget push src\${{env.PROJECT_NAME}}\bin\Release\*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
dotnet nuget push src\${{env.PROJECT_NAME}}.Signed\bin\Release\*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
} else {
echo "publish is only enabled by tagging with a release tag"
}

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
*.userosscache
*.sln.docstates
*.nuget.props
*.nuget.targets
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

View File

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

View File

@@ -1,5 +1,42 @@
# Changelog
## 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))

View File

@@ -1,4 +1,4 @@
# Markdig [![Build status](https://ci.appveyor.com/api/projects/status/hk391x8jcskxt1u8?svg=true)](https://ci.appveyor.com/project/xoofx/markdig) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
# Markdig [![Build Status](https://github.com/lunet-io/markdig/workflows/ci/badge.svg?branch=master)](https://github.com/lunet-io/markdig/actions) [![Coverage Status](https://coveralls.io/repos/github/lunet-io/markdig/badge.svg?branch=master)](https://coveralls.io/github/lunet-io/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
<img align="right" width="160px" height="160px" src="img/markdig.png">
@@ -21,7 +21,7 @@ You can **try Markdig online** and compare it to other implementations on [babel
- Even the core Markdown/CommonMark parsing is pluggable, so it allows to disable builtin Markdown/Commonmark parsing (e.g [Disable HTML parsing](https://github.com/lunet-io/markdig/blob/7964bd0160d4c18e4155127a4c863d61ebd8944a/src/Markdig/MarkdownExtensions.cs#L306)) or change behaviour (e.g change matching `#` of a headers with `@`)
- Built-in with **20+ extensions**, including:
- 2 kind of tables:
- [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from Github tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from GitHub tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
- [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- strike through `~~`,
@@ -48,9 +48,11 @@ You can **try Markdig online** and compare it to other implementations on [babel
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**YAML frontmatter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the frontmatter and to discard it from the HTML output (typically used for previewing without the frontmatter in MarkdownEditor)
- [**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
@@ -84,7 +86,7 @@ var result = Markdown.ToHtml("This is a text with some *emphasis*");
Console.WriteLine(result); // prints: <p>This is a text with some <em>emphasis</em></p>
```
In order to activate most of all advanced extensions (except Emoji, SoftLine as HardLine, JiraLinks and SmartyPants)
In order to activate most of all advanced extensions (except Emoji, SoftLine as HardLine, Bootstrap, YAML Front Matter, JiraLinks and SmartyPants)
```csharp
// Configure the pipeline with all advanced extensions active

View File

@@ -1,43 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6A19F040-BC7C-4283-873A-177B5324F1ED}</ProjectGuid>
<TargetFrameworks>net471</TargetFrameworks>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Markdig.Benchmarks</RootNamespace>
<AssemblyName>Markdig.Benchmarks</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<CopyNuGetImplementations>true</CopyNuGetImplementations>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>true</Prefer32Bit>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Remove="spec.md" />
</ItemGroup>
<ItemGroup>
<Reference Include="CommonMarkNew, Version=0.1.0.0, Culture=neutral, PublicKeyToken=001ef8810438905d, processorArchitecture=MSIL">
<HintPath>lib\CommonMarkNew.dll</HintPath>
@@ -45,31 +15,12 @@
<Aliases>newcmark</Aliases>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Build" />
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="Microsoft.Build.Utilities.v4.0" />
<Reference Include="MoonShine">
<HintPath>lib\MoonShine.dll</HintPath>
</Reference>
<Reference Include="MarkdownDeep">
<HintPath>lib\MarkdownDeep.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CommonMarkLib.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestMatchPerf.cs" />
<Compile Include="TestStringPerf.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="cmark.dll">
@@ -83,21 +34,16 @@
<Content Include="spec.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="app.config" />
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="Markdig">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Markdig\Bin\$(Configuration)\net40\Markdig.dll</HintPath>
</Reference>
<PackageReference Include="BenchmarkDotNet" Version="0.10.6" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.10.6" />
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
<PackageReference Include="MarkdownSharp" Version="1.13.0.0" />
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="0.8.31-beta" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="1.0.41.0" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Testamina.Markdig.Benchmarks")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Testamina.Markdig.Benchmarks")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6a19f040-bc7c-4283-873a-177b5324f1ed")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,8 +0,0 @@
{
"profiles": {
"CLASSIC": {
"executablePath": "Testamina.Markdig.Benchmarks.dll",
"workingDirectory": "..\\..\\artifacts\\bin\\Testamina.Markdig.Benchmarks\\Debug\\net45"
}
}
}

View File

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

View File

@@ -1,29 +0,0 @@
{
"runtimes": {
"win-x86": {},
"win-x64": {}
},
"frameworks": {
"net46": {
"compilationOptions": {
"define": [
"CLASSIC"
]
},
"frameworkAssemblies": {
"Microsoft.Build": "4.0.0.0",
"Microsoft.Build.Framework": "4.0.0.0",
"Microsoft.Build.Utilities.v4.0": "4.0.0.0",
"System.Management": "4.0.0.0"
}
}
},
"dependencies": {
"BenchmarkDotNet": "0.10.6",
"BenchmarkDotNet.Diagnostics.Windows": "0.10.6",
"CommonMark.NET": "0.15.1",
"MarkdownSharp": "1.13.0.0",
"Microsoft.Diagnostics.Runtime": "0.8.31-beta",
"Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41.0"
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>Markdig.Signed</PackageId>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Markdig\**\*.cs" Exclude="..\Markdig\obj\**;..\Markdig\bin\**">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
</ItemGroup>
<Import Project="..\Markdig\Markdig.targets" />
</Project>

View File

@@ -1,13 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net451;netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="2.8.1">
<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.11.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
</ItemGroup>

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Markdig.Extensions.AutoLinks;
using NUnit.Framework;
@@ -8,6 +9,63 @@ 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()
{

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-05 16:06:14
// Generated: 2019-05-15 02:46:55
// --------------------------------
// Abbreviations

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-15 05:20:50
// Generated: 2020-01-13 21:08:58
// --------------------------------
// Emoji
@@ -18,7 +18,7 @@ namespace Markdig.Tests.Specs.Emoji
//
// ## Emoji
//
// Emoji and smiley can be converted to their respective unicode characters:
// Emoji shortcodes and smileys can be converted to their respective unicode characters:
[Test]
public void ExtensionsEmoji_Example001()
{
@@ -52,7 +52,7 @@ namespace Markdig.Tests.Specs.Emoji
TestParser.TestSpec("These are not:) an emoji with a:) x:angry:x", "<p>These are not:) an emoji with a:) x:angry:x</p>", "emojis|advanced+emojis");
}
// Emoji can be followed by close punctuation (or any other characters):
// Emojis can be followed by close punctuation (or any other characters):
[Test]
public void ExtensionsEmoji_Example003()
{
@@ -69,7 +69,7 @@ namespace Markdig.Tests.Specs.Emoji
TestParser.TestSpec("We all need :), it makes us :muscle:. (and :ok_hand:).", "<p>We all need 😃, it makes us 💪. (and 👌).</p>", "emojis|advanced+emojis");
}
// Sentences can end with Emoji:
// Sentences can end with emojis:
[Test]
public void ExtensionsEmoji_Example004()
{

View File

@@ -4,7 +4,7 @@ This section describes the different extensions supported:
## Emoji
Emoji and smiley can be converted to their respective unicode characters:
Emoji shortcodes and smileys can be converted to their respective unicode characters:
```````````````````````````````` example
This is a test with a :) and a :angry: smiley
@@ -20,7 +20,7 @@ These are not:) an emoji with a:) x:angry:x
<p>These are not:) an emoji with a:) x:angry:x</p>
````````````````````````````````
Emoji can be followed by close punctuation (or any other characters):
Emojis can be followed by close punctuation (or any other characters):
```````````````````````````````` example
We all need :), it makes us :muscle:. (and :ok_hand:).
@@ -28,7 +28,7 @@ We all need :), it makes us :muscle:. (and :ok_hand:).
<p>We all need 😃, it makes us 💪. (and 👌).</p>
````````````````````````````````
Sentences can end with Emoji:
Sentences can end with emojis:
```````````````````````````````` example
This is a sentence :ok_hand:

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-05 16:06:14
// Generated: 2019-08-01 13:57:17
// --------------------------------
// Generic Attributes
@@ -79,5 +79,28 @@ namespace Markdig.Tests.Specs.GenericAttributes
Console.WriteLine("Example 2\nSection Extensions / Generic Attributes\n");
TestParser.TestSpec("{#fenced-id .fenced-class}\n~~~\nThis is a fenced with attached attributes\n~~~ ", "<pre><code id=\"fenced-id\" class=\"fenced-class\">This is a fenced with attached attributes\n</code></pre>", "attributes|advanced");
}
// Attribute values can be one character long
[Test]
public void ExtensionsGenericAttributes_Example003()
{
// Example 3
// Section: Extensions / Generic Attributes
//
// The following Markdown:
// [Foo](url){data-x=1}
//
// [Foo](url){data-x='1'}
//
// [Foo](url){data-x=11}
//
// Should be rendered as:
// <p><a href="url" data-x="1">Foo</a></p>
// <p><a href="url" data-x="1">Foo</a></p>
// <p><a href="url" data-x="11">Foo</a></p>
Console.WriteLine("Example 3\nSection Extensions / Generic Attributes\n");
TestParser.TestSpec("[Foo](url){data-x=1}\n\n[Foo](url){data-x='1'}\n\n[Foo](url){data-x=11}", "<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"11\">Foo</a></p>", "attributes|advanced");
}
}
}

View File

@@ -47,3 +47,17 @@ This is a fenced with attached attributes
<pre><code id="fenced-id" class="fenced-class">This is a fenced with attached attributes
</code></pre>
````````````````````````````````
Attribute values can be one character long
```````````````````````````````` example
[Foo](url){data-x=1}
[Foo](url){data-x='1'}
[Foo](url){data-x=11}
.
<p><a href="url" data-x="1">Foo</a></p>
<p><a href="url" data-x="1">Foo</a></p>
<p><a href="url" data-x="11">Foo</a></p>
````````````````````````````````

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-05 16:06:14
// Generated: 2020-04-18 06:41:26
// --------------------------------
// Grid Tables
@@ -62,8 +62,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 +73,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 +112,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 +135,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 +152,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 +163,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 +179,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 +190,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 +215,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 +228,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 +245,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 +258,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 +279,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 +302,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 +323,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 +345,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

@@ -42,8 +42,8 @@ The following is a valid row separator
| This is | a table |
.
<table>
<col style="width:50%">
<col style="width:50%">
<col style="width:50%" />
<col style="width:50%" />
<tbody>
<tr>
<td>This is</td>
@@ -74,9 +74,9 @@ A regular row can continue a previous regular row when column separator `|` are
| Col1c |
.
<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
@@ -105,8 +105,8 @@ A row header is separated using `+========+` instead of `+---------+`:
+=========+=========+
.
<table>
<col style="width:50%">
<col style="width:50%">
<col style="width:50%" />
<col style="width:50%" />
<thead>
<tr>
<th>This is</th>
@@ -123,8 +123,8 @@ The last column separator `|` may be omitted:
| This is | a table with a longer text in the second column
.
<table>
<col style="width:50%">
<col style="width:50%">
<col style="width:50%" />
<col style="width:50%" />
<tbody>
<tr>
<td>This is</td>
@@ -150,9 +150,9 @@ So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
+----+--------+----+
.
<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>
@@ -172,9 +172,9 @@ Alignment might be specified on the first row using the character `:`:
+-----+-----+-----+
.
<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>
@@ -197,9 +197,9 @@ Alignment might be specified on the first row using the character `:`:
+---+---+---+
.
<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>
@@ -232,9 +232,9 @@ A grid table may have cells with both colspan and rowspan:
+---+---+---+
.
<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

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-29 18:40:06
// Generated: 2019-05-15 02:46:20
// --------------------------------
// Media
@@ -47,19 +47,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" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><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><video width="500" height="281" controls=""><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <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>
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\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" 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/\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" 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\" 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");
}
}
}

View File

@@ -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" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><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><video width="500" height="281" controls=""><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<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>
````````````````````````````````

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-05 16:06:14
// Generated: 2019-08-01 12:33:23
// --------------------------------
// Smarty Pants
@@ -140,13 +140,13 @@ namespace Markdig.Tests.Specs.SmartyPants
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// This is a 'text <<with' a another text>>
// This is 'a "text 'with" a another text'
//
// Should be rendered as:
// <p>This is a &lsquo;text &lt;&lt;with&rsquo; a another text&gt;&gt;</p>
// <p>This is &lsquo;a &ldquo;text 'with&rdquo; a another text&rsquo;</p>
Console.WriteLine("Example 8\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("This is a 'text <<with' a another text>>", "<p>This is a &lsquo;text &lt;&lt;with&rsquo; a another text&gt;&gt;</p>", "pipetables+smartypants|advanced+smartypants");
TestParser.TestSpec("This is 'a \"text 'with\" a another text'", "<p>This is &lsquo;a &ldquo;text 'with&rdquo; a another text&rsquo;</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
@@ -156,20 +156,36 @@ namespace Markdig.Tests.Specs.SmartyPants
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// This is a 'text <<with' a another text>>
//
// Should be rendered as:
// <p>This is a &lsquo;text &lt;&lt;with&rsquo; a another text&gt;&gt;</p>
Console.WriteLine("Example 9\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("This is a 'text <<with' a another text>>", "<p>This is a &lsquo;text &lt;&lt;with&rsquo; a another text&gt;&gt;</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
public void ExtensionsSmartyPantsQuotes_Example010()
{
// Example 10
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// This is a <<text 'with>> a another text'
//
// Should be rendered as:
// <p>This is a &laquo;text 'with&raquo; a another text'</p>
Console.WriteLine("Example 9\nSection Extensions / SmartyPants Quotes\n");
Console.WriteLine("Example 10\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("This is a <<text 'with>> a another text'", "<p>This is a &laquo;text 'with&raquo; a another text'</p>", "pipetables+smartypants|advanced+smartypants");
}
// Quotes requires to have the same rules than emphasis `_` regarding left/right frankling rules:
[Test]
public void ExtensionsSmartyPantsQuotes_Example010()
public void ExtensionsSmartyPantsQuotes_Example011()
{
// Example 10
// Example 11
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
@@ -178,24 +194,8 @@ namespace Markdig.Tests.Specs.SmartyPants
// Should be rendered as:
// <p>It's not quotes'</p>
Console.WriteLine("Example 10\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("It's not quotes'", "<p>It's not quotes'</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
public void ExtensionsSmartyPantsQuotes_Example011()
{
// Example 11
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// They are ' not matching quotes '
//
// Should be rendered as:
// <p>They are ' not matching quotes '</p>
Console.WriteLine("Example 11\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("They are ' not matching quotes '", "<p>They are ' not matching quotes '</p>", "pipetables+smartypants|advanced+smartypants");
TestParser.TestSpec("It's not quotes'", "<p>It's not quotes'</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
@@ -205,20 +205,36 @@ namespace Markdig.Tests.Specs.SmartyPants
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// They are ' not matching quotes '
//
// Should be rendered as:
// <p>They are ' not matching quotes '</p>
Console.WriteLine("Example 12\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("They are ' not matching quotes '", "<p>They are ' not matching quotes '</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
public void ExtensionsSmartyPantsQuotes_Example013()
{
// Example 13
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// They are' not matching 'quotes
//
// Should be rendered as:
// <p>They are' not matching 'quotes</p>
Console.WriteLine("Example 12\nSection Extensions / SmartyPants Quotes\n");
Console.WriteLine("Example 13\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("They are' not matching 'quotes", "<p>They are' not matching 'quotes</p>", "pipetables+smartypants|advanced+smartypants");
}
// An emphasis starting inside left/right quotes will span over the right quote:
[Test]
public void ExtensionsSmartyPantsQuotes_Example013()
public void ExtensionsSmartyPantsQuotes_Example014()
{
// Example 13
// Example 14
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
@@ -227,9 +243,26 @@ namespace Markdig.Tests.Specs.SmartyPants
// Should be rendered as:
// <p>This is &ldquo;a <em>text&rdquo; with an emphasis</em></p>
Console.WriteLine("Example 13\nSection Extensions / SmartyPants Quotes\n");
Console.WriteLine("Example 14\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("This is \"a *text\" with an emphasis*", "<p>This is &ldquo;a <em>text&rdquo; with an emphasis</em></p>", "pipetables+smartypants|advanced+smartypants");
}
// Multiple sets of quotes can be used
[Test]
public void ExtensionsSmartyPantsQuotes_Example015()
{
// Example 15
// Section: Extensions / SmartyPants Quotes
//
// The following Markdown:
// "aaa" "bbb" "ccc" "ddd"
//
// Should be rendered as:
// <p>&ldquo;aaa&rdquo; &ldquo;bbb&rdquo; &ldquo;ccc&rdquo; &ldquo;ddd&rdquo;</p>
Console.WriteLine("Example 15\nSection Extensions / SmartyPants Quotes\n");
TestParser.TestSpec("\"aaa\" \"bbb\" \"ccc\" \"ddd\"", "<p>&ldquo;aaa&rdquo; &ldquo;bbb&rdquo; &ldquo;ccc&rdquo; &ldquo;ddd&rdquo;</p>", "pipetables+smartypants|advanced+smartypants");
}
}
[TestFixture]
@@ -237,9 +270,9 @@ namespace Markdig.Tests.Specs.SmartyPants
{
// ## SmartyPants Separators
[Test]
public void ExtensionsSmartyPantsSeparators_Example014()
public void ExtensionsSmartyPantsSeparators_Example016()
{
// Example 14
// Example 16
// Section: Extensions / SmartyPants Separators
//
// The following Markdown:
@@ -248,14 +281,14 @@ namespace Markdig.Tests.Specs.SmartyPants
// Should be rendered as:
// <p>This is a &ndash; text</p>
Console.WriteLine("Example 14\nSection Extensions / SmartyPants Separators\n");
Console.WriteLine("Example 16\nSection Extensions / SmartyPants Separators\n");
TestParser.TestSpec("This is a -- text", "<p>This is a &ndash; text</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
public void ExtensionsSmartyPantsSeparators_Example015()
public void ExtensionsSmartyPantsSeparators_Example017()
{
// Example 15
// Example 17
// Section: Extensions / SmartyPants Separators
//
// The following Markdown:
@@ -264,14 +297,14 @@ namespace Markdig.Tests.Specs.SmartyPants
// Should be rendered as:
// <p>This is a &mdash; text</p>
Console.WriteLine("Example 15\nSection Extensions / SmartyPants Separators\n");
Console.WriteLine("Example 17\nSection Extensions / SmartyPants Separators\n");
TestParser.TestSpec("This is a --- text", "<p>This is a &mdash; text</p>", "pipetables+smartypants|advanced+smartypants");
}
[Test]
public void ExtensionsSmartyPantsSeparators_Example016()
public void ExtensionsSmartyPantsSeparators_Example018()
{
// Example 16
// Example 18
// Section: Extensions / SmartyPants Separators
//
// The following Markdown:
@@ -280,15 +313,15 @@ namespace Markdig.Tests.Specs.SmartyPants
// Should be rendered as:
// <p>This is a en ellipsis&hellip;</p>
Console.WriteLine("Example 16\nSection Extensions / SmartyPants Separators\n");
Console.WriteLine("Example 18\nSection Extensions / SmartyPants Separators\n");
TestParser.TestSpec("This is a en ellipsis...", "<p>This is a en ellipsis&hellip;</p>", "pipetables+smartypants|advanced+smartypants");
}
// Check that a smartypants are not breaking pipetable parsing:
[Test]
public void ExtensionsSmartyPantsSeparators_Example017()
public void ExtensionsSmartyPantsSeparators_Example019()
{
// Example 17
// Example 19
// Section: Extensions / SmartyPants Separators
//
// The following Markdown:
@@ -312,15 +345,15 @@ namespace Markdig.Tests.Specs.SmartyPants
// </tbody>
// </table>
Console.WriteLine("Example 17\nSection Extensions / SmartyPants Separators\n");
Console.WriteLine("Example 19\nSection Extensions / SmartyPants Separators\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>", "pipetables+smartypants|advanced+smartypants");
}
// Check quotes and dash:
[Test]
public void ExtensionsSmartyPantsSeparators_Example018()
public void ExtensionsSmartyPantsSeparators_Example020()
{
// Example 18
// Example 20
// Section: Extensions / SmartyPants Separators
//
// The following Markdown:
@@ -329,7 +362,7 @@ namespace Markdig.Tests.Specs.SmartyPants
// Should be rendered as:
// <p>A &ldquo;quote&rdquo; with a &mdash;</p>
Console.WriteLine("Example 18\nSection Extensions / SmartyPants Separators\n");
Console.WriteLine("Example 20\nSection Extensions / SmartyPants Separators\n");
TestParser.TestSpec("A \"quote\" with a ---", "<p>A &ldquo;quote&rdquo; with a &mdash;</p>", "pipetables+smartypants|advanced+smartypants");
}
}

View File

@@ -52,6 +52,12 @@ This is a "text 'with" a another text'
<p>This is a &ldquo;text 'with&rdquo; a another text'</p>
````````````````````````````````
```````````````````````````````` example
This is 'a "text 'with" a another text'
.
<p>This is &lsquo;a &ldquo;text 'with&rdquo; a another text&rsquo;</p>
````````````````````````````````
```````````````````````````````` example
This is a 'text <<with' a another text>>
.
@@ -91,6 +97,14 @@ This is "a *text" with an emphasis*
<p>This is &ldquo;a <em>text&rdquo; with an emphasis</em></p>
````````````````````````````````
Multiple sets of quotes can be used
```````````````````````````````` example
"aaa" "bbb" "ccc" "ddd"
.
<p>&ldquo;aaa&rdquo; &ldquo;bbb&rdquo; &ldquo;ccc&rdquo; &ldquo;ddd&rdquo;</p>
````````````````````````````````
## SmartyPants Separators
```````````````````````````````` example

View File

@@ -0,0 +1,70 @@
using System;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
{
public class TestContainerBlocks
{
private class MockContainerBlock : ContainerBlock
{
public MockContainerBlock()
: base(null)
{
}
}
[Test]
public void CanBeCleared()
{
ContainerBlock container = new MockContainerBlock();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
var paragraph = new ParagraphBlock();
Assert.Null(paragraph.Parent);
container.Add(paragraph);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container, paragraph.Parent);
Assert.AreSame(paragraph, container.LastChild);
container.Clear();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
Assert.Null(paragraph.Parent);
}
[Test]
public void CanBeInsertedInto()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
var two = new ParagraphBlock();
container.Insert(1, two);
Assert.AreEqual(2, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container[1], two);
var three = new ParagraphBlock();
container.Insert(0, three);
Assert.AreEqual(3, container.Count);
Assert.AreSame(container[0], three);
Assert.AreSame(container[1], one);
Assert.AreSame(container[2], two);
Assert.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
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections;
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
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomEmoji(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
emojiToUnicode[":smiley:"] = "♥";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomSmiley(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":)", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
emojiToUnicode[":smiley:"] = "♥";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomSmiley(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
public void TestCustomEmojiValidation()
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
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,6 +1,7 @@
using NUnit.Framework;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System;
using System.Linq;
using System.Collections.Generic;
@@ -12,24 +13,91 @@ namespace Markdig.Tests
[Test]
public void TestSchemas()
{
foreach (var markdown in TestParser.SpecsMarkdown)
foreach (var syntaxTree in TestParser.SpecsSyntaxTrees)
{
AssertSameDescendantsOrder(markdown);
AssertIEnumerablesAreEqual(
Descendants_Legacy(syntaxTree),
syntaxTree.Descendants());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
syntaxTree.Descendants<ParagraphBlock>());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<ParagraphBlock>(),
(syntaxTree as ContainerBlock).Descendants<ParagraphBlock>());
AssertIEnumerablesAreEqual(
syntaxTree.Descendants().OfType<LiteralInline>(),
syntaxTree.Descendants<LiteralInline>());
foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
{
Assert.AreSame(Array.Empty<ListBlock>(), literalInline.Descendants<ListBlock>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), literalInline.Descendants<ParagraphBlock>());
Assert.AreSame(Array.Empty<ContainerInline>(), literalInline.Descendants<ContainerInline>());
}
foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
{
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
(containerInline as MarkdownObject).Descendants<LiteralInline>());
if (containerInline.FirstChild is null)
{
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerInline as MarkdownObject).Descendants<LiteralInline>());
}
Assert.AreSame(Array.Empty<ListBlock>(), containerInline.Descendants<ListBlock>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), containerInline.Descendants<ParagraphBlock>());
}
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
{
AssertIEnumerablesAreEqual(
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
paragraphBlock.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), paragraphBlock.Descendants<ParagraphBlock>());
}
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
{
AssertIEnumerablesAreEqual(
containerBlock.Descendants<LiteralInline>(),
(containerBlock as MarkdownObject).Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
containerBlock.Descendants<ParagraphBlock>(),
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());
if (containerBlock.Count == 0)
{
Assert.AreSame(Array.Empty<LiteralInline>(), containerBlock.Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerBlock as Block).Descendants<LiteralInline>());
Assert.AreSame(Array.Empty<LiteralInline>(), (containerBlock as MarkdownObject).Descendants<LiteralInline>());
}
}
}
}
private void AssertSameDescendantsOrder(string markdown)
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
{
var syntaxTree = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
var firstList = new List<T>(first);
var secondList = new List<T>(second);
var descendants_legacy = Descendants_Legacy(syntaxTree).ToList();
var descendants_new = syntaxTree.Descendants().ToList();
Assert.AreEqual(firstList.Count, secondList.Count);
Assert.AreEqual(descendants_legacy.Count, descendants_new.Count);
for (int i = 0; i < descendants_legacy.Count; i++)
for (int i = 0; i < firstList.Count; i++)
{
Assert.AreSame(descendants_legacy[i], descendants_new[i]);
Assert.AreSame(firstList[i], secondList[i]);
}
}

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -18,60 +18,60 @@ namespace Markdig.Tests
public void TestEmpty()
{
var lineReader = new LineReader("");
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyLf()
{
var lineReader = new LineReader("\n\n\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyCr()
{
var lineReader = new LineReader("\r\r\r");
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(1, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLinesOnlyCrLf()
{
var lineReader = new LineReader("\r\n\r\n\r\n");
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(2, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual(string.Empty, lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestNoEndOfLine()
{
var lineReader = new LineReader("123");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestLf()
{
var lineReader = new LineReader("123\n");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
@@ -79,29 +79,29 @@ namespace Markdig.Tests
{
// When limited == true, we limit the internal buffer exactly after the first new line char '\n'
var lineReader = new LineReader("123\n456");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCr()
{
var lineReader = new LineReader("123\r");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCr2()
{
var lineReader = new LineReader("123\r456");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(4, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
@@ -110,19 +110,19 @@ namespace Markdig.Tests
// When limited == true, we limit the internal buffer exactly after the first new line char '\r'
// and we check that we don't get a new line for `\n`
var lineReader = new LineReader("123\r\n");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine().Text);
}
[Test]
public void TestCrLf2()
{
var lineReader = new LineReader("123\r\n456");
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
Assert.AreEqual("123", lineReader.ReadLine().ToString());
Assert.AreEqual(5, lineReader.SourcePosition);
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
Assert.Null(lineReader.ReadLine()?.ToString());
Assert.AreEqual("456", lineReader.ReadLine().ToString());
Assert.Null(lineReader.ReadLine().Text);
}
}
}

View File

@@ -322,6 +322,16 @@ namespace Markdig.Tests
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
}
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
[TestCase("* Dogs*?--in *my* house?", "-dogs--in-my-house")]
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
[TestCase("3. Applications", "3-applications")]
[TestCase("33", "33")]
public void TestUrilizeGfm(string input, string expectedResult)
{
Assert.AreEqual(expectedResult, LinkHelper.UrilizeAsGfm(input));
}
[TestCase("abc", "abc")]
[TestCase("a-c", "a-c")]
[TestCase("a c", "a-c")]

View File

@@ -0,0 +1,148 @@
using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using System.IO;
namespace Markdig.Tests
{
public class TestMarkdigCoreApi
{
[Test]
public void TestToHtml()
{
string html = Markdown.ToHtml("This is a text with some *emphasis*");
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/");
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
[Test]
public void TestToHtmlWithPipeline()
{
var pipeline = new MarkdownPipelineBuilder()
.Build();
string html = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreNotEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
html = Markdown.ToHtml("This is a text with a https://link.tld/", pipeline);
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
[Test]
public void TestToHtmlWithWriter()
{
StringWriter writer = new StringWriter();
_ = Markdown.ToHtml("This is a text with some *emphasis*", writer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer = new StringWriter();
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
_ = Markdown.ToHtml("This is a text with a https://link.tld/", writer, pipeline);
html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
[Test]
public void TestConvert()
{
StringWriter writer = new StringWriter();
HtmlRenderer renderer = new HtmlRenderer(writer);
_ = Markdown.Convert("This is a text with some *emphasis*", renderer);
string html = writer.ToString();
Assert.AreEqual("<p>This is a text with some <em>emphasis</em></p>\n", html);
writer = new StringWriter();
renderer = new HtmlRenderer(writer);
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
_ = Markdown.Convert("This is a text with a https://link.tld/", renderer, pipeline);
html = writer.ToString();
Assert.AreEqual("<p>This is a text with a <a href=\"https://link.tld/\">https://link.tld/</a></p>\n", html);
}
[Test]
public void TestParse()
{
const string markdown = "This is a text with some *emphasis*";
var pipeline = new MarkdownPipelineBuilder()
.UsePreciseSourceLocation()
.Build();
MarkdownDocument document = Markdown.Parse(markdown, pipeline);
Assert.AreEqual(1, document.LineCount);
Assert.AreEqual(markdown.Length, document.Span.Length);
Assert.AreEqual(1, document.LineStartIndexes.Count);
Assert.AreEqual(0, document.LineStartIndexes[0]);
Assert.AreEqual(1, document.Count);
ParagraphBlock paragraph = document[0] as ParagraphBlock;
Assert.NotNull(paragraph);
Assert.AreEqual(markdown.Length, paragraph.Span.Length);
LiteralInline literal = paragraph.Inline.FirstChild as LiteralInline;
Assert.NotNull(literal);
Assert.AreEqual("This is a text with some ", literal.ToString());
EmphasisInline emphasis = literal.NextSibling as EmphasisInline;
Assert.NotNull(emphasis);
Assert.AreEqual("*emphasis*".Length, emphasis.Span.Length);
LiteralInline emphasisLiteral = emphasis.FirstChild as LiteralInline;
Assert.NotNull(emphasisLiteral);
Assert.AreEqual("emphasis", emphasisLiteral.ToString());
Assert.Null(emphasisLiteral.NextSibling);
Assert.Null(emphasis.NextSibling);
}
[Test]
public void TestNormalize()
{
string normalized = Markdown.Normalize("Heading\n=======");
Assert.AreEqual("# Heading", normalized);
}
public void TestNormalizeWithWriter()
{
StringWriter writer = new StringWriter();
_ = Markdown.Normalize("Heading\n=======", writer);
string normalized = writer.ToString();
Assert.AreEqual("# Heading", normalized);
}
[Test]
public void TestToPlainText()
{
string plainText = Markdown.ToPlainText("*Hello*, [world](http://example.com)!");
Assert.AreEqual("Hello, world!\n", plainText);
}
[Test]
public void TestToPlainTextWithWriter()
{
StringWriter writer = new StringWriter();
_ = Markdown.ToPlainText("*Hello*, [world](http://example.com)!", writer);
string plainText = writer.ToString();
Assert.AreEqual("Hello, world!\n", plainText);
}
}
}

View File

@@ -0,0 +1,87 @@
using Markdig.Extensions.MediaLinks;
using NUnit.Framework;
using System;
using System.Text.RegularExpressions;
namespace Markdig.Tests
{
[TestFixture]
public class TestMediaLinks
{
private MarkdownPipeline GetPipeline(MediaOptions options = null)
{
return new MarkdownPipelineBuilder()
.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")]
public void TestBuiltInHosts(string markdown, string expected)
{
string html = Markdown.ToHtml(markdown, GetPipeline());
Assert.AreEqual(html, expected);
}
private class TestHostProvider : IHostProvider
{
public string Class { get; } = "regex";
public bool AllowFullScreen { get; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
{
iframeUrl = null;
var uri = isSchemaRelative ? "//" + mediaUri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Scheme, UriFormat.UriEscaped) : mediaUri.ToString();
if (!matcher.IsMatch(uri))
return false;
iframeUrl = matcher.Replace(uri, replacement);
return true;
}
private Regex matcher;
private string replacement;
public TestHostProvider(string provider, string replace)
{
matcher = new Regex(provider);
replacement = replace;
}
}
[Test]
[TestCase("![p1](https://sample.com/video.mp4)", "<p><iframe src=\"https://example.com/video.mp4\" 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")]
public void TestCustomHostProvider(string markdown, string expected, string provider, string replace)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
{
Hosts =
{
new TestHostProvider(provider, replace),
}
}));
Assert.AreEqual(html, expected);
}
[Test]
[TestCase("![static mp4](//sample.com/video.mp4)", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "")]
[TestCase(@"![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" 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")]
public void TestCustomClass(string markdown, string expected, string klass)
{
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
{
Class = klass,
}));
Assert.AreEqual(html, expected);
}
}
}

View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
@@ -33,9 +34,9 @@ namespace Markdig.Tests
// 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.AddSeconds(2);
testTime = testTime.AddMinutes(3);
// This might not catch a changed spec every time, but should most of the time. Otherwise CI will catch it
// 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,
@@ -151,8 +152,20 @@ namespace Markdig.Tests
/// 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)
@@ -161,10 +174,16 @@ namespace Markdig.Tests
.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++)
{
SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
string markdown = SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
SpecsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
}
}

View File

@@ -1,5 +1,5 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Linq;
@@ -19,7 +19,7 @@ namespace Markdig.Tests
var link = doc.Descendants<ParagraphBlock>().SelectMany(x => x.Inline.Descendants<LinkInline>()).FirstOrDefault(l => l.IsImage);
Assert.AreEqual("/yoyo", link?.Url);
}
[Test]
public void TestListBug2()
{
@@ -53,11 +53,11 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
[Test]
public void TestEmptyLiteral()
{
var text = @"> *some text*
var text = @"> *some text*
> some other text";
var doc = Markdown.Parse(text);
Assert.True(doc.Descendants().OfType<LiteralInline>().All(x => !x.Content.IsEmpty),
Assert.True(doc.Descendants<LiteralInline>().All(x => !x.Content.IsEmpty),
"There should not have any empty literals");
}
@@ -195,8 +195,8 @@ Paragraph
";
var expected = @"<table class=""table"">
<col style=""width:50%"">
<col style=""width:50%"">
<col style=""width:50%"" />
<col style=""width:50%"" />
<thead>
<tr>
<th>a</th>
@@ -219,7 +219,7 @@ Paragraph
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
// Reuse the same pipeline
// Reuse the same pipeline
var result1 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
var result2 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
@@ -269,7 +269,7 @@ Paragraph
//| Yes |
//| ``` |
//+===================================+======================================+
//| This is a second line |
//| This is a second line |
//+-----------------------------------+--------------------------------------+
//:::spoiler {#yessss}

View File

@@ -0,0 +1,36 @@
using Markdig.Extensions.SmartyPants;
using NUnit.Framework;
namespace Markdig.Tests
{
public class TestSmartyPants
{
[Test]
public void MappingCanBeReconfigured()
{
SmartyPantOptions options = new SmartyPantOptions();
options.Mapping[SmartyPantType.LeftAngleQuote] = "foo";
options.Mapping[SmartyPantType.RightAngleQuote] = "bar";
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants(options)
.Build();
TestParser.TestSpec("<<test>>", "<p>footestbar</p>", pipeline);
}
[Test]
public void MappingCanBeReconfigured_HandlesRemovedMappings()
{
SmartyPantOptions options = new SmartyPantOptions();
options.Mapping.Remove(SmartyPantType.LeftAngleQuote);
options.Mapping.Remove(SmartyPantType.RightAngleQuote);
var pipeline = new MarkdownPipelineBuilder()
.UseSmartyPants(options)
.Build();
TestParser.TestSpec("<<test>>", "<p>&laquo;test&raquo;</p>", pipeline);
}
}
}

View File

@@ -6,8 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Markdig.Extensions.Footnotes;
using Markdig.Helpers;
using Markdig.Extensions.Footnotes;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -147,7 +146,7 @@ literal ( 0, 6) 6-7
public void TestFootnoteLinkReferenceDefinition()
{
// 01 2 345678
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants().OfType<FootnoteLinkReferenceDefinition>().FirstOrDefault();
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants<FootnoteLinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(footnote);
Assert.AreEqual(2, footnote.Line);
@@ -160,7 +159,7 @@ literal ( 0, 6) 6-7
{
// 0 1
// 0123456789012345
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(0, link.Line);
@@ -175,7 +174,7 @@ literal ( 0, 6) 6-7
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
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);
@@ -213,7 +212,7 @@ literal ( 0, 4) 4-5
{
// 0 1
// 01 2 3456789012345
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
@@ -226,7 +225,7 @@ literal ( 0, 4) 4-5
{
// 0 1
// 01 2 34567890123456789
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
@@ -240,7 +239,7 @@ literal ( 0, 4) 4-5
{
// 0 1
// 01 2 3456789012345
var link = Markdown.Parse("0\n\n01![234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
var link = Markdown.Parse("0\n\n01![234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
Assert.NotNull(link);
Assert.AreEqual(new SourceSpan(5, 15), link.Span);
@@ -408,7 +407,7 @@ literal ( 1, 2) 6-6
6. Bar
987123. FooBar";
test = test.Replace("\r\n", "\n");
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<ListBlock>().FirstOrDefault();
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<ListBlock>().FirstOrDefault();
Assert.NotNull(list);
Assert.AreEqual(1, list.Line);

View File

@@ -1,8 +1,9 @@
using NUnit.Framework;
using NUnit.Framework;
using System.Collections.Generic;
using System.Text;
using Markdig.Helpers;
using Markdig.Syntax;
using System;
namespace Markdig.Tests
{
@@ -27,6 +28,8 @@ namespace Markdig.Tests
var chars = ToString(text.ToCharIterator());
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
TextAssert.AreEqual("ABC\nE\nF", text.ToString());
}
[Test]
@@ -81,8 +84,18 @@ namespace Markdig.Tests
public void TestSkipWhitespaces()
{
var text = new StringLineGroup(" ABC").ToCharIterator();
Assert.True(text.TrimStart());
Assert.False(text.TrimStart());
Assert.AreEqual('A', text.CurrentChar);
text = new StringLineGroup(" ").ToCharIterator();
Assert.True(text.TrimStart());
Assert.AreEqual('\0', text.CurrentChar);
var slice = new StringSlice(" ABC");
Assert.False(slice.TrimStart());
slice = new StringSlice(" ");
Assert.True(slice.TrimStart());
}
[Test]
@@ -116,5 +129,31 @@ namespace Markdig.Tests
var result = ToString(text);
TextAssert.AreEqual("ABC \n DEF ", result);
}
[Test]
public void TestStringLineGroupIteratorPeekChar()
{
var iterator = new StringLineGroup(4)
{
new StringSlice("ABC"),
new StringSlice("E"),
new StringSlice("F")
}.ToCharIterator();
Assert.AreEqual('A', iterator.CurrentChar);
Assert.AreEqual('A', iterator.PeekChar(0));
Assert.AreEqual('B', iterator.PeekChar());
Assert.AreEqual('B', iterator.PeekChar(1));
Assert.AreEqual('C', iterator.PeekChar(2));
Assert.AreEqual('\n', iterator.PeekChar(3));
Assert.AreEqual('E', iterator.PeekChar(4));
Assert.AreEqual('\n', iterator.PeekChar(5));
Assert.AreEqual('F', iterator.PeekChar(6));
Assert.AreEqual('\0', iterator.PeekChar(7)); // There is no \n appended to the last line
Assert.AreEqual('\0', iterator.PeekChar(8));
Assert.AreEqual('\0', iterator.PeekChar(100));
Assert.Throws<ArgumentOutOfRangeException>(() => iterator.PeekChar(-1));
}
}
}

View File

@@ -9,6 +9,7 @@
<RuntimeFrameworkVersion>2.0.0</RuntimeFrameworkVersion>
<ApplicationInsightsResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsResourceId>
<ApplicationInsightsAnnotationResourceId>/subscriptions/b6745039-70e7-4641-994b-5457cb220e2a/resourcegroups/Default-ApplicationInsights-EastUS/providers/microsoft.insights/components/Markdig.WebApp</ApplicationInsightsAnnotationResourceId>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
@@ -22,16 +23,16 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.0" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.2.1" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -43,8 +43,8 @@ namespace Markdig.WebApp
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
//loggerFactory.AddConsole(Configuration.GetSection("Logging"));
//loggerFactory.AddDebug();
app.UseMvc();
}

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

@@ -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

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
@@ -115,7 +116,7 @@ namespace Markdig.Extensions.Abbreviations
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))

View File

@@ -22,6 +22,7 @@ namespace Markdig.Extensions.AutoIdentifiers
{
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.
@@ -159,17 +160,11 @@ namespace Markdig.Extensions.AutoIdentifiers
}
// Use internally a HtmlRenderer to strip links from a heading
var headingWriter = new StringWriter();
var stripRenderer = new HtmlRenderer(headingWriter)
{
// Set to false both to avoid having any HTML tags in the output
EnableHtmlForInline = false,
EnableHtmlEscape = false
};
var stripRenderer = rendererCache.Get();
stripRenderer.Render(headingBlock.Inline);
var headingText = headingWriter.ToString();
headingWriter.GetStringBuilder().Length = 0;
var headingText = stripRenderer.Writer.ToString();
rendererCache.Release(stripRenderer);
// Urilize the link
headingText = (options & AutoIdentifierOptions.GitHub) != 0
@@ -195,5 +190,25 @@ namespace Markdig.Extensions.AutoIdentifiers
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

@@ -14,7 +14,7 @@ namespace Markdig.Extensions.AutoLinks
/// <summary>
/// The inline parser used to for autolinks.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
/// <seealso cref="InlineParser" />
public class AutoLinkParser : InlineParser
{
/// <summary>
@@ -30,10 +30,14 @@ namespace Markdig.Extensions.AutoLinks
'f', // for ftp://
'm', // for mailto:
'w', // for www.
};
}
public readonly AutoLinkOptions Options;
};
_listOfCharCache = new ListOfCharCache();
}
public readonly AutoLinkOptions Options;
private readonly ListOfCharCache _listOfCharCache;
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
@@ -44,159 +48,162 @@ namespace Markdig.Extensions.AutoLinks
return false;
}
List<char> pendingEmphasis;
// Check that an autolink is possible in the current context
if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
{
return false;
}
var startPosition = slice.Start;
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
string link;
if (!LinkHelper.TryParseUrl(ref slice, out link, true))
{
return false;
}
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
if (pendingEmphasis != null)
{
for (int i = link.Length - 1; i >= 0; i--)
{
if (pendingEmphasis.Contains(link[i]))
{
slice.Start--;
}
else
{
if (i < link.Length - 1)
{
link = link.Substring(0, i + 1);
}
break;
}
}
}
// Post-check URL
switch (c)
{
case 'h':
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'f':
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
{
return false;
}
break;
case 'm':
int atIndex = link.IndexOf('@');
if (atIndex == -1 ||
atIndex == 7) // mailto:@ - no email part
{
return false;
}
domainOffset = atIndex + 1;
break;
}
if (!LinkHelper.IsValidDomain(link, domainOffset))
List<char> pendingEmphasis = _listOfCharCache.Get();
try
{
return false;
// 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()
{
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;
}
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)
finally
{
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
_listOfCharCache.Release(pendingEmphasis);
}
return true;
}
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, out List<char> pendingEmphasis)
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List<char> pendingEmphasis)
{
pendingEmphasis = null;
// Case where there is a pending HtmlInline <a>
var currentInline = processor.Inline;
while (currentInline != null)
{
var htmlInline = currentInline as HtmlInline;
if (htmlInline != null)
if (currentInline is HtmlInline htmlInline)
{
// If we have a </a> we don't expect nested <a>
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
@@ -211,7 +218,7 @@ namespace Markdig.Extensions.AutoLinks
}
}
// Check previous sibling and parents in the tree
// Check previous sibling and parents in the tree
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
}
@@ -221,8 +228,7 @@ namespace Markdig.Extensions.AutoLinks
int countBrackets = 0;
while (currentInline != null)
{
var linkDelimiterInline = currentInline as LinkDelimiterInline;
if (linkDelimiterInline != null && linkDelimiterInline.IsActive)
if (currentInline is LinkDelimiterInline linkDelimiterInline && linkDelimiterInline.IsActive)
{
if (linkDelimiterInline.Type == DelimiterType.Open)
{
@@ -236,14 +242,8 @@ namespace Markdig.Extensions.AutoLinks
else
{
// Record all pending characters for emphasis
var emphasisDelimiter = currentInline as EmphasisDelimiterInline;
if (emphasisDelimiter != null)
if (currentInline is EmphasisDelimiterInline emphasisDelimiter)
{
if (pendingEmphasis == null)
{
// Not optimized for GC, but we don't expect this case much
pendingEmphasis = new List<char>();
}
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
{
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
@@ -254,6 +254,14 @@ namespace Markdig.Extensions.AutoLinks
}
return countBrackets <= 0;
}
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
{
protected override void Reset(List<char> instance)
{
instance.Clear();
}
}
}
}

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.
@@ -51,8 +51,7 @@ namespace Markdig.Extensions.Bootstrap
}
else if (node is Inline)
{
var link = node as LinkInline;
if (link != null && link.IsImage)
if (node is LinkInline link && link.IsImage)
{
link.GetAttributes().AddClass("img-fluid");
}

View File

@@ -7,24 +7,24 @@ using Markdig.Renderers;
namespace Markdig.Extensions.Emoji
{
/// <summary>
/// Extension to allow emoji and smiley replacement.
/// Extension to allow emoji shortcodes and smileys replacement.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
public EmojiExtension(bool enableSmiley = true)
{
EnableSmiley = enableSmiley;
public EmojiExtension(EmojiMapping emojiMapping)
{
EmojiMapping = emojiMapping;
}
public bool EnableSmiley { get; set; }
public EmojiMapping EmojiMapping { get; }
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new EmojiParser(EnableSmiley));
pipeline.InlineParsers.Insert(0, new EmojiParser(EmojiMapping));
}
}

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.
@@ -8,7 +8,7 @@ using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Emoji
{
/// <summary>
/// An emoji inline
/// An emoji inline.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.Inline" />
public class EmojiInline : LiteralInline
@@ -32,7 +32,7 @@ namespace Markdig.Extensions.Emoji
}
/// <summary>
/// Gets or sets the original match string (either an emoji or a text smiley)
/// Gets or sets the original match string (either an emoji shortcode or a text smiley)
/// </summary>
public string Match { get; set; }
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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.
@@ -16,7 +16,7 @@ namespace Markdig.Extensions.GenericAttributes
/// Extension that allows to attach HTML attributes to the previous <see cref="Inline"/> or current <see cref="Block"/>.
/// This extension should be enabled last after enabling other extensions.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
/// <seealso cref="IMarkdownExtension" />
public class GenericAttributesExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
@@ -29,8 +29,7 @@ namespace Markdig.Extensions.GenericAttributes
// Plug into all IAttributesParseable
foreach (var parser in pipeline.BlockParsers)
{
var attributesParseable = parser as IAttributesParseable;
if (attributesParseable != null)
if (parser is IAttributesParseable attributesParseable)
{
attributesParseable.TryParseAttributes = TryProcessAttributesForHeading;
}
@@ -53,8 +52,7 @@ namespace Markdig.Extensions.GenericAttributes
var copy = line;
copy.Start = indexOfAttributes;
var startOfAttributes = copy.Start;
HtmlAttributes attributes;
if (GenericAttributesParser.TryParse(ref copy, out attributes))
if (GenericAttributesParser.TryParse(ref copy, out HtmlAttributes attributes))
{
var htmlAttributes = block.GetAttributes();
attributes.CopyTo(htmlAttributes);

View File

@@ -14,7 +14,7 @@ namespace Markdig.Extensions.GenericAttributes
/// <summary>
/// An inline parser used to parse a HTML attributes that can be attached to the previous <see cref="Inline"/> or current <see cref="Block"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
/// <seealso cref="InlineParser" />
public class GenericAttributesParser : InlineParser
{
/// <summary>
@@ -27,9 +27,8 @@ namespace Markdig.Extensions.GenericAttributes
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
HtmlAttributes attributes;
var startPosition = slice.Start;
if (TryParse(ref slice, out attributes))
if (TryParse(ref slice, out HtmlAttributes attributes))
{
var inline = processor.Inline;
@@ -50,8 +49,10 @@ namespace Markdig.Extensions.GenericAttributes
// If the current block is a Paragraph, but only the HtmlAttributes is used,
// Try to attach the attributes to the following block
var paragraph = objectToAttach as ParagraphBlock;
if (paragraph != null && paragraph.Inline.FirstChild == null && processor.Inline == null && slice.IsEmptyOrWhitespace())
if (objectToAttach is ParagraphBlock paragraph &&
paragraph.Inline.FirstChild == null &&
processor.Inline == null &&
slice.IsEmptyOrWhitespace())
{
var parent = paragraph.Parent;
var indexOfParagraph = parent.IndexOf(paragraph);
@@ -67,9 +68,7 @@ namespace Markdig.Extensions.GenericAttributes
attributes.CopyTo(currentHtmlAttributes, true, false);
// Update the position of the attributes
int line;
int column;
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out line, out column);
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out int line, out int column);
currentHtmlAttributes.Line = line;
currentHtmlAttributes.Column = column;
currentHtmlAttributes.Span.End = currentHtmlAttributes.Span.Start + slice.Start - startPosition - 1;
@@ -223,6 +222,7 @@ namespace Markdig.Extensions.GenericAttributes
{
// Parse until we match a space or a special html character
startValue = line.Start;
bool valid = false;
while (true)
{
if (c == '\0')
@@ -234,9 +234,10 @@ namespace Markdig.Extensions.GenericAttributes
break;
}
c = line.NextChar();
valid = true;
}
endValue = line.Start - 1;
if (endValue == startValue)
if (!valid)
{
break;
}

View File

@@ -38,7 +38,7 @@ namespace Markdig.Extensions.Globalization
var attributes = node.GetAttributes();
attributes.AddPropertyIfNotExist("dir", "rtl");
if (node is Table table)
if (node is Table)
{
attributes.AddPropertyIfNotExist("align", "right");
}
@@ -71,19 +71,16 @@ namespace Markdig.Extensions.Globalization
}
else if (item is LiteralInline literal)
{
return StartsWithRtlCharacter(literal.ToString());
return StartsWithRtlCharacter(literal.Content);
}
foreach (var descendant in item.Descendants())
foreach (var paragraph in item.Descendants<ParagraphBlock>())
{
if (descendant is ParagraphBlock p)
foreach (var inline in paragraph.Inline)
{
foreach (var i in p.Inline)
if (inline is LiteralInline literal)
{
if (i is LiteralInline l)
{
return StartsWithRtlCharacter(l.ToString());
}
return StartsWithRtlCharacter(literal.Content);
}
}
}
@@ -91,9 +88,9 @@ namespace Markdig.Extensions.Globalization
return false;
}
private bool StartsWithRtlCharacter(string text)
private bool StartsWithRtlCharacter(StringSlice slice)
{
foreach (var c in CharHelper.ToUtf32(text))
foreach (var c in CharHelper.ToUtf32(slice))
{
if (CharHelper.IsRightToLeft(c))
return true;

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.
@@ -45,16 +45,16 @@ namespace Markdig.Extensions.ListExtras
if ((isRomanLow || isRomanUp) && (pendingBulletType == '\0' || pendingBulletType == 'i' || pendingBulletType == 'I'))
{
int startChar = state.Start;
int endChar = 0;
// With a roman, we can have multiple characters
// Note that we don't validate roman numbers
while (isRomanLow ? CharHelper.IsRomanLetterLowerPartial(c) : CharHelper.IsRomanLetterUpperPartial(c))
do
{
endChar = state.Start;
c = state.NextChar();
}
while (isRomanLow ? CharHelper.IsRomanLetterLowerPartial(c) : CharHelper.IsRomanLetterUpperPartial(c));
result.OrderedStart = CharHelper.RomanToArabic(state.Line.Text.Substring(startChar, endChar - startChar + 1)).ToString();
int orderValue = CharHelper.RomanToArabic(state.Line.Text.AsSpan(startChar, state.Start - startChar));
result.OrderedStart = CharHelper.SmallNumberToString(orderValue);
result.BulletType = isRomanLow ? 'i' : 'I';
result.DefaultOrderedStart = isRomanLow ? "i" : "I";
}
@@ -62,7 +62,7 @@ namespace Markdig.Extensions.ListExtras
{
// otherwise we expect a regular alpha lettered list with a single character.
var isUpper = c.IsAlphaUpper();
result.OrderedStart = (Char.ToUpperInvariant(c) - 64).ToString();
result.OrderedStart = CharHelper.SmallNumberToString((c | 0x20) - 'a' + 1);
result.BulletType = isUpper ? 'A' : 'a';
result.DefaultOrderedStart = isUpper ? "A" : "a";
state.NextChar();

View File

@@ -0,0 +1,139 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Markdig.Extensions.MediaLinks
{
public class HostProviderBuilder
{
private sealed class DelegateProvider : IHostProvider
{
public string HostPrefix { get; set; }
public Func<Uri, string> Delegate { get; set; }
public bool AllowFullScreen { get; set; } = true;
public string Class { get; set; }
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
{
if (!mediaUri.Host.StartsWith(HostPrefix, StringComparison.OrdinalIgnoreCase))
{
iframeUrl = null;
return false;
}
iframeUrl = Delegate(mediaUri);
return !string.IsNullOrEmpty(iframeUrl);
}
}
/// <summary>
/// Create a <see cref="IHostProvider"/> with delegate handler.
/// </summary>
/// <param name="hostPrefix">Prefix of host that can be handled.</param>
/// <param name="handler">Handler that generate iframe url, if uri cannot be handled, it can return <see langword="null"/>.</param>
/// <param name="allowFullScreen">Should the generated iframe has allowfullscreen attribute.</param>
/// <param name="iframeClass">"class" attribute of generated iframe.</param>
/// <returns>A <see cref="IHostProvider"/> with delegate handler.</returns>
public static IHostProvider Create(string hostPrefix, Func<Uri, string> handler, bool allowFullScreen = true, string iframeClass = null)
{
if (string.IsNullOrEmpty(hostPrefix))
ThrowHelper.ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
if (handler == null)
ThrowHelper.ArgumentNullException(nameof(handler));
return new DelegateProvider { HostPrefix = hostPrefix, Delegate = handler, AllowFullScreen = allowFullScreen, Class = iframeClass };
}
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
= new Dictionary<string, IHostProvider>(StringComparer.OrdinalIgnoreCase)
{
["YouTube"] = Create("www.youtube.com", YouTube, iframeClass: "youtube"),
["YouTubeShortened"] = Create("youtu.be", YouTubeShortened, iframeClass: "youtube"),
["Vimeo"] = Create("vimeo.com", Vimeo, iframeClass: "vimeo"),
["Yandex"] = Create("music.yandex.ru", Yandex, allowFullScreen: false, iframeClass: "yandex"),
["Odnoklassniki"] = Create("ok.ru", Odnoklassniki, iframeClass: "odnoklassniki"),
};
#region Known providers
private static readonly string[] SplitAnd = { "&" };
private static string[] SplitQuery(Uri uri)
{
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
}
private static string YouTube(Uri uri)
{
string uriPath = uri.AbsolutePath;
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
{
return uri.ToString();
}
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var queryParams = SplitQuery(uri);
return BuildYouTubeIframeUrl(
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
);
}
private static string YouTubeShortened(Uri uri)
{
return BuildYouTubeIframeUrl(
uri.AbsolutePath.Substring(1),
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
);
}
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
{
if (string.IsNullOrEmpty(videoId))
{
return null;
}
string url = $"https://www.youtube.com/embed/{videoId}";
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
}
private static string Vimeo(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://player.vimeo.com/video/{ items[items.Length - 1] }" : null;
}
private static string Odnoklassniki(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://ok.ru/videoembed/{ items[items.Length - 1] }" : null;
}
private static string Yandex(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
var albumKeyword
= items.Skip(0).FirstOrDefault();
var albumId
= items.Skip(1).FirstOrDefault();
var trackKeyword
= items.Skip(2).FirstOrDefault();
var trackId
= items.Skip(3).FirstOrDefault();
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
{
return null;
}
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
}
#endregion
}
}

View File

@@ -0,0 +1,36 @@
// 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.MediaLinks
{
/// <summary>
/// Provides url for media links.
/// </summary>
public interface IHostProvider
{
/// <summary>
/// "class" attribute of generated iframe.
/// </summary>
string Class { get; }
/// <summary>
/// Generate url for iframe.
/// </summary>
/// <param name="mediaUri">Input media uri.</param>
/// <param name="isSchemaRelative"><see langword="true"/> if <paramref name="mediaUri"/> is a schema relative uri, i.e. uri starts with "//".</param>
/// <param name="iframeUrl">Generated url for iframe.</param>
/// <seealso href="https://tools.ietf.org/html/rfc3986#section-4.2"/>
bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl);
/// <summary>
/// Should the generated iframe has allowfullscreen attribute.
/// </summary>
/// <remarks>
/// Should be false for audio embedding.
/// </remarks>
bool AllowFullScreen { get; }
}
}

View File

@@ -3,7 +3,6 @@
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Markdig.Renderers;
using Markdig.Renderers.Html;
@@ -55,18 +54,28 @@ namespace Markdig.Extensions.MediaLinks
}
Uri uri;
bool isSchemaRelative = false;
// Only process absolute Uri
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) || !uri.IsAbsoluteUri)
{
return false;
// see https://tools.ietf.org/html/rfc3986#section-4.2
// since relative uri doesn't support many properties, "http" is used as a placeholder here.
if (linkInline.Url.StartsWith("//") && Uri.TryCreate("http:" + linkInline.Url, UriKind.Absolute, out uri))
{
isSchemaRelative = true;
}
else
{
return false;
}
}
if (TryRenderIframeFromKnownProviders(uri, renderer, linkInline))
if (TryRenderIframeFromKnownProviders(uri, isSchemaRelative, renderer, linkInline))
{
return true;
}
if (TryGuessAudioVideoFile(uri, renderer, linkInline))
if (TryGuessAudioVideoFile(uri, isSchemaRelative, renderer, linkInline))
{
return true;
}
@@ -86,7 +95,7 @@ namespace Markdig.Extensions.MediaLinks
return htmlAttributes;
}
private bool TryGuessAudioVideoFile(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
private bool TryGuessAudioVideoFile(Uri uri, bool isSchemaRelative, HtmlRenderer renderer, LinkInline linkInline)
{
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
// Otherwise try to detect if we have an audio/video from the file extension
@@ -106,6 +115,10 @@ namespace Markdig.Extensions.MediaLinks
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
}
htmlAttributes.AddPropertyIfNotExist("controls", null);
if (!string.IsNullOrEmpty(Options.Class))
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
renderer.WriteAttributes(htmlAttributes);
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
@@ -115,38 +128,18 @@ namespace Markdig.Extensions.MediaLinks
return false;
}
#region Known providers
private class KnownProvider
private bool TryRenderIframeFromKnownProviders(Uri uri, bool isSchemaRelative, HtmlRenderer renderer, LinkInline linkInline)
{
public string HostPrefix { get; set; }
public Func<Uri, string> Delegate { get; set; }
public bool AllowFullScreen { get; set; } = true; //Should be false for audio embedding
}
private static readonly List<KnownProvider> KnownHosts = new List<KnownProvider>()
{
new KnownProvider {HostPrefix = "www.youtube.com", Delegate = YouTube},
new KnownProvider {HostPrefix = "youtu.be", Delegate = YouTubeShortened},
new KnownProvider {HostPrefix = "vimeo.com", Delegate = Vimeo},
new KnownProvider {HostPrefix = "music.yandex.ru", Delegate = Yandex, AllowFullScreen = false},
new KnownProvider {HostPrefix = "ok.ru", Delegate = Odnoklassniki},
};
private bool TryRenderIframeFromKnownProviders(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
{
var foundProvider =
KnownHosts
.Where(pair => uri.Host.StartsWith(pair.HostPrefix, StringComparison.OrdinalIgnoreCase)) // when host is match
.Select(provider =>
new
{
provider.AllowFullScreen,
Result = provider.Delegate(uri) // try to call delegate to get iframeUrl
}
)
.FirstOrDefault(provider => provider.Result != null); // use first success
IHostProvider foundProvider = null;
string iframeUrl = null;
foreach (var provider in Options.Hosts)
{
if (!provider.TryHandle(uri, isSchemaRelative, out iframeUrl))
continue;
foundProvider = provider;
break;
}
if (foundProvider == null)
{
@@ -155,17 +148,20 @@ namespace Markdig.Extensions.MediaLinks
var htmlAttributes = GetHtmlAttributes(linkInline);
renderer.Write("<iframe src=\"");
renderer.WriteEscapeUrl(foundProvider.Result);
renderer.WriteEscapeUrl(iframeUrl);
renderer.Write("\"");
if(!string.IsNullOrEmpty(Options.Width))
if (!string.IsNullOrEmpty(Options.Width))
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
if (!string.IsNullOrEmpty(Options.Height))
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
if (!string.IsNullOrEmpty(Options.Class))
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
if (!string.IsNullOrEmpty(Options.Class) || !string.IsNullOrEmpty(foundProvider.Class))
htmlAttributes.AddPropertyIfNotExist("class",
(!string.IsNullOrEmpty(Options.Class) && !string.IsNullOrEmpty(foundProvider.Class))
? Options.Class + " " + foundProvider.Class
: Options.Class + foundProvider.Class);
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
if (foundProvider.AllowFullScreen)
@@ -177,81 +173,5 @@ namespace Markdig.Extensions.MediaLinks
return true;
}
private static readonly string[] SplitAnd = {"&"};
private static string[] SplitQuery(Uri uri)
{
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
}
private static string YouTube(Uri uri)
{
string uriPath = uri.AbsolutePath;
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
{
return uri.ToString();
}
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var queryParams = SplitQuery(uri);
return BuildYouTubeIframeUrl(
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
);
}
private static string YouTubeShortened(Uri uri)
{
return BuildYouTubeIframeUrl(
uri.AbsolutePath.Substring(1),
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
);
}
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
{
if (string.IsNullOrEmpty(videoId))
{
return null;
}
string url = $"https://www.youtube.com/embed/{videoId}";
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
}
private static string Vimeo(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://player.vimeo.com/video/{items[items.Length - 1]}" : null;
}
private static string Odnoklassniki(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://ok.ru/videoembed/{items[items.Length - 1]}" : null;
}
private static string Yandex(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
var albumKeyword
= items.Skip(0).FirstOrDefault();
var albumId
= items.Skip(1).FirstOrDefault();
var trackKeyword
= items.Skip(2).FirstOrDefault();
var trackId
= items.Skip(3).FirstOrDefault();
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
{
return null;
}
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
}
#endregion
}
}

View File

@@ -76,12 +76,14 @@ namespace Markdig.Extensions.MediaLinks
{".ecelp7470", "audio/vnd.nuera.ecelp7470"},
{".ecelp9600", "audio/vnd.nuera.ecelp9600"},
{".oga", "audio/ogg"},
{".ogg", "audio/ogg"},
{".weba", "audio/webm"},
{".ram", "audio/x-pn-realaudio"},
{".rmp", "audio/x-pn-realaudio-plugin"},
{".au", "audio/basic"},
{".wav", "audio/x-wav"},
};
Hosts = new List<IHostProvider>(HostProviderBuilder.KnownHosts.Values);
}
public string Width { get; set; }
@@ -91,5 +93,7 @@ namespace Markdig.Extensions.MediaLinks
public string Class { get; set; }
public Dictionary<string, string> ExtensionToMimeType { get; }
public List<IHostProvider> Hosts { get; }
}
}

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 Markdig.Helpers;
using Markdig.Renderers;
namespace Markdig.Extensions.SelfPipeline
@@ -29,7 +30,7 @@ namespace Markdig.Extensions.SelfPipeline
tag = string.IsNullOrEmpty(tag) ? DefaultTag : tag;
if (tag.IndexOfAny(new []{'<', '>'}) >= 0)
{
throw new ArgumentException("Tag cannot contain `<` or `>` characters", nameof(tag));
ThrowHelper.ArgumentException("Tag cannot contain `<` or `>` characters", nameof(tag));
}
if (defaultExtensions != null)
@@ -57,7 +58,7 @@ namespace Markdig.Extensions.SelfPipeline
// Make sure that this pipeline has only one extension (itself)
if (pipeline.Extensions.Count > 1)
{
throw new InvalidOperationException(
ThrowHelper.InvalidOperationException(
"The SelfPipeline extension cannot be configured with other extensions");
}
}
@@ -74,7 +75,7 @@ namespace Markdig.Extensions.SelfPipeline
/// <exception cref="System.ArgumentNullException"></exception>
public MarkdownPipeline CreatePipelineFromInput(string inputText)
{
if (inputText == null) throw new ArgumentNullException(nameof(inputText));
if (inputText == null) ThrowHelper.ArgumentNullException(nameof(inputText));
var builder = new MarkdownPipelineBuilder();
string defaultConfig = DefaultExtensions;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -10,7 +10,7 @@ namespace Markdig.Extensions.SmartyPants
/// <summary>
/// A HTML renderer for a <see cref="SmartyPant"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{SmartyPant}" />
/// <seealso cref="HtmlObjectRenderer{SmartyPant}" />
public class HtmlSmartyPantRenderer : HtmlObjectRenderer<SmartyPant>
{
private static readonly SmartyPantOptions DefaultOptions = new SmartyPantOptions();
@@ -21,17 +21,15 @@ namespace Markdig.Extensions.SmartyPants
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer"/> class.
/// </summary>
/// <param name="options">The options.</param>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public HtmlSmartyPantRenderer(SmartyPantOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
this.options = options;
this.options = options ?? throw new ArgumentNullException(nameof(options));
}
protected override void Write(HtmlRenderer renderer, SmartyPant obj)
{
string text;
if (!options.Mapping.TryGetValue(obj.Type, out text))
if (!options.Mapping.TryGetValue(obj.Type, out string text))
{
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Diagnostics;
@@ -45,5 +45,15 @@ namespace Markdig.Extensions.SmartyPants
}
return OpeningCharacter != 0 ? OpeningCharacter.ToString() : string.Empty;
}
public LiteralInline AsLiteralInline()
{
return new LiteralInline(ToString())
{
Span = Span,
Line = Line,
Column = Column,
};
}
}
}

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.
@@ -37,8 +37,7 @@ namespace Markdig.Extensions.SmartyPants
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
if (renderer is HtmlRenderer htmlRenderer)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlSmartyPantRenderer>())
{

View File

@@ -49,7 +49,7 @@ namespace Markdig.Extensions.SmartyPants
{
case '\'':
type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines
if (slice.PeekChar(1) == '\'')
if (slice.PeekChar() == '\'')
{
slice.NextChar();
type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines
@@ -95,9 +95,7 @@ namespace Markdig.Extensions.SmartyPants
// Skip char
c = slice.NextChar();
bool canOpen;
bool canClose;
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out bool canOpen, out bool canClose);
bool postProcess = false;
@@ -156,11 +154,9 @@ namespace Markdig.Extensions.SmartyPants
}
// Create the SmartyPant inline
int line;
int column;
var pant = new SmartyPant()
{
Span = {Start = processor.GetSourcePosition(startingPosition, out line, out column)},
Span = { Start = processor.GetSourcePosition(startingPosition, out int line, out int column) },
Line = line,
Column = column,
OpeningCharacter = openingChar,
@@ -195,96 +191,90 @@ namespace Markdig.Extensions.SmartyPants
return quotePants;
}
private readonly struct Opener
{
public readonly int Type;
public readonly int Index;
public Opener(int type, int index)
{
Type = type;
Index = index;
}
}
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline inline)
{
processor.Block.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
var pants = (ListSmartyPants) processor.ParserStates[Index];
// We only change quote into left or right quotes if we find proper balancing
var previousIndices = new int[3] {-1, -1, -1};
Stack<Opener> openers = new Stack<Opener>(4);
for (int i = 0; i < pants.Count; i++)
{
var quote = pants[i];
var quoteType = quote.Type;
int currentTypeIndex = -1;
SmartyPantType expectedLeftQuote = 0;
SmartyPantType expectedRightQuote = 0;
int type;
bool isLeft;
if (quote.Type == SmartyPantType.LeftQuote || quote.Type == SmartyPantType.RightQuote)
if (quoteType == SmartyPantType.LeftQuote || quoteType == SmartyPantType.RightQuote)
{
currentTypeIndex = 0;
expectedLeftQuote = SmartyPantType.LeftQuote;
expectedRightQuote = SmartyPantType.RightQuote;
type = 0;
isLeft = quoteType == SmartyPantType.LeftQuote;
}
else if (quote.Type == SmartyPantType.LeftDoubleQuote || quote.Type == SmartyPantType.RightDoubleQuote)
else if (quoteType == SmartyPantType.LeftDoubleQuote || quoteType == SmartyPantType.RightDoubleQuote)
{
currentTypeIndex = 1;
expectedLeftQuote = SmartyPantType.LeftDoubleQuote;
expectedRightQuote = SmartyPantType.RightDoubleQuote;
type = 1;
isLeft = quoteType == SmartyPantType.LeftDoubleQuote;
}
else if (quote.Type == SmartyPantType.LeftAngleQuote || quote.Type == SmartyPantType.RightAngleQuote)
else if (quoteType == SmartyPantType.LeftAngleQuote || quoteType == SmartyPantType.RightAngleQuote)
{
currentTypeIndex = 2;
expectedLeftQuote = SmartyPantType.LeftAngleQuote;
expectedRightQuote = SmartyPantType.RightAngleQuote;
}
if (currentTypeIndex < 0)
{
continue;
}
int previousIndex = previousIndices[currentTypeIndex];
var previousQuote = previousIndex >= 0 ? pants[previousIndex] : null;
if (previousQuote == null)
{
if (quote.Type == expectedLeftQuote)
{
previousIndices[currentTypeIndex] = i;
}
type = 2;
isLeft = quoteType == SmartyPantType.LeftAngleQuote;
}
else
{
if (quote.Type == expectedRightQuote)
{
// Replace all intermediate unmatched left or right SmartyPants to their literal equivalent
pants.RemoveAt(i);
i--;
for (int j = i; j > previousIndex; j--)
{
var toReplace = pants[j];
pants.RemoveAt(j);
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString())
{
Span = toReplace.Span,
Line = toReplace.Line,
Column = toReplace.Column,
});
i--;
}
quote.ReplaceBy(quote.AsLiteralInline());
continue;
}
// If we matched, we remove left/right quotes from the list
pants.RemoveAt(previousIndex);
previousIndices[currentTypeIndex] = -1;
}
else
if (isLeft)
{
openers.Push(new Opener(type, i));
}
else
{
bool found = false;
while (openers.Count > 0)
{
previousIndices[currentTypeIndex] = i;
Opener opener = openers.Pop();
var previousQuote = pants[opener.Index];
if (opener.Type == type)
{
found = true;
break;
}
else
{
previousQuote.ReplaceBy(previousQuote.AsLiteralInline());
}
}
if (!found)
{
quote.ReplaceBy(quote.AsLiteralInline());
}
}
}
// If we have any quotes lefts, replace them by there literal equivalent
foreach (var quote in pants)
foreach (var opener in openers)
{
quote.ReplaceBy(new LiteralInline(quote.ToString())
{
Span = quote.Span,
Line = quote.Line,
Column = quote.Column,
});
var quote = pants[opener.Index];
quote.ReplaceBy(quote.AsLiteralInline());
}
pants.Clear();
@@ -294,8 +284,7 @@ namespace Markdig.Extensions.SmartyPants
bool isFinalProcessing)
{
// Don't try to process anything if there are no dash
var quotePants = state.ParserStates[Index] as ListSmartyPants;
if (quotePants == null || !quotePants.HasDash)
if (!(state.ParserStates[Index] is ListSmartyPants quotePants) || !quotePants.HasDash)
{
return true;
}
@@ -309,10 +298,8 @@ namespace Markdig.Extensions.SmartyPants
{
var next = child.NextSibling;
if (child is LiteralInline)
if (child is LiteralInline literal)
{
var literal = (LiteralInline) child;
var startIndex = 0;
var indexOfDash = literal.Content.IndexOf("--", startIndex);
@@ -372,7 +359,7 @@ namespace Markdig.Extensions.SmartyPants
}
private class ListSmartyPants : List<SmartyPant>
private sealed class ListSmartyPants : List<SmartyPant>
{
public bool HasDash { get; set; }
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -138,7 +138,7 @@ namespace Markdig.Extensions.Tables
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
{
var lineStart = line.Start;
isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
isHeaderRow = line.PeekChar() == '=' || line.PeekChar(2) == '=';
hasRowSpan = false;
foreach (var columnSlice in columns)
{

View File

@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Tables
/// <summary>
/// Internal state used by the <see cref="GridTableParser"/>
/// </summary>
internal class GridTableState
internal sealed class GridTableState
{
public int Start { get; set; }

View File

@@ -41,7 +41,7 @@ namespace Markdig.Extensions.Tables
{
var width = Math.Round(tableColumnDefinition.Width*100)/100;
var widthValue = string.Format(CultureInfo.InvariantCulture, "{0:0.##}", width);
renderer.WriteLine($"<col style=\"width:{widthValue}%\">");
renderer.WriteLine($"<col style=\"width:{widthValue}%\" />");
}
}
@@ -135,4 +135,4 @@ namespace Markdig.Extensions.Tables
renderer.WriteLine("</table>");
}
}
}
}

View File

@@ -30,8 +30,7 @@ namespace Markdig.Extensions.Tables
/// <param name="options">The options.</param>
public PipeTableParser(LineBreakInlineParser lineBreakParser, PipeTableOptions options = null)
{
if (lineBreakParser == null) throw new ArgumentNullException(nameof(lineBreakParser));
this.lineBreakParser = lineBreakParser;
this.lineBreakParser = lineBreakParser ?? throw new ArgumentNullException(nameof(lineBreakParser));
OpeningCharacters = new[] { '|', '\n' };
Options = options ?? new PipeTableOptions();
}

View File

@@ -58,6 +58,11 @@ namespace Markdig.Extensions.Tables
var rowSpan = cell.RowSpan - 1;
while (rowSpan > 0)
{
if (i+rowSpan > (rows.Length-1))
{
return false;
}
rows[i + rowSpan] += cell.ColumnSpan;
rowSpan--;
}

View File

@@ -1,17 +0,0 @@
// 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.Helpers
{
/// <summary>
/// Helper class for defining Empty arrays.
/// </summary>
/// <typeparam name="T">Type of an element of the array</typeparam>
public static class ArrayHelper<T>
{
/// <summary>
/// An empty array.
/// </summary>
public static readonly T[] Empty = new T[0];
}
}

View File

@@ -2,41 +2,12 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
// The IsHighSurrogate, IsLowSurrogate and ConvertToUtf32 methods are copied from
// .Net Core source code which is under MIT license. They are copied here because
// they don't exist in `portable40-net40+sl5+win8+wp8+wpa81`, We probably should remove them
// once we dropped support for that target platform and use the official .Net methods.
//The MIT License(MIT)
//Copyright(c) .NET Foundation and Contributors
//All rights reserved.
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Diagnostics;
namespace Markdig.Helpers
{
/// <summary>
@@ -46,20 +17,20 @@ namespace Markdig.Helpers
{
public const int TabSize = 4;
public const char ZeroSafeChar = '\uFFFD';
public const char ReplacementChar = '\uFFFD';
public const string ZeroSafeString = "\uFFFD";
public const string ReplacementCharString = "\uFFFD";
private const char HighSurrogateStart = '\ud800';
private const char HighSurrogateEnd = '\udbff';
private const char LowSurrogateStart = '\udc00';
private const char LowSurrogateEnd = '\udfff';
// The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
private const int UnicodePlane01Start = 0x10000;
// We don't support LCDM
private static readonly Dictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
private static readonly Dictionary<char, int> romanMap = new Dictionary<char, int>(6) {
{ 'i', 1 }, { 'v', 5 }, { 'x', 10 },
{ 'I', 1 }, { 'V', 5 }, { 'X', 10 }
};
private static readonly char[] punctuationExceptions = { '', '-', '†', '‡' };
@@ -103,36 +74,35 @@ namespace Markdig.Helpers
}
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsRomanLetterPartial(char c)
{
// We don't support LCDM
return IsRomanLetterLowerPartial(c) || IsRomanLetterUpperPartial(c);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsRomanLetterLowerPartial(char c)
{
// We don't support LCDM
return c == 'i' || c == 'v' || c == 'x';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsRomanLetterUpperPartial(char c)
{
// We don't support LCDM
return c == 'I' || c == 'V' || c == 'X';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static int RomanToArabic(string text)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RomanToArabic(ReadOnlySpan<char> text)
{
int result = 0;
for (int i = 0; i < text.Length; i++)
{
var character = Char.ToUpperInvariant(text[i]);
var candidate = romanMap[character];
if (i + 1 < text.Length && candidate < romanMap[Char.ToUpperInvariant(text[i + 1])])
var candidate = romanMap[text[i]];
if ((uint)(i + 1) < text.Length && candidate < romanMap[text[i + 1]])
{
result -= candidate;
}
@@ -144,24 +114,26 @@ namespace Markdig.Helpers
return result;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int AddTab(int column)
{
return ((column + TabSize) / TabSize) * TabSize;
// return ((column + TabSize) / TabSize) * TabSize;
Debug.Assert(TabSize == 4, "Change the AddTab implementation if TabSize is no longer a power of 2");
return TabSize + (column & ~(TabSize - 1));
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAcrossTab(int column)
{
return (column & (TabSize - 1)) != 0;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Contains(this char[] charList, char c)
{
for (int i = 0; i < charList.Length; i++)
foreach (char ch in charList)
{
if (charList[i] == c)
if (ch == c)
{
return true;
}
@@ -169,31 +141,31 @@ namespace Markdig.Helpers
return false;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWhitespace(this char c)
{
// 2.1 Characters and lines
// A whitespace character is a space(U + 0020), tab(U + 0009), newline(U + 000A), line tabulation (U + 000B), form feed (U + 000C), or carriage return (U + 000D).
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
return c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r');
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsControl(this char c)
{
return c < ' ' || Char.IsControl(c);
return c < ' ' || char.IsControl(c);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsEscapableSymbol(this char c)
{
// char.IsSymbol also works with Unicode symbols that cannot be escaped based on the specification.
return (c > ' ' && c < '0') || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z' && c < 127) || c == '•';
}
//[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWhiteSpaceOrZero(this char c)
{
return IsWhitespace(c) || IsZero(c);
return IsZero(c) || IsWhitespace(c);
}
// Note that we are not considering the character & as a punctuation in HTML
@@ -250,19 +222,19 @@ namespace Markdig.Helpers
}
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsNewLine(this char c)
{
return c == '\n';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsZero(this char c)
{
return c == '\0';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsSpace(this char c)
{
// 2.1 Characters and lines
@@ -270,7 +242,7 @@ namespace Markdig.Helpers
return c == ' ';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsTab(this char c)
{
// 2.1 Characters and lines
@@ -278,13 +250,13 @@ namespace Markdig.Helpers
return c == '\t';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsSpaceOrTab(this char c)
{
return IsSpace(c) || IsTab(c);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static char EscapeInsecure(this char c)
{
// 2.3 Insecure characters
@@ -292,28 +264,28 @@ namespace Markdig.Helpers
return c == '\0' ? '\ufffd' : c;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAlphaUpper(this char c)
{
return c >= 'A' && c <= 'Z';
{
return (uint)(c - 'A') <= ('Z' - 'A');
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAlpha(this char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
{
return (uint)((c - 'A') & ~0x20) <= ('Z' - 'A');
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAlphaNumeric(this char c)
{
return IsAlpha(c) || IsDigit(c);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDigit(this char c)
{
return c >= '0' && c <= '9';
return (uint)(c - '0') <= ('9' - '0');
}
public static bool IsAsciiPunctuation(this char c)
@@ -359,44 +331,40 @@ namespace Markdig.Helpers
return false;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsEmailUsernameSpecialChar(char c)
{
return ".!#$%&'*+/=?^_`{|}~-+.~".IndexOf(c) >= 0;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsHighSurrogate(char c)
{
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
{
return IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsLowSurrogate(char c)
{
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd);
}
public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
{
if (!IsHighSurrogate(highSurrogate))
{
throw new ArgumentOutOfRangeException(nameof(highSurrogate), "Invalid high surrogate");
}
if (!IsLowSurrogate(lowSurrogate))
{
throw new ArgumentOutOfRangeException(nameof(lowSurrogate), "Invalid low surrogate");
}
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsInInclusiveRange(char c, char min, char max)
=> (uint)(c - min) <= (uint)(max - min);
public static IEnumerable<int> ToUtf32(string text)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsInInclusiveRange(int value, uint min, uint max)
=> ((uint)value - min) <= (max - min);
public static IEnumerable<int> ToUtf32(StringSlice text)
{
for (int i = 0; i < text.Length; i++)
for (int i = text.Start; i <= text.End; i++)
{
if (IsHighSurrogate(text[i]) && i < text.Length - 1 && IsLowSurrogate(text[i + 1]))
{
yield return ConvertToUtf32(text[i], text[i + 1]);
if (IsHighSurrogate(text[i]) && i < text.End && IsLowSurrogate(text[i + 1]))
{
Debug.Assert(char.IsSurrogatePair(text[i], text[i + 1]));
yield return char.ConvertToUtf32(text[i], text[i + 1]);
}
else
{
@@ -777,6 +745,24 @@ namespace Markdig.Helpers
c == 0x002128 || c == 0x002395 ||
c == 0x01D4A2 || c == 0x01D4BB ||
c == 0x01D546;
}
// Used by ListExtraItemParser to format numbers from 1 - 26
private static readonly string[] smallNumberStringCache = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
"20", "21", "22", "23", "24", "25", "26",
};
internal static string SmallNumberToString(int number)
{
string[] cache = smallNumberStringCache;
if ((uint)number < (uint)cache.Length)
{
return cache[number];
}
return number.ToString();
}
}
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -16,8 +17,8 @@ namespace Markdig.Helpers
public class CharacterMap<T> where T : class
{
private readonly T[] asciiMap;
private readonly Dictionary<char, T> nonAsciiMap;
private readonly bool[] isOpeningCharacter;
private readonly Dictionary<uint, T> nonAsciiMap;
private readonly BoolVector128 isOpeningCharacter;
/// <summary>
/// Initializes a new instance of the <see cref="CharacterMap{T}"/> class.
@@ -26,56 +27,41 @@ namespace Markdig.Helpers
/// <exception cref="System.ArgumentNullException"></exception>
public CharacterMap(IEnumerable<KeyValuePair<char, T>> maps)
{
if (maps == null) throw new ArgumentNullException(nameof(maps));
var charCounter = new Dictionary<char, int>();
if (maps == null) ThrowHelper.ArgumentNullException(nameof(maps));
var charSet = new HashSet<char>();
int maxChar = 0;
foreach (var map in maps)
{
var openingChar = map.Key;
charSet.Add(openingChar);
if (!charCounter.ContainsKey(openingChar))
if (openingChar < 128)
{
charCounter[openingChar] = 0;
}
charCounter[openingChar]++;
if (openingChar < 127 && openingChar > maxChar)
{
maxChar = openingChar;
}
else if (openingChar >= 127 && nonAsciiMap == null)
{
// Initialize only if with have an actual non-ASCII opening character
nonAsciiMap = new Dictionary<char, T>();
}
}
OpeningCharacters = charCounter.Keys.ToArray();
Array.Sort(OpeningCharacters);
asciiMap = new T[maxChar + 1];
isOpeningCharacter = new bool[maxChar + 1];
foreach (var state in maps)
{
var openingChar = state.Key;
T stateByChar;
if (openingChar < 127)
{
stateByChar = asciiMap[openingChar];
if (stateByChar == null)
{
asciiMap[openingChar] = state.Value;
}
isOpeningCharacter[openingChar] = true;
maxChar = Math.Max(maxChar, openingChar);
}
else
{
if (!nonAsciiMap.TryGetValue(openingChar, out stateByChar))
{
nonAsciiMap[openingChar] = state.Value;
}
nonAsciiMap ??= new Dictionary<uint, T>();
}
}
OpeningCharacters = charSet.ToArray();
Array.Sort(OpeningCharacters);
asciiMap = new T[maxChar + 1];
foreach (var state in maps)
{
char openingChar = state.Key;
if (openingChar < 128)
{
asciiMap[openingChar] ??= state.Value;
isOpeningCharacter.Set(openingChar);
}
else if (!nonAsciiMap.ContainsKey(openingChar))
{
nonAsciiMap[openingChar] = state.Value;
}
}
}
@@ -90,24 +76,26 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="openingChar">The opening character.</param>
/// <returns>A list of parsers valid for the specified opening character or null if no parsers registered.</returns>
public T this[char openingChar]
public T this[uint openingChar]
{
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
T map = null;
if (openingChar < asciiMap.Length)
T[] asciiMap = this.asciiMap;
if (openingChar < (uint)asciiMap.Length)
{
map = asciiMap[openingChar];
return asciiMap[openingChar];
}
else if (nonAsciiMap != null)
else
{
nonAsciiMap.TryGetValue(openingChar, out map);
T map = null;
nonAsciiMap?.TryGetValue(openingChar, out map);
return map;
}
return map;
}
}
/// <summary>
/// Searches for an opening character from a registered parser in the specified string.
/// </summary>
@@ -117,47 +105,112 @@ namespace Markdig.Helpers
/// <returns>Index position within the string of the first opening character found in the specified text; if not found, returns -1</returns>
public int IndexOfOpeningCharacter(string text, int start, int end)
{
var maxChar = isOpeningCharacter.Length;
#if SUPPORT_UNSAFE
unsafe
#endif
if (nonAsciiMap is null)
{
#if SUPPORT_FIXED_STRING
fixed (char* pText = text)
#else
var pText = text;
#endif
#if SUPPORT_UNSAFE
fixed (bool* openingChars = isOpeningCharacter)
#else
var openingChars = isOpeningCharacter;
#endif
#if NETCOREAPP3_1
ref char textRef = ref Unsafe.AsRef(in text.GetPinnableReference());
for (; start <= end; start++)
{
if (nonAsciiMap == null)
if (IntPtr.Size == 4)
{
for (int i = start; i <= end; i++)
uint c = Unsafe.Add(ref textRef, start);
if (c < 128 && isOpeningCharacter[c])
{
var c = pText[i];
if (c < maxChar && openingChars[c])
{
return i;
}
return start;
}
}
else
{
ulong c = Unsafe.Add(ref textRef, start);
if (c < 128 && isOpeningCharacter[c])
{
return start;
}
}
}
#else
unsafe
{
fixed (char* pText = text)
{
for (int i = start; i <= end; i++)
{
var c = pText[i];
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
char c = pText[i];
if (c < 128 && isOpeningCharacter[c])
{
return i;
}
}
}
}
#endif
return -1;
}
else
{
return IndexOfOpeningCharacterNonAscii(text, start, end);
}
}
private int IndexOfOpeningCharacterNonAscii(string text, int start, int end)
{
#if NETCOREAPP3_1
ref char textRef = ref Unsafe.AsRef(in text.GetPinnableReference());
for (int i = start; i <= end; i++)
{
char c = Unsafe.Add(ref textRef, i);
if (c < 128 ? isOpeningCharacter[c] : nonAsciiMap.ContainsKey(c))
{
return i;
}
}
#else
unsafe
{
fixed (char* pText = text)
{
for (int i = start; i <= end; i++)
{
char c = pText[i];
if (c < 128 ? isOpeningCharacter[c] : nonAsciiMap.ContainsKey(c))
{
return i;
}
}
}
}
#endif
return -1;
}
}
internal unsafe struct BoolVector128
{
private fixed bool values[128];
public void Set(char c)
{
Debug.Assert(c < 128);
values[c] = true;
}
public readonly bool this[uint c]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Debug.Assert(c < 128);
return values[c];
}
}
public readonly bool this[ulong c]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Debug.Assert(c < 128 && IntPtr.Size == 8);
return values[c];
}
}
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
/*
@@ -25,10 +26,8 @@ namespace Markdig.Helpers
/// <para>Something between a Trie and a full Radix tree, but stored linearly in memory</para>
/// </summary>
/// <typeparam name="TValue">The value associated with the key</typeparam>
internal class CompactPrefixTree<TValue>
//#if !LEGACY
// : IReadOnlyDictionary<string, TValue>, IReadOnlyList<KeyValuePair<string, TValue>>
//#endif
[ExcludeFromCodeCoverage]
internal sealed class CompactPrefixTree<TValue> : IReadOnlyDictionary<string, TValue>, IReadOnlyList<KeyValuePair<string, TValue>>
{
/// <summary>
/// Used internally to control behavior of insertion
@@ -118,7 +117,7 @@ namespace Markdig.Helpers
}
}
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureTreeCapacity(int min)
{
if (_tree.Length < min)
@@ -167,7 +166,7 @@ namespace Markdig.Helpers
}
}
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacity(int min)
{
// Expansion logic as in System.Collections.Generic.List<T>
@@ -225,7 +224,7 @@ namespace Markdig.Helpers
}
}
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureChildrenCapacity(int min)
{
if (_children.Length < min)
@@ -252,7 +251,7 @@ namespace Markdig.Helpers
// Inspired by Markdig's CharacterMap
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryGetRoot(char rootChar, out int rootNodeIndex)
{
if (rootChar < 128)
@@ -336,7 +335,7 @@ namespace Markdig.Helpers
/// <returns>The key/value pair of the element at the specified index</returns>
public KeyValuePair<string, TValue> this[int index]
{
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
if ((uint)index >= (uint)Count) ThrowHelper.ThrowIndexOutOfRangeException();
@@ -353,7 +352,7 @@ namespace Markdig.Helpers
{
get
{
if (TryMatchExact(key, out KeyValuePair<string, TValue> match))
if (TryMatchExact(key.AsSpan(), out KeyValuePair<string, TValue> match))
return match.Value;
throw new KeyNotFoundException(key);
}
@@ -365,7 +364,6 @@ namespace Markdig.Helpers
}
} // Get, Set
#if NETCORE
/// <summary>
/// Gets the value associated with the specified key
/// </summary>
@@ -380,7 +378,6 @@ namespace Markdig.Helpers
throw new KeyNotFoundException(key.ToString());
}
} // Get only
#endif
#endregion this[] accessors
@@ -711,50 +708,6 @@ namespace Markdig.Helpers
#region TryMatch longest
/// <summary>
/// Tries to find the longest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="offset">Index of the character at which to start searching</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
public bool TryMatchLongest(string text, int offset, out KeyValuePair<string, TValue> match)
{
#if NETCORE
return TryMatchLongest(text.AsSpan(offset), out match);
#else
return TryMatchLongest(text, offset, text.Length - offset, out match);
#endif
}
/// <summary>
/// Tries to find the longest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
public bool TryMatchLongest(string text, out KeyValuePair<string, TValue> match)
{
if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
#if NETCORE
return TryMatchLongest(text.AsSpan(), out match);
#else
return TryMatchLongest(text, 0, text.Length, out match);
#endif
}
/// <summary>
/// Tries to find the longest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="offset">The offset in text at which to start looking for the prefix</param>
/// <param name="length">The longest prefix allowed to match</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
#if NETCORE
public bool TryMatchLongest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
=> TryMatchLongest(text.AsSpan(offset, length), out match);
/// <summary>
/// Tries to find the longest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
@@ -766,20 +719,6 @@ namespace Markdig.Helpers
match = default;
if (text.Length == 0 || !TryGetRoot(text[0], out int nodeIndex))
return false;
#else
public bool TryMatchLongest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
{
int limit = offset + length;
if (text == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
if (offset < 0 || length < 0 || text.Length < limit)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offsetLength, ExceptionReason.InvalidOffsetLength);
match = default;
if (length == 0 || !TryGetRoot(text[offset], out int nodeIndex))
return false;
#endif
int matchIndex = -1;
int depth = 1;
@@ -788,11 +727,7 @@ namespace Markdig.Helpers
if (node.ChildChar == 0) goto LeafNodeFound;
if (node.MatchIndex != -1) matchIndex = node.MatchIndex;
#if NETCORE
for (int i = 1; i < text.Length; i++)
#else
for (int i = offset + 1; i < limit; i++)
#endif
{
char c = text[i];
@@ -824,7 +759,6 @@ namespace Markdig.Helpers
LeafNodeFound:;
ref KeyValuePair<string, TValue> possibleMatch = ref _matches[node.MatchIndex];
#if NETCORE
if (possibleMatch.Key.Length <= text.Length)
{
// Check that the rest of the strings match
@@ -833,18 +767,6 @@ namespace Markdig.Helpers
matchIndex = node.MatchIndex;
}
}
#else
if (possibleMatch.Key.Length <= length)
{
// Check that the rest of the strings match
for (int i = offset + depth, j = depth; j < possibleMatch.Key.Length; i++, j++)
{
if (text[i] != possibleMatch.Key[j])
goto Return;
}
matchIndex = node.MatchIndex;
}
#endif
Return:;
if (matchIndex != -1)
@@ -859,50 +781,6 @@ namespace Markdig.Helpers
#region TryMatch exact
/// <summary>
/// Tries to find a suffix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly (text.Length - offset) characters long
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="offset">Index of the character at which to start searching</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
public bool TryMatchExact(string text, int offset, out KeyValuePair<string, TValue> match)
{
#if NETCORE
return TryMatchExact(text.AsSpan(offset), out match);
#else
return TryMatchExact(text, offset, text.Length - offset, out match);
#endif
}
/// <summary>
/// Tries to find a prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly text.Length characters long
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
public bool TryMatchExact(string text, out KeyValuePair<string, TValue> match)
{
if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
#if NETCORE
return TryMatchExact(text.AsSpan(), out match);
#else
return TryMatchExact(text, 0, text.Length, out match);
#endif
}
/// <summary>
/// Tries to find a prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly length characters long
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="offset">The offset in text at which to start looking for the prefix</param>
/// <param name="length">The longest prefix allowed to match</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
#if NETCORE
public bool TryMatchExact(string text, int offset, int length, out KeyValuePair<string, TValue> match)
=> TryMatchExact(text.AsSpan(offset, length), out match);
/// <summary>
/// Tries to find a prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly text.Length characters long
/// </summary>
@@ -914,39 +792,18 @@ namespace Markdig.Helpers
match = default;
if (text.Length == 0 || !TryGetRoot(text[0], out int nodeIndex))
return false;
#else
public bool TryMatchExact(string text, int offset, int length, out KeyValuePair<string, TValue> match)
{
int limit = offset + length;
if (text == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
if (offset < 0 || length < 0 || text.Length < limit)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offsetLength, ExceptionReason.InvalidOffsetLength);
match = default;
if (length == 0 || !TryGetRoot(text[offset], out int nodeIndex))
return false;
#endif
int depth = 1;
ref Node node = ref _tree[nodeIndex];
if (node.ChildChar == 0) goto LeafNodeFound;
#if NETCORE
if (node.MatchIndex != -1 && text.Length == 1)
#else
if (node.MatchIndex != -1 && length == 1)
#endif
{
match = _matches[node.MatchIndex];
return true;
}
#if NETCORE
for (int i = 1; i < text.Length; i++)
#else
for (int i = offset + 1; i < limit; i++)
#endif
{
char c = text[i];
@@ -975,81 +832,20 @@ namespace Markdig.Helpers
if (node.MatchIndex == -1) return false;
match = _matches[node.MatchIndex];
#if NETCORE
Debug.Assert(match.Key.Length == text.Length);
#else
Debug.Assert(match.Key.Length == length);
#endif
return true;
LeafNodeFound:;
match = _matches[node.MatchIndex];
#if NETCORE
return match.Key.Length == text.Length &&
text.Slice(depth).Equals(match.Key.AsSpan(depth), StringComparison.Ordinal);
#else
if (match.Key.Length == length)
{
// Check that the rest of the strings match
for (int i = offset + depth, j = depth; j < match.Key.Length; i++, j++)
{
if (text[i] != match.Key[j])
return false;
}
return true;
}
return false;
#endif
}
#endregion TryMatch exact
#region TryMatch shortest
/// <summary>
/// Tries to find the shortest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="offset">Index of the character at which to start searching</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
public bool TryMatchShortest(string text, int offset, out KeyValuePair<string, TValue> match)
{
#if NETCORE
return TryMatchShortest(text.AsSpan(offset), out match);
#else
return TryMatchShortest(text, offset, text.Length - offset, out match);
#endif
}
/// <summary>
/// Tries to find the shortest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
public bool TryMatchShortest(string text, out KeyValuePair<string, TValue> match)
{
if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
#if NETCORE
return TryMatchShortest(text.AsSpan(), out match);
#else
return TryMatchShortest(text, 0, text.Length, out match);
#endif
}
/// <summary>
/// Tries to find the shortest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
/// <param name="text">The text in which to search for the prefix</param>
/// <param name="offset">The offset in text at which to start looking for the prefix</param>
/// <param name="length">The longest prefix allowed to match</param>
/// <param name="match">The found prefix and the corresponding value</param>
/// <returns>True if a match was found, false otherwise</returns>
#if NETCORE
public bool TryMatchShortest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
=> TryMatchShortest(text.AsSpan(offset, length), out match);
/// <summary>
/// Tries to find the shortest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
/// </summary>
@@ -1061,20 +857,7 @@ namespace Markdig.Helpers
match = default;
if (text.Length == 0 || !TryGetRoot(text[0], out int nodeIndex))
return false;
#else
public bool TryMatchShortest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
{
int limit = offset + length;
if (text == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
if (offset < 0 || length < 0 || text.Length < limit)
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offsetLength, ExceptionReason.InvalidOffsetLength);
match = default;
if (length == 0 || !TryGetRoot(text[offset], out int nodeIndex))
return false;
#endif
ref Node node = ref _tree[nodeIndex];
if (node.MatchIndex != -1)
{
@@ -1082,11 +865,7 @@ namespace Markdig.Helpers
return true;
}
#if NETCORE
for (int i = 1; i < text.Length; i++)
#else
for (int i = offset + 1; i < limit; i++)
#endif
{
char c = text[i];
@@ -1129,7 +908,7 @@ namespace Markdig.Helpers
/// <param name="key">The key to locate in this <see cref="CompactPrefixTree{TValue}"/></param>
/// <returns>True if the key is contained in this PrefixTree, false otherwise.</returns>
public bool ContainsKey(string key)
=> TryMatchExact(key, out _);
=> TryMatchExact(key.AsSpan(), out _);
/// <summary>
/// Gets the value associated with the specified key
@@ -1139,7 +918,7 @@ namespace Markdig.Helpers
/// <returns>True if the key is contained in this PrefixTree, false otherwise.</returns>
public bool TryGetValue(string key, out TValue value)
{
bool ret = TryMatchExact(key, out KeyValuePair<string, TValue> match);
bool ret = TryMatchExact(key.AsSpan(), out KeyValuePair<string, TValue> match);
value = match.Value;
return ret;
}
@@ -1173,9 +952,8 @@ namespace Markdig.Helpers
/// </summary>
/// <returns></returns>
public IEnumerator<KeyValuePair<string, TValue>> GetEnumerator() => new Enumerator(_matches);
//#if !LEGACY
//IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_matches);
//#endif
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_matches);
/// <summary>
/// Enumerates the elements of a <see cref="CompactPrefixTree{TValue}"/>

View File

@@ -0,0 +1,86 @@
using System.Threading;
namespace Markdig.Helpers
{
internal sealed class CustomArrayPool<T>
{
private sealed class Bucket
{
private readonly T[][] _buffers;
private int _index;
private int _lock;
public Bucket(int numberOfBuffers)
{
_buffers = new T[numberOfBuffers][];
}
public T[] Rent()
{
T[][] buffers = _buffers;
T[] buffer = null;
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
{
int index = _index;
if ((uint)index < (uint)buffers.Length)
{
buffer = buffers[index];
buffers[index] = null;
_index = index + 1;
}
Interlocked.Decrement(ref _lock);
}
return buffer;
}
public void Return(T[] array)
{
var buffers = _buffers;
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
{
int index = _index - 1;
if ((uint)index < (uint)buffers.Length)
{
buffers[index] = array;
_index = index;
}
Interlocked.Decrement(ref _lock);
}
}
}
private readonly Bucket _bucket4, _bucket8, _bucket16, _bucket32;
public CustomArrayPool(int size4, int size8, int size16, int size32)
{
_bucket4 = new Bucket(size4);
_bucket8 = new Bucket(size8);
_bucket16 = new Bucket(size16);
_bucket32 = new Bucket(size32);
}
private Bucket SelectBucket(int length)
{
switch (length)
{
case 4: return _bucket4;
case 8: return _bucket8;
case 16: return _bucket16;
case 32: return _bucket32;
default: return null;
}
}
public T[] Rent(int length)
{
return SelectBucket(length)?.Rent() ?? new T[length];
}
public void Return(T[] array)
{
SelectBucket(array.Length)?.Return(array);
}
}
}

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.
@@ -33,6 +33,7 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Markdig.Helpers
{
@@ -46,11 +47,10 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="entity">The entity without <c>&amp;</c> and <c>;</c> symbols, for example, <c>copy</c>.</param>
/// <returns>The unicode character set or <c>null</c> if the entity was not recognized.</returns>
public static string DecodeEntity(string entity)
public static string DecodeEntity(ReadOnlySpan<char> entity)
{
string result;
if (EntityMap.TryGetValue(entity, out result))
return result;
if (EntityMap.TryMatchExact(entity, out KeyValuePair<string, string> result))
return result.Value;
return null;
}
@@ -61,25 +61,49 @@ namespace Markdig.Helpers
/// <returns>The unicode character set or <c>null</c> if the entity was not recognized.</returns>
public static string DecodeEntity(int utf32)
{
if (utf32 < 0 || utf32 > 1114111 || (utf32 >= 55296 && utf32 <= 57343))
return null;
if (!CharHelper.IsInInclusiveRange(utf32, 1, 1114111) || CharHelper.IsInInclusiveRange(utf32, 55296, 57343))
return CharHelper.ReplacementCharString;
if (utf32 < 65536)
return char.ToString((char)utf32);
utf32 -= 65536;
return new string(new char[]
return new string(
#if NETCORE
stackalloc
#else
new
#endif
char[]
{
(char)(utf32 / 1024 + 55296),
(char)(utf32 % 1024 + 56320)
(char)((uint)utf32 / 1024 + 55296),
(char)((uint)utf32 % 1024 + 56320)
});
}
#region [ EntityMap ]
public static void DecodeEntity(int utf32, StringBuilder sb)
{
if (!CharHelper.IsInInclusiveRange(utf32, 1, 1114111) || CharHelper.IsInInclusiveRange(utf32, 55296, 57343))
{
sb.Append(CharHelper.ReplacementChar);
}
else if (utf32 < 65536)
{
sb.Append((char)utf32);
}
else
{
utf32 -= 65536;
sb.Append((char)((uint)utf32 / 1024 + 55296));
sb.Append((char)((uint)utf32 % 1024 + 56320));
}
}
#region [ EntityMap ]
/// <summary>
/// Source: http://www.w3.org/html/wg/drafts/html/master/syntax.html#named-character-references
/// </summary>
private static readonly Dictionary<string, string> EntityMap = new Dictionary<string, string>(2125, StringComparer.Ordinal)
private static readonly CompactPrefixTree<string> EntityMap = new CompactPrefixTree<string>(2125, 3385, 3510)
{
{ "Aacute", "\u00C1" },
{ "aacute", "\u00E1" },
@@ -2207,6 +2231,6 @@ namespace Markdig.Helpers
{ "zwj", "\u200D" },
{ "zwnj", "\u200C" }
};
#endregion
#endregion
}
}

View File

@@ -51,7 +51,7 @@ namespace Markdig.Helpers
public static bool TryParseHtmlTag(ref StringSlice text, StringBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (builder == null) ThrowHelper.ArgumentNullException(nameof(builder));
var c = text.CurrentChar;
if (c != '<')
{
@@ -373,7 +373,7 @@ namespace Markdig.Helpers
}
builder.Append('-');
builder.Append('-');
if (text.PeekChar(1) == '>')
if (text.PeekChar() == '>')
{
return false;
}
@@ -450,11 +450,7 @@ namespace Markdig.Helpers
while ((searchPos = text.IndexOfAny(search, searchPos)) != -1)
{
if (sb == null)
{
sb = StringBuilderCache.Local();
sb.Length = 0;
}
sb ??= StringBuilderCache.Local();
c = text[searchPos];
if (removeBackSlash && c == '\\')
{
@@ -472,10 +468,7 @@ namespace Markdig.Helpers
}
else if (c == '&')
{
int entityNameStart;
int entityNameLength;
int numericEntity;
var match = ScanEntity(new StringSlice(text, searchPos, text.Length - 1), out numericEntity, out entityNameStart, out entityNameLength);
var match = ScanEntity(new StringSlice(text, searchPos, text.Length - 1), out int numericEntity, out int entityNameStart, out int entityNameLength);
if (match == 0)
{
searchPos++;
@@ -486,8 +479,7 @@ namespace Markdig.Helpers
if (entityNameLength > 0)
{
var namedEntity = new StringSlice(text, entityNameStart, entityNameStart + entityNameLength - 1);
var decoded = EntityHelper.DecodeEntity(namedEntity.ToString());
var decoded = EntityHelper.DecodeEntity(text.AsSpan(entityNameStart, entityNameLength));
if (decoded != null)
{
sb.Append(text, lastPos, searchPos - match - lastPos);
@@ -498,36 +490,18 @@ namespace Markdig.Helpers
else if (numericEntity >= 0)
{
sb.Append(text, lastPos, searchPos - match - lastPos);
if (numericEntity == 0)
{
sb.Append('\0'.EscapeInsecure());
}
else
{
var decoded = EntityHelper.DecodeEntity(numericEntity);
if (decoded != null)
{
sb.Append(decoded);
}
else
{
sb.Append('\uFFFD');
}
}
EntityHelper.DecodeEntity(numericEntity, sb);
lastPos = searchPos;
}
}
}
}
if (sb == null)
if (sb == null || lastPos == 0)
return text;
sb.Append(text, lastPos, text.Length - lastPos);
var result = sb.ToString();
sb.Length = 0;
return result;
return sb.GetStringAndReset();
}
/// <summary>

View File

@@ -1,10 +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.IO;
using System.Text;
namespace Markdig.Helpers
{
@@ -13,17 +12,19 @@ namespace Markdig.Helpers
/// </summary>
public struct LineReader
{
private readonly string text;
private readonly string _text;
/// <summary>
/// Initializes a new instance of the <see cref="LineReader"/> class.
/// </summary>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentOutOfRangeException">bufferSize cannot be &lt;= 0</exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException">bufferSize cannot be &lt;= 0</exception>
public LineReader(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
this.text = text;
if (text is null)
ThrowHelper.ArgumentNullException_text();
_text = text;
SourcePosition = 0;
}
@@ -36,36 +37,31 @@ namespace Markdig.Helpers
/// Reads a new line from the underlying <see cref="TextReader"/> and update the <see cref="SourcePosition"/> for the next line.
/// </summary>
/// <returns>A new line or null if the end of <see cref="TextReader"/> has been reached</returns>
public StringSlice? ReadLine()
public StringSlice ReadLine()
{
if (SourcePosition >= text.Length)
{
return null;
}
string text = _text;
int sourcePosition = SourcePosition;
var startPosition = SourcePosition;
var position = SourcePosition;
var slice = new StringSlice(text, startPosition, startPosition);
for (;position < text.Length; position++)
for (int i = sourcePosition; i < text.Length; i++)
{
var c = text[position];
char c = text[i];
if (c == '\r' || c == '\n')
{
slice.End = position - 1;
if (c == '\r' && position + 1 < text.Length && text[position + 1] == '\n')
{
position++;
}
position++;
SourcePosition = position;
var slice = new StringSlice(text, sourcePosition, i - 1);
if (c == '\r' && (uint)(i + 1) < (uint)text.Length && text[i + 1] == '\n')
i++;
SourcePosition = i + 1;
return slice;
}
}
slice.End = position - 1;
SourcePosition = position;
if (sourcePosition >= text.Length)
return default;
return slice;
SourcePosition = int.MaxValue;
return new StringSlice(text, sourcePosition, text.Length - 1);
}
}
}
}

View File

@@ -101,18 +101,16 @@ namespace Markdig.Helpers
var headingBuffer = StringBuilderCache.Local();
for (int i = 0; i < headingText.Length; i++)
{
var c = char.ToLowerInvariant(headingText[i]);
var c = headingText[i];
if (char.IsLetterOrDigit(c) || c == ' ' || c == '-' || c == '_')
{
headingBuffer.Append(c == ' ' ? '-' : c);
headingBuffer.Append(c == ' ' ? '-' : char.ToLowerInvariant(c));
}
}
var result = headingBuffer.ToString();
headingBuffer.Length = 0;
return result;
return headingBuffer.GetStringAndReset();
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsReservedPunctuation(char c)
{
return c == '_' || c == '-' || c == '.';
@@ -427,7 +425,6 @@ namespace Markdig.Helpers
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
buffer.Length = 0;
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
// a sequence of zero or more characters between straight single-quote characters ('), including a ' character only if it is backslash-escaped, or
@@ -519,7 +516,6 @@ namespace Markdig.Helpers
{
bool isValid = false;
var buffer = StringBuilderCache.Local();
buffer.Length = 0;
var c = text.CurrentChar;
@@ -597,20 +593,23 @@ namespace Markdig.Helpers
}
}
if (hasEscape && !c.IsAsciiPunctuation())
if (!isAutoLink)
{
buffer.Append('\\');
}
if (hasEscape && !c.IsAsciiPunctuation())
{
buffer.Append('\\');
}
// If we have an escape
if (c == '\\')
{
hasEscape = true;
c = text.NextChar();
continue;
}
// If we have an escape
if (c == '\\')
{
hasEscape = true;
c = text.NextChar();
continue;
}
hasEscape = false;
hasEscape = false;
}
if (IsEndOfUri(c, isAutoLink))
{
@@ -622,10 +621,7 @@ namespace Markdig.Helpers
{
if (c == '&')
{
int entityNameStart;
int entityNameLength;
int entityValue;
if (HtmlHelper.ScanEntity(text, out entityValue, out entityNameStart, out entityNameLength) > 0)
if (HtmlHelper.ScanEntity(text, out _, out _, out _) > 0)
{
isValid = true;
break;
@@ -654,14 +650,14 @@ namespace Markdig.Helpers
return isValid;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsTrailingUrlStopCharacter(char c)
{
// Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not be considered part of the autolink, though they may be included in the interior of the link:
return c == '?' || c == '!' || c == '.' || c == ',' || c == ':' || c == '*' || c == '*' || c == '_' || c == '~';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsEndOfUri(char c, bool isAutoLink)
{
return c == '\0' || c.IsSpaceOrTab() || c.IsControl() || (isAutoLink && c == '<'); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)

View File

@@ -1,15 +0,0 @@
// 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.Runtime.CompilerServices;
namespace Markdig.Helpers
{
/// <summary>
/// Internal helper to allow to declare a method using AggressiveInlining without being .NET 4.0+
/// </summary>
internal static class MethodImplOptionPortable
{
public const MethodImplOptions AggressiveInlining = (MethodImplOptions)256;
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -57,7 +57,7 @@ namespace Markdig.Helpers
/// <exception cref="System.ArgumentNullException">if instance is null</exception>
public void Release(T instance)
{
if (instance == null) throw new ArgumentNullException(nameof(instance));
if (instance == null) ThrowHelper.ArgumentNullException(nameof(instance));
Reset(instance);
lock (builders)
{

View File

@@ -22,85 +22,85 @@ namespace Markdig.Helpers
{
}
public bool InsertBefore<TElement>(T element) where TElement : T
public bool InsertBefore<TItem>(T item) where TItem : T
{
if (element == null) throw new ArgumentNullException(nameof(element));
if (item == null) ThrowHelper.ArgumentNullException_item();
for (int i = 0; i < Count; i++)
{
if (this[i] is TElement)
if (this[i] is TItem)
{
Insert(i, element);
Insert(i, item);
return true;
}
}
return false;
}
public TElement Find<TElement>() where TElement : T
public TItem Find<TItem>() where TItem : T
{
for (int i = 0; i < Count; i++)
{
if (this[i] is TElement)
if (this[i] is TItem)
{
return (TElement)this[i];
return (TItem)this[i];
}
}
return default;
}
public bool TryFind<TElement>(out TElement element) where TElement : T
public bool TryFind<TItem>(out TItem item) where TItem : T
{
element = Find<TElement>();
return element != null;
item = Find<TItem>();
return item != null;
}
public TElement FindExact<TElement>() where TElement : T
public TItem FindExact<TItem>() where TItem : T
{
for (int i = 0; i < Count; i++)
{
if (this[i].GetType() == typeof(TElement))
if (this[i].GetType() == typeof(TItem))
{
return (TElement)this[i];
return (TItem)this[i];
}
}
return default;
}
public void AddIfNotAlready<TElement>() where TElement : class, T, new()
public void AddIfNotAlready<TItem>() where TItem : class, T, new()
{
if (!Contains<TElement>())
if (!Contains<TItem>())
{
Add(new TElement());
Add(new TItem());
}
}
public void AddIfNotAlready<TElement>(TElement telement) where TElement : T
public void AddIfNotAlready<TItem>(TItem item) where TItem : T
{
if (!Contains<TElement>())
if (!Contains<TItem>())
{
Add(telement);
Add(item);
}
}
public bool InsertAfter<TElement>(T element) where TElement : T
public bool InsertAfter<TItem>(T item) where TItem : T
{
if (element == null) throw new ArgumentNullException(nameof(element));
if (item == null) ThrowHelper.ArgumentNullException_item();
for (int i = 0; i < Count; i++)
{
if (this[i] is TElement)
if (this[i] is TItem)
{
Insert(i + 1, element);
Insert(i + 1, item);
return true;
}
}
return false;
}
public bool Contains<TElement>() where TElement : T
public bool Contains<TItem>() where TItem : T
{
for (int i = 0; i < Count; i++)
{
if (this[i] is TElement)
if (this[i] is TItem)
{
return true;
}
@@ -109,16 +109,16 @@ namespace Markdig.Helpers
}
/// <summary>
/// Replaces <typeparamref name="TElement"/> with <paramref name="replacement"/>.
/// Replaces <typeparamref name="TItem"/> with <paramref name="replacement"/>.
/// </summary>
/// <typeparam name="TElement">Element type to find in the list</typeparam>
/// <param name="replacement">Object to replace this element with</param>
/// <typeparam name="TItem">Item type to find in the list</typeparam>
/// <param name="replacement">Object to replace this item with</param>
/// <returns><c>true</c> if a replacement was made; otherwise <c>false</c>.</returns>
public bool Replace<TElement>(T replacement) where TElement : T
public bool Replace<TItem>(T replacement) where TItem : T
{
for (var i = 0; i < Count; i++)
{
if (this[i] is TElement)
if (this[i] is TItem)
{
RemoveAt(i);
Insert(i, replacement);
@@ -129,28 +129,28 @@ namespace Markdig.Helpers
}
/// <summary>
/// Replaces <typeparamref name="TElement"/> with <paramref name="newElement"/> or adds <paramref name="newElement"/>.
/// Replaces <typeparamref name="TItem"/> with <paramref name="newItem"/> or adds <paramref name="newItem"/>.
/// </summary>
/// <typeparam name="TElement">Element type to find in the list</typeparam>
/// <param name="newElement">Object to add/replace the found element with</param>
/// <typeparam name="TItem">Item type to find in the list</typeparam>
/// <param name="newItem">Object to add/replace the found item with</param>
/// <returns><c>true</c> if a replacement was made; otherwise <c>false</c>.</returns>
public bool ReplaceOrAdd<TElement>(T newElement) where TElement : T
public bool ReplaceOrAdd<TItem>(T newItem) where TItem : T
{
if (Replace<TElement>(newElement))
if (Replace<TItem>(newItem))
return true;
Add(newElement);
Add(newItem);
return false;
}
/// <summary>
/// Removes the first occurrence of <typeparamref name="TElement"/>
/// Removes the first occurrence of <typeparamref name="TItem"/>
/// </summary>
public bool TryRemove<TElement>() where TElement : T
public bool TryRemove<TItem>() where TItem : T
{
for (int i = 0; i < Count; i++)
{
if (this[i] is TElement)
if (this[i] is TItem)
{
RemoveAt(i);
return true;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -6,11 +6,7 @@ using System.Text;
namespace Markdig.Helpers
{
/// <summary>
/// An implementation of <see cref="ObjectCache{T}"/> for <see cref="StringBuilder"/>
/// </summary>
/// <seealso cref="Markdig.Helpers.ObjectCache{StringBuilder}" />
public class StringBuilderCache : DefaultObjectCache<StringBuilder>
public static class StringBuilderCache
{
/// <summary>
/// A StringBuilder that can be used locally in a method body only.
@@ -31,13 +27,5 @@ namespace Markdig.Helpers
}
return sb;
}
protected override void Reset(StringBuilder instance)
{
if (instance.Length > 0)
{
instance.Length = 0;
}
}
}
}

View File

@@ -6,7 +6,7 @@ using System.Text;
namespace Markdig.Helpers
{
/// <summary>
/// Extensions for StringBuilder with <see cref="StringSlice"/>
/// Extensions for StringBuilder
/// </summary>
public static class StringBuilderExtensions
{
@@ -19,5 +19,12 @@ namespace Markdig.Helpers
{
return builder.Append(slice.Text, slice.Start, slice.Length);
}
internal static string GetStringAndReset(this StringBuilder builder)
{
string text = builder.ToString();
builder.Length = 0;
return text;
}
}
}

View File

@@ -77,7 +77,7 @@ namespace Markdig.Helpers
return line.Slice;
}
public override string ToString()
public readonly override string ToString()
{
return Slice.ToString();
}

View File

@@ -7,24 +7,27 @@ using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Markdig.Extensions.Tables;
namespace Markdig.Helpers
{
/// <summary>
/// A group of <see cref="StringLine"/>.
/// </summary>
/// <seealso cref="System.Collections.IEnumerable" />
/// <seealso cref="IEnumerable" />
public struct StringLineGroup : IEnumerable
{
// Feel free to change these numbers if you see a positive change
private static readonly CustomArrayPool<StringLine> _pool
= new CustomArrayPool<StringLine>(512, 386, 128, 64);
/// <summary>
/// Initializes a new instance of the <see cref="StringLineGroup"/> class.
/// </summary>
/// <param name="capacity"></param>
public StringLineGroup(int capacity)
public StringLineGroup(int capacity, bool willRelease = false)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
Lines = new StringLine[capacity];
if (capacity <= 0) ThrowHelper.ArgumentOutOfRangeException(nameof(capacity));
Lines = _pool.Rent(willRelease ? Math.Max(8, capacity) : capacity);
Count = 0;
}
@@ -35,7 +38,7 @@ namespace Markdig.Helpers
/// <exception cref="System.ArgumentNullException"></exception>
public StringLineGroup(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (text == null) ThrowHelper.ArgumentNullException_text();
Lines = new StringLine[1];
Count = 0;
Add(new StringSlice(text));
@@ -66,23 +69,20 @@ namespace Markdig.Helpers
/// <param name="index">The index.</param>
public void RemoveAt(int index)
{
if (Count - 1 == index)
{
Count--;
}
else
if (index != Count - 1)
{
Array.Copy(Lines, index + 1, Lines, index, Count - index - 1);
Lines[Count - 1] = new StringLine();
Count--;
}
Lines[Count - 1] = new StringLine();
Count--;
}
/// <summary>
/// Adds the specified line to this instance.
/// </summary>
/// <param name="line">The line.</param>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(ref StringLine line)
{
if (Count == Lines.Length) IncreaseCapacity();
@@ -93,14 +93,14 @@ namespace Markdig.Helpers
/// Adds the specified slice to this instance.
/// </summary>
/// <param name="slice">The slice.</param>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(StringSlice slice)
{
if (Count == Lines.Length) IncreaseCapacity();
Lines[Count++] = new StringLine(ref slice);
}
public override string ToString()
public readonly override string ToString()
{
return ToSlice().ToString();
}
@@ -110,22 +110,24 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="lineOffsets">The position of the `\n` line offsets from the beginning of the returned slice.</param>
/// <returns>A single slice concatenating the lines of this instance</returns>
public StringSlice ToSlice(List<LineOffset> lineOffsets = null)
public readonly StringSlice ToSlice(List<LineOffset> lineOffsets = null)
{
// Optimization case for a single line.
if (Count == 1)
{
lineOffsets?.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
return Lines[0];
}
// Optimization case when no lines
if (Count == 0)
{
return new StringSlice(string.Empty);
}
// Optimization case for a single line.
if (Count == 1)
if (lineOffsets != null && lineOffsets.Capacity < lineOffsets.Count + Count)
{
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
}
return Lines[0];
lineOffsets.Capacity = Math.Max(lineOffsets.Count + Count, lineOffsets.Capacity * 2);
}
// Else use a builder
@@ -135,32 +137,25 @@ namespace Markdig.Helpers
{
if (i > 0)
{
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[i - 1].Position, Lines[i - 1].Column, Lines[i - 1].Slice.Start - Lines[i - 1].Position, previousStartOfLine, builder.Length));
}
builder.Append('\n');
previousStartOfLine = builder.Length;
}
if (!Lines[i].Slice.IsEmpty)
ref var line = ref Lines[i];
if (!line.Slice.IsEmpty)
{
builder.Append(Lines[i].Slice.Text, Lines[i].Slice.Start, Lines[i].Slice.Length);
builder.Append(line.Slice.Text, line.Slice.Start, line.Slice.Length);
}
lineOffsets?.Add(new LineOffset(line.Position, line.Column, line.Slice.Start - line.Position, previousStartOfLine, builder.Length));
}
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[Count - 1].Position, Lines[Count - 1].Column, Lines[Count - 1].Slice.Start - Lines[Count - 1].Position, previousStartOfLine, builder.Length));
}
var str = builder.ToString();
builder.Length = 0;
return new StringSlice(str);
return new StringSlice(builder.GetStringAndReset());
}
/// <summary>
/// Converts this instance into a <see cref="ICharIterator"/>.
/// </summary>
/// <returns></returns>
public Iterator ToCharIterator()
public readonly Iterator ToCharIterator()
{
return new Iterator(this);
}
@@ -183,14 +178,24 @@ namespace Markdig.Helpers
private void IncreaseCapacity()
{
var newItems = new StringLine[Lines.Length * 2];
var newItems = _pool.Rent(Lines.Length * 2);
if (Count > 0)
{
Array.Copy(Lines, 0, newItems, 0, Count);
Array.Clear(Lines, 0, Count);
}
_pool.Return(Lines);
Lines = newItems;
}
internal void Release()
{
Array.Clear(Lines, 0, Count);
_pool.Return(Lines);
Lines = null;
Count = -1;
}
/// <summary>
/// The iterator used to iterate other the lines.
/// </summary>
@@ -207,7 +212,7 @@ namespace Markdig.Helpers
_offset = -1;
SliceIndex = 0;
CurrentChar = '\0';
End = -2;
End = -2;
for (int i = 0; i < lines.Count; i++)
{
End += lines.Lines[i].Slice.Length + 1; // Add chars
@@ -221,7 +226,7 @@ namespace Markdig.Helpers
public int End { get; private set; }
public bool IsEmpty => Start > End;
public readonly bool IsEmpty => Start > End;
public int SliceIndex { get; private set; }
@@ -252,10 +257,10 @@ namespace Markdig.Helpers
public char NextChar()
{
Start++;
_offset++;
if (Start <= End)
{
var slice = (StringSlice)_lines.Lines[SliceIndex];
var slice = _lines.Lines[SliceIndex].Slice;
_offset++;
if (_offset < slice.Length)
{
CurrentChar = slice[slice.Start + _offset];
@@ -272,43 +277,54 @@ namespace Markdig.Helpers
CurrentChar = '\0';
Start = End + 1;
SliceIndex = _lines.Count;
_offset--;
}
return CurrentChar;
}
public char PeekChar(int offset = 1)
public readonly char PeekChar(int offset = 1)
{
if (offset < 0) throw new ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
if (offset < 0) ThrowHelper.ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
if (Start + offset > End)
{
return '\0';
}
var slice = (StringSlice)_lines.Lines[SliceIndex];
if (_offset + offset >= slice.Length)
offset += _offset;
int sliceIndex = SliceIndex;
var slice = _lines.Lines[sliceIndex].Slice;
while (offset > slice.Length)
{
// We are not peeking at the same line
offset -= slice.Length + 1; // + 1 for new line
Debug.Assert(sliceIndex + 1 < _lines.Lines.Length, "'Start + offset > End' check above should prevent us from indexing out of range");
slice = _lines.Lines[++sliceIndex].Slice;
}
if (offset == slice.Length)
{
return '\n';
}
return slice[slice.Start + _offset + offset];
Debug.Assert(offset < slice.Length);
return slice[slice.Start + offset];
}
public bool TrimStart()
{
var c = CurrentChar;
bool hasSpaces = false;
while (c.IsWhitespace())
{
hasSpaces = true;
c = NextChar();
}
return hasSpaces;
return IsEmpty;
}
}
public struct LineOffset
public readonly struct LineOffset
{
public LineOffset(int linePosition, int column, int offset, int start, int end)
{

View File

@@ -38,7 +38,9 @@ namespace Markdig.Helpers
/// <exception cref="System.ArgumentNullException"></exception>
public StringSlice(string text, int start, int end)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (text is null)
ThrowHelper.ArgumentNullException_text();
Text = text;
Start = start;
End = end;
@@ -52,34 +54,50 @@ namespace Markdig.Helpers
/// <summary>
/// Gets or sets the start position within <see cref="Text"/>.
/// </summary>
public int Start { get; set; }
public int Start { readonly get; set; }
/// <summary>
/// Gets or sets the end position (inclusive) within <see cref="Text"/>.
/// </summary>
public int End { get; set; }
public int End { readonly get; set; }
/// <summary>
/// Gets the length.
/// </summary>
public int Length => End - Start + 1;
public readonly int Length => End - Start + 1;
/// <summary>
/// Gets the current character.
/// </summary>
public char CurrentChar => Start <= End ? this[Start] : '\0';
public readonly char CurrentChar
{
get
{
int start = Start;
return start <= End ? Text[start] : '\0';
}
}
/// <summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>
public bool IsEmpty => Start > End;
public readonly bool IsEmpty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Start > End;
}
/// <summary>
/// Gets the <see cref="System.Char"/> at the specified index.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>A character in the slice at the specified index (not from <see cref="Start"/> but from the begining of the slice)</returns>
public char this[int index] => Text[index];
public readonly char this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Text[index];
}
/// <summary>
/// Goes to the next character, incrementing the <see cref="Start" /> position.
@@ -87,16 +105,30 @@ namespace Markdig.Helpers
/// <returns>
/// The next character. `\0` is end of the iteration.
/// </returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public char NextChar()
{
Start++;
if (Start > End)
int start = Start;
if (start >= End)
{
Start = End + 1;
return '\0';
}
return Text[Start];
start++;
Start = start;
return Text[start];
}
/// <summary>
/// Peeks a character at the offset of 1 from the current <see cref="Start"/> position
/// inside the range <see cref="Start"/> and <see cref="End"/>, returns `\0` if outside this range.
/// </summary>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekChar()
{
int index = Start + 1;
return index <= End ? Text[index] : '\0';
}
/// <summary>
@@ -105,21 +137,22 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="offset">The offset.</param>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekChar(int offset = 1)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekChar(int offset)
{
var index = Start + offset;
return index >= Start && index <= End ? Text[index] : (char) 0;
return index >= Start && index <= End ? Text[index] : '\0';
}
/// <summary>
/// Peeks a character at the specified offset from the current beginning of the string, without taking into account <see cref="Start"/> and <see cref="End"/>
/// </summary>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekCharAbsolute(int index)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekCharAbsolute(int index)
{
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
string text = Text;
return (uint)index < (uint)text.Length ? text[index] : '\0';
}
/// <summary>
@@ -128,11 +161,12 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="offset">The offset.</param>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekCharExtra(int offset)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekCharExtra(int offset)
{
var index = Start + offset;
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
var text = Text;
return (uint)index < (uint)text.Length ? text[index] : '\0';
}
/// <summary>
@@ -141,7 +175,7 @@ namespace Markdig.Helpers
/// <param name="text">The text.</param>
/// <param name="offset">The offset.</param>
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
public bool Match(string text, int offset = 0)
public readonly bool Match(string text, int offset = 0)
{
return Match(text, End, offset);
}
@@ -153,19 +187,22 @@ namespace Markdig.Helpers
/// <param name="end">The end.</param>
/// <param name="offset">The offset.</param>
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
public bool Match(string text, int end, int offset)
public readonly bool Match(string text, int end, int offset)
{
var index = Start + offset;
int i = 0;
for (; index <= end && i < text.Length; i++, index++)
if (end - index + 1 < text.Length)
return false;
string sliceText = Text;
for (int i = 0; i < text.Length; i++, index++)
{
if (text[i] != Text[index])
if (text[i] != sliceText[index])
{
return false;
}
}
return i == text.Length;
return true;
}
/// <summary>
@@ -196,7 +233,7 @@ namespace Markdig.Helpers
/// <param name="text">The text.</param>
/// <param name="offset">The offset.</param>
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
public bool MatchLowercase(string text, int offset = 0)
public readonly bool MatchLowercase(string text, int offset = 0)
{
return MatchLowercase(text, End, offset);
}
@@ -208,19 +245,22 @@ namespace Markdig.Helpers
/// <param name="end">The end.</param>
/// <param name="offset">The offset.</param>
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
public bool MatchLowercase(string text, int end, int offset)
public readonly bool MatchLowercase(string text, int end, int offset)
{
var index = Start + offset;
int i = 0;
for (; index <= end && i < text.Length; i++, index++)
if (end - index + 1 < text.Length)
return false;
string sliceText = Text;
for (int i = 0; i < text.Length; i++, index++)
{
if (text[i] != char.ToLowerInvariant(Text[index]))
if (text[i] != char.ToLowerInvariant(sliceText[index]))
{
return false;
}
}
return i == text.Length;
return true;
}
/// <summary>
@@ -230,46 +270,30 @@ namespace Markdig.Helpers
/// <param name="offset">The offset.</param>
/// <param name="ignoreCase">true if ignore case</param>
/// <returns><c>true</c> if the text was found; <c>false</c> otherwise</returns>
public int IndexOf(string text, int offset = 0, bool ignoreCase = false)
public readonly int IndexOf(string text, int offset = 0, bool ignoreCase = false)
{
var end = End - text.Length + 1;
if (ignoreCase)
{
for (int i = Start + offset; i <= end; i++)
{
if (MatchLowercase(text, End, i - Start))
{
return i; ;
}
}
}
else
{
for (int i = Start + offset; i <= end; i++)
{
if (Match(text, End, i - Start))
{
return i; ;
}
}
}
return -1;
offset += Start;
int length = End - offset + 1;
if (length <= 0)
return -1;
return Text.IndexOf(text, offset, length, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
}
/// <summary>
/// Searches for the specified character within this slice.
/// </summary>
/// <returns>A value >= 0 if the character was found, otherwise &lt; 0</returns>
public int IndexOf(char c)
public readonly int IndexOf(char c)
{
for (int i = Start; i <= End; i++)
{
if (Text[i] == c)
{
return i;
}
}
return -1;
int start = Start;
int length = End - start + 1;
if (length <= 0)
return -1;
return Text.IndexOf(c, start, length);
}
/// <summary>
@@ -280,16 +304,15 @@ namespace Markdig.Helpers
/// </returns>
public bool TrimStart()
{
// Strip leading spaces
var start = Start;
for (; Start <= End; Start++)
{
if (!Text[Start].IsWhitespace())
{
break;
}
}
return start != Start;
string text = Text;
int end = End;
int i = Start;
while (i <= end && (uint)i < (uint)text.Length && text[i].IsWhitespace())
i++;
Start = i;
return i > end;
}
/// <summary>
@@ -298,16 +321,15 @@ namespace Markdig.Helpers
/// <param name="spaceCount">The number of spaces trimmed.</param>
public void TrimStart(out int spaceCount)
{
spaceCount = 0;
// Strip leading spaces
for (; Start <= End; Start++)
{
if (!Text[Start].IsWhitespace())
{
break;
}
spaceCount++;
}
string text = Text;
int end = End;
int i = Start;
while (i <= end && (uint)i < (uint)text.Length && text[i].IsWhitespace())
i++;
spaceCount = i - Start;
Start = i;
}
/// <summary>
@@ -316,14 +338,15 @@ namespace Markdig.Helpers
/// <returns></returns>
public bool TrimEnd()
{
for (; Start <= End; End--)
{
if (!Text[End].IsWhitespace())
{
break;
}
}
return IsEmpty;
string text = Text;
int start = Start;
int i = End;
while (start <= i && (uint)i < (uint)text.Length && text[i].IsWhitespace())
i--;
End = i;
return start > i;
}
/// <summary>
@@ -331,40 +354,51 @@ namespace Markdig.Helpers
/// </summary>
public void Trim()
{
TrimStart();
TrimEnd();
string text = Text;
int start = Start;
int end = End;
while (start <= end && (uint)start < (uint)text.Length && text[start].IsWhitespace())
start++;
while (start <= end && (uint)end < (uint)text.Length && text[end].IsWhitespace())
end--;
Start = start;
End = end;
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// Returns a <see cref="string" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// A <see cref="string" /> that represents this instance.
/// </returns>
public override string ToString()
public readonly override string ToString()
{
if (Text != null && Start <= End)
{
var length = Length;
if (Start == 0 && Text.Length == length)
{
return Text;
}
string text = Text;
int start = Start;
int length = End - start + 1;
return Text.Substring(Start, length);
if (text is null || length <= 0)
{
return string.Empty;
}
return string.Empty;
return text.Substring(start, length);
}
/// <summary>
/// Determines whether this slice is empty or made only of whitespaces.
/// </summary>
/// <returns><c>true</c> if this slice is empty or made only of whitespaces; <c>false</c> otherwise</returns>
public bool IsEmptyOrWhitespace()
public readonly bool IsEmptyOrWhitespace()
{
for (int i = Start; i <= End; i++)
string text = Text;
int end = End;
for (int i = Start; i <= end && (uint)i < (uint)text.Length; i++)
{
if (!Text[i].IsWhitespace())
if (!text[i].IsWhitespace())
{
return false;
}

View File

@@ -1,26 +1,48 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Markdig.Helpers
{
/// <summary>
/// Inspired by CoreLib, taken from https://github.com/MihaZupan/SharpCollections, cc @MihaZupan
/// </summary>
[ExcludeFromCodeCoverage]
internal static class ThrowHelper
{
public static void ArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
public static void ArgumentNullException_item() => throw new ArgumentNullException("item");
public static void ArgumentNullException_text() => throw new ArgumentNullException("text");
public static void ArgumentNullException_label() => throw new ArgumentNullException("label");
public static void ArgumentNullException_key() => throw new ArgumentNullException("key");
public static void ArgumentNullException_name() => throw new ArgumentNullException("name");
public static void ArgumentNullException_markdown() => throw new ArgumentNullException("markdown");
public static void ArgumentNullException_writer() => throw new ArgumentNullException("writer");
public static void ArgumentNullException_leafBlock() => throw new ArgumentNullException("leafBlock");
public static void ArgumentNullException_markdownObject() => throw new ArgumentNullException("markdownObject");
public static void ArgumentException(string message) => throw new ArgumentException(message);
public static void ArgumentException(string message, string paramName) => throw new ArgumentException(message, paramName);
public static void ArgumentOutOfRangeException(string paramName) => throw new ArgumentOutOfRangeException(paramName);
public static void ArgumentOutOfRangeException(string message, string paramName) => throw new ArgumentOutOfRangeException(message, paramName);
public static void ArgumentOutOfRangeException_index() => throw new ArgumentOutOfRangeException("index");
public static void InvalidOperationException(string message) => throw new InvalidOperationException(message);
public static void ThrowArgumentNullException(ExceptionArgument argument)
{
throw new ArgumentNullException(GetArgumentName(argument));
throw new ArgumentNullException(argument.ToString());
}
public static void ThrowArgumentException(ExceptionArgument argument, ExceptionReason reason)
{
throw new ArgumentException(GetArgumentName(argument), GetExceptionReason(reason));
throw new ArgumentException(argument.ToString(), GetExceptionReason(reason));
}
public static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionReason reason)
{
throw new ArgumentOutOfRangeException(GetArgumentName(argument), GetExceptionReason(reason));
throw new ArgumentOutOfRangeException(argument.ToString(), GetExceptionReason(reason));
}
public static void ThrowIndexOutOfRangeException()
@@ -28,25 +50,6 @@ namespace Markdig.Helpers
throw new IndexOutOfRangeException();
}
private static string GetArgumentName(ExceptionArgument argument)
{
switch (argument)
{
case ExceptionArgument.key:
case ExceptionArgument.input:
case ExceptionArgument.value:
case ExceptionArgument.length:
case ExceptionArgument.text:
return argument.ToString();
case ExceptionArgument.offsetLength:
return "offset and length";
default:
Debug.Assert(false, "The enum value is not defined, please check the ExceptionArgument Enum.");
return "";
}
}
private static string GetExceptionReason(ExceptionReason reason)
{
switch (reason)

View File

@@ -1,84 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
<Copyright>Alexandre Mutel</Copyright>
<AssemblyTitle>Markdig</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.17.0</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
<AssemblyName>Markdig</AssemblyName>
<PackageId>Markdig</PackageId>
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81' ">
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net35' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE;NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.17763.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(SignAssembly)' == 'true' ">
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<!-- Special packages and imports for UWP support -->
<ItemGroup>
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
</ItemGroup>
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
<Import Project="Markdig.targets" />
</Project>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
<Copyright>Alexandre Mutel</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.20.0</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>8.0</LangVersion>
<!--Add support for sourcelink-->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.*" PrivateAssets="All"/>
</ItemGroup>
</Project>

View File

@@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Reflection;
using Markdig.Extensions.SelfPipeline;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
@@ -17,11 +18,7 @@ namespace Markdig
/// </summary>
public static partial class Markdown
{
#if NETSTANDARD_11
public static readonly string Version = typeof(Markdown).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
#else
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
#endif
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.
@@ -72,10 +69,19 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static string ToHtml(string markdown, MarkdownPipeline pipeline = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
var writer = new StringWriter();
ToHtml(markdown, writer, pipeline);
return writer.ToString();
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
var renderer = pipeline.GetCacheableHtmlRenderer();
var document = Parse(markdown, pipeline);
renderer.Render(document);
renderer.Writer.Flush();
string html = renderer.Writer.ToString();
pipeline.ReleaseCacheableHtmlRenderer(renderer);
return html;
}
/// <summary>
@@ -89,9 +95,9 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
public static MarkdownDocument ToHtml(string markdown, TextWriter writer, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (writer == null) throw new ArgumentNullException(nameof(writer));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
if (writer == null) ThrowHelper.ArgumentNullException_writer();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
@@ -115,9 +121,9 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if markdown or writer variable are null</exception>
public static object Convert(string markdown, IMarkdownRenderer renderer, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
if (renderer == null) ThrowHelper.ArgumentNullException(nameof(renderer));
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
var document = Parse(markdown, pipeline, context);
@@ -133,7 +139,7 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static MarkdownDocument Parse(string markdown)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
return Parse(markdown, null);
}
@@ -147,8 +153,8 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static MarkdownDocument Parse(string markdown, MarkdownPipeline pipeline, MarkdownParserContext context = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
return MarkdownParser.Parse(markdown, pipeline, context);
@@ -175,16 +181,17 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (writer == null) throw new ArgumentNullException(nameof(writer));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
if (writer == null) ThrowHelper.ArgumentNullException_writer();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
var renderer = new HtmlRenderer(writer)
{
EnableHtmlForBlock = false,
EnableHtmlForInline = false
EnableHtmlForInline = false,
EnableHtmlEscape = false,
};
pipeline.Setup(renderer);
@@ -205,7 +212,7 @@ namespace Markdig
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static string ToPlainText(string markdown, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
var writer = new StringWriter();
ToPlainText(markdown, writer, pipeline, context);
return writer.ToString();

View File

@@ -34,7 +34,8 @@ using Markdig.Extensions.Yaml;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Extensions.Globalization;
using Markdig.Helpers;
namespace Markdig
{
/// <summary>
@@ -92,8 +93,8 @@ namespace Markdig
.UseDiagrams()
.UseAutoLinks()
.UseGenericAttributes(); // Must be last as it is one parser that is modifying other parsers
}
}
/// <summary>
/// Uses this extension to enable autolinks from text `http://`, `https://`, `ftp://`, `mailto:`, `www.xxx.yyy`
/// </summary>
@@ -138,7 +139,7 @@ namespace Markdig
{
if (pipeline.Extensions.Count != 0)
{
throw new InvalidOperationException("The SelfPipeline extension cannot be used with other extensions");
ThrowHelper.InvalidOperationException("The SelfPipeline extension cannot be used with other extensions");
}
pipeline.Extensions.Add(new SelfPipelineExtension(defaultTag, defaultExtensions));
@@ -421,20 +422,35 @@ namespace Markdig
}
/// <summary>
/// Uses the emoji and smiley extension.
/// Uses the emojis and smileys extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
/// <param name="enableSmileys">Enable smileys in addition to emoji shortcodes, <c>true</c> by default.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmileys = true)
{
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
var emojiMapping = enableSmileys ? EmojiMapping.DefaultEmojisAndSmileysMapping : EmojiMapping.DefaultEmojisOnlyMapping;
pipeline.Extensions.Add(new EmojiExtension(emojiMapping));
}
return pipeline;
}
/// <summary>
/// Uses the emojis and smileys extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="customEmojiMapping">Enable customization of the emojis and smileys mapping.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, EmojiMapping customEmojiMapping)
{
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(customEmojiMapping));
}
return pipeline;
}
/// <summary>
/// Add rel=nofollow to all links rendered to HTML.
/// </summary>

View File

@@ -5,7 +5,7 @@ namespace Markdig
/// <summary>
/// Provides a context that can be used as part of parsing Markdown documents.
/// </summary>
public sealed class MarkdownParserContext
public class MarkdownParserContext
{
/// <summary>
/// Gets or sets the context property collection.

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
@@ -20,15 +20,14 @@ namespace Markdig
/// <summary>
/// Initializes a new instance of the <see cref="MarkdownPipeline" /> class.
/// </summary>
internal MarkdownPipeline(OrderedList<IMarkdownExtension> extensions, BlockParserList blockParsers, InlineParserList inlineParsers, StringBuilderCache cache, TextWriter debugLog, ProcessDocumentDelegate documentProcessed)
internal MarkdownPipeline(OrderedList<IMarkdownExtension> extensions, BlockParserList blockParsers, InlineParserList inlineParsers, TextWriter debugLog, ProcessDocumentDelegate documentProcessed)
{
if (blockParsers == null) throw new ArgumentNullException(nameof(blockParsers));
if (inlineParsers == null) throw new ArgumentNullException(nameof(inlineParsers));
if (blockParsers == null) ThrowHelper.ArgumentNullException(nameof(blockParsers));
if (inlineParsers == null) ThrowHelper.ArgumentNullException(nameof(inlineParsers));
// Add all default parsers
Extensions = extensions;
BlockParsers = blockParsers;
InlineParsers = inlineParsers;
StringBuilderCache = cache;
DebugLog = debugLog;
DocumentProcessed = documentProcessed;
}
@@ -44,8 +43,6 @@ namespace Markdig
internal InlineParserList InlineParsers { get; }
internal StringBuilderCache StringBuilderCache { get; }
// TODO: Move the log to a better place
internal TextWriter DebugLog { get; }
@@ -57,11 +54,48 @@ namespace Markdig
/// <param name="renderer">The markdown renderer to setup</param>
public void Setup(IMarkdownRenderer renderer)
{
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
if (renderer == null) ThrowHelper.ArgumentNullException(nameof(renderer));
foreach (var extension in Extensions)
{
extension.Setup(this, renderer);
}
}
private HtmlRendererCache _rendererCache = null;
internal HtmlRenderer GetCacheableHtmlRenderer()
{
if (_rendererCache is null)
{
_rendererCache = new HtmlRendererCache
{
OnNewInstanceCreated = Setup
};
}
return _rendererCache.Get();
}
internal void ReleaseCacheableHtmlRenderer(HtmlRenderer renderer)
{
_rendererCache.Release(renderer);
}
private sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
{
public Action<HtmlRenderer> OnNewInstanceCreated;
protected override HtmlRenderer NewInstance()
{
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
OnNewInstanceCreated(renderer);
return renderer;
}
protected override void Reset(HtmlRenderer instance)
{
instance.Reset();
}
}
}
}

View File

@@ -49,8 +49,6 @@ namespace Markdig
};
Extensions = new OrderedList<IMarkdownExtension>();
StringBuilderCache = new StringBuilderCache();
}
/// <summary>
@@ -68,11 +66,6 @@ namespace Markdig
/// </summary>
public OrderedList<IMarkdownExtension> Extensions { get; }
/// <summary>
/// Gets or sets the string builder cache used by the parsers.
/// </summary>
public StringBuilderCache StringBuilderCache { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable precise source location (slower parsing but accurate position for block and inline elements)
/// </summary>
@@ -111,7 +104,7 @@ namespace Markdig
{
if (extension == null)
{
throw new InvalidOperationException("An extension cannot be null");
ThrowHelper.InvalidOperationException("An extension cannot be null");
}
extension.Setup(this);
}
@@ -120,7 +113,6 @@ namespace Markdig
new OrderedList<IMarkdownExtension>(Extensions),
new BlockParserList(BlockParsers),
new InlineParserList(InlineParsers),
StringBuilderCache,
DebugLog,
GetDocumentProcessed)
{

View File

@@ -25,9 +25,9 @@ namespace Markdig.Parsers
{
if (OpeningCharacters != null)
{
for (int i = 0; i < OpeningCharacters.Length; i++)
foreach (char openingChar in OpeningCharacters)
{
if (OpeningCharacters[i] == c)
if (openingChar == c)
{
return true;
}

View File

@@ -24,7 +24,6 @@ namespace Markdig.Parsers
// These properties are not changing between a parent and a children BlockProcessor
this.root = root;
this.parserStateCache = root.parserStateCache;
StringBuilders = root.StringBuilders;
Document = root.Document;
Parsers = root.Parsers;
@@ -41,13 +40,11 @@ namespace Markdig.Parsers
/// <param name="parsers">The list of parsers.</param>
/// <exception cref="System.ArgumentNullException">
/// </exception>
public BlockProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, BlockParserList parsers, MarkdownParserContext context)
public BlockProcessor(MarkdownDocument document, BlockParserList parsers, MarkdownParserContext context)
{
if (stringBuilders == null) throw new ArgumentNullException(nameof(stringBuilders));
if (document == null) throw new ArgumentNullException(nameof(document));
if (parsers == null) throw new ArgumentNullException(nameof(parsers));
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
if (parsers == null) ThrowHelper.ArgumentNullException(nameof(parsers));
parserStateCache = new BlockParserStateCache(this);
StringBuilders = stringBuilders;
Document = document;
document.IsOpen = true;
Parsers = parsers;
@@ -153,11 +150,6 @@ namespace Markdig.Parsers
/// </summary>
public int StartBeforeIndent { get; private set; }
/// <summary>
/// Gets the cache of string builders.
/// </summary>
public StringBuilderCache StringBuilders { get; }
/// <summary>
/// Gets the current stack of <see cref="Block"/> being processed.
/// </summary>
@@ -400,8 +392,8 @@ namespace Markdig.Parsers
/// <exception cref="System.ArgumentException">The block must be opened</exception>
public void Open(Block block)
{
if (block == null) throw new ArgumentNullException(nameof(block));
if (!block.IsOpen) throw new ArgumentException("The block must be opened", nameof(block));
if (block == null) ThrowHelper.ArgumentNullException(nameof(block));
if (!block.IsOpen) ThrowHelper.ArgumentException("The block must be opened", nameof(block));
OpenedBlocks.Add(block);
}
@@ -477,7 +469,7 @@ namespace Markdig.Parsers
{
if (this == root)
{
throw new InvalidOperationException("Cannot release the root parser state");
ThrowHelper.InvalidOperationException("Cannot release the root parser state");
}
parserStateCache.Release(this);
}
@@ -500,6 +492,11 @@ namespace Markdig.Parsers
if (!block.Parser.Close(this, block))
{
block.Parent?.Remove(block);
if (block is LeafBlock leaf)
{
leaf.Lines.Release();
}
}
else
{
@@ -628,7 +625,7 @@ namespace Markdig.Parsers
// If a parser is adding a block, it must be the last of the list
if ((i + 1) < OpenedBlocks.Count && NewBlocks.Count > 0)
{
throw new InvalidOperationException("A pending parser cannot add a new block when it is not the last pending block");
ThrowHelper.InvalidOperationException("A pending parser cannot add a new block when it is not the last pending block");
}
// If we have a leaf block
@@ -675,7 +672,7 @@ namespace Markdig.Parsers
// Security check so that the parser can't go into a crazy infinite loop if one extension is messing
if (previousStart == Start)
{
throw new InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse blocks at line [{LineIndex}] with line [{Line}]");
ThrowHelper.InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse blocks at line [{LineIndex}] with line [{Line}]");
}
previousStart = Start;
@@ -805,7 +802,7 @@ namespace Markdig.Parsers
if (block.Parser == null)
{
throw new InvalidOperationException($"The new block [{block.GetType()}] must have a valid Parser property");
ThrowHelper.InvalidOperationException($"The new block [{block.GetType()}] must have a valid Parser property");
}
block.Line = LineIndex;
@@ -821,7 +818,7 @@ namespace Markdig.Parsers
if (newBlocks.Count > 0)
{
throw new InvalidOperationException(
ThrowHelper.InvalidOperationException(
"The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed");
}
}
@@ -873,7 +870,7 @@ namespace Markdig.Parsers
NewBlocks.Clear();
}
private class BlockParserStateCache : ObjectCache<BlockProcessor>
private sealed class BlockParserStateCache : ObjectCache<BlockProcessor>
{
private readonly BlockProcessor root;

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Runtime.CompilerServices;
@@ -16,7 +16,7 @@ namespace Markdig.Parsers
/// </summary>
/// <param name="blockState">State of the block.</param>
/// <returns><c>true</c> if the block state is in discard state</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsDiscard(this BlockState blockState)
{
return blockState == BlockState.ContinueDiscard || blockState == BlockState.BreakDiscard;
@@ -27,7 +27,7 @@ namespace Markdig.Parsers
/// </summary>
/// <param name="blockState">State of the block.</param>
/// <returns><c>true</c> if the block state is in continue state</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsContinue(this BlockState blockState)
{
return blockState == BlockState.Continue || blockState == BlockState.ContinueDiscard;
@@ -38,7 +38,7 @@ namespace Markdig.Parsers
/// </summary>
/// <param name="blockState">State of the block.</param>
/// <returns><c>true</c> if the block state is in break state</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsBreak(this BlockState blockState)
{
return blockState == BlockState.Break || blockState == BlockState.BreakDiscard;

View File

@@ -124,7 +124,9 @@ namespace Markdig.Parsers
}
else
{
infoString = line.ToString().Trim();
var lineCopy = line;
lineCopy.Trim();
infoString = lineCopy.ToString();
}
fenced.Info = HtmlHelper.Unescape(infoString);

View File

@@ -100,7 +100,7 @@ namespace Markdig.Parsers
if (c == '!')
{
c = line.NextChar();
if (c == '-' && line.PeekChar(1) == '-')
if (c == '-' && line.PeekChar() == '-')
{
return CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition); // group 2
}
@@ -140,7 +140,7 @@ namespace Markdig.Parsers
}
if (
!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar(1) == '>') || c.IsWhitespace() ||
!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() ||
c == '\0'))
{
return BlockState.None;

View File

@@ -36,12 +36,10 @@ namespace Markdig.Parsers
/// <param name="inlineCreated">The inline created event.</param>
/// <exception cref="System.ArgumentNullException">
/// </exception>
public InlineProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, InlineParserList parsers, bool preciseSourcelocation, MarkdownParserContext context)
public InlineProcessor(MarkdownDocument document, InlineParserList parsers, bool preciseSourcelocation, MarkdownParserContext context)
{
if (stringBuilders == null) throw new ArgumentNullException(nameof(stringBuilders));
if (document == null) throw new ArgumentNullException(nameof(document));
if (parsers == null) throw new ArgumentNullException(nameof(parsers));
StringBuilders = stringBuilders;
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
if (parsers == null) ThrowHelper.ArgumentNullException(nameof(parsers));
Document = document;
Parsers = parsers;
Context = context;
@@ -91,11 +89,6 @@ namespace Markdig.Parsers
/// </summary>
public MarkdownDocument Document { get; }
/// <summary>
/// Gets the cache string builders.
/// </summary>
public StringBuilderCache StringBuilders { get; }
/// <summary>
/// Gets or sets the index of the line from the begining of the document being processed.
/// </summary>
@@ -176,7 +169,7 @@ namespace Markdig.Parsers
/// <param name="leafBlock">The leaf block.</param>
public void ProcessInlineLeaf(LeafBlock leafBlock)
{
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
if (leafBlock == null) ThrowHelper.ArgumentNullException_leafBlock();
// clear parser states
Array.Clear(ParserStates, 0, ParserStates.Length);
@@ -192,7 +185,7 @@ namespace Markdig.Parsers
previousLineIndexForSliceOffset = 0;
lineOffsets.Clear();
var text = leafBlock.Lines.ToSlice(lineOffsets);
leafBlock.Lines = new StringLineGroup();
leafBlock.Lines.Release();
int previousStart = -1;
@@ -201,7 +194,7 @@ namespace Markdig.Parsers
// Security check so that the parser can't go into a crazy infinite loop if one extension is messing
if (previousStart == text.Start)
{
throw new InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse inlines for block [{leafBlock.GetType().Name}] at position ({leafBlock.ToPositionText()}");
ThrowHelper.InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse inlines for block [{leafBlock.GetType().Name}] at position ({leafBlock.ToPositionText()}");
}
previousStart = text.Start;

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