Compare commits

...

61 Commits

Author SHA1 Message Date
Alexandre Mutel
9af462e412 Bump to 0.18.2 2020-03-08 10:15:30 +01:00
Alexandre Mutel
e1ba52da20 Merge pull request #406 from FranklinWhale/patch-1
Make output of HtmlTableRenderer XML wellformed
2020-03-08 10:09:29 +01:00
Alexandre Mutel
70ee97d5c7 Merge pull request #404 from webmaster442/master
Fixes issue #303
2020-03-08 10:09:03 +01:00
Alexandre Mutel
da916a1700 Merge pull request #402 from KrisVandermotten/HeadingRendererBugFix
Fixed a bug in HeadingRenderer
2020-03-08 10:08:47 +01:00
Franklin Tse
5548246703 Update TestPlayParser.cs 2020-03-06 19:00:37 +08:00
Franklin Tse
097fc5c4e0 Update GridTableSpecs.md 2020-03-06 18:55:00 +08:00
Franklin Tse
168ad39ff6 Make output of HtmlTableRenderer XML wellformed 2020-03-06 18:49:22 +08:00
Alexandre Mutel
54e065aabe Merge pull request #405 from bbodenmiller/patch-1
Github -> GitHub
2020-03-06 08:56:56 +01:00
Ben Bodenmiller
98d58797d9 Github -> GitHub 2020-03-05 23:55:39 -08:00
Ruzsinszki Gábor
2f0dd2a6a7 Fixes issue #303 2020-03-03 11:10:17 +01:00
Kris Vandermotten
247209870e Fixed a bug in HeadingRenderer 2020-03-01 14:44:41 +01:00
Alexandre Mutel
ef5b958267 Merge pull request #399 from KrisVandermotten/StringLineGroup
Some optimizations in StringLineGroup
2020-02-18 21:01:55 +01:00
Kris Vandermotten
7d61df2c0c Some optimizations in StringLineGroup 2020-02-15 10:34:24 +01:00
Alexandre Mutel
51f9da1974 Merge pull request #396 from KrisVandermotten/CharacterMapBitVector128
Use BitVector128 in CharacterMap<T>
2020-02-06 06:56:32 +01:00
Kris Vandermotten
9321628b9c Use uint instead of ulong 2020-02-05 22:38:21 +01:00
Kris Vandermotten
525e2c7fb8 Use BitVector128 in CharacterMap<T> 2020-02-04 22:45:42 +01:00
Alexandre Mutel
1f9b70636f Merge pull request #394 from KrisVandermotten/charactermap
Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T>
2020-01-31 18:43:24 +01:00
Kris Vandermotten
d4f43f826f Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T> 2020-01-31 18:35:28 +01:00
Alexandre Mutel
62788b1101 Merge pull request #393 from MihaZupan/line-reader
Optimize LineReader.ReadLine
2020-01-31 08:01:42 +01:00
MihaZupan
c3c4d37b82 Optimize LineReader.ReadLine 2020-01-30 21:14:06 +01:00
Alexandre Mutel
333bf04274 Update ci.yml, fix publish 2020-01-29 08:14:26 +01:00
Alexandre Mutel
dab4b7176d Merge pull request #392 from RudeySH/patch-1
Add Bootstrap and YAML to exclusion list in readme
2020-01-27 19:00:14 +01:00
Rudey
25ec56e1be Change frontmatter to Front Matter 2020-01-27 18:50:25 +01:00
Rudey
3455fd12da Add Bootstrap and YAML to exclusion list in readme
Both the Bootstrap and YAML frontmatter aren't included in `UseAdvancedExtensions`, but the readme didn't specify that.
2020-01-27 18:30:53 +01:00
Alexandre Mutel
ae5d47a0ed Merge pull request #391 from MihaZupan/remove-uap-remains
Remove UAP specific code
2020-01-27 08:36:14 +01:00
MihaZupan
1ff721d512 Remove UAP specific code 2020-01-26 23:04:57 +01:00
Alexandre Mutel
2c130504e2 Merge pull request #389 from MihaZupan/code-coverage
Improve code coverage
2020-01-26 22:01:06 +01:00
Alexandre Mutel
6405b16692 Migrate to GitHub Actions 2020-01-26 19:42:23 +01:00
Alexandre Mutel
2e1912a23d Update projects to compile with dotnet build/test, remove uap10.0 2020-01-26 19:35:21 +01:00
MihaZupan
71c680388c Make ExcludeFromCodeCoverage compatibility stub internal 2020-01-22 02:27:50 +01:00
MihaZupan
3f50b3fd0b Add LinkHelper.UrilizeAsGfm tests 2020-01-22 02:18:24 +01:00
MihaZupan
99b250b4c9 Add ContainerBlock tests 2020-01-22 02:06:42 +01:00
MihaZupan
655bf13df0 Exclude CompactPrefixTree from code coverage 2020-01-22 01:43:12 +01:00
MihaZupan
d2c89a3a06 Add tests for core Markdown apis 2020-01-22 01:42:23 +01:00
MihaZupan
754d11fd44 Fix test execution inside VS Enterprise Live Unit Testing 2020-01-21 21:44:29 +01:00
MihaZupan
1252e49ba4 Add more custom emoji tests 2020-01-21 18:36:36 +01:00
MihaZupan
d18487ae53 Fix StringLineGroupIterator.PeekChar for cross-line peeks 2020-01-21 18:16:13 +01:00
MihaZupan
f401d5082e Add SmartyPants tests 2020-01-21 17:35:58 +01:00
Alexandre Mutel
dd6418d108 Bump to 0.18.1 2020-01-21 08:30:32 +01:00
Alexandre Mutel
7875e4bce9 Merge pull request #387 from MihaZupan/descendants-api
Add missing Descendants<T> api
2020-01-21 08:24:13 +01:00
Alexandre Mutel
2e1b1a1fdc Merge pull request #386 from mlaily/emoji-customization
Emojis and smileys customization
2020-01-21 08:23:48 +01:00
MihaZupan
fa2b157c1a Improve some CharHelper methods 2020-01-21 01:31:44 +01:00
MihaZupan
e4e6406546 Add missing Descendants<T> overload 2020-01-21 01:03:47 +01:00
Melvyn Laïly
1cff10270a Set the default emoji dictionaries capacity in their ctor 2020-01-16 09:12:05 +01:00
Melvyn Laïly
87023184cb Clarify emoji terminology (emoji "shortcode") 2020-01-16 09:12:04 +01:00
Melvyn Laïly
aecdf2192e Improve the emoji extension (code review remarks) 2020-01-16 09:12:04 +01:00
Melvyn Laïly
0a36382126 Update changelog 2020-01-16 09:12:04 +01:00
Melvyn Laïly
0057b368ec Add unit tests for custom emojis and smileys 2020-01-16 09:11:04 +01:00
Melvyn Laïly
3033284096 Re-allow emojis and smileys customization
(this feature was broken in #308)
2020-01-16 09:11:03 +01:00
Alexandre Mutel
9b1b791b18 Merge pull request #341 from OpportunityLiu/master
Add customizable media link; handle protocol-less media
2020-01-15 13:27:06 +01:00
Opportunity
2b7d205701 Merge branch 'master' into master 2020-01-15 19:35:17 +08:00
Opportunity
89b28659b1 Update src/Markdig/Extensions/MediaLinks/HostProviderBuilder.cs
Co-Authored-By: Alexandre Mutel <alexandre_mutel@live.com>
2020-01-15 19:32:37 +08:00
Opportunity
4d172bf905 Merge branch 'master' into master 2019-10-01 21:31:02 +08:00
Opportunity
890b2cda2a Update MediaLinkExtension.cs 2019-09-30 21:09:44 +08:00
OpportunityLiu
8886b48634 add tests 2019-05-16 15:58:02 +08:00
OpportunityLiu
64cd8ec262 customize iframe class of each provider 2019-05-15 10:48:12 +08:00
OpportunityLiu
a1e19912a9 update changelog 2019-05-14 17:32:09 +08:00
OpportunityLiu
ca51967fb1 fix #135 2019-05-14 17:31:03 +08:00
OpportunityLiu
bb3a4f372c add change log 2019-05-14 16:07:14 +08:00
OpportunityLiu
1d6a464c5d add unit test 2019-05-14 15:59:05 +08:00
OpportunityLiu
0fe5c17a93 Add customizable media link 2019-05-14 15:30:15 +08:00
64 changed files with 3153 additions and 2423 deletions

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

@@ -0,0 +1,63 @@
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: Add coveralls.net
run: dotnet tool install coveralls.net --version 1.0.0 --tool-path tools
- name: Build (Release)
run: dotnet build src -c Release
- 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: Coveralls
run: dotnet test src -c Release -f netcoreapp2.1 /p:Include=\"[${{env.PROJECT_NAME}}]*\" /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./coverage.info
- name: Coveralls Upload
uses: coverallsapp/github-action@v1.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: src/${{env.PROJECT_NAME}}.Tests/coverage.info
- name: Pack
run: dotnet pack src -c Release
- name: Publish
if: github.event_name == 'push'
run: |
if ( "${{github.ref}}" -match "^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$" ) {
dotnet nuget push src\${{env.PROJECT_NAME}}\bin\Release\*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
dotnet nuget push src\${{env.PROJECT_NAME}}.Signed\bin\Release\*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}}
} else {
echo "publish is only enabled by tagging with a release tag"
}

