Compare commits

...

83 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
92 changed files with 1495 additions and 1549 deletions

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"
}

View File

@@ -1,74 +0,0 @@
version: 10.0.{build}
image: Visual Studio 2019
configuration: Release
environment:
COVERALLS_REPO_TOKEN:
secure: /SEtLgIE6ZrJaWBC1xFZIeESiwfwiXEk9N4pSJ53rFhqBZC2sXJg7ZxZ1DBhnZGu
install:
- ps: >-
cd src
dotnet tool install -g coveralls.net --version 1.0.0
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: >
dotnet SpecFileGen/bin/Release/netcoreapp2.2/SpecFileGen.dll
test_script:
- cmd: >-
dotnet test Markdig.Tests -c Release --no-build
dotnet test Markdig.Tests -c Debug
dotnet test Markdig.Tests -c Release -f netcoreapp2.1 /p:CollectCoverage=true /p:Include=\"[Markdig]*\" /p:CoverletOutputFormat=opencover /p:CoverletOutput=../../coverage.xml
after_test:
- ps: >-
if($env:APPVEYOR_REPO_BRANCH -eq "master") {
cd ..
if (Test-Path "./coverage.xml") {
csmacnz.Coveralls --opencover -i "./coverage.xml" --repoToken $env:COVERALLS_REPO_TOKEN --basePath "$env:APPVEYOR_BUILD_FOLDER" --useRelativePath --commitId $env:APPVEYOR_REPO_COMMIT --commitBranch $env:APPVEYOR_REPO_BRANCH --commitAuthor $env:APPVEYOR_REPO_COMMIT_AUTHOR --commitEmail $env:APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL --commitMessage $env:APPVEYOR_REPO_COMMIT_MESSAGE --jobId $env:APPVEYOR_BUILD_NUMBER --serviceName appveyor
}
cd src
}
before_package:
- cmd: >-
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig.Signed/Markdig.Signed.csproj
artifacts:
- path: src\**\*.nupkg
name: Markdig Nugets
deploy:
- provider: NuGet
api_key:
secure: 7cthHh+wYWZjhqxaxR6QObRaRnstvFkQOY7MkxIsC5kpQEBlKZXuinf0IybbYxJt
on:
appveyor_nuget_push: true

View File

@@ -1,5 +1,26 @@
# 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))
@@ -12,7 +33,6 @@
- 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))

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) [![Coverage Status](https://coveralls.io/repos/github/lunet-io/markdig/badge.svg?branch=master)](https://coveralls.io/github/lunet-io/markdig?branch=master) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/) [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
# Markdig [![Build Status](https://github.com/lunet-io/markdig/workflows/ci/badge.svg?branch=master)](https://github.com/lunet-io/markdig/actions) [![Coverage Status](https://coveralls.io/repos/github/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,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net471</TargetFrameworks>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Remove="spec.md" />

View File

@@ -1,12 +1,13 @@
<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.6.2">
<PackageReference Include="coverlet.msbuild" Version="2.8.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

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

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

@@ -1,3 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Markdig.Extensions.Emoji;
using NUnit.Framework;
@@ -95,5 +97,33 @@ namespace Markdig.Tests
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,9 +1,9 @@
using NUnit.Framework;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System;
using System.Linq;
using System.Collections.Generic;
using Markdig.Helpers;
namespace Markdig.Tests
{
@@ -33,9 +33,9 @@ namespace Markdig.Tests
foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
{
Assert.AreSame(ArrayHelper<ListBlock>.Empty, literalInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, literalInline.Descendants<ParagraphBlock>());
Assert.AreSame(ArrayHelper<ContainerInline>.Empty, literalInline.Descendants<ContainerInline>());
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>())
@@ -50,13 +50,13 @@ namespace Markdig.Tests
if (containerInline.FirstChild is null)
{
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerInline as MarkdownObject).Descendants<LiteralInline>());
Assert.AreSame(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(ArrayHelper<ListBlock>.Empty, containerInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, containerInline.Descendants<ParagraphBlock>());
Assert.AreSame(Array.Empty<ListBlock>(), containerInline.Descendants<ListBlock>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), containerInline.Descendants<ParagraphBlock>());
}
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
@@ -65,7 +65,7 @@ namespace Markdig.Tests
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
paragraphBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, paragraphBlock.Descendants<ParagraphBlock>());
Assert.AreSame(Array.Empty<ParagraphBlock>(), paragraphBlock.Descendants<ParagraphBlock>());
}
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
@@ -80,9 +80,9 @@ namespace Markdig.Tests
if (containerBlock.Count == 0)
{
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as Block).Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as MarkdownObject).Descendants<LiteralInline>());
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>());
}
}
}

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

@@ -159,6 +159,13 @@ namespace Markdig.Tests
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)

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,7 +53,7 @@ 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);
@@ -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

@@ -3,6 +3,7 @@ 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]
@@ -126,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

@@ -1747,10 +1747,10 @@ namespace Markdig.Extensions.Emoji
public EmojiMapping(IDictionary<string, string> shortcodeToUnicode, IDictionary<string, string> smileyToShortcode)
{
if (shortcodeToUnicode == null)
throw new ArgumentNullException(nameof(shortcodeToUnicode));
ThrowHelper.ArgumentNullException(nameof(shortcodeToUnicode));
if (smileyToShortcode == null)
throw new ArgumentNullException(nameof(smileyToShortcode));
ThrowHelper.ArgumentNullException(nameof(smileyToShortcode));
// Build emojis and smileys CompactPrefixTree
@@ -1767,8 +1767,8 @@ namespace Markdig.Extensions.Emoji
foreach (var shortcode in shortcodeToUnicode)
{
if (string.IsNullOrEmpty(shortcode.Key))
throw new ArgumentException("The dictionaries cannot contain null or empty keys", nameof(shortcodeToUnicode));
if (string.IsNullOrEmpty(shortcode.Key) || string.IsNullOrEmpty(shortcode.Value))
ThrowHelper.ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(shortcodeToUnicode));
firstChars.Add(shortcode.Key[0]);
PrefixTree.Add(shortcode);
@@ -1776,16 +1776,16 @@ namespace Markdig.Extensions.Emoji
foreach (var smiley in smileyToShortcode)
{
if (string.IsNullOrEmpty(smiley.Key))
throw new ArgumentException("The dictionaries cannot contain null or empty keys", nameof(smileyToShortcode));
if (string.IsNullOrEmpty(smiley.Key) || string.IsNullOrEmpty(smiley.Value))
ThrowHelper.ArgumentException("The dictionaries cannot contain null or empty keys/values", nameof(smileyToShortcode));
if (!shortcodeToUnicode.TryGetValue(smiley.Value, out string unicode))
throw new ArgumentException(string.Format("Invalid smiley target: {0} is not present in the emoji shortcodes dictionary", smiley.Value));
ThrowHelper.ArgumentException(string.Format("Invalid smiley target: {0} is not present in the emoji shortcodes dictionary", smiley.Value));
firstChars.Add(smiley.Key[0]);
if (!PrefixTree.TryAdd(smiley.Key, unicode))
throw new ArgumentException(string.Format("Smiley {0} is already present in the emoji mapping", smiley.Key));
ThrowHelper.ArgumentException(string.Format("Smiley {0} is already present in the emoji mapping", smiley.Key));
}
OpeningCharacters = new List<char>(firstChars).ToArray();

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
@@ -34,7 +35,7 @@ namespace Markdig.Extensions.Emoji
}
// Try to match an emoji shortcode or smiley
if (!_emojiMapping.PrefixTree.TryMatchLongest(slice.Text, slice.Start, slice.Length, out KeyValuePair<string, string> match))
if (!_emojiMapping.PrefixTree.TryMatchLongest(slice.Text.AsSpan(slice.Start, slice.Length), out KeyValuePair<string, string> match))
{
return false;
}

