mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-10 05:49:27 +00:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ef1d735d5 | ||
|
|
47c6c49f5c | ||
|
|
a19f78342f | ||
|
|
edb4c6c3cb | ||
|
|
4b6d7c78f5 | ||
|
|
0386cfd617 | ||
|
|
5c52a7249d | ||
|
|
14fb550704 | ||
|
|
741d09a4e8 | ||
|
|
34d9fa2bcc | ||
|
|
8ee50a265d | ||
|
|
eb002db3c7 | ||
|
|
a15203a061 | ||
|
|
b0a2f19ed7 | ||
|
|
a9d78e04a1 | ||
|
|
a8b6e2c2e4 | ||
|
|
665f5f5e51 | ||
|
|
86a72c3582 | ||
|
|
784b999d6c | ||
|
|
be3a893d3d | ||
|
|
2679c84788 | ||
|
|
3d005d6444 | ||
|
|
09593ff3da | ||
|
|
f0269fc61f | ||
|
|
2d97628cfd | ||
|
|
ee97d32ef0 | ||
|
|
9ca5cdcb31 | ||
|
|
5f453fbe92 | ||
|
|
8300a9cca2 | ||
|
|
f7d763230d | ||
|
|
cce1b99edc | ||
|
|
2dca2119fa | ||
|
|
faf0a20816 | ||
|
|
68196c1ce6 | ||
|
|
fdf125d05d | ||
|
|
46133715ab | ||
|
|
4151ac2287 | ||
|
|
305f722899 | ||
|
|
abc8aa25b7 | ||
|
|
e6bb83ecdf | ||
|
|
e6ea8ad274 | ||
|
|
1f3cf5962f | ||
|
|
83b3427805 | ||
|
|
3ff3f1ccb9 | ||
|
|
5d91f0e26f | ||
|
|
9af462e412 | ||
|
|
e1ba52da20 | ||
|
|
70ee97d5c7 | ||
|
|
da916a1700 | ||
|
|
5548246703 | ||
|
|
097fc5c4e0 | ||
|
|
168ad39ff6 | ||
|
|
54e065aabe | ||
|
|
98d58797d9 | ||
|
|
2f0dd2a6a7 | ||
|
|
247209870e | ||
|
|
ef5b958267 | ||
|
|
7d61df2c0c | ||
|
|
51f9da1974 | ||
|
|
9321628b9c | ||
|
|
525e2c7fb8 | ||
|
|
1f9b70636f | ||
|
|
d4f43f826f | ||
|
|
62788b1101 | ||
|
|
c3c4d37b82 | ||
|
|
333bf04274 | ||
|
|
dab4b7176d | ||
|
|
25ec56e1be | ||
|
|
3455fd12da | ||
|
|
ae5d47a0ed | ||
|
|
1ff721d512 | ||
|
|
2c130504e2 | ||
|
|
6405b16692 | ||
|
|
2e1912a23d | ||
|
|
71c680388c | ||
|
|
3f50b3fd0b | ||
|
|
99b250b4c9 | ||
|
|
655bf13df0 | ||
|
|
d2c89a3a06 | ||
|
|
754d11fd44 | ||
|
|
1252e49ba4 | ||
|
|
d18487ae53 | ||
|
|
f401d5082e | ||
|
|
dd6418d108 | ||
|
|
7875e4bce9 | ||
|
|
2e1b1a1fdc | ||
|
|
fa2b157c1a | ||
|
|
e4e6406546 | ||
|
|
1cff10270a | ||
|
|
87023184cb | ||
|
|
aecdf2192e | ||
|
|
0a36382126 | ||
|
|
0057b368ec | ||
|
|
3033284096 | ||
|
|
9b1b791b18 | ||
|
|
2b7d205701 | ||
|
|
89b28659b1 | ||
|
|
446b1bcc0d | ||
|
|
ec7a4a6902 | ||
|
|
07a77142f4 | ||
|
|
3606f234b8 | ||
|
|
616eed62bd | ||
|
|
99f55e9ddc | ||
|
|
f8ab1cccc5 | ||
|
|
f24067cd16 | ||
|
|
9af96ba2b4 | ||
|
|
c99f7dd96a | ||
|
|
bf28cbd33f | ||
|
|
2040e23545 | ||
|
|
2761e36b6b | ||
|
|
891334134c | ||
|
|
0987fab6f2 | ||
|
|
ed5eea5e27 | ||
|
|
f73cbe4e76 | ||
|
|
aefad219cf | ||
|
|
76c3e88c58 | ||
|
|
afe4308e91 | ||
|
|
606556b692 | ||
|
|
253be5c362 | ||
|
|
33037d1034 | ||
|
|
f16ee828db | ||
|
|
a76305f39b | ||
|
|
f52a41e167 | ||
|
|
4d172bf905 | ||
|
|
890b2cda2a | ||
|
|
c818670919 | ||
|
|
a78a0b7016 | ||
|
|
6a0c9aeb47 | ||
|
|
b1cfcf2431 | ||
|
|
b411522a23 | ||
|
|
1d2977d47b | ||
|
|
ee8c87c357 | ||
|
|
25959174d5 | ||
|
|
033ddaf6a8 | ||
|
|
f3f7584c39 | ||
|
|
d00ca4acc1 | ||
|
|
d9663ef2e6 | ||
|
|
3106a49d02 | ||
|
|
a47a6890e7 | ||
|
|
7d21f8b003 | ||
|
|
15f6205adc | ||
|
|
e6dd2cf3d4 | ||
|
|
ea6592b773 | ||
|
|
5cd20efe3e | ||
|
|
31c7ba5862 | ||
|
|
0272840a62 | ||
|
|
8886b48634 | ||
|
|
64cd8ec262 | ||
|
|
a1e19912a9 | ||
|
|
ca51967fb1 | ||
|
|
bb3a4f372c | ||
|
|
1d6a464c5d | ||
|
|
0fe5c17a93 | ||
|
|
f879d55b4a | ||
|
|
6d3a3584ac |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [xoofx]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
61
.github/workflows/ci.yml
vendored
Normal file
61
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: ci
|
||||
|
||||
env:
|
||||
PROJECT_NAME: Markdig
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'doc/**'
|
||||
- 'img/**'
|
||||
- 'changelog.md'
|
||||
- 'readme.md'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 3.0.100
|
||||
|
||||
- name: Build (Release)
|
||||
run: dotnet build src -c Release
|
||||
|
||||
- name: SpecFileGen
|
||||
run: dotnet src/SpecFileGen/bin/Release/netcoreapp2.1/SpecFileGen.dll
|
||||
|
||||
- name: Test (Release)
|
||||
run: dotnet test src -c Release
|
||||
|
||||
- name: Build & Test (Debug)
|
||||
run: dotnet test src -c Debug
|
||||
|
||||
- name: Coverlet
|
||||
run: dotnet test src -c Release -f netcoreapp2.1 /p:Include=\"[${{env.PROJECT_NAME}}]*\" /p:CollectCoverage=true /p:CoverletOutputFormat=lcov
|
||||
|
||||
- name: Coveralls Upload
|
||||
uses: coverallsapp/github-action@v1.0.1
|
||||
if: github.event_name == 'push'
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: src/${{env.PROJECT_NAME}}.Tests/coverage.netcoreapp2.1.info
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack src -c Release
|
||||
|
||||
- name: Publish
|
||||
if: github.event_name == 'push'
|
||||
run: |
|
||||
if ( "${{github.ref}}" -match "^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$" ) {
|
||||
dotnet nuget push src\${{env.PROJECT_NAME}}\bin\Release\*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
|
||||
dotnet nuget push src\${{env.PROJECT_NAME}}.Signed\bin\Release\*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
|
||||
} else {
|
||||
echo "publish is only enabled by tagging with a release tag"
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
57
appveyor.yml
57
appveyor.yml
@@ -1,57 +0,0 @@
|
||||
version: 10.0.{build}
|
||||
image: Visual Studio 2017
|
||||
configuration: Release
|
||||
|
||||
install:
|
||||
- ps: >-
|
||||
cd src
|
||||
|
||||
nuget restore Markdig.sln
|
||||
|
||||
$env:MARKDIG_BUILD_NUMBER = ([int]$env:APPVEYOR_BUILD_NUMBER).ToString("000")
|
||||
|
||||
$env:MARKDIG_VERSION_SUFFIX = ""
|
||||
|
||||
$env:appveyor_nuget_push = 'false'
|
||||
|
||||
if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
|
||||
if($env:appveyor_repo_tag -eq 'True') {
|
||||
if($env:appveyor_repo_tag_name -match '^[0-9]') {
|
||||
$env:appveyor_nuget_push = 'true'
|
||||
$env:MARKDIG_VERSION_SUFFIX = ""
|
||||
}
|
||||
if($env:appveyor_repo_tag_name -eq 'latest') {
|
||||
$env:appveyor_nuget_push = 'true'
|
||||
$env:MARKDIG_VERSION_SUFFIX = "pre$env:MARKDIG_BUILD_NUMBER"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build:
|
||||
project: src/Markdig.sln
|
||||
verbosity: minimal
|
||||
|
||||
after_build:
|
||||
- cmd: >-
|
||||
dotnet run --project SpecFileGen/SpecFileGen.csproj -c Release
|
||||
|
||||
dotnet test -v n Markdig.Tests
|
||||
|
||||
before_package:
|
||||
- cmd: >-
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:Clean Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release;SignAssembly=true Markdig/Markdig.csproj
|
||||
|
||||
artifacts:
|
||||
- path: src\Markdig\Bin\Release\*.nupkg
|
||||
name: Markdig Nugets
|
||||
|
||||
deploy:
|
||||
- provider: NuGet
|
||||
api_key:
|
||||
secure: 7cthHh+wYWZjhqxaxR6QObRaRnstvFkQOY7MkxIsC5kpQEBlKZXuinf0IybbYxJt
|
||||
on:
|
||||
appveyor_nuget_push: true
|
||||
37
changelog.md
37
changelog.md
@@ -1,5 +1,42 @@
|
||||
# Changelog
|
||||
|
||||
## 0.20.0 (18 Apr 2020)
|
||||
- Markdig is now compatible only with `NETStandard 2.0`, `NETStandard 2.1`, `NETCoreApp 2.1` and `NETCoreApp 3.1`.
|
||||
- Many performance improvements from [PR #416](https://github.com/lunet-io/markdig/pull/416)
|
||||
[PR #417](https://github.com/lunet-io/markdig/pull/417)
|
||||
[PR #418](https://github.com/lunet-io/markdig/pull/418)
|
||||
[PR #421](https://github.com/lunet-io/markdig/pull/421)
|
||||
[PR #422](https://github.com/lunet-io/markdig/pull/422)
|
||||
[PR #410](https://github.com/lunet-io/markdig/pull/410)
|
||||
|
||||
## 0.18.3 (8 Mar 2020)
|
||||
- Publish NuGet Symbol packages
|
||||
|
||||
## 0.18.2 (8 Mar 2020)
|
||||
- Optimize LineReader.ReadLine in [PR #393](https://github.com/lunet-io/markdig/pull/393)
|
||||
- Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T> in [PR #394](https://github.com/lunet-io/markdig/pull/394)
|
||||
- Use BitVector128 in CharacterMap<T> in [PR #396](https://github.com/lunet-io/markdig/pull/396)
|
||||
- Optimizations in StringLineGroup in [PR #399](https://github.com/lunet-io/markdig/pull/399)
|
||||
- Fixed a bug in HeadingRenderer in [PR #402](https://github.com/lunet-io/markdig/pull/402)
|
||||
- Fixes issue #303 in [PR #404](https://github.com/lunet-io/markdig/pull/404)
|
||||
- Make output of HtmlTableRenderer XML wellformed in [PR #406](https://github.com/lunet-io/markdig/pull/406)
|
||||
|
||||
## 0.18.1 (21 Jan 2020)
|
||||
- Re-allow emojis and smileys customization, that was broken in [PR #308](https://github.com/lunet-io/markdig/pull/308) ([PR #386](https://github.com/lunet-io/markdig/pull/386))
|
||||
- Add `IHostProvider` for medialink customization (#337), support protocol-less url (#135) ([(PR #341)](https://github.com/lunet-io/markdig/pull/341))
|
||||
- Add missing Descendants<T> overload ([(PR #387)](https://github.com/lunet-io/markdig/pull/387))
|
||||
|
||||
## 0.18.0 (24 Oct 2019)
|
||||
- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357))
|
||||
- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360))
|
||||
- Fix generic attributes with values of length 1 ([(PR #361)](https://github.com/lunet-io/markdig/pull/361))
|
||||
- Fix link text balanced bracket matching ([(PR #375)](https://github.com/lunet-io/markdig/pull/375))
|
||||
- Improve overall performance and substantially reduce allocations ([(PR #377)](https://github.com/lunet-io/markdig/pull/377))
|
||||
|
||||
## 0.17.1 (04 July 2019)
|
||||
- Fix regression when escaping HTML characters ([(PR #340)](https://github.com/lunet-io/markdig/pull/340))
|
||||
- Update Emoji Dictionary ([(PR #346)](https://github.com/lunet-io/markdig/pull/346))
|
||||
|
||||
## 0.17.0 (10 May 2019)
|
||||
- Update to latest CommonMark specs 0.29 ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
|
||||
- Add `AutoLinkOptions` with `OpenInNewWindow`, `UseHttpsForWWWLinks` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
|
||||
|
||||
12
readme.md
12
readme.md
@@ -1,4 +1,4 @@
|
||||
# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
# Markdig [](https://github.com/lunet-io/markdig/actions) [](https://coveralls.io/github/lunet-io/markdig?branch=master) [](https://www.nuget.org/packages/Markdig/) [](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
|
||||
|
||||
@@ -1,43 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6A19F040-BC7C-4283-873A-177B5324F1ED}</ProjectGuid>
|
||||
<TargetFrameworks>net471</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Markdig.Benchmarks</RootNamespace>
|
||||
<AssemblyName>Markdig.Benchmarks</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<CopyNuGetImplementations>true</CopyNuGetImplementations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="spec.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CommonMarkNew, Version=0.1.0.0, Culture=neutral, PublicKeyToken=001ef8810438905d, processorArchitecture=MSIL">
|
||||
<HintPath>lib\CommonMarkNew.dll</HintPath>
|
||||
@@ -45,31 +15,12 @@
|
||||
<Aliases>newcmark</Aliases>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="MoonShine">
|
||||
<HintPath>lib\MoonShine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MarkdownDeep">
|
||||
<HintPath>lib\MarkdownDeep.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CommonMarkLib.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestMatchPerf.cs" />
|
||||
<Compile Include="TestStringPerf.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="cmark.dll">
|
||||
@@ -83,21 +34,16 @@
|
||||
<Content Include="spec.md">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="app.config" />
|
||||
<None Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Markdig">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Markdig\Bin\$(Configuration)\net40\Markdig.dll</HintPath>
|
||||
</Reference>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.6" />
|
||||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.10.6" />
|
||||
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
|
||||
<PackageReference Include="MarkdownSharp" Version="1.13.0.0" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="0.8.31-beta" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="1.0.41.0" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Markdig\Markdig.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Testamina.Markdig.Benchmarks")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Testamina.Markdig.Benchmarks")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("6a19f040-bc7c-4283-873a-177b5324f1ed")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"CLASSIC": {
|
||||
"executablePath": "Testamina.Markdig.Benchmarks.dll",
|
||||
"workingDirectory": "..\\..\\artifacts\\bin\\Testamina.Markdig.Benchmarks\\Debug\\net45"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"runtimes": {
|
||||
"win-x86": {},
|
||||
"win-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net46": {
|
||||
"compilationOptions": {
|
||||
"define": [
|
||||
"CLASSIC"
|
||||
]
|
||||
},
|
||||
"frameworkAssemblies": {
|
||||
"Microsoft.Build": "4.0.0.0",
|
||||
"Microsoft.Build.Framework": "4.0.0.0",
|
||||
"Microsoft.Build.Utilities.v4.0": "4.0.0.0",
|
||||
"System.Management": "4.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"BenchmarkDotNet": "0.10.6",
|
||||
"BenchmarkDotNet.Diagnostics.Windows": "0.10.6",
|
||||
"CommonMark.NET": "0.15.1",
|
||||
"MarkdownSharp": "1.13.0.0",
|
||||
"Microsoft.Diagnostics.Runtime": "0.8.31-beta",
|
||||
"Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41.0"
|
||||
}
|
||||
}
|
||||
13
src/Markdig.Signed/Markdig.Signed.csproj
Normal file
13
src/Markdig.Signed/Markdig.Signed.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<PackageId>Markdig.Signed</PackageId>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Markdig\**\*.cs" Exclude="..\Markdig\obj\**;..\Markdig\bin\**">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<Import Project="..\Markdig\Markdig.targets" />
|
||||
</Project>
|
||||
@@ -1,13 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net451;netcoreapp2.1</TargetFrameworks>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<OutputType>Library</OutputType>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="2.8.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Markdig.Extensions.AutoLinks;
|
||||
using NUnit.Framework;
|
||||
@@ -8,6 +9,63 @@ namespace Markdig.Tests
|
||||
{
|
||||
public class MiscTests
|
||||
{
|
||||
[TestCase("link [foo [bar]]")] // https://spec.commonmark.org/0.29/#example-508
|
||||
[TestCase("link [foo][bar]")]
|
||||
[TestCase("link [][foo][bar][]")]
|
||||
[TestCase("link [][foo][bar][[]]")]
|
||||
[TestCase("link [foo] [bar]")]
|
||||
[TestCase("link [[foo] [] [bar] [[abc]def]]")]
|
||||
[TestCase("[]")]
|
||||
[TestCase("[ ]")]
|
||||
[TestCase("[bar][]")]
|
||||
[TestCase("[bar][ foo]")]
|
||||
[TestCase("[bar][foo ][]")]
|
||||
[TestCase("[bar][fo[ ]o ][][]")]
|
||||
[TestCase("[a]b[c[d[e]f]g]h")]
|
||||
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o")]
|
||||
[TestCase("a[b[c[d]e]f[g]h]i[] [][foo][bar][] foo [j]k[l[m]n]o")]
|
||||
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o[][]")]
|
||||
public void LinkTextMayContainBalancedBrackets(string linkText)
|
||||
{
|
||||
string markdown = $"[{linkText}](/uri)";
|
||||
string expected = $@"<p><a href=""/uri"">{linkText}</a></p>";
|
||||
|
||||
TestParser.TestSpec(markdown, expected);
|
||||
|
||||
// Make the link text unbalanced
|
||||
foreach (var bracketIndex in linkText
|
||||
.Select((c, i) => new Tuple<char, int>(c, i))
|
||||
.Where(t => t.Item1 == '[' || t.Item1 == ']')
|
||||
.Select(t => t.Item2))
|
||||
{
|
||||
string brokenLinkText = linkText.Remove(bracketIndex, 1);
|
||||
|
||||
markdown = $"[{brokenLinkText}](/uri)";
|
||||
expected = $@"<p><a href=""/uri"">{brokenLinkText}</a></p>";
|
||||
|
||||
string actual = Markdown.ToHtml(markdown);
|
||||
Assert.AreNotEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsIssue356Corrected()
|
||||
{
|
||||
string input = @"https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97";
|
||||
string expected = @"<p><a href=""https://foo.bar/path/%5C#m4mv5W0GYKZpGvfA.97"">https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97</a></p>";
|
||||
|
||||
TestParser.TestSpec($"<{input}>", expected);
|
||||
TestParser.TestSpec(input, expected, "autolinks|advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAltTextIsCorrectlyEscaped()
|
||||
{
|
||||
TestParser.TestSpec(
|
||||
@"",
|
||||
@"<p><img src=""girl.png"" alt=""This is image alt text with quotation ' and double quotation "hello" world"" /></p>");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangelogPRLinksMatchDescription()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-05 16:06:14
|
||||
// Generated: 2019-05-15 02:46:55
|
||||
|
||||
// --------------------------------
|
||||
// Abbreviations
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-15 05:20:50
|
||||
// Generated: 2020-01-13 21:08:58
|
||||
|
||||
// --------------------------------
|
||||
// Emoji
|
||||
@@ -18,7 +18,7 @@ namespace Markdig.Tests.Specs.Emoji
|
||||
//
|
||||
// ## Emoji
|
||||
//
|
||||
// Emoji and smiley can be converted to their respective unicode characters:
|
||||
// Emoji shortcodes and smileys can be converted to their respective unicode characters:
|
||||
[Test]
|
||||
public void ExtensionsEmoji_Example001()
|
||||
{
|
||||
@@ -52,7 +52,7 @@ namespace Markdig.Tests.Specs.Emoji
|
||||
TestParser.TestSpec("These are not:) an emoji with a:) x:angry:x", "<p>These are not:) an emoji with a:) x:angry:x</p>", "emojis|advanced+emojis");
|
||||
}
|
||||
|
||||
// Emoji can be followed by close punctuation (or any other characters):
|
||||
// Emojis can be followed by close punctuation (or any other characters):
|
||||
[Test]
|
||||
public void ExtensionsEmoji_Example003()
|
||||
{
|
||||
@@ -69,7 +69,7 @@ namespace Markdig.Tests.Specs.Emoji
|
||||
TestParser.TestSpec("We all need :), it makes us :muscle:. (and :ok_hand:).", "<p>We all need 😃, it makes us 💪. (and 👌).</p>", "emojis|advanced+emojis");
|
||||
}
|
||||
|
||||
// Sentences can end with Emoji:
|
||||
// Sentences can end with emojis:
|
||||
[Test]
|
||||
public void ExtensionsEmoji_Example004()
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ This section describes the different extensions supported:
|
||||
|
||||
## Emoji
|
||||
|
||||
Emoji and smiley can be converted to their respective unicode characters:
|
||||
Emoji shortcodes and smileys can be converted to their respective unicode characters:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a test with a :) and a :angry: smiley
|
||||
@@ -20,7 +20,7 @@ These are not:) an emoji with a:) x:angry:x
|
||||
<p>These are not:) an emoji with a:) x:angry:x</p>
|
||||
````````````````````````````````
|
||||
|
||||
Emoji can be followed by close punctuation (or any other characters):
|
||||
Emojis can be followed by close punctuation (or any other characters):
|
||||
|
||||
```````````````````````````````` example
|
||||
We all need :), it makes us :muscle:. (and :ok_hand:).
|
||||
@@ -28,7 +28,7 @@ We all need :), it makes us :muscle:. (and :ok_hand:).
|
||||
<p>We all need 😃, it makes us 💪. (and 👌).</p>
|
||||
````````````````````````````````
|
||||
|
||||
Sentences can end with Emoji:
|
||||
Sentences can end with emojis:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a sentence :ok_hand:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-05 16:06:14
|
||||
// Generated: 2019-08-01 13:57:17
|
||||
|
||||
// --------------------------------
|
||||
// Generic Attributes
|
||||
@@ -79,5 +79,28 @@ namespace Markdig.Tests.Specs.GenericAttributes
|
||||
Console.WriteLine("Example 2\nSection Extensions / Generic Attributes\n");
|
||||
TestParser.TestSpec("{#fenced-id .fenced-class}\n~~~\nThis is a fenced with attached attributes\n~~~ ", "<pre><code id=\"fenced-id\" class=\"fenced-class\">This is a fenced with attached attributes\n</code></pre>", "attributes|advanced");
|
||||
}
|
||||
|
||||
// Attribute values can be one character long
|
||||
[Test]
|
||||
public void ExtensionsGenericAttributes_Example003()
|
||||
{
|
||||
// Example 3
|
||||
// Section: Extensions / Generic Attributes
|
||||
//
|
||||
// The following Markdown:
|
||||
// [Foo](url){data-x=1}
|
||||
//
|
||||
// [Foo](url){data-x='1'}
|
||||
//
|
||||
// [Foo](url){data-x=11}
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p><a href="url" data-x="1">Foo</a></p>
|
||||
// <p><a href="url" data-x="1">Foo</a></p>
|
||||
// <p><a href="url" data-x="11">Foo</a></p>
|
||||
|
||||
Console.WriteLine("Example 3\nSection Extensions / Generic Attributes\n");
|
||||
TestParser.TestSpec("[Foo](url){data-x=1}\n\n[Foo](url){data-x='1'}\n\n[Foo](url){data-x=11}", "<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"11\">Foo</a></p>", "attributes|advanced");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,3 +47,17 @@ This is a fenced with attached attributes
|
||||
<pre><code id="fenced-id" class="fenced-class">This is a fenced with attached attributes
|
||||
</code></pre>
|
||||
````````````````````````````````
|
||||
|
||||
Attribute values can be one character long
|
||||
|
||||
```````````````````````````````` example
|
||||
[Foo](url){data-x=1}
|
||||
|
||||
[Foo](url){data-x='1'}
|
||||
|
||||
[Foo](url){data-x=11}
|
||||
.
|
||||
<p><a href="url" data-x="1">Foo</a></p>
|
||||
<p><a href="url" data-x="1">Foo</a></p>
|
||||
<p><a href="url" data-x="11">Foo</a></p>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-29 18:40:06
|
||||
// Generated: 2019-05-15 02:46:20
|
||||
|
||||
// --------------------------------
|
||||
// Media
|
||||
@@ -47,19 +47,19 @@ namespace Markdig.Tests.Specs.Media
|
||||
// 
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><video width="500" height="281" controls=""><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
|
||||
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
|
||||
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" class="yandex" frameborder="0"></iframe></p>
|
||||
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
|
||||
Console.WriteLine("Example 1\nSection Extensions / Media links\n");
|
||||
TestParser.TestSpec("\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
|
||||
TestParser.TestSpec("\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ Allows to embed audio/video links to popular website:
|
||||
|
||||

|
||||
.
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><video width="500" height="281" controls=""><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
|
||||
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
|
||||
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" class="yandex" frameborder="0"></iframe></p>
|
||||
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
|
||||
````````````````````````````````
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-05 16:06:14
|
||||
// Generated: 2019-08-01 12:33:23
|
||||
|
||||
// --------------------------------
|
||||
// Smarty Pants
|
||||
@@ -140,13 +140,13 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// This is a 'text <<with' a another text>>
|
||||
// This is 'a "text 'with" a another text'
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a ‘text <<with’ a another text>></p>
|
||||
// <p>This is ‘a “text 'with” a another text’</p>
|
||||
|
||||
Console.WriteLine("Example 8\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is a 'text <<with' a another text>>", "<p>This is a ‘text <<with’ a another text>></p>", "pipetables+smartypants|advanced+smartypants");
|
||||
TestParser.TestSpec("This is 'a \"text 'with\" a another text'", "<p>This is ‘a “text 'with” a another text’</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -156,20 +156,36 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// This is a 'text <<with' a another text>>
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a ‘text <<with’ a another text>></p>
|
||||
|
||||
Console.WriteLine("Example 9\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is a 'text <<with' a another text>>", "<p>This is a ‘text <<with’ a another text>></p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example010()
|
||||
{
|
||||
// Example 10
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// This is a <<text 'with>> a another text'
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a «text 'with» a another text'</p>
|
||||
|
||||
Console.WriteLine("Example 9\nSection Extensions / SmartyPants Quotes\n");
|
||||
Console.WriteLine("Example 10\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is a <<text 'with>> a another text'", "<p>This is a «text 'with» a another text'</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Quotes requires to have the same rules than emphasis `_` regarding left/right frankling rules:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example010()
|
||||
public void ExtensionsSmartyPantsQuotes_Example011()
|
||||
{
|
||||
// Example 10
|
||||
// Example 11
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -178,24 +194,8 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>It's not quotes'</p>
|
||||
|
||||
Console.WriteLine("Example 10\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("It's not quotes'", "<p>It's not quotes'</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example011()
|
||||
{
|
||||
// Example 11
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// They are ' not matching quotes '
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>They are ' not matching quotes '</p>
|
||||
|
||||
Console.WriteLine("Example 11\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("They are ' not matching quotes '", "<p>They are ' not matching quotes '</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
TestParser.TestSpec("It's not quotes'", "<p>It's not quotes'</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -205,20 +205,36 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// They are ' not matching quotes '
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>They are ' not matching quotes '</p>
|
||||
|
||||
Console.WriteLine("Example 12\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("They are ' not matching quotes '", "<p>They are ' not matching quotes '</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example013()
|
||||
{
|
||||
// Example 13
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// They are' not matching 'quotes
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>They are' not matching 'quotes</p>
|
||||
|
||||
Console.WriteLine("Example 12\nSection Extensions / SmartyPants Quotes\n");
|
||||
Console.WriteLine("Example 13\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("They are' not matching 'quotes", "<p>They are' not matching 'quotes</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// An emphasis starting inside left/right quotes will span over the right quote:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example013()
|
||||
public void ExtensionsSmartyPantsQuotes_Example014()
|
||||
{
|
||||
// Example 13
|
||||
// Example 14
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -227,9 +243,26 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is “a <em>text” with an emphasis</em></p>
|
||||
|
||||
Console.WriteLine("Example 13\nSection Extensions / SmartyPants Quotes\n");
|
||||
Console.WriteLine("Example 14\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is \"a *text\" with an emphasis*", "<p>This is “a <em>text” with an emphasis</em></p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Multiple sets of quotes can be used
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example015()
|
||||
{
|
||||
// Example 15
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// "aaa" "bbb" "ccc" "ddd"
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>“aaa” “bbb” “ccc” “ddd”</p>
|
||||
|
||||
Console.WriteLine("Example 15\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("\"aaa\" \"bbb\" \"ccc\" \"ddd\"", "<p>“aaa” “bbb” “ccc” “ddd”</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
@@ -237,9 +270,9 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
{
|
||||
// ## SmartyPants Separators
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example014()
|
||||
public void ExtensionsSmartyPantsSeparators_Example016()
|
||||
{
|
||||
// Example 14
|
||||
// Example 16
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -248,14 +281,14 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is a – text</p>
|
||||
|
||||
Console.WriteLine("Example 14\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 16\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("This is a -- text", "<p>This is a – text</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example015()
|
||||
public void ExtensionsSmartyPantsSeparators_Example017()
|
||||
{
|
||||
// Example 15
|
||||
// Example 17
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -264,14 +297,14 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is a — text</p>
|
||||
|
||||
Console.WriteLine("Example 15\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 17\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("This is a --- text", "<p>This is a — text</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example016()
|
||||
public void ExtensionsSmartyPantsSeparators_Example018()
|
||||
{
|
||||
// Example 16
|
||||
// Example 18
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -280,15 +313,15 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is a en ellipsis…</p>
|
||||
|
||||
Console.WriteLine("Example 16\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 18\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("This is a en ellipsis...", "<p>This is a en ellipsis…</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Check that a smartypants are not breaking pipetable parsing:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example017()
|
||||
public void ExtensionsSmartyPantsSeparators_Example019()
|
||||
{
|
||||
// Example 17
|
||||
// Example 19
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -312,15 +345,15 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example 17\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 19\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("a | b\n-- | --\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Check quotes and dash:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example018()
|
||||
public void ExtensionsSmartyPantsSeparators_Example020()
|
||||
{
|
||||
// Example 18
|
||||
// Example 20
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -329,7 +362,7 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>A “quote” with a —</p>
|
||||
|
||||
Console.WriteLine("Example 18\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 20\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("A \"quote\" with a ---", "<p>A “quote” with a —</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ This is a "text 'with" a another text'
|
||||
<p>This is a “text 'with” a another text'</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is 'a "text 'with" a another text'
|
||||
.
|
||||
<p>This is ‘a “text 'with” a another text’</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a 'text <<with' a another text>>
|
||||
.
|
||||
@@ -91,6 +97,14 @@ This is "a *text" with an emphasis*
|
||||
<p>This is “a <em>text” with an emphasis</em></p>
|
||||
````````````````````````````````
|
||||
|
||||
Multiple sets of quotes can be used
|
||||
|
||||
```````````````````````````````` example
|
||||
"aaa" "bbb" "ccc" "ddd"
|
||||
.
|
||||
<p>“aaa” “bbb” “ccc” “ddd”</p>
|
||||
````````````````````````````````
|
||||
|
||||
## SmartyPants Separators
|
||||
|
||||
```````````````````````````````` example
|
||||
|
||||
70
src/Markdig.Tests/TestContainerBlocks.cs
Normal file
70
src/Markdig.Tests/TestContainerBlocks.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/Markdig.Tests/TestCustomEmojis.cs
Normal file
129
src/Markdig.Tests/TestCustomEmojis.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Extensions.Emoji;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCustomEmojis
|
||||
{
|
||||
[Test]
|
||||
[TestCase(":smiley:", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
|
||||
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
|
||||
public void TestCustomEmoji(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = new Dictionary<string, string>();
|
||||
var smileyToEmoji = new Dictionary<string, string>();
|
||||
|
||||
emojiToUnicode[":smiley:"] = "♥";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(":testheart:", "<p>♥</p>\n")]
|
||||
[TestCase("hello", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
|
||||
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
|
||||
public void TestCustomSmiley(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = new Dictionary<string, string>();
|
||||
var smileyToEmoji = new Dictionary<string, string>();
|
||||
|
||||
emojiToUnicode[":testheart:"] = "♥";
|
||||
smileyToEmoji["hello"] = ":testheart:";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(":smiley:", "<p>♥</p>\n")]
|
||||
[TestCase(":)", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
|
||||
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
|
||||
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
|
||||
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
|
||||
|
||||
emojiToUnicode[":smiley:"] = "♥";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(":testheart:", "<p>♥</p>\n")]
|
||||
[TestCase("hello", "<p>♥</p>\n")]
|
||||
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
|
||||
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
|
||||
public void TestOverrideDefaultWithCustomSmiley(string input, string expected)
|
||||
{
|
||||
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
|
||||
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
|
||||
|
||||
emojiToUnicode[":testheart:"] = "♥";
|
||||
smileyToEmoji["hello"] = ":testheart:";
|
||||
|
||||
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
|
||||
.Build();
|
||||
|
||||
var actual = Markdown.ToHtml(input, pipeline);
|
||||
Assert.AreEqual(expected, actual);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomEmojiValidation()
|
||||
{
|
||||
var emojiToUnicode = new Dictionary<string, string>();
|
||||
var smileyToEmoji = new Dictionary<string, string>();
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(null, smileyToEmoji));
|
||||
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(emojiToUnicode, null));
|
||||
|
||||
emojiToUnicode.Add("null-value", null);
|
||||
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
|
||||
emojiToUnicode.Clear();
|
||||
|
||||
smileyToEmoji.Add("null-value", null);
|
||||
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
|
||||
smileyToEmoji.Clear();
|
||||
|
||||
smileyToEmoji.Add("foo", "something-that-does-not-exist-in-emojiToUnicode");
|
||||
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
|
||||
smileyToEmoji.Clear();
|
||||
|
||||
emojiToUnicode.Add("a", "aaa");
|
||||
emojiToUnicode.Add("b", "bbb");
|
||||
emojiToUnicode.Add("c", "ccc");
|
||||
smileyToEmoji.Add("a", "c"); // "a" already exists in emojiToUnicode
|
||||
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using NUnit.Framework;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
@@ -12,24 +13,91 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void TestSchemas()
|
||||
{
|
||||
foreach (var markdown in TestParser.SpecsMarkdown)
|
||||
foreach (var syntaxTree in TestParser.SpecsSyntaxTrees)
|
||||
{
|
||||
AssertSameDescendantsOrder(markdown);
|
||||
AssertIEnumerablesAreEqual(
|
||||
Descendants_Legacy(syntaxTree),
|
||||
syntaxTree.Descendants());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
syntaxTree.Descendants().OfType<ParagraphBlock>(),
|
||||
syntaxTree.Descendants<ParagraphBlock>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
syntaxTree.Descendants().OfType<ParagraphBlock>(),
|
||||
(syntaxTree as ContainerBlock).Descendants<ParagraphBlock>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
syntaxTree.Descendants().OfType<LiteralInline>(),
|
||||
syntaxTree.Descendants<LiteralInline>());
|
||||
|
||||
foreach (LiteralInline literalInline in syntaxTree.Descendants<LiteralInline>())
|
||||
{
|
||||
Assert.AreSame(Array.Empty<ListBlock>(), literalInline.Descendants<ListBlock>());
|
||||
Assert.AreSame(Array.Empty<ParagraphBlock>(), literalInline.Descendants<ParagraphBlock>());
|
||||
Assert.AreSame(Array.Empty<ContainerInline>(), literalInline.Descendants<ContainerInline>());
|
||||
}
|
||||
|
||||
foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
|
||||
{
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerInline.FindDescendants<LiteralInline>(),
|
||||
containerInline.Descendants<LiteralInline>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerInline.FindDescendants<LiteralInline>(),
|
||||
(containerInline as MarkdownObject).Descendants<LiteralInline>());
|
||||
|
||||
if (containerInline.FirstChild is null)
|
||||
{
|
||||
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.Descendants<LiteralInline>());
|
||||
Assert.AreSame(Array.Empty<LiteralInline>(), containerInline.FindDescendants<LiteralInline>());
|
||||
Assert.AreSame(Array.Empty<LiteralInline>(), (containerInline as MarkdownObject).Descendants<LiteralInline>());
|
||||
}
|
||||
|
||||
Assert.AreSame(Array.Empty<ListBlock>(), containerInline.Descendants<ListBlock>());
|
||||
Assert.AreSame(Array.Empty<ParagraphBlock>(), containerInline.Descendants<ParagraphBlock>());
|
||||
}
|
||||
|
||||
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
|
||||
{
|
||||
AssertIEnumerablesAreEqual(
|
||||
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
|
||||
paragraphBlock.Descendants<LiteralInline>());
|
||||
|
||||
Assert.AreSame(Array.Empty<ParagraphBlock>(), paragraphBlock.Descendants<ParagraphBlock>());
|
||||
}
|
||||
|
||||
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
|
||||
{
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerBlock.Descendants<LiteralInline>(),
|
||||
(containerBlock as MarkdownObject).Descendants<LiteralInline>());
|
||||
|
||||
AssertIEnumerablesAreEqual(
|
||||
containerBlock.Descendants<ParagraphBlock>(),
|
||||
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());
|
||||
|
||||
if (containerBlock.Count == 0)
|
||||
{
|
||||
Assert.AreSame(Array.Empty<LiteralInline>(), containerBlock.Descendants<LiteralInline>());
|
||||
Assert.AreSame(Array.Empty<LiteralInline>(), (containerBlock as Block).Descendants<LiteralInline>());
|
||||
Assert.AreSame(Array.Empty<LiteralInline>(), (containerBlock as MarkdownObject).Descendants<LiteralInline>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertSameDescendantsOrder(string markdown)
|
||||
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
|
||||
{
|
||||
var syntaxTree = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
|
||||
var firstList = new List<T>(first);
|
||||
var secondList = new List<T>(second);
|
||||
|
||||
var descendants_legacy = Descendants_Legacy(syntaxTree).ToList();
|
||||
var descendants_new = syntaxTree.Descendants().ToList();
|
||||
Assert.AreEqual(firstList.Count, secondList.Count);
|
||||
|
||||
Assert.AreEqual(descendants_legacy.Count, descendants_new.Count);
|
||||
|
||||
for (int i = 0; i < descendants_legacy.Count; i++)
|
||||
for (int i = 0; i < firstList.Count; i++)
|
||||
{
|
||||
Assert.AreSame(descendants_legacy[i], descendants_new[i]);
|
||||
Assert.AreSame(firstList[i], secondList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
48
src/Markdig.Tests/TestExceptionNotThrown.cs
Normal file
48
src/Markdig.Tests/TestExceptionNotThrown.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
|
||||
148
src/Markdig.Tests/TestMarkdigCoreApi.cs
Normal file
148
src/Markdig.Tests/TestMarkdigCoreApi.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
src/Markdig.Tests/TestMediaLinks.cs
Normal file
87
src/Markdig.Tests/TestMediaLinks.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Markdig.Extensions.MediaLinks;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestMediaLinks
|
||||
{
|
||||
private MarkdownPipeline GetPipeline(MediaOptions options = null)
|
||||
{
|
||||
return new MarkdownPipelineBuilder()
|
||||
.UseMediaLinks(options)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n")]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
[TestCase("", "<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n")]
|
||||
public void TestBuiltInHosts(string markdown, string expected)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline());
|
||||
Assert.AreEqual(html, expected);
|
||||
}
|
||||
|
||||
private class TestHostProvider : IHostProvider
|
||||
{
|
||||
public string Class { get; } = "regex";
|
||||
public bool AllowFullScreen { get; }
|
||||
|
||||
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
|
||||
{
|
||||
iframeUrl = null;
|
||||
var uri = isSchemaRelative ? "//" + mediaUri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Scheme, UriFormat.UriEscaped) : mediaUri.ToString();
|
||||
if (!matcher.IsMatch(uri))
|
||||
return false;
|
||||
iframeUrl = matcher.Replace(uri, replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Regex matcher;
|
||||
private string replacement;
|
||||
|
||||
public TestHostProvider(string provider, string replace)
|
||||
{
|
||||
matcher = new Regex(provider);
|
||||
replacement = replace;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("", "<p><iframe src=\"https://example.com/video.mp4\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1")]
|
||||
[TestCase("", "<p><iframe src=\"https://example.com/video.mp4\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^//sample.com/(.+)$", @"https://example.com/$1")]
|
||||
[TestCase("", "<p><iframe src=\"https://example.com/video.mp4?token=aaabbb\" width=\"500\" height=\"281\" class=\"regex\" frameborder=\"0\"></iframe></p>\n", @"^https?://sample.com/(.+)$", @"https://example.com/$1?token=aaabbb")]
|
||||
public void TestCustomHostProvider(string markdown, string expected, string provider, string replace)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
|
||||
{
|
||||
Hosts =
|
||||
{
|
||||
new TestHostProvider(provider, replace),
|
||||
}
|
||||
}));
|
||||
Assert.AreEqual(html, expected);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "")]
|
||||
[TestCase("", "<p><video width=\"500\" height=\"281\" controls=\"\" class=\"k\"><source type=\"video/mp4\" src=\"//sample.com/video.mp4\"></source></video></p>\n", "k")]
|
||||
[TestCase(@"", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"k youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n", "k")]
|
||||
public void TestCustomClass(string markdown, string expected, string klass)
|
||||
{
|
||||
string html = Markdown.ToHtml(markdown, GetPipeline(new MediaOptions
|
||||
{
|
||||
Class = klass,
|
||||
}));
|
||||
Assert.AreEqual(html, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Markdig.Extensions.JiraLinks;
|
||||
using Markdig.Syntax;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
@@ -33,9 +34,9 @@ namespace Markdig.Tests
|
||||
|
||||
// If file creation times aren't preserved by git, add some leeway
|
||||
// If specs have come from git, assume that they were regenerated since CI would fail otherwise
|
||||
testTime = testTime.AddSeconds(2);
|
||||
testTime = testTime.AddMinutes(3);
|
||||
|
||||
// This might not catch a changed spec every time, but should most of the time. Otherwise CI will catch it
|
||||
// This might not catch a changed spec every time, but should at least sometimes. Otherwise CI will catch it
|
||||
|
||||
// This could also trigger, if a user has modified the spec file but reverted the change - can't think of a good workaround
|
||||
Assert.Less(specTime, testTime,
|
||||
@@ -151,8 +152,20 @@ namespace Markdig.Tests
|
||||
/// Contains the markdown source for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
|
||||
/// </summary>
|
||||
public static readonly string[] SpecsMarkdown;
|
||||
/// <summary>
|
||||
/// Contains the markdown syntax tree for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
|
||||
/// </summary>
|
||||
public static readonly MarkdownDocument[] SpecsSyntaxTrees;
|
||||
|
||||
static TestParser()
|
||||
{
|
||||
const string RunningInsideVisualStudioPath = "\\src\\.vs\\markdig\\";
|
||||
int index = TestsDirectory.IndexOf(RunningInsideVisualStudioPath);
|
||||
if (index != -1)
|
||||
{
|
||||
TestsDirectory = TestsDirectory.Substring(0, index) + "\\src\\Markdig.Tests";
|
||||
}
|
||||
|
||||
SpecsFilePaths = Directory.GetDirectories(TestsDirectory)
|
||||
.Where(dir => dir.EndsWith("Specs"))
|
||||
.SelectMany(dir => Directory.GetFiles(dir)
|
||||
@@ -161,10 +174,16 @@ namespace Markdig.Tests
|
||||
.ToArray();
|
||||
|
||||
SpecsMarkdown = new string[SpecsFilePaths.Length];
|
||||
SpecsSyntaxTrees = new MarkdownDocument[SpecsFilePaths.Length];
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseAdvancedExtensions()
|
||||
.Build();
|
||||
|
||||
for (int i = 0; i < SpecsFilePaths.Length; i++)
|
||||
{
|
||||
SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
|
||||
string markdown = SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
|
||||
SpecsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -19,7 +19,7 @@ namespace Markdig.Tests
|
||||
var link = doc.Descendants<ParagraphBlock>().SelectMany(x => x.Inline.Descendants<LinkInline>()).FirstOrDefault(l => l.IsImage);
|
||||
Assert.AreEqual("/yoyo", link?.Url);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestListBug2()
|
||||
{
|
||||
@@ -53,11 +53,11 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
|
||||
[Test]
|
||||
public void TestEmptyLiteral()
|
||||
{
|
||||
var text = @"> *some text*
|
||||
var text = @"> *some text*
|
||||
> some other text";
|
||||
var doc = Markdown.Parse(text);
|
||||
|
||||
Assert.True(doc.Descendants().OfType<LiteralInline>().All(x => !x.Content.IsEmpty),
|
||||
Assert.True(doc.Descendants<LiteralInline>().All(x => !x.Content.IsEmpty),
|
||||
"There should not have any empty literals");
|
||||
}
|
||||
|
||||
@@ -195,8 +195,8 @@ Paragraph
|
||||
";
|
||||
|
||||
var expected = @"<table class=""table"">
|
||||
<col style=""width:50%"">
|
||||
<col style=""width:50%"">
|
||||
<col style=""width:50%"" />
|
||||
<col style=""width:50%"" />
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
@@ -219,7 +219,7 @@ Paragraph
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
|
||||
|
||||
// Reuse the same pipeline
|
||||
// Reuse the same pipeline
|
||||
var result1 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
|
||||
var result2 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
|
||||
|
||||
@@ -269,7 +269,7 @@ Paragraph
|
||||
//| Yes |
|
||||
//| ``` |
|
||||
//+===================================+======================================+
|
||||
//| This is a second line |
|
||||
//| This is a second line |
|
||||
//+-----------------------------------+--------------------------------------+
|
||||
|
||||
//:::spoiler {#yessss}
|
||||
|
||||
36
src/Markdig.Tests/TestSmartyPants.cs
Normal file
36
src/Markdig.Tests/TestSmartyPants.cs
Normal 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>«test»</p>", pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Markdig.Extensions.Footnotes;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Extensions.Footnotes;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
@@ -147,7 +146,7 @@ literal ( 0, 6) 6-7
|
||||
public void TestFootnoteLinkReferenceDefinition()
|
||||
{
|
||||
// 01 2 345678
|
||||
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants().OfType<FootnoteLinkReferenceDefinition>().FirstOrDefault();
|
||||
var footnote = Markdown.Parse("0\n\n [^1]:", new MarkdownPipelineBuilder().UsePreciseSourceLocation().UseFootnotes().Build()).Descendants<FootnoteLinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(footnote);
|
||||
|
||||
Assert.AreEqual(2, footnote.Line);
|
||||
@@ -160,7 +159,7 @@ literal ( 0, 6) 6-7
|
||||
{
|
||||
// 0 1
|
||||
// 0123456789012345
|
||||
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
|
||||
var link = Markdown.Parse("[234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(0, link.Line);
|
||||
@@ -175,7 +174,7 @@ literal ( 0, 6) 6-7
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 34567890123456789
|
||||
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkReferenceDefinition>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n [234]: /56 'yo' ", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkReferenceDefinition>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(2, link.Line);
|
||||
@@ -213,7 +212,7 @@ literal ( 0, 4) 4-5
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 3456789012345
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
@@ -226,7 +225,7 @@ literal ( 0, 4) 4-5
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 34567890123456789
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
@@ -240,7 +239,7 @@ literal ( 0, 4) 4-5
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 3456789012345
|
||||
var link = Markdown.Parse("0\n\n01", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
var link = Markdown.Parse("0\n\n01", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(5, 15), link.Span);
|
||||
@@ -408,7 +407,7 @@ literal ( 1, 2) 6-6
|
||||
6. Bar
|
||||
987123. FooBar";
|
||||
test = test.Replace("\r\n", "\n");
|
||||
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<ListBlock>().FirstOrDefault();
|
||||
var list = Markdown.Parse(test, new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants<ListBlock>().FirstOrDefault();
|
||||
Assert.NotNull(list);
|
||||
|
||||
Assert.AreEqual(1, list.Line);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
using System;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
@@ -27,6 +28,8 @@ namespace Markdig.Tests
|
||||
|
||||
var chars = ToString(text.ToCharIterator());
|
||||
TextAssert.AreEqual("ABC\nE\nF", chars.ToString());
|
||||
|
||||
TextAssert.AreEqual("ABC\nE\nF", text.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -81,8 +84,18 @@ namespace Markdig.Tests
|
||||
public void TestSkipWhitespaces()
|
||||
{
|
||||
var text = new StringLineGroup(" ABC").ToCharIterator();
|
||||
Assert.True(text.TrimStart());
|
||||
Assert.False(text.TrimStart());
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
|
||||
text = new StringLineGroup(" ").ToCharIterator();
|
||||
Assert.True(text.TrimStart());
|
||||
Assert.AreEqual('\0', text.CurrentChar);
|
||||
|
||||
var slice = new StringSlice(" ABC");
|
||||
Assert.False(slice.TrimStart());
|
||||
|
||||
slice = new StringSlice(" ");
|
||||
Assert.True(slice.TrimStart());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -116,5 +129,31 @@ namespace Markdig.Tests
|
||||
var result = ToString(text);
|
||||
TextAssert.AreEqual("ABC \n DEF ", result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStringLineGroupIteratorPeekChar()
|
||||
{
|
||||
var iterator = new StringLineGroup(4)
|
||||
{
|
||||
new StringSlice("ABC"),
|
||||
new StringSlice("E"),
|
||||
new StringSlice("F")
|
||||
}.ToCharIterator();
|
||||
|
||||
Assert.AreEqual('A', iterator.CurrentChar);
|
||||
Assert.AreEqual('A', iterator.PeekChar(0));
|
||||
Assert.AreEqual('B', iterator.PeekChar());
|
||||
Assert.AreEqual('B', iterator.PeekChar(1));
|
||||
Assert.AreEqual('C', iterator.PeekChar(2));
|
||||
Assert.AreEqual('\n', iterator.PeekChar(3));
|
||||
Assert.AreEqual('E', iterator.PeekChar(4));
|
||||
Assert.AreEqual('\n', iterator.PeekChar(5));
|
||||
Assert.AreEqual('F', iterator.PeekChar(6));
|
||||
Assert.AreEqual('\0', iterator.PeekChar(7)); // There is no \n appended to the last line
|
||||
Assert.AreEqual('\0', iterator.PeekChar(8));
|
||||
Assert.AreEqual('\0', iterator.PeekChar(100));
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => iterator.PeekChar(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
{
|
||||
private const string AutoIdentifierKey = "AutoIdentifier";
|
||||
private readonly AutoIdentifierOptions options;
|
||||
private readonly StripRendererCache rendererCache = new StripRendererCache();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
|
||||
@@ -159,17 +160,11 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
}
|
||||
|
||||
// Use internally a HtmlRenderer to strip links from a heading
|
||||
var headingWriter = new StringWriter();
|
||||
var stripRenderer = new HtmlRenderer(headingWriter)
|
||||
{
|
||||
// Set to false both to avoid having any HTML tags in the output
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false
|
||||
};
|
||||
var stripRenderer = rendererCache.Get();
|
||||
|
||||
stripRenderer.Render(headingBlock.Inline);
|
||||
var headingText = headingWriter.ToString();
|
||||
headingWriter.GetStringBuilder().Length = 0;
|
||||
var headingText = stripRenderer.Writer.ToString();
|
||||
rendererCache.Release(stripRenderer);
|
||||
|
||||
// Urilize the link
|
||||
headingText = (options & AutoIdentifierOptions.GitHub) != 0
|
||||
@@ -195,5 +190,25 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
|
||||
attributes.Id = headingId;
|
||||
}
|
||||
|
||||
private sealed class StripRendererCache : ObjectCache<HtmlRenderer>
|
||||
{
|
||||
protected override HtmlRenderer NewInstance()
|
||||
{
|
||||
var headingWriter = new StringWriter();
|
||||
var stripRenderer = new HtmlRenderer(headingWriter)
|
||||
{
|
||||
// Set to false both to avoid having any HTML tags in the output
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false
|
||||
};
|
||||
return stripRenderer;
|
||||
}
|
||||
|
||||
protected override void Reset(HtmlRenderer instance)
|
||||
{
|
||||
instance.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
/// <summary>
|
||||
/// The inline parser used to for autolinks.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
/// <seealso cref="InlineParser" />
|
||||
public class AutoLinkParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
@@ -30,10 +30,14 @@ namespace Markdig.Extensions.AutoLinks
|
||||
'f', // for ftp://
|
||||
'm', // for mailto:
|
||||
'w', // for www.
|
||||
};
|
||||
}
|
||||
|
||||
public readonly AutoLinkOptions Options;
|
||||
};
|
||||
|
||||
_listOfCharCache = new ListOfCharCache();
|
||||
}
|
||||
|
||||
public readonly AutoLinkOptions Options;
|
||||
|
||||
private readonly ListOfCharCache _listOfCharCache;
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
@@ -44,159 +48,162 @@ namespace Markdig.Extensions.AutoLinks
|
||||
return false;
|
||||
}
|
||||
|
||||
List<char> pendingEmphasis;
|
||||
// Check that an autolink is possible in the current context
|
||||
if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
int domainOffset = 0;
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
// Precheck URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (slice.MatchLowercase("ttp://", 1))
|
||||
{
|
||||
domainOffset = 7; // http://
|
||||
}
|
||||
else if (slice.MatchLowercase("ttps://", 1))
|
||||
{
|
||||
domainOffset = 8; // https://
|
||||
}
|
||||
else return false;
|
||||
break;
|
||||
case 'f':
|
||||
if (!slice.MatchLowercase("tp://", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 6; // ftp://
|
||||
break;
|
||||
case 'm':
|
||||
if (!slice.MatchLowercase("ailto:", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 4; // www.
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
string link;
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out link, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
|
||||
if (pendingEmphasis != null)
|
||||
{
|
||||
for (int i = link.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pendingEmphasis.Contains(link[i]))
|
||||
{
|
||||
slice.Start--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < link.Length - 1)
|
||||
{
|
||||
link = link.Substring(0, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-check URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
int atIndex = link.IndexOf('@');
|
||||
if (atIndex == -1 ||
|
||||
atIndex == 7) // mailto:@ - no email part
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = atIndex + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!LinkHelper.IsValidDomain(link, domainOffset))
|
||||
List<char> pendingEmphasis = _listOfCharCache.Get();
|
||||
try
|
||||
{
|
||||
return false;
|
||||
// Check that an autolink is possible in the current context
|
||||
if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
int domainOffset = 0;
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
// Precheck URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (slice.MatchLowercase("ttp://", 1))
|
||||
{
|
||||
domainOffset = 7; // http://
|
||||
}
|
||||
else if (slice.MatchLowercase("ttps://", 1))
|
||||
{
|
||||
domainOffset = 8; // https://
|
||||
}
|
||||
else return false;
|
||||
break;
|
||||
case 'f':
|
||||
if (!slice.MatchLowercase("tp://", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 6; // ftp://
|
||||
break;
|
||||
case 'm':
|
||||
if (!slice.MatchLowercase("ailto:", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 4; // www.
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out string link, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
|
||||
if (pendingEmphasis.Count > 0)
|
||||
{
|
||||
for (int i = link.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pendingEmphasis.Contains(link[i]))
|
||||
{
|
||||
slice.Start--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < link.Length - 1)
|
||||
{
|
||||
link = link.Substring(0, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-check URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
int atIndex = link.IndexOf('@');
|
||||
if (atIndex == -1 ||
|
||||
atIndex == 7) // mailto:@ - no email part
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = atIndex + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!LinkHelper.IsValidDomain(link, domainOffset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inline = new LinkInline()
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
|
||||
IsClosed = true,
|
||||
IsAutoLink = true,
|
||||
};
|
||||
|
||||
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
|
||||
|
||||
inline.Span.End = inline.Span.Start + link.Length - 1;
|
||||
inline.UrlSpan = inline.Span;
|
||||
inline.AppendChild(new LiteralInline()
|
||||
{
|
||||
Span = inline.Span,
|
||||
Line = line,
|
||||
Column = column,
|
||||
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
|
||||
IsClosed = true
|
||||
});
|
||||
processor.Inline = inline;
|
||||
|
||||
if (Options.OpenInNewWindow)
|
||||
{
|
||||
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var inline = new LinkInline()
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
|
||||
IsClosed = true,
|
||||
IsAutoLink = true,
|
||||
};
|
||||
|
||||
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
|
||||
|
||||
inline.Span.End = inline.Span.Start + link.Length - 1;
|
||||
inline.UrlSpan = inline.Span;
|
||||
inline.AppendChild(new LiteralInline()
|
||||
{
|
||||
Span = inline.Span,
|
||||
Line = line,
|
||||
Column = column,
|
||||
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
|
||||
IsClosed = true
|
||||
});
|
||||
processor.Inline = inline;
|
||||
|
||||
if (Options.OpenInNewWindow)
|
||||
finally
|
||||
{
|
||||
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
|
||||
_listOfCharCache.Release(pendingEmphasis);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, out List<char> pendingEmphasis)
|
||||
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List<char> pendingEmphasis)
|
||||
{
|
||||
pendingEmphasis = null;
|
||||
|
||||
// Case where there is a pending HtmlInline <a>
|
||||
var currentInline = processor.Inline;
|
||||
while (currentInline != null)
|
||||
{
|
||||
var htmlInline = currentInline as HtmlInline;
|
||||
if (htmlInline != null)
|
||||
if (currentInline is HtmlInline htmlInline)
|
||||
{
|
||||
// If we have a </a> we don't expect nested <a>
|
||||
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -211,7 +218,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
}
|
||||
}
|
||||
|
||||
// Check previous sibling and parents in the tree
|
||||
// Check previous sibling and parents in the tree
|
||||
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
|
||||
}
|
||||
|
||||
@@ -221,8 +228,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
int countBrackets = 0;
|
||||
while (currentInline != null)
|
||||
{
|
||||
var linkDelimiterInline = currentInline as LinkDelimiterInline;
|
||||
if (linkDelimiterInline != null && linkDelimiterInline.IsActive)
|
||||
if (currentInline is LinkDelimiterInline linkDelimiterInline && linkDelimiterInline.IsActive)
|
||||
{
|
||||
if (linkDelimiterInline.Type == DelimiterType.Open)
|
||||
{
|
||||
@@ -236,14 +242,8 @@ namespace Markdig.Extensions.AutoLinks
|
||||
else
|
||||
{
|
||||
// Record all pending characters for emphasis
|
||||
var emphasisDelimiter = currentInline as EmphasisDelimiterInline;
|
||||
if (emphasisDelimiter != null)
|
||||
if (currentInline is EmphasisDelimiterInline emphasisDelimiter)
|
||||
{
|
||||
if (pendingEmphasis == null)
|
||||
{
|
||||
// Not optimized for GC, but we don't expect this case much
|
||||
pendingEmphasis = new List<char>();
|
||||
}
|
||||
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
|
||||
{
|
||||
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
|
||||
@@ -254,6 +254,14 @@ namespace Markdig.Extensions.AutoLinks
|
||||
}
|
||||
|
||||
return countBrackets <= 0;
|
||||
}
|
||||
|
||||
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
|
||||
{
|
||||
protected override void Reset(List<char> instance)
|
||||
{
|
||||
instance.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
@@ -51,8 +51,7 @@ namespace Markdig.Extensions.Bootstrap
|
||||
}
|
||||
else if (node is Inline)
|
||||
{
|
||||
var link = node as LinkInline;
|
||||
if (link != null && link.IsImage)
|
||||
if (node is LinkInline link && link.IsImage)
|
||||
{
|
||||
link.GetAttributes().AddClass("img-fluid");
|
||||
}
|
||||
|
||||
@@ -7,24 +7,24 @@ using Markdig.Renderers;
|
||||
namespace Markdig.Extensions.Emoji
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to allow emoji and smiley replacement.
|
||||
/// Extension to allow emoji shortcodes and smileys replacement.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class EmojiExtension : IMarkdownExtension
|
||||
{
|
||||
public EmojiExtension(bool enableSmiley = true)
|
||||
{
|
||||
EnableSmiley = enableSmiley;
|
||||
public EmojiExtension(EmojiMapping emojiMapping)
|
||||
{
|
||||
EmojiMapping = emojiMapping;
|
||||
}
|
||||
|
||||
public bool EnableSmiley { get; set; }
|
||||
|
||||
|
||||
public EmojiMapping EmojiMapping { get; }
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
if (!pipeline.InlineParsers.Contains<EmojiParser>())
|
||||
{
|
||||
// Insert the parser before any other parsers
|
||||
pipeline.InlineParsers.Insert(0, new EmojiParser(EnableSmiley));
|
||||
pipeline.InlineParsers.Insert(0, new EmojiParser(EmojiMapping));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
@@ -8,7 +8,7 @@ using Markdig.Syntax.Inlines;
|
||||
namespace Markdig.Extensions.Emoji
|
||||
{
|
||||
/// <summary>
|
||||
/// An emoji inline
|
||||
/// An emoji inline.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Syntax.Inlines.Inline" />
|
||||
public class EmojiInline : LiteralInline
|
||||
@@ -32,7 +32,7 @@ namespace Markdig.Extensions.Emoji
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original match string (either an emoji or a text smiley)
|
||||
/// Gets or sets the original match string (either an emoji shortcode or a text smiley)
|
||||
/// </summary>
|
||||
public string Match { get; set; }
|
||||
}
|
||||
|
||||
1794
src/Markdig/Extensions/Emoji/EmojiMapping.cs
Normal file
1794
src/Markdig/Extensions/Emoji/EmojiMapping.cs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
/// Extension that allows to attach HTML attributes to the previous <see cref="Inline"/> or current <see cref="Block"/>.
|
||||
/// This extension should be enabled last after enabling other extensions.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
/// <seealso cref="IMarkdownExtension" />
|
||||
public class GenericAttributesExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
@@ -29,8 +29,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
// Plug into all IAttributesParseable
|
||||
foreach (var parser in pipeline.BlockParsers)
|
||||
{
|
||||
var attributesParseable = parser as IAttributesParseable;
|
||||
if (attributesParseable != null)
|
||||
if (parser is IAttributesParseable attributesParseable)
|
||||
{
|
||||
attributesParseable.TryParseAttributes = TryProcessAttributesForHeading;
|
||||
}
|
||||
@@ -53,8 +52,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
var copy = line;
|
||||
copy.Start = indexOfAttributes;
|
||||
var startOfAttributes = copy.Start;
|
||||
HtmlAttributes attributes;
|
||||
if (GenericAttributesParser.TryParse(ref copy, out attributes))
|
||||
if (GenericAttributesParser.TryParse(ref copy, out HtmlAttributes attributes))
|
||||
{
|
||||
var htmlAttributes = block.GetAttributes();
|
||||
attributes.CopyTo(htmlAttributes);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
/// <summary>
|
||||
/// An inline parser used to parse a HTML attributes that can be attached to the previous <see cref="Inline"/> or current <see cref="Block"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
/// <seealso cref="InlineParser" />
|
||||
public class GenericAttributesParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
@@ -27,9 +27,8 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
HtmlAttributes attributes;
|
||||
var startPosition = slice.Start;
|
||||
if (TryParse(ref slice, out attributes))
|
||||
if (TryParse(ref slice, out HtmlAttributes attributes))
|
||||
{
|
||||
var inline = processor.Inline;
|
||||
|
||||
@@ -50,8 +49,10 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
|
||||
// If the current block is a Paragraph, but only the HtmlAttributes is used,
|
||||
// Try to attach the attributes to the following block
|
||||
var paragraph = objectToAttach as ParagraphBlock;
|
||||
if (paragraph != null && paragraph.Inline.FirstChild == null && processor.Inline == null && slice.IsEmptyOrWhitespace())
|
||||
if (objectToAttach is ParagraphBlock paragraph &&
|
||||
paragraph.Inline.FirstChild == null &&
|
||||
processor.Inline == null &&
|
||||
slice.IsEmptyOrWhitespace())
|
||||
{
|
||||
var parent = paragraph.Parent;
|
||||
var indexOfParagraph = parent.IndexOf(paragraph);
|
||||
@@ -67,9 +68,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
attributes.CopyTo(currentHtmlAttributes, true, false);
|
||||
|
||||
// Update the position of the attributes
|
||||
int line;
|
||||
int column;
|
||||
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out line, out column);
|
||||
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out int line, out int column);
|
||||
currentHtmlAttributes.Line = line;
|
||||
currentHtmlAttributes.Column = column;
|
||||
currentHtmlAttributes.Span.End = currentHtmlAttributes.Span.Start + slice.Start - startPosition - 1;
|
||||
@@ -223,6 +222,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
{
|
||||
// Parse until we match a space or a special html character
|
||||
startValue = line.Start;
|
||||
bool valid = false;
|
||||
while (true)
|
||||
{
|
||||
if (c == '\0')
|
||||
@@ -234,9 +234,10 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
break;
|
||||
}
|
||||
c = line.NextChar();
|
||||
valid = true;
|
||||
}
|
||||
endValue = line.Start - 1;
|
||||
if (endValue == startValue)
|
||||
if (!valid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Markdig.Extensions.Globalization
|
||||
var attributes = node.GetAttributes();
|
||||
attributes.AddPropertyIfNotExist("dir", "rtl");
|
||||
|
||||
if (node is Table table)
|
||||
if (node is Table)
|
||||
{
|
||||
attributes.AddPropertyIfNotExist("align", "right");
|
||||
}
|
||||
@@ -71,19 +71,16 @@ namespace Markdig.Extensions.Globalization
|
||||
}
|
||||
else if (item is LiteralInline literal)
|
||||
{
|
||||
return StartsWithRtlCharacter(literal.ToString());
|
||||
return StartsWithRtlCharacter(literal.Content);
|
||||
}
|
||||
|
||||
foreach (var descendant in item.Descendants())
|
||||
foreach (var paragraph in item.Descendants<ParagraphBlock>())
|
||||
{
|
||||
if (descendant is ParagraphBlock p)
|
||||
foreach (var inline in paragraph.Inline)
|
||||
{
|
||||
foreach (var i in p.Inline)
|
||||
if (inline is LiteralInline literal)
|
||||
{
|
||||
if (i is LiteralInline l)
|
||||
{
|
||||
return StartsWithRtlCharacter(l.ToString());
|
||||
}
|
||||
return StartsWithRtlCharacter(literal.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,9 +88,9 @@ namespace Markdig.Extensions.Globalization
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool StartsWithRtlCharacter(string text)
|
||||
private bool StartsWithRtlCharacter(StringSlice slice)
|
||||
{
|
||||
foreach (var c in CharHelper.ToUtf32(text))
|
||||
foreach (var c in CharHelper.ToUtf32(slice))
|
||||
{
|
||||
if (CharHelper.IsRightToLeft(c))
|
||||
return true;
|
||||
|
||||
@@ -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();
|
||||
|
||||
139
src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs
Normal file
139
src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
public class HostProviderBuilder
|
||||
{
|
||||
private sealed class DelegateProvider : IHostProvider
|
||||
{
|
||||
public string HostPrefix { get; set; }
|
||||
public Func<Uri, string> Delegate { get; set; }
|
||||
public bool AllowFullScreen { get; set; } = true;
|
||||
public string Class { get; set; }
|
||||
|
||||
public bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl)
|
||||
{
|
||||
if (!mediaUri.Host.StartsWith(HostPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
iframeUrl = null;
|
||||
return false;
|
||||
}
|
||||
iframeUrl = Delegate(mediaUri);
|
||||
return !string.IsNullOrEmpty(iframeUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IHostProvider"/> with delegate handler.
|
||||
/// </summary>
|
||||
/// <param name="hostPrefix">Prefix of host that can be handled.</param>
|
||||
/// <param name="handler">Handler that generate iframe url, if uri cannot be handled, it can return <see langword="null"/>.</param>
|
||||
/// <param name="allowFullScreen">Should the generated iframe has allowfullscreen attribute.</param>
|
||||
/// <param name="iframeClass">"class" attribute of generated iframe.</param>
|
||||
/// <returns>A <see cref="IHostProvider"/> with delegate handler.</returns>
|
||||
public static IHostProvider Create(string hostPrefix, Func<Uri, string> handler, bool allowFullScreen = true, string iframeClass = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hostPrefix))
|
||||
ThrowHelper.ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
|
||||
if (handler == null)
|
||||
ThrowHelper.ArgumentNullException(nameof(handler));
|
||||
|
||||
return new DelegateProvider { HostPrefix = hostPrefix, Delegate = handler, AllowFullScreen = allowFullScreen, Class = iframeClass };
|
||||
}
|
||||
|
||||
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
|
||||
= new Dictionary<string, IHostProvider>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["YouTube"] = Create("www.youtube.com", YouTube, iframeClass: "youtube"),
|
||||
["YouTubeShortened"] = Create("youtu.be", YouTubeShortened, iframeClass: "youtube"),
|
||||
["Vimeo"] = Create("vimeo.com", Vimeo, iframeClass: "vimeo"),
|
||||
["Yandex"] = Create("music.yandex.ru", Yandex, allowFullScreen: false, iframeClass: "yandex"),
|
||||
["Odnoklassniki"] = Create("ok.ru", Odnoklassniki, iframeClass: "odnoklassniki"),
|
||||
};
|
||||
|
||||
#region Known providers
|
||||
|
||||
private static readonly string[] SplitAnd = { "&" };
|
||||
private static string[] SplitQuery(Uri uri)
|
||||
{
|
||||
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
|
||||
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private static string YouTube(Uri uri)
|
||||
{
|
||||
string uriPath = uri.AbsolutePath;
|
||||
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri.ToString();
|
||||
}
|
||||
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var queryParams = SplitQuery(uri);
|
||||
return BuildYouTubeIframeUrl(
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string YouTubeShortened(Uri uri)
|
||||
{
|
||||
return BuildYouTubeIframeUrl(
|
||||
uri.AbsolutePath.Substring(1),
|
||||
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string url = $"https://www.youtube.com/embed/{videoId}";
|
||||
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
|
||||
}
|
||||
|
||||
private static string Vimeo(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
return items.Length > 0 ? $"https://player.vimeo.com/video/{ items[items.Length - 1] }" : null;
|
||||
}
|
||||
|
||||
private static string Odnoklassniki(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
return items.Length > 0 ? $"https://ok.ru/videoembed/{ items[items.Length - 1] }" : null;
|
||||
}
|
||||
|
||||
private static string Yandex(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
var albumKeyword
|
||||
= items.Skip(0).FirstOrDefault();
|
||||
var albumId
|
||||
= items.Skip(1).FirstOrDefault();
|
||||
var trackKeyword
|
||||
= items.Skip(2).FirstOrDefault();
|
||||
var trackId
|
||||
= items.Skip(3).FirstOrDefault();
|
||||
|
||||
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
36
src/Markdig/Extensions/MediaLinks/IHostProvider.cs
Normal file
36
src/Markdig/Extensions/MediaLinks/IHostProvider.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides url for media links.
|
||||
/// </summary>
|
||||
public interface IHostProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// "class" attribute of generated iframe.
|
||||
/// </summary>
|
||||
string Class { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate url for iframe.
|
||||
/// </summary>
|
||||
/// <param name="mediaUri">Input media uri.</param>
|
||||
/// <param name="isSchemaRelative"><see langword="true"/> if <paramref name="mediaUri"/> is a schema relative uri, i.e. uri starts with "//".</param>
|
||||
/// <param name="iframeUrl">Generated url for iframe.</param>
|
||||
/// <seealso href="https://tools.ietf.org/html/rfc3986#section-4.2"/>
|
||||
bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl);
|
||||
|
||||
/// <summary>
|
||||
/// Should the generated iframe has allowfullscreen attribute.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should be false for audio embedding.
|
||||
/// </remarks>
|
||||
bool AllowFullScreen { get; }
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
@@ -55,18 +54,28 @@ namespace Markdig.Extensions.MediaLinks
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
bool isSchemaRelative = false;
|
||||
// Only process absolute Uri
|
||||
if (!Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) || !uri.IsAbsoluteUri)
|
||||
{
|
||||
return false;
|
||||
// see https://tools.ietf.org/html/rfc3986#section-4.2
|
||||
// since relative uri doesn't support many properties, "http" is used as a placeholder here.
|
||||
if (linkInline.Url.StartsWith("//") && Uri.TryCreate("http:" + linkInline.Url, UriKind.Absolute, out uri))
|
||||
{
|
||||
isSchemaRelative = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryRenderIframeFromKnownProviders(uri, renderer, linkInline))
|
||||
if (TryRenderIframeFromKnownProviders(uri, isSchemaRelative, renderer, linkInline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGuessAudioVideoFile(uri, renderer, linkInline))
|
||||
if (TryGuessAudioVideoFile(uri, isSchemaRelative, renderer, linkInline))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -86,7 +95,7 @@ namespace Markdig.Extensions.MediaLinks
|
||||
return htmlAttributes;
|
||||
}
|
||||
|
||||
private bool TryGuessAudioVideoFile(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
|
||||
private bool TryGuessAudioVideoFile(Uri uri, bool isSchemaRelative, HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
|
||||
// Otherwise try to detect if we have an audio/video from the file extension
|
||||
@@ -106,6 +115,10 @@ namespace Markdig.Extensions.MediaLinks
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
}
|
||||
htmlAttributes.AddPropertyIfNotExist("controls", null);
|
||||
|
||||
if (!string.IsNullOrEmpty(Options.Class))
|
||||
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
|
||||
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
|
||||
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
|
||||
@@ -115,38 +128,18 @@ namespace Markdig.Extensions.MediaLinks
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Known providers
|
||||
|
||||
private class KnownProvider
|
||||
private bool TryRenderIframeFromKnownProviders(Uri uri, bool isSchemaRelative, HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
public string HostPrefix { get; set; }
|
||||
public Func<Uri, string> Delegate { get; set; }
|
||||
public bool AllowFullScreen { get; set; } = true; //Should be false for audio embedding
|
||||
}
|
||||
|
||||
private static readonly List<KnownProvider> KnownHosts = new List<KnownProvider>()
|
||||
{
|
||||
new KnownProvider {HostPrefix = "www.youtube.com", Delegate = YouTube},
|
||||
new KnownProvider {HostPrefix = "youtu.be", Delegate = YouTubeShortened},
|
||||
new KnownProvider {HostPrefix = "vimeo.com", Delegate = Vimeo},
|
||||
new KnownProvider {HostPrefix = "music.yandex.ru", Delegate = Yandex, AllowFullScreen = false},
|
||||
new KnownProvider {HostPrefix = "ok.ru", Delegate = Odnoklassniki},
|
||||
};
|
||||
|
||||
|
||||
private bool TryRenderIframeFromKnownProviders(Uri uri, HtmlRenderer renderer, LinkInline linkInline)
|
||||
{
|
||||
var foundProvider =
|
||||
KnownHosts
|
||||
.Where(pair => uri.Host.StartsWith(pair.HostPrefix, StringComparison.OrdinalIgnoreCase)) // when host is match
|
||||
.Select(provider =>
|
||||
new
|
||||
{
|
||||
provider.AllowFullScreen,
|
||||
Result = provider.Delegate(uri) // try to call delegate to get iframeUrl
|
||||
}
|
||||
)
|
||||
.FirstOrDefault(provider => provider.Result != null); // use first success
|
||||
IHostProvider foundProvider = null;
|
||||
string iframeUrl = null;
|
||||
foreach (var provider in Options.Hosts)
|
||||
{
|
||||
if (!provider.TryHandle(uri, isSchemaRelative, out iframeUrl))
|
||||
continue;
|
||||
foundProvider = provider;
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundProvider == null)
|
||||
{
|
||||
@@ -155,17 +148,20 @@ namespace Markdig.Extensions.MediaLinks
|
||||
|
||||
var htmlAttributes = GetHtmlAttributes(linkInline);
|
||||
renderer.Write("<iframe src=\"");
|
||||
renderer.WriteEscapeUrl(foundProvider.Result);
|
||||
renderer.WriteEscapeUrl(iframeUrl);
|
||||
renderer.Write("\"");
|
||||
|
||||
if(!string.IsNullOrEmpty(Options.Width))
|
||||
if (!string.IsNullOrEmpty(Options.Width))
|
||||
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
|
||||
|
||||
if (!string.IsNullOrEmpty(Options.Height))
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
|
||||
if (!string.IsNullOrEmpty(Options.Class))
|
||||
htmlAttributes.AddPropertyIfNotExist("class", Options.Class);
|
||||
if (!string.IsNullOrEmpty(Options.Class) || !string.IsNullOrEmpty(foundProvider.Class))
|
||||
htmlAttributes.AddPropertyIfNotExist("class",
|
||||
(!string.IsNullOrEmpty(Options.Class) && !string.IsNullOrEmpty(foundProvider.Class))
|
||||
? Options.Class + " " + foundProvider.Class
|
||||
: Options.Class + foundProvider.Class);
|
||||
|
||||
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
|
||||
if (foundProvider.AllowFullScreen)
|
||||
@@ -177,81 +173,5 @@ namespace Markdig.Extensions.MediaLinks
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static readonly string[] SplitAnd = {"&"};
|
||||
private static string[] SplitQuery(Uri uri)
|
||||
{
|
||||
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
|
||||
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
private static string YouTube(Uri uri)
|
||||
{
|
||||
string uriPath = uri.AbsolutePath;
|
||||
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return uri.ToString();
|
||||
}
|
||||
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var queryParams = SplitQuery(uri);
|
||||
return BuildYouTubeIframeUrl(
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
|
||||
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string YouTubeShortened(Uri uri)
|
||||
{
|
||||
return BuildYouTubeIframeUrl(
|
||||
uri.AbsolutePath.Substring(1),
|
||||
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
|
||||
);
|
||||
}
|
||||
|
||||
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
|
||||
{
|
||||
if (string.IsNullOrEmpty(videoId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string url = $"https://www.youtube.com/embed/{videoId}";
|
||||
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
|
||||
}
|
||||
|
||||
private static string Vimeo(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
return items.Length > 0 ? $"https://player.vimeo.com/video/{items[items.Length - 1]}" : null;
|
||||
}
|
||||
|
||||
private static string Odnoklassniki(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
return items.Length > 0 ? $"https://ok.ru/videoembed/{items[items.Length - 1]}" : null;
|
||||
}
|
||||
|
||||
private static string Yandex(Uri uri)
|
||||
{
|
||||
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
|
||||
var albumKeyword
|
||||
= items.Skip(0).FirstOrDefault();
|
||||
var albumId
|
||||
= items.Skip(1).FirstOrDefault();
|
||||
var trackKeyword
|
||||
= items.Skip(2).FirstOrDefault();
|
||||
var trackId
|
||||
= items.Skip(3).FirstOrDefault();
|
||||
|
||||
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,12 +76,14 @@ namespace Markdig.Extensions.MediaLinks
|
||||
{".ecelp7470", "audio/vnd.nuera.ecelp7470"},
|
||||
{".ecelp9600", "audio/vnd.nuera.ecelp9600"},
|
||||
{".oga", "audio/ogg"},
|
||||
{".ogg", "audio/ogg"},
|
||||
{".weba", "audio/webm"},
|
||||
{".ram", "audio/x-pn-realaudio"},
|
||||
{".rmp", "audio/x-pn-realaudio-plugin"},
|
||||
{".au", "audio/basic"},
|
||||
{".wav", "audio/x-wav"},
|
||||
};
|
||||
Hosts = new List<IHostProvider>(HostProviderBuilder.KnownHosts.Values);
|
||||
}
|
||||
|
||||
public string Width { get; set; }
|
||||
@@ -91,5 +93,7 @@ namespace Markdig.Extensions.MediaLinks
|
||||
public string Class { get; set; }
|
||||
|
||||
public Dictionary<string, string> ExtensionToMimeType { get; }
|
||||
|
||||
public List<IHostProvider> Hosts { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System;
|
||||
@@ -10,7 +10,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
/// <summary>
|
||||
/// A HTML renderer for a <see cref="SmartyPant"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{SmartyPant}" />
|
||||
/// <seealso cref="HtmlObjectRenderer{SmartyPant}" />
|
||||
public class HtmlSmartyPantRenderer : HtmlObjectRenderer<SmartyPant>
|
||||
{
|
||||
private static readonly SmartyPantOptions DefaultOptions = new SmartyPantOptions();
|
||||
@@ -21,17 +21,15 @@ namespace Markdig.Extensions.SmartyPants
|
||||
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public HtmlSmartyPantRenderer(SmartyPantOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
this.options = options;
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
protected override void Write(HtmlRenderer renderer, SmartyPant obj)
|
||||
{
|
||||
string text;
|
||||
if (!options.Mapping.TryGetValue(obj.Type, out text))
|
||||
if (!options.Mapping.TryGetValue(obj.Type, out string text))
|
||||
{
|
||||
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System.Diagnostics;
|
||||
@@ -45,5 +45,15 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
return OpeningCharacter != 0 ? OpeningCharacter.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
public LiteralInline AsLiteralInline()
|
||||
{
|
||||
return new LiteralInline(ToString())
|
||||
{
|
||||
Span = Span,
|
||||
Line = Line,
|
||||
Column = Column,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
@@ -37,8 +37,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
if (renderer is HtmlRenderer htmlRenderer)
|
||||
{
|
||||
if (!htmlRenderer.ObjectRenderers.Contains<HtmlSmartyPantRenderer>())
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
{
|
||||
case '\'':
|
||||
type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines
|
||||
if (slice.PeekChar(1) == '\'')
|
||||
if (slice.PeekChar() == '\'')
|
||||
{
|
||||
slice.NextChar();
|
||||
type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines
|
||||
@@ -95,9 +95,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
// Skip char
|
||||
c = slice.NextChar();
|
||||
|
||||
bool canOpen;
|
||||
bool canClose;
|
||||
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
|
||||
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out bool canOpen, out bool canClose);
|
||||
|
||||
bool postProcess = false;
|
||||
|
||||
@@ -156,11 +154,9 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
|
||||
// Create the SmartyPant inline
|
||||
int line;
|
||||
int column;
|
||||
var pant = new SmartyPant()
|
||||
{
|
||||
Span = {Start = processor.GetSourcePosition(startingPosition, out line, out column)},
|
||||
Span = { Start = processor.GetSourcePosition(startingPosition, out int line, out int column) },
|
||||
Line = line,
|
||||
Column = column,
|
||||
OpeningCharacter = openingChar,
|
||||
@@ -195,96 +191,90 @@ namespace Markdig.Extensions.SmartyPants
|
||||
return quotePants;
|
||||
}
|
||||
|
||||
private readonly struct Opener
|
||||
{
|
||||
public readonly int Type;
|
||||
public readonly int Index;
|
||||
|
||||
public Opener(int type, int index)
|
||||
{
|
||||
Type = type;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline inline)
|
||||
{
|
||||
processor.Block.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
|
||||
|
||||
var pants = (ListSmartyPants) processor.ParserStates[Index];
|
||||
|
||||
// We only change quote into left or right quotes if we find proper balancing
|
||||
var previousIndices = new int[3] {-1, -1, -1};
|
||||
Stack<Opener> openers = new Stack<Opener>(4);
|
||||
|
||||
for (int i = 0; i < pants.Count; i++)
|
||||
{
|
||||
var quote = pants[i];
|
||||
var quoteType = quote.Type;
|
||||
|
||||
int currentTypeIndex = -1;
|
||||
SmartyPantType expectedLeftQuote = 0;
|
||||
SmartyPantType expectedRightQuote = 0;
|
||||
int type;
|
||||
bool isLeft;
|
||||
|
||||
if (quote.Type == SmartyPantType.LeftQuote || quote.Type == SmartyPantType.RightQuote)
|
||||
if (quoteType == SmartyPantType.LeftQuote || quoteType == SmartyPantType.RightQuote)
|
||||
{
|
||||
currentTypeIndex = 0;
|
||||
expectedLeftQuote = SmartyPantType.LeftQuote;
|
||||
expectedRightQuote = SmartyPantType.RightQuote;
|
||||
type = 0;
|
||||
isLeft = quoteType == SmartyPantType.LeftQuote;
|
||||
}
|
||||
else if (quote.Type == SmartyPantType.LeftDoubleQuote || quote.Type == SmartyPantType.RightDoubleQuote)
|
||||
else if (quoteType == SmartyPantType.LeftDoubleQuote || quoteType == SmartyPantType.RightDoubleQuote)
|
||||
{
|
||||
currentTypeIndex = 1;
|
||||
expectedLeftQuote = SmartyPantType.LeftDoubleQuote;
|
||||
expectedRightQuote = SmartyPantType.RightDoubleQuote;
|
||||
type = 1;
|
||||
isLeft = quoteType == SmartyPantType.LeftDoubleQuote;
|
||||
}
|
||||
else if (quote.Type == SmartyPantType.LeftAngleQuote || quote.Type == SmartyPantType.RightAngleQuote)
|
||||
else if (quoteType == SmartyPantType.LeftAngleQuote || quoteType == SmartyPantType.RightAngleQuote)
|
||||
{
|
||||
currentTypeIndex = 2;
|
||||
expectedLeftQuote = SmartyPantType.LeftAngleQuote;
|
||||
expectedRightQuote = SmartyPantType.RightAngleQuote;
|
||||
}
|
||||
|
||||
if (currentTypeIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int previousIndex = previousIndices[currentTypeIndex];
|
||||
var previousQuote = previousIndex >= 0 ? pants[previousIndex] : null;
|
||||
if (previousQuote == null)
|
||||
{
|
||||
if (quote.Type == expectedLeftQuote)
|
||||
{
|
||||
previousIndices[currentTypeIndex] = i;
|
||||
}
|
||||
type = 2;
|
||||
isLeft = quoteType == SmartyPantType.LeftAngleQuote;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (quote.Type == expectedRightQuote)
|
||||
{
|
||||
// Replace all intermediate unmatched left or right SmartyPants to their literal equivalent
|
||||
pants.RemoveAt(i);
|
||||
i--;
|
||||
for (int j = i; j > previousIndex; j--)
|
||||
{
|
||||
var toReplace = pants[j];
|
||||
pants.RemoveAt(j);
|
||||
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString())
|
||||
{
|
||||
Span = toReplace.Span,
|
||||
Line = toReplace.Line,
|
||||
Column = toReplace.Column,
|
||||
});
|
||||
i--;
|
||||
}
|
||||
quote.ReplaceBy(quote.AsLiteralInline());
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we matched, we remove left/right quotes from the list
|
||||
pants.RemoveAt(previousIndex);
|
||||
previousIndices[currentTypeIndex] = -1;
|
||||
}
|
||||
else
|
||||
if (isLeft)
|
||||
{
|
||||
openers.Push(new Opener(type, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
while (openers.Count > 0)
|
||||
{
|
||||
previousIndices[currentTypeIndex] = i;
|
||||
Opener opener = openers.Pop();
|
||||
var previousQuote = pants[opener.Index];
|
||||
|
||||
if (opener.Type == type)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousQuote.ReplaceBy(previousQuote.AsLiteralInline());
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
quote.ReplaceBy(quote.AsLiteralInline());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have any quotes lefts, replace them by there literal equivalent
|
||||
foreach (var quote in pants)
|
||||
foreach (var opener in openers)
|
||||
{
|
||||
quote.ReplaceBy(new LiteralInline(quote.ToString())
|
||||
{
|
||||
Span = quote.Span,
|
||||
Line = quote.Line,
|
||||
Column = quote.Column,
|
||||
});
|
||||
var quote = pants[opener.Index];
|
||||
quote.ReplaceBy(quote.AsLiteralInline());
|
||||
}
|
||||
|
||||
pants.Clear();
|
||||
@@ -294,8 +284,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
bool isFinalProcessing)
|
||||
{
|
||||
// Don't try to process anything if there are no dash
|
||||
var quotePants = state.ParserStates[Index] as ListSmartyPants;
|
||||
if (quotePants == null || !quotePants.HasDash)
|
||||
if (!(state.ParserStates[Index] is ListSmartyPants quotePants) || !quotePants.HasDash)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -309,10 +298,8 @@ namespace Markdig.Extensions.SmartyPants
|
||||
{
|
||||
var next = child.NextSibling;
|
||||
|
||||
if (child is LiteralInline)
|
||||
if (child is LiteralInline literal)
|
||||
{
|
||||
var literal = (LiteralInline) child;
|
||||
|
||||
var startIndex = 0;
|
||||
|
||||
var indexOfDash = literal.Content.IndexOf("--", startIndex);
|
||||
@@ -372,7 +359,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
|
||||
|
||||
private class ListSmartyPants : List<SmartyPant>
|
||||
private sealed class ListSmartyPants : List<SmartyPant>
|
||||
{
|
||||
public bool HasDash { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
@@ -138,7 +138,7 @@ namespace Markdig.Extensions.Tables
|
||||
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
|
||||
{
|
||||
var lineStart = line.Start;
|
||||
isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
|
||||
isHeaderRow = line.PeekChar() == '=' || line.PeekChar(2) == '=';
|
||||
hasRowSpan = false;
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Tables
|
||||
/// <summary>
|
||||
/// Internal state used by the <see cref="GridTableParser"/>
|
||||
/// </summary>
|
||||
internal class GridTableState
|
||||
internal sealed class GridTableState
|
||||
{
|
||||
public int Start { get; set; }
|
||||
|
||||
|
||||
@@ -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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -2,41 +2,12 @@
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
|
||||
// The IsHighSurrogate, IsLowSurrogate and ConvertToUtf32 methods are copied from
|
||||
// .Net Core source code which is under MIT license. They are copied here because
|
||||
// they don't exist in `portable40-net40+sl5+win8+wp8+wpa81`, We probably should remove them
|
||||
// once we dropped support for that target platform and use the official .Net methods.
|
||||
|
||||
//The MIT License(MIT)
|
||||
|
||||
//Copyright(c) .NET Foundation and Contributors
|
||||
|
||||
//All rights reserved.
|
||||
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
@@ -46,20 +17,20 @@ namespace Markdig.Helpers
|
||||
{
|
||||
public const int TabSize = 4;
|
||||
|
||||
public const char ZeroSafeChar = '\uFFFD';
|
||||
public const char ReplacementChar = '\uFFFD';
|
||||
|
||||
public const string ZeroSafeString = "\uFFFD";
|
||||
public const string ReplacementCharString = "\uFFFD";
|
||||
|
||||
private const char HighSurrogateStart = '\ud800';
|
||||
private const char HighSurrogateEnd = '\udbff';
|
||||
private const char LowSurrogateStart = '\udc00';
|
||||
private const char LowSurrogateEnd = '\udfff';
|
||||
|
||||
// The starting codepoint for Unicode plane 1. Plane 1 contains 0x010000 ~ 0x01ffff.
|
||||
private const int UnicodePlane01Start = 0x10000;
|
||||
|
||||
// We don't support LCDM
|
||||
private static readonly Dictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
|
||||
private static readonly Dictionary<char, int> romanMap = new Dictionary<char, int>(6) {
|
||||
{ 'i', 1 }, { 'v', 5 }, { 'x', 10 },
|
||||
{ 'I', 1 }, { 'V', 5 }, { 'X', 10 }
|
||||
};
|
||||
|
||||
private static readonly char[] punctuationExceptions = { '−', '-', '†', '‡' };
|
||||
|
||||
@@ -103,36 +74,35 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsRomanLetterPartial(char c)
|
||||
{
|
||||
// We don't support LCDM
|
||||
return IsRomanLetterLowerPartial(c) || IsRomanLetterUpperPartial(c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsRomanLetterLowerPartial(char c)
|
||||
{
|
||||
// We don't support LCDM
|
||||
return c == 'i' || c == 'v' || c == 'x';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsRomanLetterUpperPartial(char c)
|
||||
{
|
||||
// We don't support LCDM
|
||||
return c == 'I' || c == 'V' || c == 'X';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static int RomanToArabic(string text)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int RomanToArabic(ReadOnlySpan<char> text)
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
var character = Char.ToUpperInvariant(text[i]);
|
||||
var candidate = romanMap[character];
|
||||
if (i + 1 < text.Length && candidate < romanMap[Char.ToUpperInvariant(text[i + 1])])
|
||||
var candidate = romanMap[text[i]];
|
||||
if ((uint)(i + 1) < text.Length && candidate < romanMap[text[i + 1]])
|
||||
{
|
||||
result -= candidate;
|
||||
}
|
||||
@@ -144,24 +114,26 @@ namespace Markdig.Helpers
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int AddTab(int column)
|
||||
{
|
||||
return ((column + TabSize) / TabSize) * TabSize;
|
||||
// return ((column + TabSize) / TabSize) * TabSize;
|
||||
Debug.Assert(TabSize == 4, "Change the AddTab implementation if TabSize is no longer a power of 2");
|
||||
return TabSize + (column & ~(TabSize - 1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAcrossTab(int column)
|
||||
{
|
||||
return (column & (TabSize - 1)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Contains(this char[] charList, char c)
|
||||
{
|
||||
for (int i = 0; i < charList.Length; i++)
|
||||
foreach (char ch in charList)
|
||||
{
|
||||
if (charList[i] == c)
|
||||
if (ch == c)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -169,31 +141,31 @@ namespace Markdig.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsWhitespace(this char c)
|
||||
{
|
||||
// 2.1 Characters and lines
|
||||
// A whitespace character is a space(U + 0020), tab(U + 0009), newline(U + 000A), line tabulation (U + 000B), form feed (U + 000C), or carriage return (U + 000D).
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
|
||||
return c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r');
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsControl(this char c)
|
||||
{
|
||||
return c < ' ' || Char.IsControl(c);
|
||||
return c < ' ' || char.IsControl(c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsEscapableSymbol(this char c)
|
||||
{
|
||||
// char.IsSymbol also works with Unicode symbols that cannot be escaped based on the specification.
|
||||
return (c > ' ' && c < '0') || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z' && c < 127) || c == '•';
|
||||
}
|
||||
|
||||
//[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsWhiteSpaceOrZero(this char c)
|
||||
{
|
||||
return IsWhitespace(c) || IsZero(c);
|
||||
return IsZero(c) || IsWhitespace(c);
|
||||
}
|
||||
|
||||
// Note that we are not considering the character & as a punctuation in HTML
|
||||
@@ -250,19 +222,19 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsNewLine(this char c)
|
||||
{
|
||||
return c == '\n';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsZero(this char c)
|
||||
{
|
||||
return c == '\0';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsSpace(this char c)
|
||||
{
|
||||
// 2.1 Characters and lines
|
||||
@@ -270,7 +242,7 @@ namespace Markdig.Helpers
|
||||
return c == ' ';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsTab(this char c)
|
||||
{
|
||||
// 2.1 Characters and lines
|
||||
@@ -278,13 +250,13 @@ namespace Markdig.Helpers
|
||||
return c == '\t';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsSpaceOrTab(this char c)
|
||||
{
|
||||
return IsSpace(c) || IsTab(c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static char EscapeInsecure(this char c)
|
||||
{
|
||||
// 2.3 Insecure characters
|
||||
@@ -292,28 +264,28 @@ namespace Markdig.Helpers
|
||||
return c == '\0' ? '\ufffd' : c;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAlphaUpper(this char c)
|
||||
{
|
||||
return c >= 'A' && c <= 'Z';
|
||||
{
|
||||
return (uint)(c - 'A') <= ('Z' - 'A');
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAlpha(this char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
{
|
||||
return (uint)((c - 'A') & ~0x20) <= ('Z' - 'A');
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAlphaNumeric(this char c)
|
||||
{
|
||||
return IsAlpha(c) || IsDigit(c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDigit(this char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
return (uint)(c - '0') <= ('9' - '0');
|
||||
}
|
||||
|
||||
public static bool IsAsciiPunctuation(this char c)
|
||||
@@ -359,44 +331,40 @@ namespace Markdig.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsEmailUsernameSpecialChar(char c)
|
||||
{
|
||||
return ".!#$%&'*+/=?^_`{|}~-+.~".IndexOf(c) >= 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsHighSurrogate(char c)
|
||||
{
|
||||
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
|
||||
{
|
||||
return IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsLowSurrogate(char c)
|
||||
{
|
||||
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
|
||||
return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd);
|
||||
}
|
||||
|
||||
public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
|
||||
{
|
||||
if (!IsHighSurrogate(highSurrogate))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(highSurrogate), "Invalid high surrogate");
|
||||
}
|
||||
if (!IsLowSurrogate(lowSurrogate))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lowSurrogate), "Invalid low surrogate");
|
||||
}
|
||||
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsInInclusiveRange(char c, char min, char max)
|
||||
=> (uint)(c - min) <= (uint)(max - min);
|
||||
|
||||
public static IEnumerable<int> ToUtf32(string text)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static bool IsInInclusiveRange(int value, uint min, uint max)
|
||||
=> ((uint)value - min) <= (max - min);
|
||||
|
||||
public static IEnumerable<int> ToUtf32(StringSlice text)
|
||||
{
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
for (int i = text.Start; i <= text.End; i++)
|
||||
{
|
||||
if (IsHighSurrogate(text[i]) && i < text.Length - 1 && IsLowSurrogate(text[i + 1]))
|
||||
{
|
||||
yield return ConvertToUtf32(text[i], text[i + 1]);
|
||||
if (IsHighSurrogate(text[i]) && i < text.End && IsLowSurrogate(text[i + 1]))
|
||||
{
|
||||
Debug.Assert(char.IsSurrogatePair(text[i], text[i + 1]));
|
||||
yield return char.ConvertToUtf32(text[i], text[i + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -777,6 +745,24 @@ namespace Markdig.Helpers
|
||||
c == 0x002128 || c == 0x002395 ||
|
||||
c == 0x01D4A2 || c == 0x01D4BB ||
|
||||
c == 0x01D546;
|
||||
}
|
||||
|
||||
// Used by ListExtraItemParser to format numbers from 1 - 26
|
||||
private static readonly string[] smallNumberStringCache = {
|
||||
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
||||
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
|
||||
"20", "21", "22", "23", "24", "25", "26",
|
||||
};
|
||||
|
||||
internal static string SmallNumberToString(int number)
|
||||
{
|
||||
string[] cache = smallNumberStringCache;
|
||||
if ((uint)number < (uint)cache.Length)
|
||||
{
|
||||
return cache[number];
|
||||
}
|
||||
|
||||
return number.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -16,8 +17,8 @@ namespace Markdig.Helpers
|
||||
public class CharacterMap<T> where T : class
|
||||
{
|
||||
private readonly T[] asciiMap;
|
||||
private readonly Dictionary<char, T> nonAsciiMap;
|
||||
private readonly bool[] isOpeningCharacter;
|
||||
private readonly Dictionary<uint, T> nonAsciiMap;
|
||||
private readonly BoolVector128 isOpeningCharacter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CharacterMap{T}"/> class.
|
||||
@@ -26,56 +27,41 @@ namespace Markdig.Helpers
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public CharacterMap(IEnumerable<KeyValuePair<char, T>> maps)
|
||||
{
|
||||
if (maps == null) throw new ArgumentNullException(nameof(maps));
|
||||
var charCounter = new Dictionary<char, int>();
|
||||
if (maps == null) ThrowHelper.ArgumentNullException(nameof(maps));
|
||||
var charSet = new HashSet<char>();
|
||||
int maxChar = 0;
|
||||
|
||||
foreach (var map in maps)
|
||||
{
|
||||
var openingChar = map.Key;
|
||||
charSet.Add(openingChar);
|
||||
|
||||
if (!charCounter.ContainsKey(openingChar))
|
||||
if (openingChar < 128)
|
||||
{
|
||||
charCounter[openingChar] = 0;
|
||||
}
|
||||
charCounter[openingChar]++;
|
||||
|
||||
if (openingChar < 127 && openingChar > maxChar)
|
||||
{
|
||||
maxChar = openingChar;
|
||||
}
|
||||
else if (openingChar >= 127 && nonAsciiMap == null)
|
||||
{
|
||||
// Initialize only if with have an actual non-ASCII opening character
|
||||
nonAsciiMap = new Dictionary<char, T>();
|
||||
}
|
||||
}
|
||||
OpeningCharacters = charCounter.Keys.ToArray();
|
||||
Array.Sort(OpeningCharacters);
|
||||
|
||||
asciiMap = new T[maxChar + 1];
|
||||
isOpeningCharacter = new bool[maxChar + 1];
|
||||
|
||||
foreach (var state in maps)
|
||||
{
|
||||
var openingChar = state.Key;
|
||||
T stateByChar;
|
||||
if (openingChar < 127)
|
||||
{
|
||||
stateByChar = asciiMap[openingChar];
|
||||
|
||||
if (stateByChar == null)
|
||||
{
|
||||
asciiMap[openingChar] = state.Value;
|
||||
}
|
||||
isOpeningCharacter[openingChar] = true;
|
||||
maxChar = Math.Max(maxChar, openingChar);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!nonAsciiMap.TryGetValue(openingChar, out stateByChar))
|
||||
{
|
||||
nonAsciiMap[openingChar] = state.Value;
|
||||
}
|
||||
nonAsciiMap ??= new Dictionary<uint, T>();
|
||||
}
|
||||
}
|
||||
|
||||
OpeningCharacters = charSet.ToArray();
|
||||
Array.Sort(OpeningCharacters);
|
||||
|
||||
asciiMap = new T[maxChar + 1];
|
||||
|
||||
foreach (var state in maps)
|
||||
{
|
||||
char openingChar = state.Key;
|
||||
if (openingChar < 128)
|
||||
{
|
||||
asciiMap[openingChar] ??= state.Value;
|
||||
isOpeningCharacter.Set(openingChar);
|
||||
}
|
||||
else if (!nonAsciiMap.ContainsKey(openingChar))
|
||||
{
|
||||
nonAsciiMap[openingChar] = state.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,24 +76,26 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <param name="openingChar">The opening character.</param>
|
||||
/// <returns>A list of parsers valid for the specified opening character or null if no parsers registered.</returns>
|
||||
public T this[char openingChar]
|
||||
public T this[uint openingChar]
|
||||
{
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
T map = null;
|
||||
if (openingChar < asciiMap.Length)
|
||||
T[] asciiMap = this.asciiMap;
|
||||
if (openingChar < (uint)asciiMap.Length)
|
||||
{
|
||||
map = asciiMap[openingChar];
|
||||
return asciiMap[openingChar];
|
||||
}
|
||||
else if (nonAsciiMap != null)
|
||||
else
|
||||
{
|
||||
nonAsciiMap.TryGetValue(openingChar, out map);
|
||||
T map = null;
|
||||
nonAsciiMap?.TryGetValue(openingChar, out map);
|
||||
return map;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Searches for an opening character from a registered parser in the specified string.
|
||||
/// </summary>
|
||||
@@ -117,47 +105,112 @@ namespace Markdig.Helpers
|
||||
/// <returns>Index position within the string of the first opening character found in the specified text; if not found, returns -1</returns>
|
||||
public int IndexOfOpeningCharacter(string text, int start, int end)
|
||||
{
|
||||
var maxChar = isOpeningCharacter.Length;
|
||||
#if SUPPORT_UNSAFE
|
||||
unsafe
|
||||
#endif
|
||||
if (nonAsciiMap is null)
|
||||
{
|
||||
#if SUPPORT_FIXED_STRING
|
||||
fixed (char* pText = text)
|
||||
#else
|
||||
var pText = text;
|
||||
#endif
|
||||
#if SUPPORT_UNSAFE
|
||||
fixed (bool* openingChars = isOpeningCharacter)
|
||||
#else
|
||||
var openingChars = isOpeningCharacter;
|
||||
#endif
|
||||
#if NETCOREAPP3_1
|
||||
ref char textRef = ref Unsafe.AsRef(in text.GetPinnableReference());
|
||||
for (; start <= end; start++)
|
||||
{
|
||||
if (nonAsciiMap == null)
|
||||
if (IntPtr.Size == 4)
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
uint c = Unsafe.Add(ref textRef, start);
|
||||
if (c < 128 && isOpeningCharacter[c])
|
||||
{
|
||||
var c = pText[i];
|
||||
if (c < maxChar && openingChars[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong c = Unsafe.Add(ref textRef, start);
|
||||
if (c < 128 && isOpeningCharacter[c])
|
||||
{
|
||||
return start;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
unsafe
|
||||
{
|
||||
fixed (char* pText = text)
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
var c = pText[i];
|
||||
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
|
||||
char c = pText[i];
|
||||
if (c < 128 && isOpeningCharacter[c])
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return IndexOfOpeningCharacterNonAscii(text, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
private int IndexOfOpeningCharacterNonAscii(string text, int start, int end)
|
||||
{
|
||||
#if NETCOREAPP3_1
|
||||
ref char textRef = ref Unsafe.AsRef(in text.GetPinnableReference());
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
char c = Unsafe.Add(ref textRef, i);
|
||||
if (c < 128 ? isOpeningCharacter[c] : nonAsciiMap.ContainsKey(c))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
#else
|
||||
unsafe
|
||||
{
|
||||
fixed (char* pText = text)
|
||||
{
|
||||
for (int i = start; i <= end; i++)
|
||||
{
|
||||
char c = pText[i];
|
||||
if (c < 128 ? isOpeningCharacter[c] : nonAsciiMap.ContainsKey(c))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe struct BoolVector128
|
||||
{
|
||||
private fixed bool values[128];
|
||||
|
||||
public void Set(char c)
|
||||
{
|
||||
Debug.Assert(c < 128);
|
||||
values[c] = true;
|
||||
}
|
||||
|
||||
public readonly bool this[uint c]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
Debug.Assert(c < 128);
|
||||
return values[c];
|
||||
}
|
||||
}
|
||||
public readonly bool this[ulong c]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
Debug.Assert(c < 128 && IntPtr.Size == 8);
|
||||
return values[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
/*
|
||||
@@ -25,10 +26,8 @@ namespace Markdig.Helpers
|
||||
/// <para>Something between a Trie and a full Radix tree, but stored linearly in memory</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The value associated with the key</typeparam>
|
||||
internal class CompactPrefixTree<TValue>
|
||||
//#if !LEGACY
|
||||
// : IReadOnlyDictionary<string, TValue>, IReadOnlyList<KeyValuePair<string, TValue>>
|
||||
//#endif
|
||||
[ExcludeFromCodeCoverage]
|
||||
internal sealed class CompactPrefixTree<TValue> : IReadOnlyDictionary<string, TValue>, IReadOnlyList<KeyValuePair<string, TValue>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Used internally to control behavior of insertion
|
||||
@@ -118,7 +117,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureTreeCapacity(int min)
|
||||
{
|
||||
if (_tree.Length < min)
|
||||
@@ -167,7 +166,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureCapacity(int min)
|
||||
{
|
||||
// Expansion logic as in System.Collections.Generic.List<T>
|
||||
@@ -225,7 +224,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureChildrenCapacity(int min)
|
||||
{
|
||||
if (_children.Length < min)
|
||||
@@ -252,7 +251,7 @@ namespace Markdig.Helpers
|
||||
|
||||
// Inspired by Markdig's CharacterMap
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryGetRoot(char rootChar, out int rootNodeIndex)
|
||||
{
|
||||
if (rootChar < 128)
|
||||
@@ -336,7 +335,7 @@ namespace Markdig.Helpers
|
||||
/// <returns>The key/value pair of the element at the specified index</returns>
|
||||
public KeyValuePair<string, TValue> this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if ((uint)index >= (uint)Count) ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
@@ -353,7 +352,7 @@ namespace Markdig.Helpers
|
||||
{
|
||||
get
|
||||
{
|
||||
if (TryMatchExact(key, out KeyValuePair<string, TValue> match))
|
||||
if (TryMatchExact(key.AsSpan(), out KeyValuePair<string, TValue> match))
|
||||
return match.Value;
|
||||
throw new KeyNotFoundException(key);
|
||||
}
|
||||
@@ -365,7 +364,6 @@ namespace Markdig.Helpers
|
||||
}
|
||||
} // Get, Set
|
||||
|
||||
#if NETCORE
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key
|
||||
/// </summary>
|
||||
@@ -380,7 +378,6 @@ namespace Markdig.Helpers
|
||||
throw new KeyNotFoundException(key.ToString());
|
||||
}
|
||||
} // Get only
|
||||
#endif
|
||||
|
||||
#endregion this[] accessors
|
||||
|
||||
@@ -711,50 +708,6 @@ namespace Markdig.Helpers
|
||||
|
||||
#region TryMatch longest
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the longest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="offset">Index of the character at which to start searching</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
public bool TryMatchLongest(string text, int offset, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
#if NETCORE
|
||||
return TryMatchLongest(text.AsSpan(offset), out match);
|
||||
#else
|
||||
return TryMatchLongest(text, offset, text.Length - offset, out match);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the longest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
public bool TryMatchLongest(string text, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
|
||||
#if NETCORE
|
||||
return TryMatchLongest(text.AsSpan(), out match);
|
||||
#else
|
||||
return TryMatchLongest(text, 0, text.Length, out match);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the longest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="offset">The offset in text at which to start looking for the prefix</param>
|
||||
/// <param name="length">The longest prefix allowed to match</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
#if NETCORE
|
||||
public bool TryMatchLongest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
|
||||
=> TryMatchLongest(text.AsSpan(offset, length), out match);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the longest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
@@ -766,20 +719,6 @@ namespace Markdig.Helpers
|
||||
match = default;
|
||||
if (text.Length == 0 || !TryGetRoot(text[0], out int nodeIndex))
|
||||
return false;
|
||||
#else
|
||||
public bool TryMatchLongest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
int limit = offset + length;
|
||||
if (text == null)
|
||||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
|
||||
|
||||
if (offset < 0 || length < 0 || text.Length < limit)
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offsetLength, ExceptionReason.InvalidOffsetLength);
|
||||
|
||||
match = default;
|
||||
if (length == 0 || !TryGetRoot(text[offset], out int nodeIndex))
|
||||
return false;
|
||||
#endif
|
||||
|
||||
int matchIndex = -1;
|
||||
int depth = 1;
|
||||
@@ -788,11 +727,7 @@ namespace Markdig.Helpers
|
||||
if (node.ChildChar == 0) goto LeafNodeFound;
|
||||
if (node.MatchIndex != -1) matchIndex = node.MatchIndex;
|
||||
|
||||
#if NETCORE
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
#else
|
||||
for (int i = offset + 1; i < limit; i++)
|
||||
#endif
|
||||
{
|
||||
char c = text[i];
|
||||
|
||||
@@ -824,7 +759,6 @@ namespace Markdig.Helpers
|
||||
|
||||
LeafNodeFound:;
|
||||
ref KeyValuePair<string, TValue> possibleMatch = ref _matches[node.MatchIndex];
|
||||
#if NETCORE
|
||||
if (possibleMatch.Key.Length <= text.Length)
|
||||
{
|
||||
// Check that the rest of the strings match
|
||||
@@ -833,18 +767,6 @@ namespace Markdig.Helpers
|
||||
matchIndex = node.MatchIndex;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (possibleMatch.Key.Length <= length)
|
||||
{
|
||||
// Check that the rest of the strings match
|
||||
for (int i = offset + depth, j = depth; j < possibleMatch.Key.Length; i++, j++)
|
||||
{
|
||||
if (text[i] != possibleMatch.Key[j])
|
||||
goto Return;
|
||||
}
|
||||
matchIndex = node.MatchIndex;
|
||||
}
|
||||
#endif
|
||||
|
||||
Return:;
|
||||
if (matchIndex != -1)
|
||||
@@ -859,50 +781,6 @@ namespace Markdig.Helpers
|
||||
|
||||
#region TryMatch exact
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a suffix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly (text.Length - offset) characters long
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="offset">Index of the character at which to start searching</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
public bool TryMatchExact(string text, int offset, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
#if NETCORE
|
||||
return TryMatchExact(text.AsSpan(offset), out match);
|
||||
#else
|
||||
return TryMatchExact(text, offset, text.Length - offset, out match);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly text.Length characters long
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
public bool TryMatchExact(string text, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
|
||||
#if NETCORE
|
||||
return TryMatchExact(text.AsSpan(), out match);
|
||||
#else
|
||||
return TryMatchExact(text, 0, text.Length, out match);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly length characters long
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="offset">The offset in text at which to start looking for the prefix</param>
|
||||
/// <param name="length">The longest prefix allowed to match</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
#if NETCORE
|
||||
public bool TryMatchExact(string text, int offset, int length, out KeyValuePair<string, TValue> match)
|
||||
=> TryMatchExact(text.AsSpan(offset, length), out match);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/> and is exactly text.Length characters long
|
||||
/// </summary>
|
||||
@@ -914,39 +792,18 @@ namespace Markdig.Helpers
|
||||
match = default;
|
||||
if (text.Length == 0 || !TryGetRoot(text[0], out int nodeIndex))
|
||||
return false;
|
||||
#else
|
||||
public bool TryMatchExact(string text, int offset, int length, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
int limit = offset + length;
|
||||
if (text == null)
|
||||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
|
||||
|
||||
if (offset < 0 || length < 0 || text.Length < limit)
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offsetLength, ExceptionReason.InvalidOffsetLength);
|
||||
|
||||
match = default;
|
||||
if (length == 0 || !TryGetRoot(text[offset], out int nodeIndex))
|
||||
return false;
|
||||
#endif
|
||||
int depth = 1;
|
||||
|
||||
ref Node node = ref _tree[nodeIndex];
|
||||
if (node.ChildChar == 0) goto LeafNodeFound;
|
||||
#if NETCORE
|
||||
if (node.MatchIndex != -1 && text.Length == 1)
|
||||
#else
|
||||
if (node.MatchIndex != -1 && length == 1)
|
||||
#endif
|
||||
{
|
||||
match = _matches[node.MatchIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
#if NETCORE
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
#else
|
||||
for (int i = offset + 1; i < limit; i++)
|
||||
#endif
|
||||
{
|
||||
char c = text[i];
|
||||
|
||||
@@ -975,81 +832,20 @@ namespace Markdig.Helpers
|
||||
|
||||
if (node.MatchIndex == -1) return false;
|
||||
match = _matches[node.MatchIndex];
|
||||
#if NETCORE
|
||||
Debug.Assert(match.Key.Length == text.Length);
|
||||
#else
|
||||
Debug.Assert(match.Key.Length == length);
|
||||
#endif
|
||||
return true;
|
||||
|
||||
LeafNodeFound:;
|
||||
match = _matches[node.MatchIndex];
|
||||
#if NETCORE
|
||||
|
||||
return match.Key.Length == text.Length &&
|
||||
text.Slice(depth).Equals(match.Key.AsSpan(depth), StringComparison.Ordinal);
|
||||
#else
|
||||
if (match.Key.Length == length)
|
||||
{
|
||||
// Check that the rest of the strings match
|
||||
for (int i = offset + depth, j = depth; j < match.Key.Length; i++, j++)
|
||||
{
|
||||
if (text[i] != match.Key[j])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion TryMatch exact
|
||||
|
||||
#region TryMatch shortest
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the shortest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="offset">Index of the character at which to start searching</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
public bool TryMatchShortest(string text, int offset, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
#if NETCORE
|
||||
return TryMatchShortest(text.AsSpan(offset), out match);
|
||||
#else
|
||||
return TryMatchShortest(text, offset, text.Length - offset, out match);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the shortest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
public bool TryMatchShortest(string text, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
if (text == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
|
||||
#if NETCORE
|
||||
return TryMatchShortest(text.AsSpan(), out match);
|
||||
#else
|
||||
return TryMatchShortest(text, 0, text.Length, out match);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the shortest prefix of text, starting at offset, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
/// <param name="text">The text in which to search for the prefix</param>
|
||||
/// <param name="offset">The offset in text at which to start looking for the prefix</param>
|
||||
/// <param name="length">The longest prefix allowed to match</param>
|
||||
/// <param name="match">The found prefix and the corresponding value</param>
|
||||
/// <returns>True if a match was found, false otherwise</returns>
|
||||
#if NETCORE
|
||||
public bool TryMatchShortest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
|
||||
=> TryMatchShortest(text.AsSpan(offset, length), out match);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the shortest prefix of text, that is contained in this <see cref="CompactPrefixTree{TValue}"/>
|
||||
/// </summary>
|
||||
@@ -1061,20 +857,7 @@ namespace Markdig.Helpers
|
||||
match = default;
|
||||
if (text.Length == 0 || !TryGetRoot(text[0], out int nodeIndex))
|
||||
return false;
|
||||
#else
|
||||
public bool TryMatchShortest(string text, int offset, int length, out KeyValuePair<string, TValue> match)
|
||||
{
|
||||
int limit = offset + length;
|
||||
if (text == null)
|
||||
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
|
||||
|
||||
if (offset < 0 || length < 0 || text.Length < limit)
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.offsetLength, ExceptionReason.InvalidOffsetLength);
|
||||
|
||||
match = default;
|
||||
if (length == 0 || !TryGetRoot(text[offset], out int nodeIndex))
|
||||
return false;
|
||||
#endif
|
||||
ref Node node = ref _tree[nodeIndex];
|
||||
if (node.MatchIndex != -1)
|
||||
{
|
||||
@@ -1082,11 +865,7 @@ namespace Markdig.Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
#if NETCORE
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
#else
|
||||
for (int i = offset + 1; i < limit; i++)
|
||||
#endif
|
||||
{
|
||||
char c = text[i];
|
||||
|
||||
@@ -1129,7 +908,7 @@ namespace Markdig.Helpers
|
||||
/// <param name="key">The key to locate in this <see cref="CompactPrefixTree{TValue}"/></param>
|
||||
/// <returns>True if the key is contained in this PrefixTree, false otherwise.</returns>
|
||||
public bool ContainsKey(string key)
|
||||
=> TryMatchExact(key, out _);
|
||||
=> TryMatchExact(key.AsSpan(), out _);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value associated with the specified key
|
||||
@@ -1139,7 +918,7 @@ namespace Markdig.Helpers
|
||||
/// <returns>True if the key is contained in this PrefixTree, false otherwise.</returns>
|
||||
public bool TryGetValue(string key, out TValue value)
|
||||
{
|
||||
bool ret = TryMatchExact(key, out KeyValuePair<string, TValue> match);
|
||||
bool ret = TryMatchExact(key.AsSpan(), out KeyValuePair<string, TValue> match);
|
||||
value = match.Value;
|
||||
return ret;
|
||||
}
|
||||
@@ -1173,9 +952,8 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<KeyValuePair<string, TValue>> GetEnumerator() => new Enumerator(_matches);
|
||||
//#if !LEGACY
|
||||
//IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_matches);
|
||||
//#endif
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(_matches);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the elements of a <see cref="CompactPrefixTree{TValue}"/>
|
||||
|
||||
86
src/Markdig/Helpers/CustomArrayPool.cs
Normal file
86
src/Markdig/Helpers/CustomArrayPool.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
internal sealed class CustomArrayPool<T>
|
||||
{
|
||||
private sealed class Bucket
|
||||
{
|
||||
private readonly T[][] _buffers;
|
||||
|
||||
private int _index;
|
||||
private int _lock;
|
||||
|
||||
public Bucket(int numberOfBuffers)
|
||||
{
|
||||
_buffers = new T[numberOfBuffers][];
|
||||
}
|
||||
|
||||
public T[] Rent()
|
||||
{
|
||||
T[][] buffers = _buffers;
|
||||
T[] buffer = null;
|
||||
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
|
||||
{
|
||||
int index = _index;
|
||||
if ((uint)index < (uint)buffers.Length)
|
||||
{
|
||||
buffer = buffers[index];
|
||||
buffers[index] = null;
|
||||
_index = index + 1;
|
||||
}
|
||||
Interlocked.Decrement(ref _lock);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void Return(T[] array)
|
||||
{
|
||||
var buffers = _buffers;
|
||||
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
|
||||
{
|
||||
int index = _index - 1;
|
||||
if ((uint)index < (uint)buffers.Length)
|
||||
{
|
||||
buffers[index] = array;
|
||||
_index = index;
|
||||
}
|
||||
Interlocked.Decrement(ref _lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bucket _bucket4, _bucket8, _bucket16, _bucket32;
|
||||
|
||||
public CustomArrayPool(int size4, int size8, int size16, int size32)
|
||||
{
|
||||
_bucket4 = new Bucket(size4);
|
||||
_bucket8 = new Bucket(size8);
|
||||
_bucket16 = new Bucket(size16);
|
||||
_bucket32 = new Bucket(size32);
|
||||
}
|
||||
|
||||
private Bucket SelectBucket(int length)
|
||||
{
|
||||
switch (length)
|
||||
{
|
||||
case 4: return _bucket4;
|
||||
case 8: return _bucket8;
|
||||
case 16: return _bucket16;
|
||||
case 32: return _bucket32;
|
||||
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Rent(int length)
|
||||
{
|
||||
return SelectBucket(length)?.Rent() ?? new T[length];
|
||||
}
|
||||
|
||||
public void Return(T[] array)
|
||||
{
|
||||
SelectBucket(array.Length)?.Return(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>&</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
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace Markdig.Helpers
|
||||
|
||||
public static bool TryParseHtmlTag(ref StringSlice text, StringBuilder builder)
|
||||
{
|
||||
if (builder == null) throw new ArgumentNullException(nameof(builder));
|
||||
if (builder == null) ThrowHelper.ArgumentNullException(nameof(builder));
|
||||
var c = text.CurrentChar;
|
||||
if (c != '<')
|
||||
{
|
||||
@@ -373,7 +373,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
builder.Append('-');
|
||||
builder.Append('-');
|
||||
if (text.PeekChar(1) == '>')
|
||||
if (text.PeekChar() == '>')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -450,11 +450,7 @@ namespace Markdig.Helpers
|
||||
|
||||
while ((searchPos = text.IndexOfAny(search, searchPos)) != -1)
|
||||
{
|
||||
if (sb == null)
|
||||
{
|
||||
sb = StringBuilderCache.Local();
|
||||
sb.Length = 0;
|
||||
}
|
||||
sb ??= StringBuilderCache.Local();
|
||||
c = text[searchPos];
|
||||
if (removeBackSlash && c == '\\')
|
||||
{
|
||||
@@ -472,10 +468,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
else if (c == '&')
|
||||
{
|
||||
int entityNameStart;
|
||||
int entityNameLength;
|
||||
int numericEntity;
|
||||
var match = ScanEntity(new StringSlice(text, searchPos, text.Length - 1), out numericEntity, out entityNameStart, out entityNameLength);
|
||||
var match = ScanEntity(new StringSlice(text, searchPos, text.Length - 1), out int numericEntity, out int entityNameStart, out int entityNameLength);
|
||||
if (match == 0)
|
||||
{
|
||||
searchPos++;
|
||||
@@ -486,8 +479,7 @@ namespace Markdig.Helpers
|
||||
|
||||
if (entityNameLength > 0)
|
||||
{
|
||||
var namedEntity = new StringSlice(text, entityNameStart, entityNameStart + entityNameLength - 1);
|
||||
var decoded = EntityHelper.DecodeEntity(namedEntity.ToString());
|
||||
var decoded = EntityHelper.DecodeEntity(text.AsSpan(entityNameStart, entityNameLength));
|
||||
if (decoded != null)
|
||||
{
|
||||
sb.Append(text, lastPos, searchPos - match - lastPos);
|
||||
@@ -498,36 +490,18 @@ namespace Markdig.Helpers
|
||||
else if (numericEntity >= 0)
|
||||
{
|
||||
sb.Append(text, lastPos, searchPos - match - lastPos);
|
||||
if (numericEntity == 0)
|
||||
{
|
||||
sb.Append('\0'.EscapeInsecure());
|
||||
}
|
||||
else
|
||||
{
|
||||
var decoded = EntityHelper.DecodeEntity(numericEntity);
|
||||
if (decoded != null)
|
||||
{
|
||||
sb.Append(decoded);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append('\uFFFD');
|
||||
}
|
||||
}
|
||||
|
||||
EntityHelper.DecodeEntity(numericEntity, sb);
|
||||
lastPos = searchPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sb == null)
|
||||
if (sb == null || lastPos == 0)
|
||||
return text;
|
||||
|
||||
sb.Append(text, lastPos, text.Length - lastPos);
|
||||
var result = sb.ToString();
|
||||
sb.Length = 0;
|
||||
return result;
|
||||
return sb.GetStringAndReset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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 <= 0</exception>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">bufferSize cannot be <= 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,18 +101,16 @@ namespace Markdig.Helpers
|
||||
var headingBuffer = StringBuilderCache.Local();
|
||||
for (int i = 0; i < headingText.Length; i++)
|
||||
{
|
||||
var c = char.ToLowerInvariant(headingText[i]);
|
||||
var c = headingText[i];
|
||||
if (char.IsLetterOrDigit(c) || c == ' ' || c == '-' || c == '_')
|
||||
{
|
||||
headingBuffer.Append(c == ' ' ? '-' : c);
|
||||
headingBuffer.Append(c == ' ' ? '-' : char.ToLowerInvariant(c));
|
||||
}
|
||||
}
|
||||
var result = headingBuffer.ToString();
|
||||
headingBuffer.Length = 0;
|
||||
return result;
|
||||
return headingBuffer.GetStringAndReset();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsReservedPunctuation(char c)
|
||||
{
|
||||
return c == '_' || c == '-' || c == '.';
|
||||
@@ -427,7 +425,6 @@ namespace Markdig.Helpers
|
||||
{
|
||||
bool isValid = false;
|
||||
var buffer = StringBuilderCache.Local();
|
||||
buffer.Length = 0;
|
||||
|
||||
// a sequence of zero or more characters between straight double-quote characters ("), including a " character only if it is backslash-escaped, or
|
||||
// a sequence of zero or more characters between straight single-quote characters ('), including a ' character only if it is backslash-escaped, or
|
||||
@@ -519,7 +516,6 @@ namespace Markdig.Helpers
|
||||
{
|
||||
bool isValid = false;
|
||||
var buffer = StringBuilderCache.Local();
|
||||
buffer.Length = 0;
|
||||
|
||||
var c = text.CurrentChar;
|
||||
|
||||
@@ -597,20 +593,23 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
if (!isAutoLink)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
c = text.NextChar();
|
||||
continue;
|
||||
}
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
c = text.NextChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
hasEscape = false;
|
||||
}
|
||||
|
||||
if (IsEndOfUri(c, isAutoLink))
|
||||
{
|
||||
@@ -622,10 +621,7 @@ namespace Markdig.Helpers
|
||||
{
|
||||
if (c == '&')
|
||||
{
|
||||
int entityNameStart;
|
||||
int entityNameLength;
|
||||
int entityValue;
|
||||
if (HtmlHelper.ScanEntity(text, out entityValue, out entityNameStart, out entityNameLength) > 0)
|
||||
if (HtmlHelper.ScanEntity(text, out _, out _, out _) > 0)
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
@@ -654,14 +650,14 @@ namespace Markdig.Helpers
|
||||
return isValid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsTrailingUrlStopCharacter(char c)
|
||||
{
|
||||
// Trailing punctuation (specifically, ?, !, ., ,, :, *, _, and ~) will not be considered part of the autolink, though they may be included in the interior of the link:
|
||||
return c == '?' || c == '!' || c == '.' || c == ',' || c == ':' || c == '*' || c == '*' || c == '_' || c == '~';
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsEndOfUri(char c, bool isAutoLink)
|
||||
{
|
||||
return c == '\0' || c.IsSpaceOrTab() || c.IsControl() || (isAutoLink && c == '<'); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Text;
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for StringBuilder with <see cref="StringSlice"/>
|
||||
/// Extensions for StringBuilder
|
||||
/// </summary>
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
@@ -19,5 +19,12 @@ namespace Markdig.Helpers
|
||||
{
|
||||
return builder.Append(slice.Text, slice.Start, slice.Length);
|
||||
}
|
||||
|
||||
internal static string GetStringAndReset(this StringBuilder builder)
|
||||
{
|
||||
string text = builder.ToString();
|
||||
builder.Length = 0;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Markdig.Helpers
|
||||
return line.Slice;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return Slice.ToString();
|
||||
}
|
||||
|
||||
@@ -7,24 +7,27 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Markdig.Extensions.Tables;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A group of <see cref="StringLine"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Collections.IEnumerable" />
|
||||
/// <seealso cref="IEnumerable" />
|
||||
public struct StringLineGroup : IEnumerable
|
||||
{
|
||||
// Feel free to change these numbers if you see a positive change
|
||||
private static readonly CustomArrayPool<StringLine> _pool
|
||||
= new CustomArrayPool<StringLine>(512, 386, 128, 64);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringLineGroup"/> class.
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public StringLineGroup(int capacity)
|
||||
public StringLineGroup(int capacity, bool willRelease = false)
|
||||
{
|
||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
Lines = new StringLine[capacity];
|
||||
if (capacity <= 0) ThrowHelper.ArgumentOutOfRangeException(nameof(capacity));
|
||||
Lines = _pool.Rent(willRelease ? Math.Max(8, capacity) : capacity);
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,7 @@ namespace Markdig.Helpers
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public StringLineGroup(string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
if (text == null) ThrowHelper.ArgumentNullException_text();
|
||||
Lines = new StringLine[1];
|
||||
Count = 0;
|
||||
Add(new StringSlice(text));
|
||||
@@ -66,23 +69,20 @@ namespace Markdig.Helpers
|
||||
/// <param name="index">The index.</param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (Count - 1 == index)
|
||||
{
|
||||
Count--;
|
||||
}
|
||||
else
|
||||
if (index != Count - 1)
|
||||
{
|
||||
Array.Copy(Lines, index + 1, Lines, index, Count - index - 1);
|
||||
Lines[Count - 1] = new StringLine();
|
||||
Count--;
|
||||
}
|
||||
|
||||
Lines[Count - 1] = new StringLine();
|
||||
Count--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified line to this instance.
|
||||
/// </summary>
|
||||
/// <param name="line">The line.</param>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(ref StringLine line)
|
||||
{
|
||||
if (Count == Lines.Length) IncreaseCapacity();
|
||||
@@ -93,14 +93,14 @@ namespace Markdig.Helpers
|
||||
/// Adds the specified slice to this instance.
|
||||
/// </summary>
|
||||
/// <param name="slice">The slice.</param>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(StringSlice slice)
|
||||
{
|
||||
if (Count == Lines.Length) IncreaseCapacity();
|
||||
Lines[Count++] = new StringLine(ref slice);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return ToSlice().ToString();
|
||||
}
|
||||
@@ -110,22 +110,24 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <param name="lineOffsets">The position of the `\n` line offsets from the beginning of the returned slice.</param>
|
||||
/// <returns>A single slice concatenating the lines of this instance</returns>
|
||||
public StringSlice ToSlice(List<LineOffset> lineOffsets = null)
|
||||
public readonly StringSlice ToSlice(List<LineOffset> lineOffsets = null)
|
||||
{
|
||||
// Optimization case for a single line.
|
||||
if (Count == 1)
|
||||
{
|
||||
lineOffsets?.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
|
||||
return Lines[0];
|
||||
}
|
||||
|
||||
// Optimization case when no lines
|
||||
if (Count == 0)
|
||||
{
|
||||
return new StringSlice(string.Empty);
|
||||
}
|
||||
|
||||
// Optimization case for a single line.
|
||||
if (Count == 1)
|
||||
if (lineOffsets != null && lineOffsets.Capacity < lineOffsets.Count + Count)
|
||||
{
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
|
||||
}
|
||||
return Lines[0];
|
||||
lineOffsets.Capacity = Math.Max(lineOffsets.Count + Count, lineOffsets.Capacity * 2);
|
||||
}
|
||||
|
||||
// Else use a builder
|
||||
@@ -135,32 +137,25 @@ namespace Markdig.Helpers
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(new LineOffset(Lines[i - 1].Position, Lines[i - 1].Column, Lines[i - 1].Slice.Start - Lines[i - 1].Position, previousStartOfLine, builder.Length));
|
||||
}
|
||||
builder.Append('\n');
|
||||
previousStartOfLine = builder.Length;
|
||||
}
|
||||
if (!Lines[i].Slice.IsEmpty)
|
||||
ref var line = ref Lines[i];
|
||||
if (!line.Slice.IsEmpty)
|
||||
{
|
||||
builder.Append(Lines[i].Slice.Text, Lines[i].Slice.Start, Lines[i].Slice.Length);
|
||||
builder.Append(line.Slice.Text, line.Slice.Start, line.Slice.Length);
|
||||
}
|
||||
|
||||
lineOffsets?.Add(new LineOffset(line.Position, line.Column, line.Slice.Start - line.Position, previousStartOfLine, builder.Length));
|
||||
}
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(new LineOffset(Lines[Count - 1].Position, Lines[Count - 1].Column, Lines[Count - 1].Slice.Start - Lines[Count - 1].Position, previousStartOfLine, builder.Length));
|
||||
}
|
||||
var str = builder.ToString();
|
||||
builder.Length = 0;
|
||||
return new StringSlice(str);
|
||||
return new StringSlice(builder.GetStringAndReset());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance into a <see cref="ICharIterator"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Iterator ToCharIterator()
|
||||
public readonly Iterator ToCharIterator()
|
||||
{
|
||||
return new Iterator(this);
|
||||
}
|
||||
@@ -183,14 +178,24 @@ namespace Markdig.Helpers
|
||||
|
||||
private void IncreaseCapacity()
|
||||
{
|
||||
var newItems = new StringLine[Lines.Length * 2];
|
||||
var newItems = _pool.Rent(Lines.Length * 2);
|
||||
if (Count > 0)
|
||||
{
|
||||
Array.Copy(Lines, 0, newItems, 0, Count);
|
||||
Array.Clear(Lines, 0, Count);
|
||||
}
|
||||
_pool.Return(Lines);
|
||||
Lines = newItems;
|
||||
}
|
||||
|
||||
internal void Release()
|
||||
{
|
||||
Array.Clear(Lines, 0, Count);
|
||||
_pool.Return(Lines);
|
||||
Lines = null;
|
||||
Count = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The iterator used to iterate other the lines.
|
||||
/// </summary>
|
||||
@@ -207,7 +212,7 @@ namespace Markdig.Helpers
|
||||
_offset = -1;
|
||||
SliceIndex = 0;
|
||||
CurrentChar = '\0';
|
||||
End = -2;
|
||||
End = -2;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
End += lines.Lines[i].Slice.Length + 1; // Add chars
|
||||
@@ -221,7 +226,7 @@ namespace Markdig.Helpers
|
||||
|
||||
public int End { get; private set; }
|
||||
|
||||
public bool IsEmpty => Start > End;
|
||||
public readonly bool IsEmpty => Start > End;
|
||||
|
||||
public int SliceIndex { get; private set; }
|
||||
|
||||
@@ -252,10 +257,10 @@ namespace Markdig.Helpers
|
||||
public char NextChar()
|
||||
{
|
||||
Start++;
|
||||
_offset++;
|
||||
if (Start <= End)
|
||||
{
|
||||
var slice = (StringSlice)_lines.Lines[SliceIndex];
|
||||
var slice = _lines.Lines[SliceIndex].Slice;
|
||||
_offset++;
|
||||
if (_offset < slice.Length)
|
||||
{
|
||||
CurrentChar = slice[slice.Start + _offset];
|
||||
@@ -272,43 +277,54 @@ namespace Markdig.Helpers
|
||||
CurrentChar = '\0';
|
||||
Start = End + 1;
|
||||
SliceIndex = _lines.Count;
|
||||
_offset--;
|
||||
}
|
||||
return CurrentChar;
|
||||
}
|
||||
|
||||
public char PeekChar(int offset = 1)
|
||||
public readonly char PeekChar(int offset = 1)
|
||||
{
|
||||
if (offset < 0) throw new ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
|
||||
if (offset < 0) ThrowHelper.ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
|
||||
|
||||
if (Start + offset > End)
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
|
||||
var slice = (StringSlice)_lines.Lines[SliceIndex];
|
||||
if (_offset + offset >= slice.Length)
|
||||
offset += _offset;
|
||||
|
||||
int sliceIndex = SliceIndex;
|
||||
var slice = _lines.Lines[sliceIndex].Slice;
|
||||
|
||||
while (offset > slice.Length)
|
||||
{
|
||||
// We are not peeking at the same line
|
||||
offset -= slice.Length + 1; // + 1 for new line
|
||||
|
||||
Debug.Assert(sliceIndex + 1 < _lines.Lines.Length, "'Start + offset > End' check above should prevent us from indexing out of range");
|
||||
slice = _lines.Lines[++sliceIndex].Slice;
|
||||
}
|
||||
|
||||
if (offset == slice.Length)
|
||||
{
|
||||
return '\n';
|
||||
}
|
||||
|
||||
return slice[slice.Start + _offset + offset];
|
||||
Debug.Assert(offset < slice.Length);
|
||||
return slice[slice.Start + offset];
|
||||
}
|
||||
|
||||
public bool TrimStart()
|
||||
{
|
||||
var c = CurrentChar;
|
||||
bool hasSpaces = false;
|
||||
while (c.IsWhitespace())
|
||||
{
|
||||
hasSpaces = true;
|
||||
c = NextChar();
|
||||
}
|
||||
return hasSpaces;
|
||||
return IsEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LineOffset
|
||||
public readonly struct LineOffset
|
||||
{
|
||||
public LineOffset(int linePosition, int column, int offset, int start, int end)
|
||||
{
|
||||
|
||||
@@ -38,7 +38,9 @@ namespace Markdig.Helpers
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public StringSlice(string text, int start, int end)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
if (text is null)
|
||||
ThrowHelper.ArgumentNullException_text();
|
||||
|
||||
Text = text;
|
||||
Start = start;
|
||||
End = end;
|
||||
@@ -52,34 +54,50 @@ namespace Markdig.Helpers
|
||||
/// <summary>
|
||||
/// Gets or sets the start position within <see cref="Text"/>.
|
||||
/// </summary>
|
||||
public int Start { get; set; }
|
||||
public int Start { readonly get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the end position (inclusive) within <see cref="Text"/>.
|
||||
/// </summary>
|
||||
public int End { get; set; }
|
||||
public int End { readonly get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length.
|
||||
/// </summary>
|
||||
public int Length => End - Start + 1;
|
||||
public readonly int Length => End - Start + 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current character.
|
||||
/// </summary>
|
||||
public char CurrentChar => Start <= End ? this[Start] : '\0';
|
||||
public readonly char CurrentChar
|
||||
{
|
||||
get
|
||||
{
|
||||
int start = Start;
|
||||
return start <= End ? Text[start] : '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty => Start > End;
|
||||
public readonly bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Start > End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Char"/> at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>A character in the slice at the specified index (not from <see cref="Start"/> but from the begining of the slice)</returns>
|
||||
public char this[int index] => Text[index];
|
||||
public readonly char this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Text[index];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Goes to the next character, incrementing the <see cref="Start" /> position.
|
||||
@@ -87,16 +105,30 @@ namespace Markdig.Helpers
|
||||
/// <returns>
|
||||
/// The next character. `\0` is end of the iteration.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public char NextChar()
|
||||
{
|
||||
Start++;
|
||||
if (Start > End)
|
||||
int start = Start;
|
||||
if (start >= End)
|
||||
{
|
||||
Start = End + 1;
|
||||
return '\0';
|
||||
}
|
||||
return Text[Start];
|
||||
start++;
|
||||
Start = start;
|
||||
return Text[start];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a character at the offset of 1 from the current <see cref="Start"/> position
|
||||
/// inside the range <see cref="Start"/> and <see cref="End"/>, returns `\0` if outside this range.
|
||||
/// </summary>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char PeekChar()
|
||||
{
|
||||
int index = Start + 1;
|
||||
return index <= End ? Text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,21 +137,22 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekChar(int offset = 1)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char PeekChar(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= Start && index <= End ? Text[index] : (char) 0;
|
||||
return index >= Start && index <= End ? Text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a character at the specified offset from the current beginning of the string, without taking into account <see cref="Start"/> and <see cref="End"/>
|
||||
/// </summary>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekCharAbsolute(int index)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char PeekCharAbsolute(int index)
|
||||
{
|
||||
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
|
||||
string text = Text;
|
||||
return (uint)index < (uint)text.Length ? text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,11 +161,12 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekCharExtra(int offset)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly char PeekCharExtra(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
|
||||
var text = Text;
|
||||
return (uint)index < (uint)text.Length ? text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,7 +175,7 @@ namespace Markdig.Helpers
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool Match(string text, int offset = 0)
|
||||
public readonly bool Match(string text, int offset = 0)
|
||||
{
|
||||
return Match(text, End, offset);
|
||||
}
|
||||
@@ -153,19 +187,22 @@ namespace Markdig.Helpers
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool Match(string text, int end, int offset)
|
||||
public readonly bool Match(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
|
||||
if (end - index + 1 < text.Length)
|
||||
return false;
|
||||
|
||||
string sliceText = Text;
|
||||
for (int i = 0; i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != Text[index])
|
||||
if (text[i] != sliceText[index])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +233,7 @@ namespace Markdig.Helpers
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool MatchLowercase(string text, int offset = 0)
|
||||
public readonly bool MatchLowercase(string text, int offset = 0)
|
||||
{
|
||||
return MatchLowercase(text, End, offset);
|
||||
}
|
||||
@@ -208,19 +245,22 @@ namespace Markdig.Helpers
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool MatchLowercase(string text, int end, int offset)
|
||||
public readonly bool MatchLowercase(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
|
||||
if (end - index + 1 < text.Length)
|
||||
return false;
|
||||
|
||||
string sliceText = Text;
|
||||
for (int i = 0; i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != char.ToLowerInvariant(Text[index]))
|
||||
if (text[i] != char.ToLowerInvariant(sliceText[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,46 +270,30 @@ namespace Markdig.Helpers
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="ignoreCase">true if ignore case</param>
|
||||
/// <returns><c>true</c> if the text was found; <c>false</c> otherwise</returns>
|
||||
public int IndexOf(string text, int offset = 0, bool ignoreCase = false)
|
||||
public readonly int IndexOf(string text, int offset = 0, bool ignoreCase = false)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
if (ignoreCase)
|
||||
{
|
||||
for (int i = Start + offset; i <= end; i++)
|
||||
{
|
||||
if (MatchLowercase(text, End, i - Start))
|
||||
{
|
||||
return i; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = Start + offset; i <= end; i++)
|
||||
{
|
||||
if (Match(text, End, i - Start))
|
||||
{
|
||||
return i; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
offset += Start;
|
||||
int length = End - offset + 1;
|
||||
|
||||
if (length <= 0)
|
||||
return -1;
|
||||
|
||||
return Text.IndexOf(text, offset, length, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified character within this slice.
|
||||
/// </summary>
|
||||
/// <returns>A value >= 0 if the character was found, otherwise < 0</returns>
|
||||
public int IndexOf(char c)
|
||||
public readonly int IndexOf(char c)
|
||||
{
|
||||
for (int i = Start; i <= End; i++)
|
||||
{
|
||||
if (Text[i] == c)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
int start = Start;
|
||||
int length = End - start + 1;
|
||||
|
||||
if (length <= 0)
|
||||
return -1;
|
||||
|
||||
return Text.IndexOf(c, start, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -280,16 +304,15 @@ namespace Markdig.Helpers
|
||||
/// </returns>
|
||||
public bool TrimStart()
|
||||
{
|
||||
// Strip leading spaces
|
||||
var start = Start;
|
||||
for (; Start <= End; Start++)
|
||||
{
|
||||
if (!Text[Start].IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return start != Start;
|
||||
string text = Text;
|
||||
int end = End;
|
||||
int i = Start;
|
||||
|
||||
while (i <= end && (uint)i < (uint)text.Length && text[i].IsWhitespace())
|
||||
i++;
|
||||
|
||||
Start = i;
|
||||
return i > end;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -298,16 +321,15 @@ namespace Markdig.Helpers
|
||||
/// <param name="spaceCount">The number of spaces trimmed.</param>
|
||||
public void TrimStart(out int spaceCount)
|
||||
{
|
||||
spaceCount = 0;
|
||||
// Strip leading spaces
|
||||
for (; Start <= End; Start++)
|
||||
{
|
||||
if (!Text[Start].IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
spaceCount++;
|
||||
}
|
||||
string text = Text;
|
||||
int end = End;
|
||||
int i = Start;
|
||||
|
||||
while (i <= end && (uint)i < (uint)text.Length && text[i].IsWhitespace())
|
||||
i++;
|
||||
|
||||
spaceCount = i - Start;
|
||||
Start = i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -316,14 +338,15 @@ namespace Markdig.Helpers
|
||||
/// <returns></returns>
|
||||
public bool TrimEnd()
|
||||
{
|
||||
for (; Start <= End; End--)
|
||||
{
|
||||
if (!Text[End].IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return IsEmpty;
|
||||
string text = Text;
|
||||
int start = Start;
|
||||
int i = End;
|
||||
|
||||
while (start <= i && (uint)i < (uint)text.Length && text[i].IsWhitespace())
|
||||
i--;
|
||||
|
||||
End = i;
|
||||
return start > i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -331,40 +354,51 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
public void Trim()
|
||||
{
|
||||
TrimStart();
|
||||
TrimEnd();
|
||||
string text = Text;
|
||||
int start = Start;
|
||||
int end = End;
|
||||
|
||||
while (start <= end && (uint)start < (uint)text.Length && text[start].IsWhitespace())
|
||||
start++;
|
||||
|
||||
while (start <= end && (uint)end < (uint)text.Length && text[end].IsWhitespace())
|
||||
end--;
|
||||
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// A <see cref="string" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
public readonly override string ToString()
|
||||
{
|
||||
if (Text != null && Start <= End)
|
||||
{
|
||||
var length = Length;
|
||||
if (Start == 0 && Text.Length == length)
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
string text = Text;
|
||||
int start = Start;
|
||||
int length = End - start + 1;
|
||||
|
||||
return Text.Substring(Start, length);
|
||||
if (text is null || length <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
return text.Substring(start, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this slice is empty or made only of whitespaces.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this slice is empty or made only of whitespaces; <c>false</c> otherwise</returns>
|
||||
public bool IsEmptyOrWhitespace()
|
||||
public readonly bool IsEmptyOrWhitespace()
|
||||
{
|
||||
for (int i = Start; i <= End; i++)
|
||||
string text = Text;
|
||||
int end = End;
|
||||
|
||||
for (int i = Start; i <= end && (uint)i < (uint)text.Length; i++)
|
||||
{
|
||||
if (!Text[i].IsWhitespace())
|
||||
if (!text[i].IsWhitespace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,84 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
|
||||
<Copyright>Alexandre Mutel</Copyright>
|
||||
<AssemblyTitle>Markdig</AssemblyTitle>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.17.0</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
|
||||
<AssemblyName>Markdig</AssemblyName>
|
||||
<PackageId>Markdig</PackageId>
|
||||
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
|
||||
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
|
||||
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81' ">
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE;NETCORE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.17763.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
|
||||
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(SignAssembly)' == 'true' ">
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Special packages and imports for UWP support -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
|
||||
<Import Project="Markdig.targets" />
|
||||
</Project>
|
||||
|
||||
48
src/Markdig/Markdig.targets
Normal file
48
src/Markdig/Markdig.targets
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
|
||||
<Copyright>Alexandre Mutel</Copyright>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.20.0</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp2.1;netcoreapp3.1</TargetFrameworks>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
|
||||
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<!--Add support for sourcelink-->
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
|
||||
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.*" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Markdig.Extensions.SelfPipeline;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Normalize;
|
||||
@@ -17,11 +18,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
public static partial class Markdown
|
||||
{
|
||||
#if NETSTANDARD_11
|
||||
public static readonly string Version = typeof(Markdown).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
||||
#else
|
||||
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes the specified markdown to a normalized markdown text.
|
||||
@@ -72,10 +69,19 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
|
||||
public static string ToHtml(string markdown, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
var writer = new StringWriter();
|
||||
ToHtml(markdown, writer, pipeline);
|
||||
return writer.ToString();
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
|
||||
var renderer = pipeline.GetCacheableHtmlRenderer();
|
||||
|
||||
var document = Parse(markdown, pipeline);
|
||||
renderer.Render(document);
|
||||
renderer.Writer.Flush();
|
||||
|
||||
string html = renderer.Writer.ToString();
|
||||
pipeline.ReleaseCacheableHtmlRenderer(renderer);
|
||||
return html;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -89,9 +95,9 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
|
||||
public static MarkdownDocument ToHtml(string markdown, TextWriter writer, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (writer == null) ThrowHelper.ArgumentNullException_writer();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
|
||||
// We override the renderer with our own writer
|
||||
@@ -115,9 +121,9 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if markdown or writer variable are null</exception>
|
||||
public static object Convert(string markdown, IMarkdownRenderer renderer, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (renderer == null) ThrowHelper.ArgumentNullException(nameof(renderer));
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
var document = Parse(markdown, pipeline, context);
|
||||
@@ -133,7 +139,7 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
|
||||
public static MarkdownDocument Parse(string markdown)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
return Parse(markdown, null);
|
||||
}
|
||||
|
||||
@@ -147,8 +153,8 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
|
||||
public static MarkdownDocument Parse(string markdown, MarkdownPipeline pipeline, MarkdownParserContext context = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
return MarkdownParser.Parse(markdown, pipeline, context);
|
||||
@@ -175,16 +181,17 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
|
||||
public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
if (writer == null) ThrowHelper.ArgumentNullException_writer();
|
||||
pipeline ??= new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
|
||||
// We override the renderer with our own writer
|
||||
var renderer = new HtmlRenderer(writer)
|
||||
{
|
||||
EnableHtmlForBlock = false,
|
||||
EnableHtmlForInline = false
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false,
|
||||
};
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
@@ -205,7 +212,7 @@ namespace Markdig
|
||||
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
|
||||
public static string ToPlainText(string markdown, MarkdownPipeline pipeline = null, MarkdownParserContext context = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (markdown == null) ThrowHelper.ArgumentNullException_markdown();
|
||||
var writer = new StringWriter();
|
||||
ToPlainText(markdown, writer, pipeline, context);
|
||||
return writer.ToString();
|
||||
|
||||
@@ -34,7 +34,8 @@ using Markdig.Extensions.Yaml;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Parsers.Inlines;
|
||||
using Markdig.Extensions.Globalization;
|
||||
|
||||
using Markdig.Helpers;
|
||||
|
||||
namespace Markdig
|
||||
{
|
||||
/// <summary>
|
||||
@@ -92,8 +93,8 @@ namespace Markdig
|
||||
.UseDiagrams()
|
||||
.UseAutoLinks()
|
||||
.UseGenericAttributes(); // Must be last as it is one parser that is modifying other parsers
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses this extension to enable autolinks from text `http://`, `https://`, `ftp://`, `mailto:`, `www.xxx.yyy`
|
||||
/// </summary>
|
||||
@@ -138,7 +139,7 @@ namespace Markdig
|
||||
{
|
||||
if (pipeline.Extensions.Count != 0)
|
||||
{
|
||||
throw new InvalidOperationException("The SelfPipeline extension cannot be used with other extensions");
|
||||
ThrowHelper.InvalidOperationException("The SelfPipeline extension cannot be used with other extensions");
|
||||
}
|
||||
|
||||
pipeline.Extensions.Add(new SelfPipelineExtension(defaultTag, defaultExtensions));
|
||||
@@ -421,20 +422,35 @@ namespace Markdig
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the emoji and smiley extension.
|
||||
/// Uses the emojis and smileys extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
|
||||
/// <param name="enableSmileys">Enable smileys in addition to emoji shortcodes, <c>true</c> by default.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
|
||||
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmileys = true)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<EmojiExtension>())
|
||||
{
|
||||
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
|
||||
var emojiMapping = enableSmileys ? EmojiMapping.DefaultEmojisAndSmileysMapping : EmojiMapping.DefaultEmojisOnlyMapping;
|
||||
pipeline.Extensions.Add(new EmojiExtension(emojiMapping));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the emojis and smileys extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <param name="customEmojiMapping">Enable customization of the emojis and smileys mapping.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, EmojiMapping customEmojiMapping)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<EmojiExtension>())
|
||||
{
|
||||
pipeline.Extensions.Add(new EmojiExtension(customEmojiMapping));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
/// <summary>
|
||||
/// Add rel=nofollow to all links rendered to HTML.
|
||||
/// </summary>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System;
|
||||
@@ -20,15 +20,14 @@ namespace Markdig
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarkdownPipeline" /> class.
|
||||
/// </summary>
|
||||
internal MarkdownPipeline(OrderedList<IMarkdownExtension> extensions, BlockParserList blockParsers, InlineParserList inlineParsers, StringBuilderCache cache, TextWriter debugLog, ProcessDocumentDelegate documentProcessed)
|
||||
internal MarkdownPipeline(OrderedList<IMarkdownExtension> extensions, BlockParserList blockParsers, InlineParserList inlineParsers, TextWriter debugLog, ProcessDocumentDelegate documentProcessed)
|
||||
{
|
||||
if (blockParsers == null) throw new ArgumentNullException(nameof(blockParsers));
|
||||
if (inlineParsers == null) throw new ArgumentNullException(nameof(inlineParsers));
|
||||
if (blockParsers == null) ThrowHelper.ArgumentNullException(nameof(blockParsers));
|
||||
if (inlineParsers == null) ThrowHelper.ArgumentNullException(nameof(inlineParsers));
|
||||
// Add all default parsers
|
||||
Extensions = extensions;
|
||||
BlockParsers = blockParsers;
|
||||
InlineParsers = inlineParsers;
|
||||
StringBuilderCache = cache;
|
||||
DebugLog = debugLog;
|
||||
DocumentProcessed = documentProcessed;
|
||||
}
|
||||
@@ -44,8 +43,6 @@ namespace Markdig
|
||||
|
||||
internal InlineParserList InlineParsers { get; }
|
||||
|
||||
internal StringBuilderCache StringBuilderCache { get; }
|
||||
|
||||
// TODO: Move the log to a better place
|
||||
internal TextWriter DebugLog { get; }
|
||||
|
||||
@@ -57,11 +54,48 @@ namespace Markdig
|
||||
/// <param name="renderer">The markdown renderer to setup</param>
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
{
|
||||
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
|
||||
if (renderer == null) ThrowHelper.ArgumentNullException(nameof(renderer));
|
||||
foreach (var extension in Extensions)
|
||||
{
|
||||
extension.Setup(this, renderer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HtmlRendererCache _rendererCache = null;
|
||||
|
||||
internal HtmlRenderer GetCacheableHtmlRenderer()
|
||||
{
|
||||
if (_rendererCache is null)
|
||||
{
|
||||
_rendererCache = new HtmlRendererCache
|
||||
{
|
||||
OnNewInstanceCreated = Setup
|
||||
};
|
||||
}
|
||||
return _rendererCache.Get();
|
||||
}
|
||||
internal void ReleaseCacheableHtmlRenderer(HtmlRenderer renderer)
|
||||
{
|
||||
_rendererCache.Release(renderer);
|
||||
}
|
||||
|
||||
private sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
|
||||
{
|
||||
public Action<HtmlRenderer> OnNewInstanceCreated;
|
||||
|
||||
protected override HtmlRenderer NewInstance()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
OnNewInstanceCreated(renderer);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
protected override void Reset(HtmlRenderer instance)
|
||||
{
|
||||
instance.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Markdig.Parsers
|
||||
// These properties are not changing between a parent and a children BlockProcessor
|
||||
this.root = root;
|
||||
this.parserStateCache = root.parserStateCache;
|
||||
StringBuilders = root.StringBuilders;
|
||||
Document = root.Document;
|
||||
Parsers = root.Parsers;
|
||||
|
||||
@@ -41,13 +40,11 @@ namespace Markdig.Parsers
|
||||
/// <param name="parsers">The list of parsers.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// </exception>
|
||||
public BlockProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, BlockParserList parsers, MarkdownParserContext context)
|
||||
public BlockProcessor(MarkdownDocument document, BlockParserList parsers, MarkdownParserContext context)
|
||||
{
|
||||
if (stringBuilders == null) throw new ArgumentNullException(nameof(stringBuilders));
|
||||
if (document == null) throw new ArgumentNullException(nameof(document));
|
||||
if (parsers == null) throw new ArgumentNullException(nameof(parsers));
|
||||
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
|
||||
if (parsers == null) ThrowHelper.ArgumentNullException(nameof(parsers));
|
||||
parserStateCache = new BlockParserStateCache(this);
|
||||
StringBuilders = stringBuilders;
|
||||
Document = document;
|
||||
document.IsOpen = true;
|
||||
Parsers = parsers;
|
||||
@@ -153,11 +150,6 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
public int StartBeforeIndent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache of string builders.
|
||||
/// </summary>
|
||||
public StringBuilderCache StringBuilders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current stack of <see cref="Block"/> being processed.
|
||||
/// </summary>
|
||||
@@ -400,8 +392,8 @@ namespace Markdig.Parsers
|
||||
/// <exception cref="System.ArgumentException">The block must be opened</exception>
|
||||
public void Open(Block block)
|
||||
{
|
||||
if (block == null) throw new ArgumentNullException(nameof(block));
|
||||
if (!block.IsOpen) throw new ArgumentException("The block must be opened", nameof(block));
|
||||
if (block == null) ThrowHelper.ArgumentNullException(nameof(block));
|
||||
if (!block.IsOpen) ThrowHelper.ArgumentException("The block must be opened", nameof(block));
|
||||
OpenedBlocks.Add(block);
|
||||
}
|
||||
|
||||
@@ -477,7 +469,7 @@ namespace Markdig.Parsers
|
||||
{
|
||||
if (this == root)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot release the root parser state");
|
||||
ThrowHelper.InvalidOperationException("Cannot release the root parser state");
|
||||
}
|
||||
parserStateCache.Release(this);
|
||||
}
|
||||
@@ -500,6 +492,11 @@ namespace Markdig.Parsers
|
||||
if (!block.Parser.Close(this, block))
|
||||
{
|
||||
block.Parent?.Remove(block);
|
||||
|
||||
if (block is LeafBlock leaf)
|
||||
{
|
||||
leaf.Lines.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -628,7 +625,7 @@ namespace Markdig.Parsers
|
||||
// If a parser is adding a block, it must be the last of the list
|
||||
if ((i + 1) < OpenedBlocks.Count && NewBlocks.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("A pending parser cannot add a new block when it is not the last pending block");
|
||||
ThrowHelper.InvalidOperationException("A pending parser cannot add a new block when it is not the last pending block");
|
||||
}
|
||||
|
||||
// If we have a leaf block
|
||||
@@ -675,7 +672,7 @@ namespace Markdig.Parsers
|
||||
// Security check so that the parser can't go into a crazy infinite loop if one extension is messing
|
||||
if (previousStart == Start)
|
||||
{
|
||||
throw new InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse blocks at line [{LineIndex}] with line [{Line}]");
|
||||
ThrowHelper.InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse blocks at line [{LineIndex}] with line [{Line}]");
|
||||
}
|
||||
previousStart = Start;
|
||||
|
||||
@@ -805,7 +802,7 @@ namespace Markdig.Parsers
|
||||
|
||||
if (block.Parser == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The new block [{block.GetType()}] must have a valid Parser property");
|
||||
ThrowHelper.InvalidOperationException($"The new block [{block.GetType()}] must have a valid Parser property");
|
||||
}
|
||||
|
||||
block.Line = LineIndex;
|
||||
@@ -821,7 +818,7 @@ namespace Markdig.Parsers
|
||||
|
||||
if (newBlocks.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
ThrowHelper.InvalidOperationException(
|
||||
"The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed");
|
||||
}
|
||||
}
|
||||
@@ -873,7 +870,7 @@ namespace Markdig.Parsers
|
||||
NewBlocks.Clear();
|
||||
}
|
||||
|
||||
private class BlockParserStateCache : ObjectCache<BlockProcessor>
|
||||
private sealed class BlockParserStateCache : ObjectCache<BlockProcessor>
|
||||
{
|
||||
private readonly BlockProcessor root;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -124,7 +124,9 @@ namespace Markdig.Parsers
|
||||
}
|
||||
else
|
||||
{
|
||||
infoString = line.ToString().Trim();
|
||||
var lineCopy = line;
|
||||
lineCopy.Trim();
|
||||
infoString = lineCopy.ToString();
|
||||
}
|
||||
|
||||
fenced.Info = HtmlHelper.Unescape(infoString);
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Markdig.Parsers
|
||||
if (c == '!')
|
||||
{
|
||||
c = line.NextChar();
|
||||
if (c == '-' && line.PeekChar(1) == '-')
|
||||
if (c == '-' && line.PeekChar() == '-')
|
||||
{
|
||||
return CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition); // group 2
|
||||
}
|
||||
@@ -140,7 +140,7 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
if (
|
||||
!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar(1) == '>') || c.IsWhitespace() ||
|
||||
!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() ||
|
||||
c == '\0'))
|
||||
{
|
||||
return BlockState.None;
|
||||
|
||||
@@ -36,12 +36,10 @@ namespace Markdig.Parsers
|
||||
/// <param name="inlineCreated">The inline created event.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// </exception>
|
||||
public InlineProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, InlineParserList parsers, bool preciseSourcelocation, MarkdownParserContext context)
|
||||
public InlineProcessor(MarkdownDocument document, InlineParserList parsers, bool preciseSourcelocation, MarkdownParserContext context)
|
||||
{
|
||||
if (stringBuilders == null) throw new ArgumentNullException(nameof(stringBuilders));
|
||||
if (document == null) throw new ArgumentNullException(nameof(document));
|
||||
if (parsers == null) throw new ArgumentNullException(nameof(parsers));
|
||||
StringBuilders = stringBuilders;
|
||||
if (document == null) ThrowHelper.ArgumentNullException(nameof(document));
|
||||
if (parsers == null) ThrowHelper.ArgumentNullException(nameof(parsers));
|
||||
Document = document;
|
||||
Parsers = parsers;
|
||||
Context = context;
|
||||
@@ -91,11 +89,6 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
public MarkdownDocument Document { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache string builders.
|
||||
/// </summary>
|
||||
public StringBuilderCache StringBuilders { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the line from the begining of the document being processed.
|
||||
/// </summary>
|
||||
@@ -176,7 +169,7 @@ namespace Markdig.Parsers
|
||||
/// <param name="leafBlock">The leaf block.</param>
|
||||
public void ProcessInlineLeaf(LeafBlock leafBlock)
|
||||
{
|
||||
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
|
||||
if (leafBlock == null) ThrowHelper.ArgumentNullException_leafBlock();
|
||||
|
||||
// clear parser states
|
||||
Array.Clear(ParserStates, 0, ParserStates.Length);
|
||||
@@ -192,7 +185,7 @@ namespace Markdig.Parsers
|
||||
previousLineIndexForSliceOffset = 0;
|
||||
lineOffsets.Clear();
|
||||
var text = leafBlock.Lines.ToSlice(lineOffsets);
|
||||
leafBlock.Lines = new StringLineGroup();
|
||||
leafBlock.Lines.Release();
|
||||
|
||||
int previousStart = -1;
|
||||
|
||||
@@ -201,7 +194,7 @@ namespace Markdig.Parsers
|
||||
// Security check so that the parser can't go into a crazy infinite loop if one extension is messing
|
||||
if (previousStart == text.Start)
|
||||
{
|
||||
throw new InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse inlines for block [{leafBlock.GetType().Name}] at position ({leafBlock.ToPositionText()}");
|
||||
ThrowHelper.InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse inlines for block [{leafBlock.GetType().Name}] at position ({leafBlock.ToPositionText()}");
|
||||
}
|
||||
previousStart = text.Start;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user