View File

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

View File

@@ -1,5 +1,19 @@
# Changelog
## 0.18.2 (8 Mar 2020)
- Optimize LineReader.ReadLine in [PR #393](https://github.com/lunet-io/markdig/pull/393)
- Use HashSet<T> instead of Dictionary<TKey, TValue> in CharacterMap<T> in [PR #394](https://github.com/lunet-io/markdig/pull/394)
- Use BitVector128 in CharacterMap<T> in [PR #396](https://github.com/lunet-io/markdig/pull/396)
- Optimizations in StringLineGroup in [PR #399](https://github.com/lunet-io/markdig/pull/399)
- Fixed a bug in HeadingRenderer in [PR #402](https://github.com/lunet-io/markdig/pull/402)
- Fixes issue #303 in [PR #404](https://github.com/lunet-io/markdig/pull/404)
- Make output of HtmlTableRenderer XML wellformed in [PR #406](https://github.com/lunet-io/markdig/pull/406)
## 0.18.1 (21 Jan 2020)
- Re-allow emojis and smileys customization, that was broken in [PR #308](https://github.com/lunet-io/markdig/pull/308) ([PR #386](https://github.com/lunet-io/markdig/pull/386))
- Add `IHostProvider` for medialink customization (#337), support protocol-less url (#135) ([(PR #341)](https://github.com/lunet-io/markdig/pull/341))
- Add missing Descendants<T> overload ([(PR #387)](https://github.com/lunet-io/markdig/pull/387))
## 0.18.0 (24 Oct 2019)
- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357))
- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360))

View File

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

View File

@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net471</TargetFrameworks>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<None Remove="spec.md" />

View File

@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net451;netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>net451;netcoreapp2.1</TargetFrameworks>
<OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// Generated: 2019-04-29 18:40:06
// Generated: 2019-05-15 02:46:20
// --------------------------------
// Media
@@ -47,19 +47,19 @@ namespace Markdig.Tests.Specs.Media
// ![ok.ru](https://ok.ru/video/26870090463)
//
// Should be rendered as:
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" frameborder="0" allowfullscreen=""></iframe></p>
// <p><video width="500" height="281" controls=""><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
// <p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" class="yandex" frameborder="0"></iframe></p>
// <p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
Console.WriteLine("Example 1\nSection Extensions / Media links\n");
TestParser.TestSpec("![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)\n\n![youtube.com with t](https://www.youtube.com/watch?v=mswPy5bt3TQ&t=100)\n\n![youtu.be](https://youtu.be/mswPy5bt3TQ)\n\n![youtu.be with t](https://youtu.be/mswPy5bt3TQ?t=100)\n\n![youtube.com/embed 1](https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0)\n \n![youtube.com/embed 2](https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6)\n\n![vimeo](https://vimeo.com/8607834)\n\n![static mp4](https://sample.com/video.mp4)\n\n![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)\n\n![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
TestParser.TestSpec("![youtube.com](https://www.youtube.com/watch?v=mswPy5bt3TQ)\n\n![youtube.com with t](https://www.youtube.com/watch?v=mswPy5bt3TQ&t=100)\n\n![youtu.be](https://youtu.be/mswPy5bt3TQ)\n\n![youtu.be with t](https://youtu.be/mswPy5bt3TQ?t=100)\n\n![youtube.com/embed 1](https://www.youtube.com/embed/mswPy5bt3TQ?start=100&rel=0)\n \n![youtube.com/embed 2](https://www.youtube.com/embed?listType=playlist&list=PLC77007E23FF423C6)\n\n![vimeo](https://vimeo.com/8607834)\n\n![static mp4](https://sample.com/video.mp4)\n\n![yandex.ru](https://music.yandex.ru/album/411845/track/4402274)\n\n![ok.ru](https://ok.ru/video/26870090463)", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6\" width=\"500\" height=\"281\" class=\"youtube\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" class=\"vimeo\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>\n<p><video width=\"500\" height=\"281\" controls=\"\"><source type=\"video/mp4\" src=\"https://sample.com/video.mp4\"></source></video></p>\n<p><iframe src=\"https://music.yandex.ru/iframe/#track/4402274/411845/\" width=\"500\" height=\"281\" class=\"yandex\" frameborder=\"0\"></iframe></p>\n<p><iframe src=\"https://ok.ru/videoembed/26870090463\" width=\"500\" height=\"281\" class=\"odnoklassniki\" frameborder=\"0\" allowfullscreen=\"\"></iframe></p>", "medialinks|advanced+medialinks");
}
}
}

View File

@@ -27,14 +27,14 @@ Allows to embed audio/video links to popular website:
![ok.ru](https://ok.ru/video/26870090463)
.
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ?start=100&amp;rel=0" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://www.youtube.com/embed?listType=playlist&amp;list=PLC77007E23FF423C6" width="500" height="281" class="youtube" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" class="vimeo" frameborder="0" allowfullscreen=""></iframe></p>
<p><video width="500" height="281" controls=""><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" frameborder="0"></iframe></p>
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" frameborder="0" allowfullscreen=""></iframe></p>
<p><iframe src="https://music.yandex.ru/iframe/#track/4402274/411845/" width="500" height="281" class="yandex" frameborder="0"></iframe></p>
<p><iframe src="https://ok.ru/videoembed/26870090463" width="500" height="281" class="odnoklassniki" frameborder="0" allowfullscreen=""></iframe></p>
````````````````````````````````

View File

@@ -0,0 +1,70 @@
using System;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
{
public class TestContainerBlocks
{
private class MockContainerBlock : ContainerBlock
{
public MockContainerBlock()
: base(null)
{
}
}
[Test]
public void CanBeCleared()
{
ContainerBlock container = new MockContainerBlock();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
var paragraph = new ParagraphBlock();
Assert.Null(paragraph.Parent);
container.Add(paragraph);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container, paragraph.Parent);
Assert.AreSame(paragraph, container.LastChild);
container.Clear();
Assert.AreEqual(0, container.Count);
Assert.Null(container.LastChild);
Assert.Null(paragraph.Parent);
}
[Test]
public void CanBeInsertedInto()
{
ContainerBlock container = new MockContainerBlock();
var one = new ParagraphBlock();
container.Insert(0, one);
Assert.AreEqual(1, container.Count);
Assert.AreSame(container[0], one);
var two = new ParagraphBlock();
container.Insert(1, two);
Assert.AreEqual(2, container.Count);
Assert.AreSame(container[0], one);
Assert.AreSame(container[1], two);
var three = new ParagraphBlock();
container.Insert(0, three);
Assert.AreEqual(3, container.Count);
Assert.AreSame(container[0], three);
Assert.AreSame(container[1], one);
Assert.AreSame(container[2], two);
Assert.Throws<ArgumentNullException>(() => container.Insert(0, null));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(4, new ParagraphBlock()));
Assert.Throws<ArgumentOutOfRangeException>(() => container.Insert(-1, new ParagraphBlock()));
Assert.Throws<ArgumentException>(() => container.Insert(0, one)); // one already has a parent
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Markdig.Extensions.Emoji;
using NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public class TestCustomEmojis
{
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomEmoji(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
emojiToUnicode[":smiley:"] = "♥";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>:confused:</p>\n")] // default emoji does not work
[TestCase(":/", "<p>:/</p>\n")] // default smiley does not work
public void TestCustomSmiley(string input, string expected)
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":smiley:", "<p>♥</p>\n")]
[TestCase(":)", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomEmoji(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
emojiToUnicode[":smiley:"] = "♥";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
[TestCase(":testheart:", "<p>♥</p>\n")]
[TestCase("hello", "<p>♥</p>\n")]
[TestCase(":confused:", "<p>😕</p>\n")] // default emoji still works
[TestCase(":/", "<p>😕</p>\n")] // default smiley still works
public void TestOverrideDefaultWithCustomSmiley(string input, string expected)
{
var emojiToUnicode = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
var smileyToEmoji = EmojiMapping.GetDefaultSmileyToEmojiShortcode();
emojiToUnicode[":testheart:"] = "♥";
smileyToEmoji["hello"] = ":testheart:";
var customMapping = new EmojiMapping(emojiToUnicode, smileyToEmoji);
var pipeline = new MarkdownPipelineBuilder()
.UseEmojiAndSmiley(customEmojiMapping: customMapping)
.Build();
var actual = Markdown.ToHtml(input, pipeline);
Assert.AreEqual(expected, actual);
}
[Test]
public void TestCustomEmojiValidation()
{
var emojiToUnicode = new Dictionary<string, string>();
var smileyToEmoji = new Dictionary<string, string>();
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(null, smileyToEmoji));
Assert.Throws<ArgumentNullException>(() => new EmojiMapping(emojiToUnicode, null));
emojiToUnicode.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
emojiToUnicode.Clear();
smileyToEmoji.Add("null-value", null);
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
smileyToEmoji.Add("foo", "something-that-does-not-exist-in-emojiToUnicode");
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
smileyToEmoji.Clear();
emojiToUnicode.Add("a", "aaa");
emojiToUnicode.Add("b", "bbb");
emojiToUnicode.Add("c", "ccc");
smileyToEmoji.Add("a", "c"); // "a" already exists in emojiToUnicode
Assert.Throws<ArgumentException>(() => new EmojiMapping(emojiToUnicode, smileyToEmoji));
}
}
}

View File

@@ -3,6 +3,7 @@ using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Linq;
using System.Collections.Generic;
using Markdig.Helpers;
namespace Markdig.Tests
{
@@ -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(ArrayHelper<ListBlock>.Empty, literalInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, literalInline.Descendants<ParagraphBlock>());
Assert.AreSame(ArrayHelper<ContainerInline>.Empty, literalInline.Descendants<ContainerInline>());
}
foreach (ContainerInline containerInline in syntaxTree.Descendants<ContainerInline>())
{
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
containerInline.Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
containerInline.FindDescendants<LiteralInline>(),
(containerInline as MarkdownObject).Descendants<LiteralInline>());
if (containerInline.FirstChild is null)
{
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerInline.FindDescendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerInline as MarkdownObject).Descendants<LiteralInline>());
}
Assert.AreSame(ArrayHelper<ListBlock>.Empty, containerInline.Descendants<ListBlock>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, containerInline.Descendants<ParagraphBlock>());
}
foreach (ParagraphBlock paragraphBlock in syntaxTree.Descendants<ParagraphBlock>())
{
AssertIEnumerablesAreEqual(
(paragraphBlock as MarkdownObject).Descendants<LiteralInline>(),
paragraphBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<ParagraphBlock>.Empty, paragraphBlock.Descendants<ParagraphBlock>());
}
foreach (ContainerBlock containerBlock in syntaxTree.Descendants<ContainerBlock>())
{
AssertIEnumerablesAreEqual(
containerBlock.Descendants<LiteralInline>(),
(containerBlock as MarkdownObject).Descendants<LiteralInline>());
AssertIEnumerablesAreEqual(
containerBlock.Descendants<ParagraphBlock>(),
(containerBlock as MarkdownObject).Descendants<ParagraphBlock>());
if (containerBlock.Count == 0)
{
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, containerBlock.Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as Block).Descendants<LiteralInline>());
Assert.AreSame(ArrayHelper<LiteralInline>.Empty, (containerBlock as MarkdownObject).Descendants<LiteralInline>());
}
}
}
}
private void AssertSameDescendantsOrder(string markdown)
private static void AssertIEnumerablesAreEqual<T>(IEnumerable<T> first, IEnumerable<T> second)
{
var syntaxTree = Markdown.Parse(markdown, new MarkdownPipelineBuilder().UseAdvancedExtensions().Build());
var firstList = new List<T>(first);
var secondList = new List<T>(second);
var descendants_legacy = Descendants_Legacy(syntaxTree).ToList();
var descendants_new = syntaxTree.Descendants().ToList();
Assert.AreEqual(firstList.Count, secondList.Count);
Assert.AreEqual(descendants_legacy.Count, descendants_new.Count);
for (int i = 0; i < descendants_legacy.Count; i++)
for (int i = 0; i < firstList.Count; i++)
{
Assert.AreSame(descendants_legacy[i], descendants_new[i]);
Assert.AreSame(firstList[i], secondList[i]);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using Markdig.Syntax;
using NUnit.Framework;
namespace Markdig.Tests
@@ -151,8 +152,20 @@ namespace Markdig.Tests
/// Contains the markdown source for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
/// </summary>
public static readonly string[] SpecsMarkdown;
/// <summary>
/// Contains the markdown syntax tree for specification files (order is the same as in <see cref="SpecsFilePaths"/>)
/// </summary>
public static readonly MarkdownDocument[] SpecsSyntaxTrees;
static TestParser()
{
const string RunningInsideVisualStudioPath = "\\src\\.vs\\markdig\\";
int index = TestsDirectory.IndexOf(RunningInsideVisualStudioPath);
if (index != -1)
{
TestsDirectory = TestsDirectory.Substring(0, index) + "\\src\\Markdig.Tests";
}
SpecsFilePaths = Directory.GetDirectories(TestsDirectory)
.Where(dir => dir.EndsWith("Specs"))
.SelectMany(dir => Directory.GetFiles(dir)
@@ -161,10 +174,16 @@ namespace Markdig.Tests
.ToArray();
SpecsMarkdown = new string[SpecsFilePaths.Length];
SpecsSyntaxTrees = new MarkdownDocument[SpecsFilePaths.Length];
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
for (int i = 0; i < SpecsFilePaths.Length; i++)
{
SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
string markdown = SpecsMarkdown[i] = File.ReadAllText(SpecsFilePaths[i]);
SpecsSyntaxTrees[i] = Markdown.Parse(markdown, pipeline);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -51,8 +51,7 @@ namespace Markdig.Extensions.Bootstrap
}
else if (node is Inline)
{
var link = node as LinkInline;
if (link != null && link.IsImage)
if (node is LinkInline link && link.IsImage)
{
link.GetAttributes().AddClass("img-fluid");
}

View File

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

View File

@@ -1,4 +1,4 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
@@ -8,7 +8,7 @@ using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Emoji
{
/// <summary>
/// An emoji inline
/// An emoji inline.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.Inline" />
public class EmojiInline : LiteralInline
@@ -32,7 +32,7 @@ namespace Markdig.Extensions.Emoji
}
/// <summary>
/// Gets or sets the original match string (either an emoji or a text smiley)
/// Gets or sets the original match string (either an emoji shortcode or a text smiley)
/// </summary>
public string Match { get; set; }
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,137 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
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))
throw new ArgumentException("hostPrefix is null or empty.", nameof(hostPrefix));
if (handler == null)
throw new ArgumentNullException(nameof(handler));
return new DelegateProvider { HostPrefix = hostPrefix, Delegate = handler, AllowFullScreen = allowFullScreen, Class = iframeClass };
}
internal static Dictionary<string, IHostProvider> KnownHosts { get; }
= new Dictionary<string, IHostProvider>(StringComparer.OrdinalIgnoreCase)
{
["YouTube"] = Create("www.youtube.com", YouTube, iframeClass: "youtube"),
["YouTubeShortened"] = Create("youtu.be", YouTubeShortened, iframeClass: "youtube"),
["Vimeo"] = Create("vimeo.com", Vimeo, iframeClass: "vimeo"),
["Yandex"] = Create("music.yandex.ru", Yandex, allowFullScreen: false, iframeClass: "yandex"),
["Odnoklassniki"] = Create("ok.ru", Odnoklassniki, iframeClass: "odnoklassniki"),
};
#region Known providers
private static readonly string[] SplitAnd = { "&" };
private static string[] SplitQuery(Uri uri)
{
var query = uri.Query.Substring(uri.Query.IndexOf('?') + 1);
return query.Split(SplitAnd, StringSplitOptions.RemoveEmptyEntries);
}
private static string YouTube(Uri uri)
{
string uriPath = uri.AbsolutePath;
if (string.Equals(uriPath, "/embed", StringComparison.OrdinalIgnoreCase) || uriPath.StartsWith("/embed/", StringComparison.OrdinalIgnoreCase))
{
return uri.ToString();
}
if (!string.Equals(uriPath, "/watch", StringComparison.OrdinalIgnoreCase) && !uriPath.StartsWith("/watch/", StringComparison.OrdinalIgnoreCase))
{
return null;
}
var queryParams = SplitQuery(uri);
return BuildYouTubeIframeUrl(
queryParams.FirstOrDefault(p => p.StartsWith("v="))?.Substring(2),
queryParams.FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
);
}
private static string YouTubeShortened(Uri uri)
{
return BuildYouTubeIframeUrl(
uri.AbsolutePath.Substring(1),
SplitQuery(uri).FirstOrDefault(p => p.StartsWith("t="))?.Substring(2)
);
}
private static string BuildYouTubeIframeUrl(string videoId, string startTime)
{
if (string.IsNullOrEmpty(videoId))
{
return null;
}
string url = $"https://www.youtube.com/embed/{videoId}";
return string.IsNullOrEmpty(startTime) ? url : $"{url}?start={startTime}";
}
private static string Vimeo(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://player.vimeo.com/video/{ items[items.Length - 1] }" : null;
}
private static string Odnoklassniki(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
return items.Length > 0 ? $"https://ok.ru/videoembed/{ items[items.Length - 1] }" : null;
}
private static string Yandex(Uri uri)
{
var items = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped).Split('/');
var albumKeyword
= items.Skip(0).FirstOrDefault();
var albumId
= items.Skip(1).FirstOrDefault();
var trackKeyword
= items.Skip(2).FirstOrDefault();
var trackId
= items.Skip(3).FirstOrDefault();
if (albumKeyword != "album" || albumId == null || trackKeyword != "track" || trackId == null)
{
return null;
}
return $"https://music.yandex.ru/iframe/#track/{trackId}/{albumId}/";
}
#endregion
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
namespace Markdig.Extensions.MediaLinks
{
/// <summary>
/// Provides url for media links.
/// </summary>
public interface IHostProvider
{
/// <summary>
/// "class" attribute of generated iframe.
/// </summary>
string Class { get; }
/// <summary>
/// Generate url for iframe.
/// </summary>
/// <param name="mediaUri">Input media uri.</param>
/// <param name="isSchemaRelative"><see langword="true"/> if <paramref name="mediaUri"/> is a schema relative uri, i.e. uri starts with "//".</param>
/// <param name="iframeUrl">Generated url for iframe.</param>
/// <seealso href="https://tools.ietf.org/html/rfc3986#section-4.2"/>
bool TryHandle(Uri mediaUri, bool isSchemaRelative, out string iframeUrl);
/// <summary>
/// Should the generated iframe has allowfullscreen attribute.
/// </summary>
/// <remarks>
/// Should be false for audio embedding.
/// </remarks>
bool AllowFullScreen { get; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -177,7 +177,7 @@ namespace Markdig.Helpers
{
// 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)]
@@ -303,8 +303,8 @@ namespace Markdig.Helpers
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsAlpha(this char c)
{
return ((uint)(c - 'a') <= ('z' - 'a')) || ((uint)(c - 'A') <= ('Z' - 'A'));
{
return (uint)((c - 'A') & ~0x20) <= ('Z' - 'A');
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
@@ -370,16 +370,20 @@ namespace Markdig.Helpers
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsHighSurrogate(char c)
{
return ((c >= HighSurrogateStart) && (c <= HighSurrogateEnd));
{
return IsInInclusiveRange(c, HighSurrogateStart, HighSurrogateEnd);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static bool IsLowSurrogate(char c)
{
return ((c >= LowSurrogateStart) && (c <= LowSurrogateEnd));
return IsInInclusiveRange(c, LowSurrogateStart, LowSurrogateEnd);
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsInInclusiveRange(char c, char min, char max)
=> (uint) (c - min) <= (uint) (max - min);
public static int ConvertToUtf32(char highSurrogate, char lowSurrogate)
{
if (!IsHighSurrogate(highSurrogate))
@@ -393,13 +397,14 @@ namespace Markdig.Helpers
return (((highSurrogate - HighSurrogateStart) * 0x400) + (lowSurrogate - LowSurrogateStart) + UnicodePlane01Start);
}
public static IEnumerable<int> ToUtf32(string text)
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
{

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -17,7 +18,7 @@ namespace Markdig.Helpers
{
private readonly T[] asciiMap;
private readonly Dictionary<char, T> nonAsciiMap;
private readonly bool[] isOpeningCharacter;
private readonly BitVector128 isOpeningCharacter;
/// <summary>
/// Initializes a new instance of the <see cref="CharacterMap{T}"/> class.
@@ -27,40 +28,36 @@ namespace Markdig.Helpers
public CharacterMap(IEnumerable<KeyValuePair<char, T>> maps)
{
if (maps == null) throw new ArgumentNullException(nameof(maps));
var charCounter = new Dictionary<char, int>();
var charSet = new HashSet<char>();
int maxChar = 0;
foreach (var map in maps)
{
var openingChar = map.Key;
if (!charCounter.ContainsKey(openingChar))
{
charCounter[openingChar] = 0;
}
charCounter[openingChar]++;
charSet.Add(openingChar);
if (openingChar < 127 && openingChar > maxChar)
if (openingChar < 128 && openingChar > maxChar)
{
maxChar = openingChar;
}
else if (openingChar >= 127 && nonAsciiMap == null)
else if (openingChar >= 128 && nonAsciiMap == null)
{
// Initialize only if with have an actual non-ASCII opening character
nonAsciiMap = new Dictionary<char, T>();
}
}
OpeningCharacters = charCounter.Keys.ToArray();
OpeningCharacters = charSet.ToArray();
Array.Sort(OpeningCharacters);
asciiMap = new T[maxChar + 1];
isOpeningCharacter = new bool[maxChar + 1];
var isOpeningCharacter = new BitVector128();
foreach (var state in maps)
{
var openingChar = state.Key;
T stateByChar;
if (openingChar < 127)
if (openingChar < 128)
{
stateByChar = asciiMap[openingChar];
@@ -68,7 +65,7 @@ namespace Markdig.Helpers
{
asciiMap[openingChar] = state.Value;
}
isOpeningCharacter[openingChar] = true;
isOpeningCharacter.Set(openingChar);
}
else
{
@@ -78,6 +75,8 @@ namespace Markdig.Helpers
}
}
}
this.isOpeningCharacter = isOpeningCharacter;
}
/// <summary>
@@ -117,19 +116,18 @@ 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;
var openingChars = isOpeningCharacter;
unsafe
{
fixed (char* pText = text)
fixed (bool* openingChars = isOpeningCharacter)
{
if (nonAsciiMap == null)
{
for (int i = start; i <= end; i++)
{
var c = pText[i];
if (c < maxChar && openingChars[c])
if (c < 128 && openingChars[c])
{
return i;
}
@@ -140,7 +138,7 @@ namespace Markdig.Helpers
for (int i = start; i <= end; i++)
{
var c = pText[i];
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
if (c < 128 ? openingChars[c] : nonAsciiMap.ContainsKey(c))
{
return i;
}
@@ -150,5 +148,26 @@ namespace Markdig.Helpers
}
return -1;
}
internal unsafe struct BitVector128
{
fixed uint values[4];
public void Set(char c)
{
Debug.Assert(c < 128);
values[c >> 5] |= (uint)1 << c;
}
public readonly bool this[char c]
{
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
get
{
Debug.Assert(c < 128);
return (values[c >> 5] & (uint)1 << c) != 0;
}
}
}
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
/*
@@ -25,6 +26,7 @@ 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>
[ExcludeFromCodeCoverage]
internal sealed class CompactPrefixTree<TValue>
//#if !LEGACY
// : IReadOnlyDictionary<string, TValue>, IReadOnlyList<KeyValuePair<string, TValue>>

View File

@@ -0,0 +1,10 @@
#if NET35
namespace System.Diagnostics.CodeAnalysis
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)]
internal class ExcludeFromCodeCoverageAttribute : Attribute
{
}
}
#endif

View File

@@ -1,10 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.IO;
using System.Text;
namespace Markdig.Helpers
{
@@ -13,17 +12,16 @@ namespace Markdig.Helpers
/// </summary>
public struct LineReader
{
private readonly string text;
private readonly string _text;
/// <summary>
/// Initializes a new instance of the <see cref="LineReader"/> class.
/// </summary>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentOutOfRangeException">bufferSize cannot be &lt;= 0</exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException">bufferSize cannot be &lt;= 0</exception>
public LineReader(string text)
{
if (text == null) throw new ArgumentNullException(nameof(text));
this.text = text;
_text = text ?? throw new ArgumentNullException(nameof(text));
SourcePosition = 0;
}
@@ -36,36 +34,31 @@ namespace Markdig.Helpers
/// Reads a new line from the underlying <see cref="TextReader"/> and update the <see cref="SourcePosition"/> for the next line.
/// </summary>
/// <returns>A new line or null if the end of <see cref="TextReader"/> has been reached</returns>
public StringSlice? ReadLine()
public StringSlice ReadLine()
{
if (SourcePosition >= text.Length)
{
return null;
}
string text = _text;
int sourcePosition = SourcePosition;
var startPosition = SourcePosition;
var position = SourcePosition;
var slice = new StringSlice(text, startPosition, startPosition);
for (;position < text.Length; position++)
for (int i = sourcePosition; i < text.Length; i++)
{
var c = text[position];
char c = text[i];
if (c == '\r' || c == '\n')
{
slice.End = position - 1;
if (c == '\r' && position + 1 < text.Length && text[position + 1] == '\n')
{
position++;
}
position++;
SourcePosition = position;
var slice = new StringSlice(text, sourcePosition, i - 1);
if (c == '\r' && (uint)(i + 1) < (uint)text.Length && text[i + 1] == '\n')
i++;
SourcePosition = i + 1;
return slice;
}
}
slice.End = position - 1;
SourcePosition = position;
if (sourcePosition >= text.Length)
return default;
return slice;
SourcePosition = int.MaxValue;
return new StringSlice(text, sourcePosition, text.Length - 1);
}
}
}
}

View File

@@ -101,15 +101,13 @@ 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)]

View File

@@ -5,6 +5,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Markdig.Helpers
@@ -68,16 +69,13 @@ 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>
@@ -114,22 +112,19 @@ namespace Markdig.Helpers
/// <returns>A single slice concatenating the lines of this instance</returns>
public readonly StringSlice ToSlice(List<LineOffset> lineOffsets = null)
{
// Optimization case for a single line.
if (Count == 1)
{
lineOffsets?.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
return Lines[0];
}
// Optimization case when no lines
if (Count == 0)
{
return new StringSlice(string.Empty);
}
// Optimization case for a single line.
if (Count == 1)
{
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
}
return Lines[0];
}
if (lineOffsets != null && lineOffsets.Capacity < lineOffsets.Count + Count)
{
lineOffsets.Capacity = Math.Max(lineOffsets.Count + Count, lineOffsets.Capacity * 2);
@@ -142,21 +137,16 @@ namespace Markdig.Helpers
{
if (i > 0)
{
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[i - 1].Position, Lines[i - 1].Column, Lines[i - 1].Slice.Start - Lines[i - 1].Position, previousStartOfLine, builder.Length));
}
builder.Append('\n');
previousStartOfLine = builder.Length;
}
if (!Lines[i].Slice.IsEmpty)
ref var line = ref Lines[i];
if (!line.Slice.IsEmpty)
{
builder.Append(Lines[i].Slice.Text, Lines[i].Slice.Start, Lines[i].Slice.Length);
builder.Append(line.Slice.Text, line.Slice.Start, line.Slice.Length);
}
}
if (lineOffsets != null)
{
lineOffsets.Add(new LineOffset(Lines[Count - 1].Position, Lines[Count - 1].Column, Lines[Count - 1].Slice.Start - Lines[Count - 1].Position, previousStartOfLine, builder.Length));
lineOffsets?.Add(new LineOffset(line.Position, line.Column, line.Slice.Start - line.Position, previousStartOfLine, builder.Length));
}
return new StringSlice(builder.GetStringAndReset());
}
@@ -267,10 +257,10 @@ namespace Markdig.Helpers
public char NextChar()
{
Start++;
_offset++;
if (Start <= End)
{
var slice = (StringSlice)_lines.Lines[SliceIndex];
var slice = _lines.Lines[SliceIndex].Slice;
_offset++;
if (_offset < slice.Length)
{
CurrentChar = slice[slice.Start + _offset];
@@ -287,7 +277,6 @@ namespace Markdig.Helpers
CurrentChar = '\0';
Start = End + 1;
SliceIndex = _lines.Count;
_offset--;
}
return CurrentChar;
}
@@ -301,13 +290,27 @@ namespace Markdig.Helpers
return '\0';
}
var slice = (StringSlice)_lines.Lines[SliceIndex];
if (_offset + offset >= slice.Length)
offset += _offset;
int sliceIndex = SliceIndex;
var slice = _lines.Lines[sliceIndex].Slice;
while (offset > slice.Length)
{
// We are not peeking at the same line
offset -= slice.Length + 1; // + 1 for new line
Debug.Assert(sliceIndex + 1 < _lines.Lines.Length, "'Start + offset > End' check above should prevent us from indexing out of range");
slice = _lines.Lines[++sliceIndex].Slice;
}
if (offset == slice.Length)
{
return '\n';
}
return slice[slice.Start + _offset + offset];
Debug.Assert(offset < slice.Length);
return slice[slice.Start + offset];
}
public bool TrimStart()

View File

@@ -315,7 +315,8 @@ namespace Markdig.Helpers
// Strip leading spaces
for (; Start <= End; Start++)
{
if (!Text[Start].IsWhitespace())
if (Start < Text.Length
&& !Text[Start].IsWhitespace())
{
break;
}
@@ -349,7 +350,8 @@ namespace Markdig.Helpers
{
for (; Start <= End; End--)
{
if (!Text[End].IsWhitespace())
if (End < Text.Length
&& !Text[End].IsWhitespace())
{
break;
}

View File

@@ -1,11 +1,13 @@
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 ThrowArgumentNullException(ExceptionArgument argument)

View File

@@ -4,9 +4,9 @@
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
<Copyright>Alexandre Mutel</Copyright>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.18.0</VersionPrefix>
<VersionPrefix>0.18.2</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>net35;net40;netstandard2.0;netcoreapp2.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>
@@ -20,24 +20,13 @@
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.17763.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants>$(DefineConstants);UAP</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' OR '$(TargetFramework)' == 'net40'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
<PackageReference Include="jnm2.ReferenceAssemblies.net35" Version="1.0.1" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</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)')" />
</Project>

View File

@@ -17,11 +17,7 @@ namespace Markdig
/// </summary>
public static partial class Markdown
{
#if UAP
public static readonly string Version = typeof(Markdown).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
#else
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
#endif
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.

View File

@@ -92,8 +92,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>
@@ -421,20 +421,35 @@ namespace Markdig
}
/// <summary>
/// Uses the emoji and smiley extension.
/// Uses the emojis and smileys extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
/// <param name="enableSmileys">Enable smileys in addition to emoji shortcodes, <c>true</c> by default.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmileys = true)
{
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
var emojiMapping = enableSmileys ? EmojiMapping.DefaultEmojisAndSmileysMapping : EmojiMapping.DefaultEmojisOnlyMapping;
pipeline.Extensions.Add(new EmojiExtension(emojiMapping));
}
return pipeline;
}
/// <summary>
/// Uses the emojis and smileys extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="customEmojiMapping">Enable customization of the emojis and smileys mapping.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, EmojiMapping customEmojiMapping)
{
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(customEmojiMapping));
}
return pipeline;
}
/// <summary>
/// Add rel=nofollow to all links rendered to HTML.
/// </summary>

View File

@@ -116,11 +116,11 @@ namespace Markdig.Parsers
var lineText = lineReader.ReadLine();
// If this is the end of file and the last line is empty
if (lineText == null)
if (lineText.Text is null)
{
break;
}
blockProcessor.ProcessLine(lineText.Value);
blockProcessor.ProcessLine(lineText);
}
blockProcessor.CloseAll(true);
}

View File

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

View File

@@ -158,6 +158,10 @@ namespace Markdig.Syntax
{
throw new ArgumentException("Cannot add this block as it as already attached to another container (block.Parent != null)");
}
if (index < 0 || index > Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (Count == children.Length)
{
EnsureCapacity(Count + 1);

View File

@@ -1,9 +1,11 @@
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace Markdig.Syntax.Inlines
@@ -94,8 +96,18 @@ namespace Markdig.Syntax.Inlines
/// <returns>An enumeration of T</returns>
public IEnumerable<T> FindDescendants<T>() where T : Inline
{
// Fast-path an empty container to avoid allocating a Stack
if (LastChild == null) yield break;
if (FirstChild is null)
{
return ArrayHelper<T>.Empty;
}
else
{
return FindDescendantsInternal<T>();
}
}
internal IEnumerable<T> FindDescendantsInternal<T>() where T : MarkdownObject
{
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
Stack<Inline> stack = new Stack<Inline>();

View File

@@ -3,6 +3,8 @@
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using System.Diagnostics;
using Markdig.Helpers;
using Markdig.Syntax.Inlines;
namespace Markdig.Syntax
@@ -20,10 +22,6 @@ namespace Markdig.Syntax
/// <returns>An iteration over the descendant elements</returns>
public static IEnumerable<MarkdownObject> Descendants(this MarkdownObject markdownObject)
{
// Fast-path an object with no children to avoid allocating Stack objects
if (!(markdownObject is ContainerBlock) && !(markdownObject is ContainerInline)) yield break;
// TODO: A single Stack<(MarkdownObject block, bool push)> when ValueTuples are available
Stack<MarkdownObject> stack = new Stack<MarkdownObject>();
Stack<bool> pushStack = new Stack<bool>();
@@ -66,7 +64,41 @@ namespace Markdig.Syntax
}
/// <summary>
/// Iterates over the descendant elements for the specified markdown <see cref="Inline" /> element and filters by the type {T}.
/// Iterates over the descendant elements for the specified markdown element, including <see cref="Block"/> and <see cref="Inline"/> and filters by the type <typeparamref name="T"/>.
/// <para>The descendant elements are returned in DFS-like order.</para>
/// </summary>
/// <typeparam name="T">Type to use for filtering the descendants</typeparam>
/// <param name="markdownObject">The markdown object.</param>
/// <returns>An iteration over the descendant elements</returns>
public static IEnumerable<T> Descendants<T>(this MarkdownObject markdownObject) where T : MarkdownObject
{
if (typeof(T).IsSubclassOf(typeof(Block)))
{
if (markdownObject is ContainerBlock containerBlock && containerBlock.Count > 0)
{
return BlockDescendantsInternal<T>(containerBlock);
}
}
else // typeof(T).IsSubclassOf(typeof(Inline)))
{
if (markdownObject is ContainerBlock containerBlock)
{
if (containerBlock.Count > 0)
{
return InlineDescendantsInternal<T>(containerBlock);
}
}
else if (markdownObject is ContainerInline containerInline && containerInline.FirstChild != null)
{
return containerInline.FindDescendantsInternal<T>();
}
}
return ArrayHelper<T>.Empty;
}
/// <summary>
/// Iterates over the descendant elements for the specified markdown <see cref="Inline" /> element and filters by the type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">Type to use for filtering the descendants</typeparam>
/// <param name="inline">The inline markdown object.</param>
@@ -77,7 +109,7 @@ namespace Markdig.Syntax
=> inline.FindDescendants<T>();
/// <summary>
/// Iterates over the descendant elements for the specified markdown <see cref="Block" /> element and filters by the type {T}.
/// Iterates over the descendant elements for the specified markdown <see cref="Block" /> element and filters by the type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">Type to use for filtering the descendants</typeparam>
/// <param name="block">The markdown object.</param>
@@ -86,8 +118,19 @@ namespace Markdig.Syntax
/// </returns>
public static IEnumerable<T> Descendants<T>(this ContainerBlock block) where T : Block
{
// Fast-path an empty container to avoid allocating a Stack
if (block.Count == 0) yield break;
if (block != null && block.Count > 0)
{
return BlockDescendantsInternal<T>(block);
}
else
{
return ArrayHelper<T>.Empty;
}
}
private static IEnumerable<T> BlockDescendantsInternal<T>(ContainerBlock block) where T : MarkdownObject
{
Debug.Assert(typeof(T).IsSubclassOf(typeof(Block)));
Stack<Block> stack = new Stack<Block>();
@@ -115,5 +158,18 @@ namespace Markdig.Syntax
}
}
}
private static IEnumerable<T> InlineDescendantsInternal<T>(ContainerBlock block) where T : MarkdownObject
{
Debug.Assert(typeof(T).IsSubclassOf(typeof(Inline)));
foreach (MarkdownObject descendant in block.Descendants())
{
if (descendant is T descendantT)
{
yield return descendantT;
}
}
}
}
}

View File

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

View File

@@ -2,7 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
</Project>

View File

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

View File

@@ -1,56 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{33FFC0B9-0187-44F9-9424-BB5AF5B4FB84}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>UnicodeNormDApp</RootNamespace>
<AssemblyName>UnicodeNormDApp</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("mdtoc")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Alexandre Mutel")]
[assembly: AssemblyProduct("mdtoc")]
[assembly: AssemblyCopyright("Copyright © 2017 - Alexandre Mutel")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("e3cdff0f-5bfc-42e9-bdba-2797651900a2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,58 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E3CDFF0F-5BFC-42E9-BDBA-2797651900A2}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>mdtoc</RootNamespace>
<AssemblyName>mdtoc</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TargetFramework>netcoreapp3.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj">
<Project>{8a58a7e2-627c-4f41-933f-5ac92adfab48}</Project>
<Name>Markdig</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>