View File

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

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -40,9 +41,10 @@ namespace Markdig.Extensions.MediaLinks
public static IHostProvider Create(string hostPrefix, Func<Uri, string> handler, bool allowFullScreen = true, string iframeClass = null)
{
if (string.IsNullOrEmpty(hostPrefix))
throw new ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
ThrowHelper.ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
ThrowHelper.ArgumentNullException(nameof(handler));
return new DelegateProvider { HostPrefix = hostPrefix, Delegate = handler, AllowFullScreen = allowFullScreen, Class = iframeClass };
}

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

@@ -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,36 +2,6 @@
// 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;
@@ -47,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 = { '', '-', '†', '‡' };
@@ -104,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;
}
@@ -145,7 +114,7 @@ namespace Markdig.Helpers
return result;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int AddTab(int column)
{
// return ((column + TabSize) / TabSize) * TabSize;
@@ -153,18 +122,18 @@ namespace Markdig.Helpers
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;
}
@@ -172,7 +141,7 @@ namespace Markdig.Helpers
return false;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWhitespace(this char c)
{
// 2.1 Characters and lines
@@ -180,20 +149,20 @@ namespace Markdig.Helpers
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);
}
[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 IsZero(c) || IsWhitespace(c);
@@ -253,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
@@ -273,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
@@ -281,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
@@ -295,25 +264,25 @@ namespace Markdig.Helpers
return c == '\0' ? '\ufffd' : c;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAlphaUpper(this char c)
{
return (uint)(c - 'A') <= ('Z' - 'A');
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsAlpha(this char c)
{
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 (uint)(c - '0') <= ('9' - '0');
@@ -362,40 +331,31 @@ 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 IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsLowSurrogate(char c)
{
return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsInInclusiveRange(char c, char min, char max)
=> (uint) (c - min) <= (uint) (max - min);
=> (uint)(c - min) <= (uint)(max - min);
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)]
internal static bool IsInInclusiveRange(int value, uint min, uint max)
=> ((uint)value - min) <= (max - min);
public static IEnumerable<int> ToUtf32(StringSlice text)
{
@@ -785,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,38 +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;
unsafe
if (nonAsciiMap is null)
{
fixed (char* pText = text)
fixed (bool* openingChars = isOpeningCharacter)
#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 sealed 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

@@ -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 != '<')
{
@@ -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;
@@ -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

@@ -5,6 +5,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Markdig.Helpers
@@ -25,7 +26,7 @@ namespace Markdig.Helpers
/// <param name="capacity"></param>
public StringLineGroup(int capacity, bool willRelease = false)
{
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
if (capacity <= 0) ThrowHelper.ArgumentOutOfRangeException(nameof(capacity));
Lines = _pool.Rent(willRelease ? Math.Max(8, capacity) : capacity);
Count = 0;
}
@@ -37,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));
@@ -68,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();
@@ -95,7 +93,7 @@ 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();
@@ -114,22 +112,19 @@ namespace Markdig.Helpers
/// <returns>A single slice concatenating the lines of this instance</returns>
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.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];
}
if (lineOffsets != null && lineOffsets.Capacity < lineOffsets.Count + Count)
{
lineOffsets.Capacity = Math.Max(lineOffsets.Count + Count, lineOffsets.Capacity * 2);
@@ -142,21 +137,16 @@ 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);
}
}
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));
lineOffsets?.Add(new LineOffset(line.Position, line.Column, line.Slice.Start - line.Position, previousStartOfLine, builder.Length));
}
return new StringSlice(builder.GetStringAndReset());
}
@@ -267,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];
@@ -287,27 +277,40 @@ namespace Markdig.Helpers
CurrentChar = '\0';
Start = End + 1;
SliceIndex = _lines.Count;
_offset--;
}
return CurrentChar;
}
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()

View File

@@ -38,7 +38,10 @@ namespace Markdig.Helpers
/// <exception cref="System.ArgumentNullException"></exception>
public StringSlice(string text, int start, int end)
{
Text = text ?? throw new ArgumentNullException(nameof(text));
if (text is null)
ThrowHelper.ArgumentNullException_text();
Text = text;
Start = start;
End = end;
}
@@ -51,12 +54,12 @@ 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.
@@ -80,7 +83,7 @@ namespace Markdig.Helpers
/// </summary>
public readonly bool IsEmpty
{
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Start > End;
}
@@ -91,7 +94,7 @@ namespace Markdig.Helpers
/// <returns>A character in the slice at the specified index (not from <see cref="Start"/> but from the begining of the slice)</returns>
public readonly char this[int index]
{
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Text[index];
}
@@ -102,7 +105,7 @@ namespace Markdig.Helpers
/// <returns>
/// The next character. `\0` is end of the iteration.
/// </returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public char NextChar()
{
int start = Start;
@@ -121,7 +124,7 @@ namespace Markdig.Helpers
/// 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(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekChar()
{
int index = Start + 1;
@@ -134,7 +137,7 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="offset">The offset.</param>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekChar(int offset)
{
var index = Start + offset;
@@ -145,7 +148,7 @@ namespace Markdig.Helpers
/// 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)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekCharAbsolute(int index)
{
string text = Text;
@@ -158,7 +161,7 @@ namespace Markdig.Helpers
/// </summary>
/// <param name="offset">The offset.</param>
/// <returns>The character at offset, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly char PeekCharExtra(int offset)
{
var index = Start + offset;
@@ -275,13 +278,7 @@ namespace Markdig.Helpers
if (length <= 0)
return -1;
#if NETCORE
var span = Text.AsSpan(offset, length);
int index = ignoreCase ? span.IndexOf(text, StringComparison.OrdinalIgnoreCase) : span.IndexOf(text);
return index == -1 ? index : index + offset;
#else
return Text.IndexOf(text, offset, length, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
#endif
}
/// <summary>
@@ -296,12 +293,7 @@ namespace Markdig.Helpers
if (length <= 0)
return -1;
#if NETCORE
int index = Text.AsSpan(start, length).IndexOf(c);
return index == -1 ? index : index + start;
#else
return Text.IndexOf(c, start, length);
#endif
}
/// <summary>
@@ -312,15 +304,15 @@ namespace Markdig.Helpers
/// </returns>
public bool TrimStart()
{
// Strip leading spaces
for (; Start <= End; Start++)
{
if (!Text[Start].IsWhitespace())
{
break;
}
}
return IsEmpty;
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>
@@ -329,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>
@@ -347,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>
@@ -362,8 +354,18 @@ 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>
@@ -391,9 +393,12 @@ namespace Markdig.Helpers
/// <returns><c>true</c> if this slice is empty or made only of whitespaces; <c>false</c> otherwise</returns>
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

@@ -4,9 +4,9 @@
<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.18.1</VersionPrefix>
<VersionPrefix>0.20.0</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
<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>
@@ -14,17 +14,26 @@
<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)' == 'uap10.0' ">
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.17763.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants>$(DefineConstants);UAP</DefineConstants>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@@ -32,12 +41,8 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Special packages and imports for UWP support -->
<ItemGroup>
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.*" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
</ItemGroup>
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
</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 UAP
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,8 +69,8 @@ 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));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
pipeline ??= new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
var renderer = pipeline.GetCacheableHtmlRenderer();
@@ -98,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
@@ -124,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);
@@ -142,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);
}
@@ -156,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);
@@ -184,9 +181,9 @@ 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
@@ -215,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>
@@ -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));

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

@@ -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,7 +54,7 @@ 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);

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);
}
@@ -633,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
@@ -680,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;
@@ -810,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;
@@ -826,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");
}
}

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

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

View File

@@ -1,48 +1,48 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Parsers.Inlines
{
/// <summary>
/// An inline parser for a <see cref="CodeInline"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
public class CodeInlineParser : InlineParser
{
/// <summary>
/// Initializes a new instance of the <see cref="CodeInlineParser"/> class.
/// </summary>
public CodeInlineParser()
{
OpeningCharacters = new[] { '`' };
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
var match = slice.CurrentChar;
if (slice.PeekCharExtra(-1) == match)
{
return false;
}
var startPosition = slice.Start;
int openSticks = 0;
int closeSticks = 0;
// Match the opened sticks
char c = slice.CurrentChar;
while (c == match)
{
openSticks++;
c = slice.NextChar();
}
var builder = processor.StringBuilders.Get();
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Parsers.Inlines
{
/// <summary>
/// An inline parser for a <see cref="CodeInline"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.InlineParser" />
public class CodeInlineParser : InlineParser
{
/// <summary>
/// Initializes a new instance of the <see cref="CodeInlineParser"/> class.
/// </summary>
public CodeInlineParser()
{
OpeningCharacters = new[] { '`' };
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
var match = slice.CurrentChar;
if (slice.PeekCharExtra(-1) == match)
{
return false;
}
var startPosition = slice.Start;
int openSticks = 0;
int closeSticks = 0;
// Match the opened sticks
char c = slice.CurrentChar;
while (c == match)
{
openSticks++;
c = slice.NextChar();
}
var builder = StringBuilderCache.Local();
// A backtick string is a string of one or more backtick characters (`) that is neither preceded nor followed by a backtick.
// A code span begins with a backtick string and ends with a backtick string of equal length.
@@ -53,71 +53,74 @@ namespace Markdig.Parsers.Inlines
// 2. If the resulting string both begins AND ends with a space character, but does not consist entirely
// of space characters, a single space character is removed from the front and back.
// This allows you to include code that begins or ends with backtick characters, which must be separated by
// whitespace from the opening or closing backtick strings.
bool allSpace = true;
while (c != '\0')
{
// Transform '\n' into a single space
if (c == '\n')
{
c = ' ';
}
// whitespace from the opening or closing backtick strings.
bool allSpace = true;
while (c != '\0')
{
// Transform '\n' into a single space
if (c == '\n')
{
c = ' ';
}
if (c == match)
{
do
{
closeSticks++;
c = slice.NextChar();
do
{
closeSticks++;
c = slice.NextChar();
}
while (c == match);
if (openSticks == closeSticks)
{
break;
}
allSpace = false;
builder.Append(match, closeSticks);
if (openSticks == closeSticks)
{
break;
}
allSpace = false;
builder.Append(match, closeSticks);
closeSticks = 0;
}
}
else
{
builder.Append(c);
if (c != ' ')
{
allSpace = false;
}
}
c = slice.NextChar();
}
}
bool isMatching = false;
if (closeSticks == openSticks)
{
// Remove one space from front and back if the string is not all spaces
if (!allSpace && builder.Length > 2 && builder[0] == ' ' && builder[builder.Length - 1] == ' ')
{
builder.Length--;
builder.Remove(0, 1); // More expensive, alternative is to have a double-pass algorithm
}
processor.Inline = new CodeInline()
{
Delimiter = match,
Content = builder.ToString(),
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
Line = line,
Column = column
};
isMatching = true;
}
// Release the builder if not used
processor.StringBuilders.Release(builder);
return isMatching;
}
}
}
}
bool isMatching = false;
if (closeSticks == openSticks)
{
string content;
// Remove one space from front and back if the string is not all spaces
if (!allSpace && builder.Length > 2 && builder[0] == ' ' && builder[builder.Length - 1] == ' ')
{
content = builder.ToString(1, builder.Length - 2);
}
else
{
content = builder.ToString();
}
processor.Inline = new CodeInline()
{
Delimiter = match,
Content = content,
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)),
Line = line,
Column = column
};
isMatching = true;
}
return isMatching;
}
}
}

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
namespace Markdig.Parsers.Inlines
@@ -20,9 +21,9 @@ namespace Markdig.Parsers.Inlines
/// <param name="enableWithinWord">if set to <c>true</c> the emphasis can be used inside a word.</param>
public EmphasisDescriptor(char character, int minimumCount, int maximumCount, bool enableWithinWord)
{
if (minimumCount < 1) throw new ArgumentOutOfRangeException(nameof(minimumCount), "minimumCount must be >= 1");
if (maximumCount < 1) throw new ArgumentOutOfRangeException(nameof(maximumCount), "maximumCount must be >= 1");
if (minimumCount > maximumCount) throw new ArgumentOutOfRangeException(nameof(minimumCount), "minimumCount must be <= maximumCount");
if (minimumCount < 1) ThrowHelper.ArgumentOutOfRangeException(nameof(minimumCount), "minimumCount must be >= 1");
if (maximumCount < 1) ThrowHelper.ArgumentOutOfRangeException(nameof(maximumCount), "maximumCount must be >= 1");
if (minimumCount > maximumCount) ThrowHelper.ArgumentOutOfRangeException(nameof(minimumCount), "minimumCount must be <= maximumCount");
Character = character;
MinimumCount = minimumCount;

View File

@@ -77,7 +77,7 @@ namespace Markdig.Parsers.Inlines
var emphasis = EmphasisDescriptors[i];
if (Array.IndexOf(OpeningCharacters, emphasis.Character) >= 0)
{
throw new InvalidOperationException(
ThrowHelper.InvalidOperationException(
$"The character `{emphasis.Character}` is already used by another emphasis descriptor");
}

View File

@@ -2,7 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Text;
using System;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -27,10 +27,7 @@ namespace Markdig.Parsers.Inlines
public static bool TryParse(ref StringSlice slice, out string literal, out int match)
{
literal = null;
int entityNameStart;
int entityNameLength;
int entityValue;
match = HtmlHelper.ScanEntity(slice, out entityValue, out entityNameStart, out entityNameLength);
match = HtmlHelper.ScanEntity(slice, out int entityValue, out int entityNameStart, out int entityNameLength);
if (match == 0)
{
return false;
@@ -38,11 +35,11 @@ namespace Markdig.Parsers.Inlines
if (entityNameLength > 0)
{
literal = EntityHelper.DecodeEntity(new StringSlice(slice.Text, entityNameStart, entityNameStart + entityNameLength - 1).ToString());
literal = EntityHelper.DecodeEntity(slice.Text.AsSpan(entityNameStart, entityNameLength));
}
else if (entityValue >= 0)
{
literal = (entityValue == 0 ? null : EntityHelper.DecodeEntity(entityValue)) ?? CharHelper.ZeroSafeString;
literal = EntityHelper.DecodeEntity(entityValue);
}
return literal != null;
}

View File

@@ -300,19 +300,19 @@ namespace Markdig.Parsers.Inlines
private void MarkParentAsInactive(Inline inline)
{
if (inline == null)
while (inline != null)
{
return;
}
foreach (var parent in inline.FindParentOfType<LinkDelimiterInline>())
{
if (parent.IsImage)
if (inline is LinkDelimiterInline linkInline)
{
break;
if (linkInline.IsImage)
{
break;
}
linkInline.IsActive = false;
}
parent.IsActive = false;
inline = inline.Parent;
}
}
}

View File

@@ -41,14 +41,14 @@ namespace Markdig.Parsers
{
if (itemParser.OpeningCharacters == null)
{
throw new InvalidOperationException($"The list item parser of type [{itemParser.GetType()}] cannot have OpeningCharacters to null. It must define a list of valid opening characters");
ThrowHelper.InvalidOperationException($"The list item parser of type [{itemParser.GetType()}] cannot have OpeningCharacters to null. It must define a list of valid opening characters");
}
foreach (var openingCharacter in itemParser.OpeningCharacters)
{
if (tempMap.ContainsKey(openingCharacter))
{
throw new InvalidOperationException(
ThrowHelper.InvalidOperationException(
$"A list item parser with the same opening character `{openingCharacter}` is already registered");
}
tempMap.Add(openingCharacter, itemParser);
@@ -101,9 +101,8 @@ namespace Markdig.Parsers
// TODO: Check with specs, it is not clear that list marker or bullet marker must be followed by at least 1 space
// If we have already a ListItemBlock, we are going to try to append to it
var listItem = block as ListItemBlock;
result = BlockState.None;
if (listItem != null)
if (block is ListItemBlock listItem)
{
result = TryContinueListItem(processor, listItem);
}
@@ -123,12 +122,9 @@ namespace Markdig.Parsers
// Allow all blanks lines if the last block is a fenced code block
// Allow 1 blank line inside a list
// If > 1 blank line, terminate this list
var isBlankLine = state.IsBlankLine;
var isCurrentBlockBreakable = state.CurrentBlock != null && state.CurrentBlock.IsBreakable;
if (isBlankLine)
if (state.IsBlankLine)
{
if (isCurrentBlockBreakable)
if (state.CurrentBlock != null && state.CurrentBlock.IsBreakable)
{
if (!(state.NextContinue is ListBlock))
{
@@ -195,21 +191,21 @@ namespace Markdig.Parsers
var c = state.CurrentChar;
var itemParser = mapItemParsers[c];
bool isOrdered = itemParser is OrderedListItemParser;
if (itemParser == null)
{
return BlockState.None;
}
// Try to parse the list item
ListInfo listInfo;
if (!itemParser.TryParse(state, currentParent?.BulletType ?? '\0', out listInfo))
if (!itemParser.TryParse(state, currentParent?.BulletType ?? '\0', out ListInfo listInfo))
{
// Reset to an a start position
state.GoToColumn(initColumn);
return BlockState.None;
}
bool isOrdered = itemParser is OrderedListItemParser;
// Gets the current character after a successful parsing of the list information
c = state.CurrentChar;
@@ -251,11 +247,10 @@ namespace Markdig.Parsers
// Starts/continue the list unless:
// - an empty list item follows a paragraph
// - an ordered list is not starting by '1'
var isPreviousParagraph = (block ?? state.LastBlock) is ParagraphBlock;
if (isPreviousParagraph)
if ((block ?? state.LastBlock) is ParagraphBlock previousParagraph)
{
var isOpen = state.IsOpen(block ?? state.LastBlock);
if (state.IsBlankLine || (isOpen && listInfo.BulletType == '1' && listInfo.OrderedStart != "1"))
if (state.IsBlankLine ||
state.IsOpen(previousParagraph) && listInfo.BulletType == '1' && listInfo.OrderedStart != "1")
{
state.GoToColumn(initColumn);
return BlockState.None;
@@ -310,64 +305,47 @@ namespace Markdig.Parsers
public override bool Close(BlockProcessor processor, Block blockToClose)
{
var listBlock = blockToClose as ListBlock;
// Process only if we have blank lines
if (listBlock == null || listBlock.CountAllBlankLines <= 0)
if (blockToClose is ListBlock listBlock && listBlock.CountAllBlankLines > 0)
{
return true;
}
// TODO: This code is UGLY and WAY TOO LONG, simplify!
bool isLastListItem = true;
for (int listIndex = listBlock.Count - 1; listIndex >= 0; listIndex--)
{
var block = listBlock[listIndex];
var listItem = (ListItemBlock) block;
bool isLastElement = true;
for (int i = listItem.Count - 1; i >= 0; i--)
if (listBlock.Parent is ListItemBlock parentListItemBlock &&
listBlock.LastChild is ListItemBlock lastListItem &&
lastListItem.LastChild is BlankLineBlock)
{
var item = listItem[i];
if (item is BlankLineBlock)
// Inform the outer list that we have a blank line
var parentList = (ListBlock)parentListItemBlock.Parent;
parentList.CountAllBlankLines++;
parentListItemBlock.Add(new BlankLineBlock());
}
for (int listIndex = listBlock.Count - 1; listIndex >= 0; listIndex--)
{
var listItem = (ListItemBlock)listBlock[listIndex];
for (int i = listItem.Count - 1; i >= 0; i--)
{
if ((isLastElement && listIndex < listBlock.Count - 1) || (listItem.Count > 2 && (i > 0 && i < (listItem.Count - 1))))
if (listItem[i] is BlankLineBlock)
{
listBlock.IsLoose = true;
}
if (isLastElement && isLastListItem)
{
// Inform the outer list that we have a blank line
var parentListItemBlock = listBlock.Parent as ListItemBlock;
if (parentListItemBlock != null)
if (i == listItem.Count - 1 ? listIndex < listBlock.Count - 1 : i > 0)
{
var parentList = (ListBlock) parentListItemBlock.Parent;
listBlock.IsLoose = true;
}
parentList.CountAllBlankLines++;
parentListItemBlock.Add(new BlankLineBlock());
listItem.RemoveAt(i);
// If we have removed all blank lines, we can exit
listBlock.CountAllBlankLines--;
if (listBlock.CountAllBlankLines == 0)
{
goto done;
}
}
listItem.RemoveAt(i);
// If we have remove all blank lines, we can exit
listBlock.CountAllBlankLines--;
if (listBlock.CountAllBlankLines == 0)
{
break;
}
}
isLastElement = false;
}
isLastListItem = false;
}
//// Update end-position for the list
//if (listBlock.Count > 0)
//{
// listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
//}
done:
return true;
}
}

View File

@@ -42,8 +42,8 @@ namespace Markdig.Parsers
/// </exception>
private MarkdownParser(string text, MarkdownPipeline pipeline, MarkdownParserContext context)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
if (text == null) ThrowHelper.ArgumentNullException_text();
if (pipeline == null) ThrowHelper.ArgumentNullException(nameof(pipeline));
roughLineCountEstimate = text.Length / 40;
text = FixupZero(text);
@@ -51,15 +51,13 @@ namespace Markdig.Parsers
preciseSourceLocation = pipeline.PreciseSourceLocation;
// Initialize the pipeline
var stringBuilderCache = pipeline.StringBuilderCache ?? new StringBuilderCache();
document = new MarkdownDocument();
// Initialize the block parsers
blockProcessor = new BlockProcessor(stringBuilderCache, document, pipeline.BlockParsers, context);
blockProcessor = new BlockProcessor(document, pipeline.BlockParsers, context);
// Initialize the inline parsers
inlineProcessor = new InlineProcessor(stringBuilderCache, document, pipeline.InlineParsers, pipeline.PreciseSourceLocation, context)
inlineProcessor = new InlineProcessor(document, pipeline.InlineParsers, pipeline.PreciseSourceLocation, context)
{
DebugLog = pipeline.DebugLog
};
@@ -77,8 +75,8 @@ namespace Markdig.Parsers
/// <exception cref="System.ArgumentNullException">if reader variable is null</exception>
public static MarkdownDocument Parse(string text, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
{
if (text == null) throw new ArgumentNullException(nameof(text));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
if (text == null) ThrowHelper.ArgumentNullException_text();
pipeline ??= new MarkdownPipelineBuilder().Build();
// Perform the parsing
var markdownParser = new MarkdownParser(text, pipeline, context);
@@ -116,11 +114,11 @@ namespace Markdig.Parsers
var lineText = lineReader.ReadLine();
// If this is the end of file and the last line is empty
if (lineText == null)
if (lineText.Text is null)
{
break;
}
blockProcessor.ProcessLine(lineText.Value);
blockProcessor.ProcessLine(lineText);
}
blockProcessor.CloseAll(true);
}
@@ -131,7 +129,7 @@ namespace Markdig.Parsers
/// <param name="text">The text to secure.</param>
private string FixupZero(string text)
{
return text.Replace('\0', CharHelper.ZeroSafeChar);
return text.Replace('\0', CharHelper.ReplacementChar);
}
private sealed class ContainerItemCache : DefaultObjectCache<ContainerItem>

View File

@@ -33,9 +33,9 @@ namespace Markdig.Parsers
{
// Check if we have an ordered delimiter
orderedDelimiter = state.CurrentChar;
for (int i = 0; i < OrderedDelimiters.Length; i++)
foreach (char delimiter in OrderedDelimiters)
{
if (OrderedDelimiters[i] == orderedDelimiter)
if (delimiter == orderedDelimiter)
{
state.NextChar();
return true;

View File

@@ -50,23 +50,24 @@ namespace Markdig.Parsers
{
if (block is ParagraphBlock paragraph)
{
TryMatchLinkReferenceDefinition(ref paragraph.Lines, processor);
ref var lines = ref paragraph.Lines;
TryMatchLinkReferenceDefinition(ref lines, processor);
int lineCount = lines.Count;
// If Paragraph is empty, we can discard it
if (paragraph.Lines.Count == 0)
if (lineCount == 0)
{
return false;
}
var lineCount = paragraph.Lines.Count;
for (int i = 0; i < lineCount; i++)
{
paragraph.Lines.Lines[i].Slice.TrimStart();
}
if (lineCount > 0)
{
paragraph.Lines.Lines[lineCount - 1].Slice.TrimEnd();
lines.Lines[i].Slice.TrimStart();
}
lines.Lines[lineCount - 1].Slice.TrimEnd();
}
return true;
@@ -74,48 +75,14 @@ namespace Markdig.Parsers
private BlockState TryParseSetexHeading(BlockProcessor state, Block block)
{
var paragraph = (ParagraphBlock) block;
var headingChar = (char)0;
bool checkForSpaces = false;
var line = state.Line;
var c = line.CurrentChar;
while (c != '\0')
{
if (headingChar == 0)
{
if (c == '=' || c == '-')
{
headingChar = c;
continue;
}
break;
}
if (checkForSpaces)
{
if (!c.IsSpaceOrTab())
{
headingChar = (char)0;
break;
}
}
else if (c != headingChar)
{
if (c.IsSpaceOrTab())
{
checkForSpaces = true;
}
else
{
headingChar = (char)0;
break;
}
}
c = line.NextChar();
}
char headingChar = GetHeadingChar(ref line);
if (headingChar != 0)
{
var paragraph = (ParagraphBlock)block;
// If we matched a LinkReferenceDefinition before matching the heading, and the remaining
// lines are empty, we can early exit and remove the paragraph
if (!(TryMatchLinkReferenceDefinition(ref paragraph.Lines, state) && paragraph.Lines.Count == 0))
@@ -123,7 +90,7 @@ namespace Markdig.Parsers
// We discard the paragraph that will be transformed to a heading
state.Discard(paragraph);
var level = headingChar == '=' ? 1 : 2;
int level = headingChar == '=' ? 1 : 2;
var heading = new HeadingBlock(this)
{
@@ -146,7 +113,37 @@ namespace Markdig.Parsers
return BlockState.Continue;
}
private bool TryMatchLinkReferenceDefinition(ref StringLineGroup lines, BlockProcessor state)
private static char GetHeadingChar(ref StringSlice line)
{
char c = line.CurrentChar;
if (c == '=' || c == '-')
{
char headingChar = c;
while ((c = line.NextChar()) == headingChar)
{
}
if (c == '\0')
{
return headingChar;
}
while ((c = line.NextChar()).IsSpaceOrTab())
{
}
if (c == '\0')
{
return headingChar;
}
}
return (char)0;
}
private static bool TryMatchLinkReferenceDefinition(ref StringLineGroup lines, BlockProcessor state)
{
bool atLeastOneFound = false;

View File

@@ -29,7 +29,7 @@ namespace Markdig.Parsers
var parser = this[i];
if (parser == null)
{
throw new InvalidOperationException("Unexpected null parser found");
ThrowHelper.InvalidOperationException("Unexpected null parser found");
}
parser.Initialize();
@@ -100,8 +100,8 @@ namespace Markdig.Parsers
/// </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>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T[] GetParsersForOpeningCharacter(char openingChar)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] GetParsersForOpeningCharacter(uint openingChar)
{
return charMap[openingChar];
}
@@ -113,7 +113,7 @@ namespace Markdig.Parsers
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns>Index position within the string of the first opening character found in the specified text; if not found, returns -1</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int IndexOfOpeningCharacter(string text, int start, int end)
{
return charMap.IndexOfOpeningCharacter(text, start, end);

View File

@@ -23,9 +23,10 @@ namespace Markdig.Renderers.Html
protected override void Write(HtmlRenderer renderer, HeadingBlock obj)
{
var headingText = obj.Level > 0 && obj.Level <= 6
? HeadingTexts[obj.Level - 1]
: "<h" + obj.Level.ToString(CultureInfo.InvariantCulture);
int index = obj.Level - 1;
string headingText = ((uint)index < (uint)HeadingTexts.Length)
? HeadingTexts[index]
: "h" + obj.Level.ToString(CultureInfo.InvariantCulture);
if (renderer.EnableHtmlForBlock)
{

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Markdig.Helpers;
using Markdig.Syntax;
namespace Markdig.Renderers.Html
@@ -41,7 +42,7 @@ namespace Markdig.Renderers.Html
/// <param name="name">The css class name.</param>
public void AddClass(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (name == null) ThrowHelper.ArgumentNullException_name();
if (Classes == null)
{
Classes = new List<string>(2);
@@ -61,7 +62,7 @@ namespace Markdig.Renderers.Html
/// <param name="value">The value.</param>
public void AddProperty(string name, string value)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (name == null) ThrowHelper.ArgumentNullException_name();
if (Properties == null)
{
Properties = new List<KeyValuePair<string, string>>(2); // Use half list compare to default capacity (4), as we don't expect lots of classes
@@ -76,7 +77,7 @@ namespace Markdig.Renderers.Html
/// <param name="value">The value.</param>
public void AddPropertyIfNotExist(string name, object value)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (name == null) ThrowHelper.ArgumentNullException_name();
if (Properties == null)
{
Properties = new List<KeyValuePair<string, string>>(4);
@@ -104,7 +105,7 @@ namespace Markdig.Renderers.Html
/// <exception cref="System.ArgumentNullException"></exception>
public void CopyTo(HtmlAttributes htmlAttributes, bool mergeIdAndProperties = false, bool shared = true)
{
if (htmlAttributes == null) throw new ArgumentNullException(nameof(htmlAttributes));
if (htmlAttributes == null) ThrowHelper.ArgumentNullException(nameof(htmlAttributes));
// Add html htmlAttributes to the object
if (!mergeIdAndProperties || Id != null)
{

View File

@@ -91,7 +91,7 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="content">The content.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HtmlRenderer WriteEscape(string content)
{
if (string.IsNullOrEmpty(content))
@@ -107,7 +107,7 @@ namespace Markdig.Renderers
/// <param name="slice">The slice.</param>
/// <param name="softEscape">Only escape &lt; and &amp;</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HtmlRenderer WriteEscape(ref StringSlice slice, bool softEscape = false)
{
if (slice.Start > slice.End)
@@ -123,7 +123,7 @@ namespace Markdig.Renderers
/// <param name="slice">The slice.</param>
/// <param name="softEscape">Only escape &lt; and &amp;</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HtmlRenderer WriteEscape(StringSlice slice, bool softEscape = false)
{
return WriteEscape(ref slice, softEscape);
@@ -318,12 +318,12 @@ namespace Markdig.Renderers
/// <summary>
/// Writes the attached <see cref="HtmlAttributes"/> on the specified <see cref="MarkdownObject"/>.
/// </summary>
/// <param name="obj">The object.</param>
/// <param name="markdownObject">The object.</param>
/// <returns></returns>
public HtmlRenderer WriteAttributes(MarkdownObject obj)
public HtmlRenderer WriteAttributes(MarkdownObject markdownObject)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
return WriteAttributes(obj.TryGetAttributes());
if (markdownObject == null) ThrowHelper.ArgumentNullException_markdownObject();
return WriteAttributes(markdownObject.TryGetAttributes());
}
/// <summary>
@@ -383,7 +383,7 @@ namespace Markdig.Renderers
/// <returns>This instance</returns>
public HtmlRenderer WriteLeafRawLines(LeafBlock leafBlock, bool writeEndOfLines, bool escape, bool softEscape = false)
{
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
if (leafBlock == null) ThrowHelper.ArgumentNullException_leafBlock();
if (leafBlock.Lines.Lines != null)
{
var lines = leafBlock.Lines;

View File

@@ -6,6 +6,7 @@ using System;
using System.IO;
using Markdig.Syntax;
using Markdig.Renderers.Normalize.Inlines;
using Markdig.Helpers;
namespace Markdig.Renderers.Normalize
{
@@ -130,7 +131,7 @@ namespace Markdig.Renderers.Normalize
/// <returns>This instance</returns>
public NormalizeRenderer WriteLeafRawLines(LeafBlock leafBlock, bool writeEndOfLines, bool indent = false)
{
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
if (leafBlock == null) ThrowHelper.ArgumentNullException_leafBlock();
if (leafBlock.Lines.Lines != null)
{
var lines = leafBlock.Lines;

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;
@@ -56,19 +56,19 @@ namespace Markdig.Renderers
return;
}
bool saveIsFirstInContainer = IsFirstInContainer;
bool saveIsLastInContainer = IsLastInContainer;
var children = containerBlock;
for (int i = 0; i < children.Count; i++)
{
var saveIsFirstInContainer = IsFirstInContainer;
var saveIsLastInContainer = IsLastInContainer;
IsFirstInContainer = i == 0;
IsLastInContainer = i + 1 == children.Count;
Write(children[i]);
IsFirstInContainer = saveIsFirstInContainer;
IsLastInContainer = saveIsLastInContainer;
}
IsFirstInContainer = saveIsFirstInContainer;
IsLastInContainer = saveIsLastInContainer;
}
/// <summary>
@@ -82,46 +82,50 @@ namespace Markdig.Renderers
return;
}
bool saveIsFirstInContainer = IsFirstInContainer;
bool saveIsLastInContainer = IsLastInContainer;
bool isFirst = true;
var inline = containerInline.FirstChild;
while (inline != null)
{
var saveIsFirstInContainer = IsFirstInContainer;
var saveIsLastInContainer = IsLastInContainer;
IsFirstInContainer = isFirst;
IsLastInContainer = inline.NextSibling == null;
Write(inline);
inline = inline.NextSibling;
IsFirstInContainer = saveIsFirstInContainer;
IsLastInContainer = saveIsLastInContainer;
isFirst = false;
}
IsFirstInContainer = saveIsFirstInContainer;
IsLastInContainer = saveIsLastInContainer;
}
/// <summary>
/// Writes the specified Markdown object.
/// </summary>
/// <typeparam name="T">A MarkdownObject type</typeparam>
/// <param name="obj">The Markdown object to write to this renderer.</param>
public void Write<T>(T obj) where T : MarkdownObject
public void Write(MarkdownObject obj)
{
if (obj == null)
{
return;
}
// Calls before writing an object
ObjectWriteBefore?.Invoke(this, obj);
var objectType = obj.GetType();
// Calls before writing an object
var writeBefore = ObjectWriteBefore;
writeBefore?.Invoke(this, obj);
IMarkdownObjectRenderer renderer;
// Handle regular renderers
IMarkdownObjectRenderer renderer = previousObjectType == objectType ? previousRenderer : null;
if (renderer == null && !renderersPerType.TryGetValue(objectType, out renderer))
if (objectType == previousObjectType)
{
renderer = previousRenderer;
}
else if (!renderersPerType.TryGetValue(objectType, out renderer))
{
for (int i = 0; i < ObjectRenderers.Count; i++)
{
@@ -133,33 +137,25 @@ namespace Markdig.Renderers
}
}
}
if (renderer != null)
{
renderer.Write(this, obj);
}
else
{
var containerBlock = obj as ContainerBlock;
if (containerBlock != null)
{
WriteChildren(containerBlock);
}
else
{
var containerInline = obj as ContainerInline;
if (containerInline != null)
{
WriteChildren(containerInline);
}
}
}
previousObjectType = objectType;
previousRenderer = renderer;
previousObjectType = objectType;
previousRenderer = renderer;
}
else if (obj is ContainerBlock containerBlock)
{
WriteChildren(containerBlock);
}
else if (obj is ContainerInline containerInline)
{
WriteChildren(containerInline);
}
// Calls after writing an object
var writeAfter = ObjectWriteAfter;
writeAfter?.Invoke(this, obj);
ObjectWriteAfter?.Invoke(this, obj);
}
}
}

View File

@@ -26,7 +26,7 @@ namespace Markdig.Renderers
/// <exception cref="System.ArgumentNullException"></exception>
protected TextRendererBase(TextWriter writer)
{
if (writer == null) throw new ArgumentNullException(nameof(writer));
if (writer == null) ThrowHelper.ArgumentNullException_writer();
this.Writer = writer;
// By default we output a newline with '\n' only even on Windows platforms
Writer.NewLine = "\n";
@@ -43,7 +43,7 @@ namespace Markdig.Renderers
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
ThrowHelper.ArgumentNullException(nameof(value));
}
writer = value;
@@ -97,7 +97,7 @@ namespace Markdig.Renderers
}
else
{
throw new InvalidOperationException("Cannot reset this TextWriter instance");
ThrowHelper.InvalidOperationException("Cannot reset this TextWriter instance");
}
previousWasLine = true;
@@ -119,7 +119,7 @@ namespace Markdig.Renderers
public void PushIndent(string indent)
{
if (indent == null) throw new ArgumentNullException(nameof(indent));
if (indent == null) ThrowHelper.ArgumentNullException(nameof(indent));
indents.Add(indent);
}
@@ -147,7 +147,7 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="content">The content.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Write(string content)
{
WriteIndent();
@@ -161,7 +161,7 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="slice">The slice.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Write(ref StringSlice slice)
{
if (slice.Start > slice.End)
@@ -176,7 +176,7 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="slice">The slice.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Write(StringSlice slice)
{
return Write(ref slice);
@@ -187,7 +187,7 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="content">The content.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Write(char content)
{
WriteIndent();
@@ -241,7 +241,7 @@ namespace Markdig.Renderers
/// Writes a newline.
/// </summary>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T WriteLine()
{
WriteIndent();
@@ -255,7 +255,7 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="content">The content.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T WriteLine(string content)
{
WriteIndent();
@@ -269,10 +269,10 @@ namespace Markdig.Renderers
/// </summary>
/// <param name="leafBlock">The leaf block.</param>
/// <returns>This instance</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T WriteLeafInline(LeafBlock leafBlock)
{
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
if (leafBlock == null) ThrowHelper.ArgumentNullException_leafBlock();
var inline = (Inline) leafBlock.Inline;
if (inline != null)
{

View File

@@ -27,7 +27,7 @@ namespace Markdig.Syntax
/// <param name="parser">The parser used to create this block.</param>
protected ContainerBlock(BlockParser parser) : base(parser)
{
children = ArrayHelper<Block>.Empty;
children = Array.Empty<Block>();
}
/// <summary>
@@ -56,10 +56,12 @@ namespace Markdig.Syntax
public void Add(Block item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
if (item == null)
ThrowHelper.ArgumentNullException_item();
if (item.Parent != null)
{
throw new ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
ThrowHelper.ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
}
if (Count == children.Length)
@@ -104,7 +106,9 @@ namespace Markdig.Syntax
public bool Contains(Block item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
if (item == null)
ThrowHelper.ArgumentNullException_item();
for (int i = 0; i < Count; i++)
{
if (children[i] == item)
@@ -122,7 +126,9 @@ namespace Markdig.Syntax
public bool Remove(Block item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
if (item == null)
ThrowHelper.ArgumentNullException_item();
for (int i = Count - 1; i >= 0; i--)
{
if (children[i] == item)
@@ -140,7 +146,9 @@ namespace Markdig.Syntax
public int IndexOf(Block item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
if (item == null)
ThrowHelper.ArgumentNullException_item();
for (int i = 0; i < Count; i++)
{
if (children[i] == item)
@@ -153,10 +161,16 @@ namespace Markdig.Syntax
public void Insert(int index, Block item)
{
if (item == null) throw new ArgumentNullException(nameof(item));
if (item == null)
ThrowHelper.ArgumentNullException_item();
if (item.Parent != null)
{
throw new ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
ThrowHelper.ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
}
if ((uint)index > (uint)Count)
{
ThrowHelper.ArgumentOutOfRangeException_index();
}
if (Count == children.Length)
{
@@ -173,7 +187,9 @@ namespace Markdig.Syntax
public void RemoveAt(int index)
{
if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index));
if ((uint)index > (uint)Count)
ThrowHelper.ArgumentOutOfRangeException_index();
Count--;
// previous children
var item = children[index];
@@ -206,13 +222,13 @@ namespace Markdig.Syntax
public void Sort(IComparer<Block> comparer)
{
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (comparer == null) ThrowHelper.ArgumentNullException(nameof(comparer));
Array.Sort(children, 0, Count, comparer);
}
public void Sort(Comparison<Block> comparison)
{
if (comparison == null) throw new ArgumentNullException(nameof(comparison));
if (comparison == null) ThrowHelper.ArgumentNullException(nameof(comparison));
Array.Sort(children, 0, Count, new BlockComparer(comparison));
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
using System.Collections;
@@ -50,10 +51,10 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentException">Inline has already a parent</exception>
public virtual ContainerInline AppendChild(Inline child)
{
if (child == null) throw new ArgumentNullException(nameof(child));
if (child == null) ThrowHelper.ArgumentNullException(nameof(child));
if (child.Parent != null)
{
throw new ArgumentException("Inline has already a parent", nameof(child));
ThrowHelper.ArgumentException("Inline has already a parent", nameof(child));
}
if (FirstChild == null)
@@ -98,7 +99,7 @@ namespace Markdig.Syntax.Inlines
{
if (FirstChild is null)
{
return ArrayHelper<T>.Empty;
return Array.Empty<T>();
}
else
{
@@ -107,9 +108,7 @@ namespace Markdig.Syntax.Inlines
}
internal IEnumerable<T> FindDescendantsInternal<T>() where T : MarkdownObject
{
#if !UAP
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
#endif
Stack<Inline> stack = new Stack<Inline>();
@@ -147,7 +146,7 @@ namespace Markdig.Syntax.Inlines
/// <param name="parent">The parent.</param>
public void MoveChildrenAfter(Inline parent)
{
if (parent == null) throw new ArgumentNullException(nameof(parent));
if (parent == null) ThrowHelper.ArgumentNullException(nameof(parent));
var child = FirstChild;
var nextSibling = parent;
while (child != null)
@@ -168,7 +167,7 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentNullException">If the container is null</exception>
public void EmbraceChildrenBy(ContainerInline container)
{
if (container == null) throw new ArgumentNullException(nameof(container));
if (container == null) ThrowHelper.ArgumentNullException(nameof(container));
var child = FirstChild;
while (child != null)
{
@@ -240,7 +239,7 @@ namespace Markdig.Syntax.Inlines
public Enumerator(ContainerInline container) : this()
{
if (container == null) throw new ArgumentNullException(nameof(container));
if (container == null) ThrowHelper.ArgumentNullException(nameof(container));
this.container = container;
currentChild = nextChild = container.FirstChild;
}

View File

@@ -17,7 +17,7 @@ namespace Markdig.Syntax.Inlines
{
protected DelimiterInline(InlineParser parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (parser == null) ThrowHelper.ArgumentNullException(nameof(parser));
Parser = parser;
IsActive = true;
}

View File

@@ -23,7 +23,10 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="ArgumentNullException"></exception>
public EmphasisDelimiterInline(InlineParser parser, EmphasisDescriptor descriptor) : base(parser)
{
Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor));
if (descriptor is null)
ThrowHelper.ArgumentNullException(nameof(descriptor));
Descriptor = descriptor;
DelimiterChar = descriptor.Character;
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Markdig.Helpers;
using Markdig.Parsers;
namespace Markdig.Syntax.Inlines
@@ -42,10 +43,10 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentException">Inline has already a parent</exception>
public void InsertAfter(Inline next)
{
if (next == null) throw new ArgumentNullException(nameof(next));
if (next == null) ThrowHelper.ArgumentNullException(nameof(next));
if (next.Parent != null)
{
throw new ArgumentException("Inline has already a parent", nameof(next));
ThrowHelper.ArgumentException("Inline has already a parent", nameof(next));
}
var previousNext = NextSibling;
@@ -73,10 +74,10 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentException">Inline has already a parent</exception>
public void InsertBefore(Inline previous)
{
if (previous == null) throw new ArgumentNullException(nameof(previous));
if (previous == null) ThrowHelper.ArgumentNullException(nameof(previous));
if (previous.Parent != null)
{
throw new ArgumentException("Inline has already a parent", nameof(previous));
ThrowHelper.ArgumentException("Inline has already a parent", nameof(previous));
}
var previousSibling = PreviousSibling;
@@ -129,7 +130,7 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentNullException">If inline is null</exception>
public Inline ReplaceBy(Inline inline, bool copyChildren = true)
{
if (inline == null) throw new ArgumentNullException(nameof(inline));
if (inline == null) ThrowHelper.ArgumentNullException(nameof(inline));
// Save sibling
var parent = Parent;
@@ -271,7 +272,7 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentNullException"></exception>
public void DumpTo(TextWriter writer)
{
if (writer == null) throw new ArgumentNullException(nameof(writer));
if (writer == null) ThrowHelper.ArgumentNullException_writer();
DumpTo(writer, 0);
}
@@ -283,7 +284,7 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentNullException">if writer is null</exception>
public void DumpTo(TextWriter writer, int level)
{
if (writer == null) throw new ArgumentNullException(nameof(writer));
if (writer == null) ThrowHelper.ArgumentNullException_writer();
for (int i = 0; i < level; i++)
{
writer.Write(' ');

View File

@@ -39,7 +39,7 @@ namespace Markdig.Syntax.Inlines
/// <exception cref="System.ArgumentNullException"></exception>
public LiteralInline(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
if (text == null) ThrowHelper.ArgumentNullException_text();
Content = new StringSlice(text);
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
using System.Collections.Generic;
@@ -15,7 +16,7 @@ namespace Markdig.Syntax
public static bool ContainsLinkReferenceDefinition(this MarkdownDocument document, string label)
{
if (label == null) throw new ArgumentNullException(nameof(label));
if (label == null) ThrowHelper.ArgumentNullException_label();
var references = document.GetData(DocumentKey) as LinkReferenceDefinitionGroup;
if (references == null)
{
@@ -26,14 +27,14 @@ namespace Markdig.Syntax
public static void SetLinkReferenceDefinition(this MarkdownDocument document, string label, LinkReferenceDefinition linkReferenceDefinition)
{
if (label == null) throw new ArgumentNullException(nameof(label));
if (label == null) ThrowHelper.ArgumentNullException_label();
var references = document.GetLinkReferenceDefinitions();
references.Set(label, linkReferenceDefinition);
}
public static bool TryGetLinkReferenceDefinition(this MarkdownDocument document, string label, out LinkReferenceDefinition linkReferenceDefinition)
{
if (label == null) throw new ArgumentNullException(nameof(label));
if (label == null) ThrowHelper.ArgumentNullException_label();
linkReferenceDefinition = null;
var references = document.GetData(DocumentKey) as LinkReferenceDefinitionGroup;
if (references == null)

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
using System.Collections.Generic;
@@ -28,7 +29,7 @@ namespace Markdig.Syntax
public void Set(string label, LinkReferenceDefinition link)
{
if (link == null) throw new ArgumentNullException(nameof(link));
if (link == null) ThrowHelper.ArgumentNullException(nameof(link));
if (!Contains(link))
{
Add(link);

View File

@@ -1,6 +1,7 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
namespace Markdig.Syntax
@@ -55,7 +56,7 @@ namespace Markdig.Syntax
/// <exception cref="System.ArgumentNullException">if key is null</exception>
public void SetData(object key, object value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
attachedDatas = new DataEntry[1];
@@ -89,7 +90,7 @@ namespace Markdig.Syntax
/// <exception cref="System.ArgumentNullException">if key is null</exception>
public bool ContainsData(object key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
return false;
@@ -113,7 +114,7 @@ namespace Markdig.Syntax
/// <exception cref="System.ArgumentNullException">if key is null</exception>
public object GetData(object key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
return null;
@@ -136,7 +137,7 @@ namespace Markdig.Syntax
/// <exception cref="System.ArgumentNullException"></exception>
public bool RemoveData(object key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (key == null) ThrowHelper.ArgumentNullException_key();
if (attachedDatas == null)
{
return true;

View File

@@ -2,9 +2,9 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Markdig.Helpers;
using Markdig.Syntax.Inlines;
namespace Markdig.Syntax
@@ -72,15 +72,6 @@ namespace Markdig.Syntax
/// <returns>An iteration over the descendant elements</returns>
public static IEnumerable<T> Descendants<T>(this MarkdownObject markdownObject) where T : MarkdownObject
{
#if UAP
foreach (MarkdownObject descendant in markdownObject.Descendants())
{
if (descendant is T descendantT)
{
yield return descendantT;
}
}
#else
if (typeof(T).IsSubclassOf(typeof(Block)))
{
if (markdownObject is ContainerBlock containerBlock && containerBlock.Count > 0)
@@ -103,8 +94,7 @@ namespace Markdig.Syntax
}
}
return ArrayHelper<T>.Empty;
#endif
return Array.Empty<T>();
}
/// <summary>
@@ -134,15 +124,13 @@ namespace Markdig.Syntax
}
else
{
return ArrayHelper<T>.Empty;
return Array.Empty<T>();
}
}
private static IEnumerable<T> BlockDescendantsInternal<T>(ContainerBlock block) where T : MarkdownObject
{
#if !UAP
Debug.Assert(typeof(T).IsSubclassOf(typeof(Block)));
#endif
Stack<Block> stack = new Stack<Block>();
@@ -173,9 +161,7 @@ namespace Markdig.Syntax
private static IEnumerable<T> InlineDescendantsInternal<T>(ContainerBlock block) where T : MarkdownObject
{
#if !UAP
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
#endif
foreach (MarkdownObject descendant in block.Descendants())
{

View File

@@ -55,8 +55,7 @@ namespace Markdig.Syntax
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is SourceSpan && Equals((SourceSpan) obj);
return obj is SourceSpan sourceSpan && Equals(sourceSpan);
}
public override int GetHashCode()

View File

@@ -137,7 +137,7 @@ namespace SpecFileGen
if (anyChanged && Environment.GetEnvironmentVariable("CI") != null)
{
EmitError("Run SpecFileGen when changing specification files");
EmitError("Error - Specification files have changed. You must run SpecFileGen when changing specification files.");
Environment.Exit(1);
}

View File

@@ -2,7 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
</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("UnicodeNormDApp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("UnicodeNormDApp")]
[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("33ffc0b9-0187-44f9-9424-bb5af5b4fb84")]
// 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,56 +1,7 @@
<?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>{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UnicodeNormDApp</RootNamespace>
<AssemblyName>UnicodeNormDApp</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</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>

View File

@@ -5,8 +5,8 @@ VisualStudioVersion = 16.0.28407.52
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2-005C-4D13-A338-EA464BBEC60F}"
ProjectSection(SolutionItems) = preProject
..\appveyor.yml = ..\appveyor.yml
..\changelog.md = ..\changelog.md
..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml
..\license.txt = ..\license.txt
..\readme.md = ..\readme.md
EndProjectSection
@@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig", "Markdig\Markdig.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Signed", "Markdig.Signed\Markdig.Signed.csproj", "{C37C7B94-1219-4ED9-ABAC-C0B4B8FE8750}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdig.Tests", "Markdig.Tests\Markdig.Tests.csproj", "{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Tests", "Markdig.Tests\Markdig.Tests.csproj", "{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}"
ProjectSection(ProjectDependencies) = postProject
{8A58A7E2-627C-4F41-933F-5AC92ADFAB48} = {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}
EndProjectSection
@@ -27,11 +27,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Benchmarks", "Markd
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.WebApp", "Markdig.WebApp\Markdig.WebApp.csproj", "{3CAD9801-9976-46BE-BACA-F6D0D21FDC00}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnicodeNormDApp", "UnicodeNormDApp\UnicodeNormDApp.csproj", "{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnicodeNormDApp", "UnicodeNormDApp\UnicodeNormDApp.csproj", "{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mdtoc", "mdtoc\mdtoc.csproj", "{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mdtoc", "mdtoc\mdtoc.csproj", "{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpecFileGen", "SpecFileGen\SpecFileGen.csproj", "{DB6E2ED5-7884-4E97-84AF-35E2480CF685}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpecFileGen", "SpecFileGen\SpecFileGen.csproj", "{DB6E2ED5-7884-4E97-84AF-35E2480CF685}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

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

View File

@@ -1,39 +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.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("mdtoc")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Alexandre Mutel")]
[assembly: AssemblyProduct("mdtoc")]
[assembly: AssemblyCopyright("Copyright © 2017 - Alexandre Mutel")]
[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("e3cdff0f-5bfc-42e9-bdba-2797651900a2")]
// 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,58 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" 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>{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>mdtoc</RootNamespace>
<AssemblyName>mdtoc</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<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" />
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj">
<Project>{8a58a7e2-627c-4f41-933f-5ac92adfab48}</Project>
<Name>Markdig</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>