mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-04 05:44:50 +00:00
Compare commits
297 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cc5856c1c | ||
|
|
a4bb174a77 | ||
|
|
82987fa879 | ||
|
|
2df2ff17c5 | ||
|
|
4e4825cb3f | ||
|
|
ed2371beae | ||
|
|
2c2898769e | ||
|
|
001a99ab77 | ||
|
|
9233ec220c | ||
|
|
bb30dc21c5 | ||
|
|
c761fa2243 | ||
|
|
9d172ffcc1 | ||
|
|
1863e22328 | ||
|
|
c591d1950f | ||
|
|
339fcc9152 | ||
|
|
3a54f06540 | ||
|
|
3f305a25a8 | ||
|
|
5a210223b6 | ||
|
|
a7786d934d | ||
|
|
cb3c1f1505 | ||
|
|
50b33b8512 | ||
|
|
80790b5038 | ||
|
|
311c28ae60 | ||
|
|
9906a0554f | ||
|
|
7e92f1881d | ||
|
|
41911806a0 | ||
|
|
610f1519b0 | ||
|
|
29aa56b4f1 | ||
|
|
5004ecedb7 | ||
|
|
ba06a796dc | ||
|
|
b1d6f34976 | ||
|
|
28f4236a57 | ||
|
|
0739a82735 | ||
|
|
3ac9f2e788 | ||
|
|
d9607cc687 | ||
|
|
f0573ef9e2 | ||
|
|
017a3bd7e2 | ||
|
|
d6f9a1055c | ||
|
|
2612e9d565 | ||
|
|
1076d7af10 | ||
|
|
850cecfa6c | ||
|
|
bfdb03bd78 | ||
|
|
56b6e329d5 | ||
|
|
006e384d1a | ||
|
|
9cd9b683a5 | ||
|
|
e01e2c3d2b | ||
|
|
97ce0da564 | ||
|
|
26c5ce59b5 | ||
|
|
d2d94ecf39 | ||
|
|
0ec82bb81d | ||
|
|
ad9e941cb0 | ||
|
|
27dab6ca6d | ||
|
|
762196f03b | ||
|
|
26a565fa45 | ||
|
|
4369db1a43 | ||
|
|
d83f1f87cc | ||
|
|
14027f4be3 | ||
|
|
8bcfb53607 | ||
|
|
700bb21e03 | ||
|
|
1bc9d9083c | ||
|
|
1d7bb021a7 | ||
|
|
3305a74b06 | ||
|
|
6312bc0515 | ||
|
|
b595383281 | ||
|
|
bf85964543 | ||
|
|
d5b020b784 | ||
|
|
8ab0467e78 | ||
|
|
74fe3be7c7 | ||
|
|
b47ae8d9ba | ||
|
|
a926e1a326 | ||
|
|
6048406f52 | ||
|
|
1edf14c742 | ||
|
|
d4bb7c0a51 | ||
|
|
ba4e463517 | ||
|
|
2d0dae4ebb | ||
|
|
1a9d923c44 | ||
|
|
013ec0bf80 | ||
|
|
f82e874cb7 | ||
|
|
976fc569fa | ||
|
|
5aab21f0bf | ||
|
|
1df8344576 | ||
|
|
9eb84cf95e | ||
|
|
47d45b5577 | ||
|
|
ebcc286df0 | ||
|
|
f11d8037ac | ||
|
|
f668b3fe38 | ||
|
|
5ca4e31332 | ||
|
|
a8fbe79e30 | ||
|
|
bdb09b9820 | ||
|
|
79348ebea8 | ||
|
|
b366c1a5e3 | ||
|
|
a5e53b014f | ||
|
|
c2f21e21c8 | ||
|
|
81b8dce7a8 | ||
|
|
e6223f86d8 | ||
|
|
2054c16662 | ||
|
|
95641e562f | ||
|
|
e4953931c7 | ||
|
|
93b5b7a091 | ||
|
|
4f52e893ee | ||
|
|
23ede077a1 | ||
|
|
d5e6f17683 | ||
|
|
4d5980a485 | ||
|
|
19dd902519 | ||
|
|
f046a46275 | ||
|
|
25e9eafa8b | ||
|
|
f4ff981008 | ||
|
|
a1b48aff89 | ||
|
|
0aa34caa82 | ||
|
|
98ce9b1a06 | ||
|
|
ab157b21ea | ||
|
|
53c72d3031 | ||
|
|
165e2f97d0 | ||
|
|
6c577059ad | ||
|
|
0ea4dc769b | ||
|
|
a9b626e810 | ||
|
|
b90d6f9769 | ||
|
|
f0ea008c46 | ||
|
|
c43d5ccd63 | ||
|
|
d6d7b398e4 | ||
|
|
e1032e5094 | ||
|
|
a32ac298c5 | ||
|
|
2e6ab670cb | ||
|
|
72b7fce48c | ||
|
|
fa281f1ca1 | ||
|
|
1b92311aeb | ||
|
|
a593212f03 | ||
|
|
c4ec928953 | ||
|
|
e7df7fabeb | ||
|
|
1c187f2d81 | ||
|
|
da66cf90c3 | ||
|
|
0e8bd7407f | ||
|
|
d70f14addb | ||
|
|
e7b9eea3a5 | ||
|
|
97af9d822d | ||
|
|
cf7a09ab76 | ||
|
|
e755627421 | ||
|
|
f9be64a988 | ||
|
|
d65431e6cc | ||
|
|
86fb962fdb | ||
|
|
d003837b27 | ||
|
|
fd813e3c5a | ||
|
|
a3691c4423 | ||
|
|
9506f22025 | ||
|
|
06ae907949 | ||
|
|
555523b2af | ||
|
|
3b9772f772 | ||
|
|
891c80f48c | ||
|
|
105b09e1ec | ||
|
|
9fe7596a23 | ||
|
|
82af7cadc5 | ||
|
|
800c81bb9a | ||
|
|
5653a4f7ee | ||
|
|
4f14ffe63b | ||
|
|
d57acefe56 | ||
|
|
fb61e5a8da | ||
|
|
264516bfdb | ||
|
|
6d8f8996d5 | ||
|
|
ba8557d3bf | ||
|
|
d18fd0b957 | ||
|
|
fba96774f4 | ||
|
|
0b2764ea62 | ||
|
|
64d81ed47b | ||
|
|
9a9742888b | ||
|
|
5400b30a90 | ||
|
|
673f4a4beb | ||
|
|
05a27649aa | ||
|
|
26c6b12dea | ||
|
|
9a18ca222f | ||
|
|
354f31b870 | ||
|
|
c6c2f58ec0 | ||
|
|
4a146f00f6 | ||
|
|
2c10ac59d3 | ||
|
|
9a98fb9453 | ||
|
|
1c7f843a4c | ||
|
|
fdaf301bd6 | ||
|
|
5a9f2f3afe | ||
|
|
6a1b30761a | ||
|
|
8f8b08fad6 | ||
|
|
312b63a4c8 | ||
|
|
1598b538ab | ||
|
|
16ce46a741 | ||
|
|
4456598228 | ||
|
|
2a937c63b9 | ||
|
|
191bc940c7 | ||
|
|
05673758e3 | ||
|
|
cff2b9a8ca | ||
|
|
586095a475 | ||
|
|
eae2082a1e | ||
|
|
4576548df3 | ||
|
|
266e0c8bfd | ||
|
|
f5c07dbab5 | ||
|
|
c8a28a1ad7 | ||
|
|
72cc454314 | ||
|
|
7fe2c1f939 | ||
|
|
087e7a68b6 | ||
|
|
abeabf15a1 | ||
|
|
f9bfcaab7b | ||
|
|
afa0182f02 | ||
|
|
b83de5934d | ||
|
|
c294d3bfb4 | ||
|
|
a7cdb2351a | ||
|
|
46ef21a3ed | ||
|
|
18c8d0178c | ||
|
|
ebc79dafbd | ||
|
|
a1d2467643 | ||
|
|
8220f0fa56 | ||
|
|
ec385acc7f | ||
|
|
04c1cc62d4 | ||
|
|
abdbd65f60 | ||
|
|
1f32a060da | ||
|
|
699d80c150 | ||
|
|
56bcac7600 | ||
|
|
cab3365104 | ||
|
|
3821bd00fe | ||
|
|
62701fd0f1 | ||
|
|
1be5e60506 | ||
|
|
c9f1512358 | ||
|
|
8f23aed6af | ||
|
|
6a62ae9c69 | ||
|
|
2c3de5688b | ||
|
|
f3c08b4ec4 | ||
|
|
69e3baafe5 | ||
|
|
5844ccc395 | ||
|
|
be9c6fa54b | ||
|
|
d14f277c7b | ||
|
|
593bf08b92 | ||
|
|
85f631f868 | ||
|
|
5ad964bcb6 | ||
|
|
137a404bdc | ||
|
|
5204ec758a | ||
|
|
6f4fb69c62 | ||
|
|
0a1b37c965 | ||
|
|
90bdafb05a | ||
|
|
8cc668ae6d | ||
|
|
1787dc4590 | ||
|
|
0a9cc8fcd7 | ||
|
|
10c06daf5d | ||
|
|
a262e42980 | ||
|
|
4cd3d045d1 | ||
|
|
92357576b1 | ||
|
|
d45f67f8c2 | ||
|
|
2571cdffee | ||
|
|
c31cb6da27 | ||
|
|
5503929d15 | ||
|
|
ca32dda1fe | ||
|
|
12111e0b63 | ||
|
|
bdd46c0fc0 | ||
|
|
daf4c8fe86 | ||
|
|
bd2c2aff9c | ||
|
|
921f75e1f3 | ||
|
|
44a3b85f0b | ||
|
|
f9e827395b | ||
|
|
64a9a80774 | ||
|
|
e10594391d | ||
|
|
60eb03a221 | ||
|
|
c0a0f10af0 | ||
|
|
56e1ed0e25 | ||
|
|
a9f33cbca6 | ||
|
|
6f1d39e1bb | ||
|
|
838f7c5598 | ||
|
|
0f54cc5927 | ||
|
|
12745f70cf | ||
|
|
442737767f | ||
|
|
61ac46e467 | ||
|
|
85550580d5 | ||
|
|
ed69ac5fe0 | ||
|
|
0aec5a5783 | ||
|
|
c1885fe31b | ||
|
|
c2270a2b3a | ||
|
|
3e60515bb3 | ||
|
|
8550c13688 | ||
|
|
3aa65694aa | ||
|
|
52403687db | ||
|
|
3e83409cf4 | ||
|
|
9b051955bd | ||
|
|
35c8126add | ||
|
|
67e1c8ce7f | ||
|
|
6e5fbda8e5 | ||
|
|
90e7ccd80a | ||
|
|
c09b3eedd2 | ||
|
|
9441e8a04b | ||
|
|
301dc70ab4 | ||
|
|
959d6db62f | ||
|
|
38a7410e4b | ||
|
|
b941a58ad0 | ||
|
|
3fa20fa92f | ||
|
|
5dcd4ea4aa | ||
|
|
9c03683913 | ||
|
|
2fed1b3ebf | ||
|
|
9da3eef65f | ||
|
|
0b4fe3f02f | ||
|
|
96b39e1856 | ||
|
|
6d90f517cc | ||
|
|
c17630e3b6 | ||
|
|
6eecfe2edc | ||
|
|
9abeb97394 |
38
.editorconfig
Normal file
38
.editorconfig
Normal file
@@ -0,0 +1,38 @@
|
||||
# EditorConfig is awesome:http://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# All Files
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = crlf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Solution Files
|
||||
[*.sln]
|
||||
indent_style = tab
|
||||
|
||||
# XML Project Files
|
||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||
indent_size = 2
|
||||
|
||||
# Configuration Files
|
||||
[*.{json,xml,yml,config,props,targets,nuspec,resx,ruleset}]
|
||||
indent_size = 2
|
||||
|
||||
# Markdown Files
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# Web Files
|
||||
[*.{htm,html,js,ts,css,scss,less}]
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
|
||||
# Bash Files
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
46
appveyor.yml
Normal file
46
appveyor.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
version: 10.0.{build}
|
||||
image: Visual Studio 2017
|
||||
configuration: Release
|
||||
install:
|
||||
- ps: >-
|
||||
cd src
|
||||
|
||||
nuget restore Markdig.sln
|
||||
|
||||
$env:MARKDIG_BUILD_NUMBER = ([int]$env:APPVEYOR_BUILD_NUMBER).ToString("000")
|
||||
|
||||
$env:MARKDIG_VERSION_SUFFIX = ""
|
||||
|
||||
$env:appveyor_nuget_push = 'false'
|
||||
|
||||
if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
|
||||
if($env:appveyor_repo_tag -eq 'True') {
|
||||
if($env:appveyor_repo_tag_name -match '^v[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
|
||||
before_package:
|
||||
- cmd: >-
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:Clean Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release;SignAssembly=true Markdig/Markdig.csproj
|
||||
artifacts:
|
||||
- path: src\Markdig\Bin\Release\*.nupkg
|
||||
name: Markdig Nugets
|
||||
deploy:
|
||||
- provider: NuGet
|
||||
api_key:
|
||||
secure: 7cthHh+wYWZjhqxaxR6QObRaRnstvFkQOY7MkxIsC5kpQEBlKZXuinf0IybbYxJt
|
||||
on:
|
||||
appveyor_nuget_push: true
|
||||
BIN
img/BenchmarkCPU.png
Normal file
BIN
img/BenchmarkCPU.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
BIN
img/BenchmarkMemory.png
Normal file
BIN
img/BenchmarkMemory.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
BIN
img/markdig.png
Normal file
BIN
img/markdig.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
131
img/markdig.svg
Normal file
131
img/markdig.svg
Normal file
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="192"
|
||||
height="192"
|
||||
viewBox="0 0 192 192"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="markdig.svg"
|
||||
inkscape:export-filename="C:\Code\lunet-io\markdig\markdig.png"
|
||||
inkscape:export-xdpi="93.400002"
|
||||
inkscape:export-ydpi="93.400002">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.4374889"
|
||||
inkscape:cx="174.14769"
|
||||
inkscape:cy="93.189838"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
units="px" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="display:inline"
|
||||
transform="translate(0,-860.36216)">
|
||||
<path
|
||||
style="fill:#000000"
|
||||
d="M 75.009766 60.486328 L 34.648438 122.74414 C 33.793918 123.59769 37.647081 134.96384 37.052734 136.09766 L 4.0234375 177.69727 C 2.4142291 180.39677 3.2900484 182.21846 4.8730469 183.84766 C 5.9214414 184.93636 6.6591287 186.06887 8.3828125 185.67188 C 9.0750612 185.50987 10.104893 185.27338 10.875 184.76758 L 52.806641 151.81445 C 53.912466 151.23195 64.44071 154.77813 65.289062 153.92383 L 126.45312 111.46875 L 75.009766 60.486328 z M 89.632812 84.769531 L 103.77539 98.912109 L 79.027344 123.66016 L 86.238281 130.87109 L 48.621094 139.92383 L 57.435547 102.30859 L 64.884766 109.51758 L 89.632812 84.769531 z "
|
||||
transform="translate(0,860.36216)"
|
||||
id="path4140" />
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1"
|
||||
d="m 111.18463,862.06984 c -1.98231,0 -3.96282,0.78454 -5.54759,2.38445 L 88.200894,881.94537 75.123368,868.82653 c -3.169466,-3.18017 -7.92567,-3.18017 -11.095213,0 -3.169466,3.18017 -3.169466,7.95108 0,11.13109 l 13.077526,13.11885 -11.095212,10.73223 82.031621,81.49227 11.09525,-11.13109 13.87084,13.51554 c 1.57915,1.59105 3.56724,2.38445 5.54759,2.38445 1.98231,0 3.96285,-0.78453 5.54762,-2.38445 3.16947,-3.18017 3.16947,-7.95111 0,-11.13109 l -13.87083,-13.11884 17.43611,-17.09442 c 1.17983,-1.59105 1.98231,-3.5788 1.98231,-5.96329 0,-1.98351 -0.79863,-3.97554 -2.37816,-5.56446 l -70.54053,-70.35856 c -1.57914,-1.59105 -3.56724,-2.38446 -5.54758,-2.38446 z m 15.86949,20.75826 9.50139,9.5291 -4.04453,23.11372 23.04691,-4.05619 9.50138,9.5291 -36.8437,36.95052 -9.50135,-9.5291 21.13082,-21.19197 -23.04672,4.05619 4.04453,-23.11396 -21.131009,21.19198 -9.501383,-9.5291 36.843662,-36.95048 z"
|
||||
id="path4142"
|
||||
inkscape:connector-curvature="0" />
|
||||
<rect
|
||||
id="rect4168"
|
||||
mask="url(#a)"
|
||||
ry="0"
|
||||
height="0"
|
||||
width="0"
|
||||
x="141.51523"
|
||||
y="364.10403" />
|
||||
<rect
|
||||
id="rect4184"
|
||||
mask="url(#a)"
|
||||
ry="0"
|
||||
height="0"
|
||||
width="0"
|
||||
x="96.108383"
|
||||
y="352.01443" />
|
||||
<rect
|
||||
id="rect4200"
|
||||
mask="url(#a)"
|
||||
ry="2.1886277"
|
||||
height="0.14590852"
|
||||
width="0.17464182"
|
||||
x="87.014519"
|
||||
y="276.38696" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot4797"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
transform="matrix(0.1746417,0,0,0.1459084,499.69318,366.39614)"><flowRegion
|
||||
id="flowRegion4799"><rect
|
||||
id="rect4801"
|
||||
width="972.27185"
|
||||
height="618.71844"
|
||||
x="959.01355"
|
||||
y="-976.15039" /></flowRegion><flowPara
|
||||
id="flowPara4803" /></flowRoot> <g
|
||||
id="g4833"
|
||||
transform="matrix(0.09510056,0,0,0.09061765,496.09965,368.83934)">
|
||||
<rect
|
||||
id="rect4823"
|
||||
height="1"
|
||||
width="1"
|
||||
x="0"
|
||||
y="0"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4886"
|
||||
d="m 111.18463,862.06984 c -1.98231,0 -3.96282,0.78454 -5.54759,2.38445 L 88.200894,881.94537 75.123368,868.82653 c -3.169466,-3.18017 -7.92567,-3.18017 -11.095213,0 -3.169466,3.18017 -3.169466,7.95108 0,11.13109 l 13.077526,13.11885 -11.095212,10.73223 82.031621,81.49227 11.09525,-11.13109 13.87084,13.51554 c 1.57915,1.59105 3.56724,2.38445 5.54759,2.38445 1.98231,0 3.96285,-0.78453 5.54762,-2.38445 3.16947,-3.18017 3.16947,-7.95111 0,-11.13109 l -13.87083,-13.11884 17.43611,-17.09442 c 1.17983,-1.59105 1.98231,-3.5788 1.98231,-5.96329 0,-1.98351 -0.79863,-3.97554 -2.37816,-5.56446 l -70.54053,-70.35856 c -1.57914,-1.59105 -3.56724,-2.38446 -5.54758,-2.38446 z m 15.86949,20.75826 9.50139,9.5291 -4.04453,23.11372 23.04691,-4.05619 9.50138,9.5291 -36.8437,36.95052 -9.50135,-9.5291 21.13082,-21.19197 -23.04672,4.05619 4.04453,-23.11396 -21.131009,21.19198 -9.501383,-9.5291 36.843662,-36.95048 z"
|
||||
style="fill:#000000;fill-opacity:1" />
|
||||
<g
|
||||
transform="translate(234.63786,787.55486)"
|
||||
id="g4170" />
|
||||
<path
|
||||
id="path4225"
|
||||
transform="translate(0,860.36216)"
|
||||
d="M 75.009766 60.486328 L 34.648438 122.74414 C 33.793918 123.59769 37.647081 134.96384 37.052734 136.09766 L 4.0234375 177.69727 C 2.4142291 180.39677 3.2900484 182.21846 4.8730469 183.84766 C 5.9214414 184.93636 6.6591287 186.06887 8.3828125 185.67188 C 9.0750612 185.50987 10.104893 185.27338 10.875 184.76758 L 52.806641 151.81445 C 53.912466 151.23195 64.44071 154.77813 65.289062 153.92383 L 126.45312 111.46875 L 75.009766 60.486328 z M 89.632812 84.769531 L 103.77539 98.912109 L 79.027344 123.66016 L 86.238281 130.87109 L 48.621094 139.92383 L 57.435547 102.30859 L 64.884766 109.51758 L 89.632812 84.769531 z "
|
||||
style="fill:#000000" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
BIN
img/markdig128.png
Normal file
BIN
img/markdig128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
img/markdig64.png
Normal file
BIN
img/markdig64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
177
readme.md
177
readme.md
@@ -1,51 +1,71 @@
|
||||
# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/)
|
||||
# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
|
||||
Markdig is a fast, powerfull, [CommonMark](http://commonmark.org/) compliant, extensible Markdown processor for .NET.
|
||||
<img align="right" width="160px" height="160px" src="img/markdig.png">
|
||||
|
||||
Markdig is a fast, powerful, [CommonMark](http://commonmark.org/) compliant, extensible Markdown processor for .NET.
|
||||
|
||||
> NOTE: The repository is under construction. There will be a dedicated website and proper documentation at some point!
|
||||
|
||||
You can **try Markdig online** and compare it to other implementations on [babelmark3](https://babelmark.github.io/?text=Hello+**Markdig**!)
|
||||
|
||||
## Features
|
||||
|
||||
- **Very fast parser** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
|
||||
- **Abstract Syntax Tree**
|
||||
- **Very fast parser and html renderer** (no-regexp), very lightweight in terms of GC pressure. See benchmarks
|
||||
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
|
||||
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
|
||||
- Converter to **HTML**
|
||||
- Passing more than **600+ tests** from the latest [CommonMark specs](http://spec.commonmark.org/)
|
||||
- Passing more than **600+ tests** from the latest [CommonMark specs (0.28)](http://spec.commonmark.org/)
|
||||
- Includes all the core elements of CommonMark:
|
||||
- including GFM fenced code blocks.
|
||||
- including **GFM fenced code blocks**.
|
||||
- **Extensible** architecture
|
||||
- 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 **18+ extensions**, including:
|
||||
- Built-in with **20+ extensions**, including:
|
||||
- 2 kind of tables:
|
||||
- **Pipe tables** (inspired from Github tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
|
||||
- **Grid tables** (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
|
||||
- **Extra emphasis** (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
|
||||
- [**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 `~~`,
|
||||
- Subscript `~`
|
||||
- Superscript `^`
|
||||
- Inserted `++`
|
||||
- Marked `==`
|
||||
- **Special attributes** or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
|
||||
- **Definition lists** (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
|
||||
- **Footnotes** (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
|
||||
- **Auto-identifiers** for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
|
||||
- **Extra bullet lists**, supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
|
||||
- **Media support** for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
|
||||
- **Abbreviations** (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
|
||||
- **Citation** text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
|
||||
- **Custom containers** similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
|
||||
- **Figures** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
|
||||
- **Footers** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
|
||||
- **Mathematics**/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
|
||||
- **Soft lines as hard lines**
|
||||
- **Emoji** support (inspired from [Markdown-it](https://markdown-it.github.io/))
|
||||
- **SmartyPants** (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
|
||||
- **Bootstrap** class (to output bootstrap class)
|
||||
- [**Special attributes**](src/Markdig.Tests/Specs/GenericAttributesSpecs.md) or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
|
||||
- [**Definition lists**](src/Markdig.Tests/Specs/DefinitionListSpecs.md) (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
|
||||
- [**Footnotes**](src/Markdig.Tests/Specs/FootnotesSpecs.md) (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
|
||||
- [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
|
||||
- [**Auto-links**](src/Markdig.Tests/Specs/AutoLinks.md) generates links if a text starts with `http://` or `https://` or `ftp://` or `mailto:` or `www.xxx.yyy`
|
||||
- [**Task Lists**](src/Markdig.Tests/Specs/TaskListSpecs.md) inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments).
|
||||
- [**Extra bullet lists**](src/Markdig.Tests/Specs/ListExtraSpecs.md), supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
|
||||
- [**Media support**](src/Markdig.Tests/Specs/MediaSpecs.md) for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
|
||||
- [**Abbreviations**](src/Markdig.Tests/Specs/AbbreviationSpecs.md) (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
|
||||
- [**Citation**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
|
||||
- [**Custom containers**](src/Markdig.Tests/Specs/CustomContainerSpecs.md) similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
|
||||
- [**Figures**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
|
||||
- [**Footers**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
|
||||
- [**Mathematics**](src/Markdig.Tests/Specs/MathSpecs.md)/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
|
||||
- [**Soft lines as hard lines**](src/Markdig.Tests/Specs/HardlineBreakSpecs.md)
|
||||
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
|
||||
- [**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 only for [`mermaid` diagrams](https://knsv.github.io/mermaid/))
|
||||
- [**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)
|
||||
- [**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+`)
|
||||
|
||||
## Documentation
|
||||
|
||||
> The repository is under construction. There will be a dedicated website and proper documentation at some point!
|
||||
|
||||
While there is not yet a dedicated documentation, you can find from the [specs documentation](src/Markdig.Tests/Specs/readme.md) how to use these extensions.
|
||||
|
||||
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
|
||||
|
||||
## Download
|
||||
|
||||
Markdig is available as a NuGet package: [](https://www.nuget.org/packages/Markdig/)
|
||||
|
||||
Also [Markdig.Signed](https://www.nuget.org/packages/Markdig.Signed/) NuGet package provides signed assemblies.
|
||||
|
||||
## Usage
|
||||
|
||||
The main entry point for the API is the `Markdig.Markdown` class:
|
||||
@@ -57,7 +77,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 HarLine and SmartyPants)
|
||||
In order to activate most of all advanced extensions (except Emoji, SoftLine as HardLine, JiraLinks and SmartyPants)
|
||||
|
||||
```csharp
|
||||
// Configure the pipeline with all advanced extensions active
|
||||
@@ -67,6 +87,10 @@ var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
|
||||
|
||||
You can have a look at the [MarkdownExtensions](https://github.com/lunet-io/markdig/blob/master/src/Markdig/MarkdownExtensions.cs) that describes all actionable extensions (by modifying the MarkdownPipeline)
|
||||
|
||||
## Build
|
||||
|
||||
In order to build Markdig, you need to install [.NET Core RTM](https://www.microsoft.com/net/core)
|
||||
|
||||
## License
|
||||
|
||||
This software is released under the [BSD-Clause 2 license](https://github.com/lunet-io/markdig/blob/master/license.txt).
|
||||
@@ -76,24 +100,44 @@ This software is released under the [BSD-Clause 2 license](https://github.com/lu
|
||||
|
||||
This is an early preview of the benchmarking against various implementations:
|
||||
|
||||
- Markdig: itself
|
||||
- CommonMarkCpp: [cmark](https://github.com/jgm/cmark), Reference C implementation of CommonMark, no support for extensions
|
||||
- [CommonMark.NET](https://github.com/Knagis/CommonMark.NET): CommonMark implementation for .NET, no support for extensions, port of cmark
|
||||
- [CommonMarkNet (devel)](https://github.com/AMDL/CommonMark.NET/tree/pipe-tables): An evolution of CommonMark.NET, supports extensions, not released yet
|
||||
- [MarkdownDeep](https://github.com/toptensoftware/markdowndeep) another .NET implementation
|
||||
- [MarkdownSharp](https://github.com/Kiri-rin/markdownsharp): Open source C# implementation of Markdown processor, as featured on Stack Overflow, regexp based.
|
||||
- [Moonshine](https://github.com/brandonc/moonshine): popular C Markdown processor
|
||||
**C implementations**:
|
||||
|
||||
Markdig is roughly x100 times faster than MarkdownSharp and extremelly competitive to other implems (that are not feature wise comparable)
|
||||
- [cmark](https://github.com/jgm/cmark) (version: 0.25.0): Reference C implementation of CommonMark, no support for extensions
|
||||
- [Moonshine](https://github.com/brandonc/moonshine) (version: : popular C Markdown processor
|
||||
|
||||
Performance in x86:
|
||||
**.NET implementations**:
|
||||
|
||||
- [Markdig](https://github.com/lunet-io/markdig) (version: 0.5.x): itself
|
||||
- [CommonMark.NET(master)](https://github.com/Knagis/CommonMark.NET) (version: 0.11.0): CommonMark implementation for .NET, no support for extensions, port of cmark
|
||||
- [CommonMark.NET(pipe_tables)](https://github.com/AMDL/CommonMark.NET/tree/pipe-tables): An evolution of CommonMark.NET, supports extensions, not released yet
|
||||
- [MarkdownDeep](https://github.com/toptensoftware/markdowndeep) (version: 1.5.0): another .NET implementation
|
||||
- [MarkdownSharp](https://github.com/Kiri-rin/markdownsharp) (version: 1.13.0): Open source C# implementation of Markdown processor, as featured on Stack Overflow, regexp based.
|
||||
- [Marked.NET](https://github.com/T-Alex/MarkedNet) (version: 1.0.5) port of original [marked.js](https://github.com/chjj/marked) project
|
||||
- [Microsoft.DocAsCode.MarkdownLite](https://github.com/dotnet/docfx/tree/dev/src/Microsoft.DocAsCode.MarkdownLite) (version: 2.0.1) used by the [docfx](https://github.com/dotnet/docfx) project
|
||||
|
||||
**JavaScript/V8 implementations**:
|
||||
|
||||
- [Strike.V8](https://github.com/SimonCropp/Strike) (version: 1.5.0) [marked.js](https://github.com/chjj/marked) running in Google V8 (not .NET based)
|
||||
|
||||
### Analysis of the results:
|
||||
|
||||
- Markdig is roughly **x100 times faster than MarkdownSharp**, **30x times faster than docfx**
|
||||
- **Among the best in CPU**, Extremely competitive and often faster than other implementations (not feature wise equivalent)
|
||||
- **15% to 30% less allocations** and GC pressure
|
||||
|
||||
Because Marked.NET, MarkdownSharp and DocAsCode.MarkdownLite are way too slow, they are not included in the following charts:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
### Performance for x86:
|
||||
|
||||
```
|
||||
// * Summary *
|
||||
|
||||
BenchmarkDotNet-Dev=v0.9.6.0+
|
||||
BenchmarkDotNet-Dev=v0.9.7.0+
|
||||
OS=Microsoft Windows NT 6.2.9200.0
|
||||
Processor=Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, ProcessorCount=8
|
||||
Processor=Intel(R) Core(TM) i7-4770 CPU 3.40GHz, ProcessorCount=8
|
||||
Frequency=3319351 ticks, Resolution=301.2637 ns, Timer=TSC
|
||||
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE
|
||||
JitModules=clrjit-v4.6.1080.0
|
||||
@@ -101,26 +145,23 @@ JitModules=clrjit-v4.6.1080.0
|
||||
Type=Program Mode=SingleRun LaunchCount=2
|
||||
WarmupCount=2 TargetCount=10
|
||||
|
||||
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
|
||||
--------------------- |---------- |---------- |------- |------ |------- |------------------- |
|
||||
TestMarkdig | 5.4870 ms | 0.0158 ms | 193.00 | 12.00 | 84.00 | 1,425,192.72 |
|
||||
TestCommonMarkCpp | 4.0134 ms | 0.1008 ms | - | - | 180.00 | 454,859.74 |
|
||||
TestCommonMarkNet | 4.6139 ms | 0.0581 ms | 193.00 | 12.00 | 84.00 | 1,406,367.27 |
|
||||
TestCommonMarkNetNew | 5.5327 ms | 0.0461 ms | 193.00 | 96.00 | 84.00 | 1,738,465.42 |
|
||||
TestMarkdownDeep | 7.5910 ms | 0.1006 ms | 205.00 | 96.00 | 84.00 | 1,758,383.79 |
|
||||
TestMoonshine | 5.8843 ms | 0.1758 ms | - | - | 215.00 | 565,000.73 |
|
||||
|
||||
// * Diagnostic Output - MemoryDiagnoser *
|
||||
|
||||
|
||||
// ***** BenchmarkRunner: End *****
|
||||
Method | Median | StdDev |Scaled | Gen 0 | Gen 1| Gen 2|Bytes Allocated/Op |
|
||||
--------------------------- |------------ |---------- |------ | ------ |------|---------|------------------ |
|
||||
Markdig | 5.5316 ms | 0.0372 ms | 0.71 | 56.00| 21.00| 49.00| 1,285,917.31 |
|
||||
CommonMark.NET(master) | 4.7035 ms | 0.0422 ms | 0.60 | 113.00| 7.00| 49.00| 1,502,404.60 |
|
||||
CommonMark.NET(pipe_tables) | 5.6164 ms | 0.0298 ms | 0.72 | 111.00| 56.00| 49.00| 1,863,128.13 |
|
||||
MarkdownDeep | 7.8193 ms | 0.0334 ms | 1.00 | 120.00| 56.00| 49.00| 1,884,854.85 |
|
||||
cmark | 4.2698 ms | 0.1526 ms | 0.55 | -| -| -| NA |
|
||||
Moonshine | 6.0929 ms | 0.1053 ms | 1.28 | -| -| -| NA |
|
||||
Strike.V8 | 10.5895 ms | 0.0492 ms | 1.35 | -| -| -| NA |
|
||||
Marked.NET | 207.3169 ms | 5.2628 ms | 26.51 | 0.00| 0.00| 0.00| 303,125,228.65 |
|
||||
MarkdownSharp | 675.0185 ms | 2.8447 ms | 86.32 | 40.00| 27.00| 41.00| 2,413,394.17 |
|
||||
Microsoft DocfxMarkdownLite | 166.3357 ms | 0.4529 ms | 21.27 |4,452.00|948.00|11,167.00| 180,218,359.60 |
|
||||
```
|
||||
|
||||
Performance for x64:
|
||||
### Performance for x64:
|
||||
|
||||
```
|
||||
// * Summary *
|
||||
|
||||
BenchmarkDotNet-Dev=v0.9.6.0+
|
||||
OS=Microsoft Windows NT 6.2.9200.0
|
||||
Processor=Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, ProcessorCount=8
|
||||
@@ -133,25 +174,29 @@ WarmupCount=2 TargetCount=10
|
||||
|
||||
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
|
||||
--------------------- |---------- |---------- |------- |------- |------ |------------------- |
|
||||
TestMarkdig | 5.9539 ms | 0.0495 ms | 157.00 | 96.00 | 84.00 | 1,767,834.52 |
|
||||
TestCommonMarkNet | 4.3158 ms | 0.0161 ms | 157.00 | 96.00 | 84.00 | 1,747,432.06 |
|
||||
TestCommonMarkNetNew | 5.3421 ms | 0.0435 ms | 229.00 | 168.00 | 84.00 | 2,323,922.97 |
|
||||
TestMarkdownDeep | 7.4750 ms | 0.0281 ms | 318.00 | 186.00 | 84.00 | 2,576,728.69 |
|
||||
|
||||
// * Diagnostic Output - MemoryDiagnoser *
|
||||
|
||||
|
||||
// ***** BenchmarkRunner: End *****
|
||||
TestMarkdig | 5.5276 ms | 0.0402 ms | 109.00 | 96.00 | 84.00 | 1,537,027.66 |
|
||||
TestCommonMarkNet | 4.4661 ms | 0.1190 ms | 157.00 | 96.00 | 84.00 | 1,747,432.06 |
|
||||
TestCommonMarkNetNew | 5.3151 ms | 0.0815 ms | 229.00 | 168.00 | 84.00 | 2,323,922.97 |
|
||||
TestMarkdownDeep | 7.4076 ms | 0.0617 ms | 318.00 | 186.00 | 84.00 | 2,576,728.69 |
|
||||
```
|
||||
|
||||
## Donate
|
||||
|
||||
If you are using this library and find it useful for your project, please consider a donation for it!
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
|
||||
## Credits
|
||||
|
||||
Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard!
|
||||
|
||||
This project would not have been possible without this huge foundation.
|
||||
|
||||
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
|
||||
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
|
||||
|
||||
Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Helpers/EntityHelper.cs)) have been re-used from [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
|
||||
|
||||
Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
|
||||
## Author
|
||||
|
||||
Alexandre MUTEL aka [xoofx](http://xoofx.com)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Markdig.Benchmarks</RootNamespace>
|
||||
<AssemblyName>Markdig.Benchmarks</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(NuGetPackageRoot)' == ''">
|
||||
<NuGetPackageRoot>$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
</PropertyGroup>
|
||||
<ImportGroup>
|
||||
<Import Project="$(NuGetPackageRoot)\Microsoft.Diagnostics.Tracing.TraceEvent\1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('$(NuGetPackageRoot)\Microsoft.Diagnostics.Tracing.TraceEvent\1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -131,7 +131,7 @@ namespace Testamina.Markdig.Benchmarks
|
||||
var config = ManualConfig.Create(DefaultConfig.Instance);
|
||||
//var gcDiagnoser = new MemoryDiagnoser();
|
||||
//config.Add(new Job { Mode = Mode.SingleRun, LaunchCount = 2, WarmupCount = 2, IterationTime = 1024, TargetCount = 10 });
|
||||
config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
|
||||
//config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
|
||||
//config.Add(gcDiagnoser);
|
||||
|
||||
//var config = DefaultConfig.Instance;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"win-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {
|
||||
"net46": {
|
||||
"compilationOptions": {
|
||||
"define": [
|
||||
"CLASSIC"
|
||||
@@ -19,9 +19,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"BenchmarkDotNet": "0.9.6",
|
||||
"BenchmarkDotNet.Diagnostics.Windows": "0.9.6",
|
||||
"CommonMark.NET": "0.11.0",
|
||||
"BenchmarkDotNet": "0.10.6",
|
||||
"BenchmarkDotNet.Diagnostics.Windows": "0.10.6",
|
||||
"CommonMark.NET": "0.15.1",
|
||||
"MarkdownSharp": "1.13.0.0",
|
||||
"Microsoft.Diagnostics.Runtime": "0.8.31-beta",
|
||||
"Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41.0"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
@@ -38,10 +39,6 @@
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Markdig">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Markdig\Bin\$(Configuration)\net40\Markdig.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@@ -59,8 +56,14 @@
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Specs.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Specs\TestEmphasisPlus.cs" />
|
||||
<Compile Include="TestHtmlAttributes.cs" />
|
||||
<Compile Include="TestHtmlHelper.cs" />
|
||||
<Compile Include="TestLineReader.cs" />
|
||||
<Compile Include="TestLinkHelper.cs" />
|
||||
<Compile Include="TestOrderedList.cs" />
|
||||
<Compile Include="TestPragmaLines.cs" />
|
||||
<Compile Include="TestSourcePosition.cs" />
|
||||
<Compile Include="TestStringSliceList.cs" />
|
||||
<Compile Include="TestPlayParser.cs" />
|
||||
<Compile Include="TextAssert.cs" />
|
||||
@@ -69,6 +72,8 @@
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="project.json" />
|
||||
<None Include="Specs\JiraLinks.md" />
|
||||
<None Include="Specs\AutoLinks.md" />
|
||||
<None Include="Specs\AutoIdentifierSpecs.md" />
|
||||
<None Include="Specs\AbbreviationSpecs.md" />
|
||||
<None Include="Specs\FigureFooterAndCiteSpecs.md" />
|
||||
@@ -81,6 +86,11 @@
|
||||
<None Include="Specs\GridTableSpecs.md" />
|
||||
<None Include="Specs\HardlineBreakSpecs.md" />
|
||||
<None Include="Specs\BootstrapSpecs.md" />
|
||||
<None Include="Specs\DiagramsSpecs.md" />
|
||||
<None Include="Specs\NoHtmlSpecs.md" />
|
||||
<None Include="Specs\readme.md" />
|
||||
<None Include="Specs\YamlSpecs.md" />
|
||||
<None Include="Specs\TaskListSpecs.md" />
|
||||
<None Include="Specs\SmartyPantsSpecs.md" />
|
||||
<None Include="Specs\MediaSpecs.md" />
|
||||
<None Include="Specs\MathSpecs.md" />
|
||||
@@ -95,6 +105,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</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" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
||||
@@ -45,3 +45,56 @@ This is a 😃 HTML document
|
||||
.
|
||||
<p>This is a <abbr title="Hypertext Markup Language">😃 HTML</abbr> document</p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations may be similar:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[1A]: First
|
||||
*[1A1]: Second
|
||||
*[1A2]: Third
|
||||
|
||||
We can abbreviate 1A, 1A1 and 1A2!
|
||||
.
|
||||
<p>We can abbreviate <abbr title="First">1A</abbr>, <abbr title="Second">1A1</abbr> and <abbr title="Third">1A2</abbr>!</p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations should match whole word only:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[1A]: First
|
||||
|
||||
We should not abbreviate 1.1A or 11A!
|
||||
.
|
||||
<p>We should not abbreviate 1.1A or 11A!</p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations should match whole word only, even if the word is the entire content:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[1A]: First
|
||||
|
||||
1.1A
|
||||
.
|
||||
<p>1.1A</p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations should match whole word only, even if there is another glossary term:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[SCO]: First
|
||||
*[SCOM]: Second
|
||||
|
||||
SCOM
|
||||
.
|
||||
<p><abbr title="Second">SCOM</abbr></p>
|
||||
````````````````````````````````
|
||||
|
||||
Abbreviations should only match when surrounded by whitespace:
|
||||
|
||||
```````````````````````````````` example
|
||||
*[PR]: Pull Request
|
||||
|
||||
PRAA
|
||||
.
|
||||
<p>PRAA</p>
|
||||
````````````````````````````````
|
||||
78
src/Markdig.Tests/Specs/AutoLinks.md
Normal file
78
src/Markdig.Tests/Specs/AutoLinks.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Extensions
|
||||
|
||||
This section describes the different extensions supported:
|
||||
|
||||
## AutoLinks
|
||||
|
||||
Autolinks will format as a HTML link any string that starts by:
|
||||
|
||||
- `http://` or `https://`
|
||||
- `ftp://`
|
||||
- `mailto:`
|
||||
- `www.`
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a http://www.google.com URL and https://www.google.com
|
||||
This is a ftp://test.com
|
||||
And a mailto:email@toto.com
|
||||
And a plain www.google.com
|
||||
.
|
||||
<p>This is a <a href="http://www.google.com">http://www.google.com</a> URL and <a href="https://www.google.com">https://www.google.com</a>
|
||||
This is a <a href="ftp://test.com">ftp://test.com</a>
|
||||
And a <a href="mailto:email@toto.com">mailto:email@toto.com</a>
|
||||
And a plain <a href="http://www.google.com">www.google.com</a></p>
|
||||
````````````````````````````````
|
||||
|
||||
But incomplete links will not be matched:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not a http:/www.google.com URL and https:/www.google.com
|
||||
This is not a ftp:/test.com
|
||||
And not a mailto:emailtoto.com
|
||||
And not a plain www. or a www.x
|
||||
.
|
||||
<p>This is not a http:/www.google.com URL and https:/www.google.com
|
||||
This is not a ftp:/test.com
|
||||
And not a mailto:emailtoto.com
|
||||
And not a plain www. or a www.x</p>
|
||||
````````````````````````````````
|
||||
|
||||
Previous character must be a punctuation or a valid space (tab, space, new line):
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not a nhttp://www.google.com URL but this is (https://www.google.com)
|
||||
.
|
||||
<p>This is not a nhttp://www.google.com URL but this is (<a href="https://www.google.com">https://www.google.com</a>)</p>
|
||||
````````````````````````````````
|
||||
|
||||
An autolink should not interfere with an `<a>` HTML inline:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is an HTML <a href="http://www.google.com">http://www.google.com</a> link
|
||||
.
|
||||
<p>This is an HTML <a href="http://www.google.com">http://www.google.com</a> link</p>
|
||||
````````````````````````````````
|
||||
or even within emphasis:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is an HTML <a href="http://www.google.com"> **http://www.google.com** </a> link
|
||||
.
|
||||
<p>This is an HTML <a href="http://www.google.com"> <strong>http://www.google.com</strong> </a> link</p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
An autolink should not interfere with a markdown link:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is an HTML [http://www.google.com](http://www.google.com) link
|
||||
.
|
||||
<p>This is an HTML <a href="http://www.google.com">http://www.google.com</a> link</p>
|
||||
````````````````````````````````
|
||||
|
||||
A link embraced by pending emphasis should let the emphasis takes precedence if characters are placed at the end of the matched link:
|
||||
|
||||
```````````````````````````````` example
|
||||
Check **http://www.a.com** or __http://www.b.com__
|
||||
.
|
||||
<p>Check <strong><a href="http://www.a.com">http://www.a.com</a></strong> or <strong><a href="http://www.b.com">http://www.b.com</a></strong></p>
|
||||
````````````````````````````````
|
||||
@@ -105,3 +105,28 @@ Term 1
|
||||
<pre><code>: Not valid
|
||||
</code></pre>
|
||||
````````````````````````````````
|
||||
|
||||
Definition lists can be nested inside list items
|
||||
|
||||
```````````````````````````````` example
|
||||
1. First
|
||||
|
||||
2. Second
|
||||
|
||||
Term 1
|
||||
: Definition
|
||||
|
||||
Term 2
|
||||
: Second Definition
|
||||
.
|
||||
<ol>
|
||||
<li><p>First</p></li>
|
||||
<li><p>Second</p>
|
||||
<dl>
|
||||
<dt>Term 1</dt>
|
||||
<dd>Definition</dd>
|
||||
<dt>Term 2</dt>
|
||||
<dd>Second Definition</dd>
|
||||
</dl></li>
|
||||
</ol>
|
||||
````````````````````````````````
|
||||
26
src/Markdig.Tests/Specs/DiagramsSpecs.md
Normal file
26
src/Markdig.Tests/Specs/DiagramsSpecs.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Extensions
|
||||
|
||||
Adds support for diagrams extension:
|
||||
|
||||
## Mermaid diagrams
|
||||
|
||||
Using a fenced code block with the `mermaid` language info will output a `<div class='mermaid'>` instead of a `pre/code` block:
|
||||
|
||||
```````````````````````````````` example
|
||||
```mermaid
|
||||
graph TD;
|
||||
A-->B;
|
||||
A-->C;
|
||||
B-->D;
|
||||
C-->D;
|
||||
```
|
||||
.
|
||||
<div class="mermaid">graph TD;
|
||||
A-->B;
|
||||
A-->C;
|
||||
B-->D;
|
||||
C-->D;
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
TODO: Add other text diagram languages
|
||||
@@ -11,3 +11,29 @@ This is a test with a :) and a :angry: smiley
|
||||
.
|
||||
<p>This is a test with a 😃 and a 😠 smiley</p>
|
||||
````````````````````````````````
|
||||
|
||||
An emoji needs to be preceded by a space:
|
||||
|
||||
```````````````````````````````` example
|
||||
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 ponctuation (or any other characters):
|
||||
|
||||
```````````````````````````````` example
|
||||
We all need :), it makes us :muscle:. (and :ok_hand:).
|
||||
.
|
||||
<p>We all need 😃, it makes us 💪. (and 👌).</p>
|
||||
````````````````````````````````
|
||||
|
||||
Sentences can end with Emoji:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a sentance :ok_hand:
|
||||
and keeps going to the next line :)
|
||||
.
|
||||
<p>This is a sentance 👌
|
||||
and keeps going to the next line 😃</p>
|
||||
````````````````````````````````
|
||||
@@ -21,6 +21,17 @@ H~2~O is a liquid. 2^10^ is 1024
|
||||
.
|
||||
<p>H<sub>2</sub>O is a liquid. 2<sup>10</sup> is 1024</p>
|
||||
````````````````````````````````
|
||||
|
||||
Certain punctuation characters are exempted from the rule forbidding them within inline delimiters
|
||||
|
||||
```````````````````````````````` example
|
||||
One quintillionth can be expressed as 10^-18^
|
||||
|
||||
Daggers^†^ and double-daggers^‡^ can be used to denote notes.
|
||||
.
|
||||
<p>One quintillionth can be expressed as 10<sup>-18</sup></p>
|
||||
<p>Daggers<sup>†</sup> and double-daggers<sup>‡</sup> can be used to denote notes.</p>
|
||||
````````````````````````````````
|
||||
|
||||
## Inserted
|
||||
|
||||
@@ -41,3 +52,16 @@ Marked text can be used to specify that a text has been marked in a document. T
|
||||
.
|
||||
<p><mark>Marked text</mark></p>
|
||||
````````````````````````````````
|
||||
## Emphasis on Html Entities
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
This is text MyBrand ^®^ and MyTrademark ^™^
|
||||
This is text MyBrand^®^ and MyTrademark^™^
|
||||
This is text MyBrand~®~ and MyCopyright^©^
|
||||
.
|
||||
<p>This is text MyBrand <sup>®</sup> and MyTrademark <sup>TM</sup>
|
||||
This is text MyBrand<sup>®</sup> and MyTrademark<sup>TM</sup>
|
||||
This is text MyBrand<sub>®</sub> and MyCopyright<sup>©</sup></p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
@@ -61,3 +61,60 @@ multi-paragraph list items.<a href="#fnref:3" class="footnote-back-ref">↩<
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
Check with mulitple consecutive footnotes:
|
||||
|
||||
```````````````````````````````` example
|
||||
Here is a footnote[^1]. And another one[^2]. And a third one[^3]. And a fourth[^4].
|
||||
|
||||
[^1]: Footnote 1 text
|
||||
|
||||
[^2]: Footnote 2 text
|
||||
|
||||
a
|
||||
|
||||
[^3]: Footnote 3 text
|
||||
|
||||
[^4]: Footnote 4 text
|
||||
.
|
||||
<p>Here is a footnote<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. And another one<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>. And a third one<a id="fnref:3" href="#fn:3" class="footnote-ref"><sup>3</sup></a>. And a fourth<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a>.</p>
|
||||
<p>a</p>
|
||||
<div class="footnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn:1">
|
||||
<p>Footnote 1 text<a href="#fnref:1" class="footnote-back-ref">↩</a></p></li>
|
||||
<li id="fn:2">
|
||||
<p>Footnote 2 text<a href="#fnref:2" class="footnote-back-ref">↩</a></p></li>
|
||||
<li id="fn:3">
|
||||
<p>Footnote 3 text<a href="#fnref:3" class="footnote-back-ref">↩</a></p></li>
|
||||
<li id="fn:4">
|
||||
<p>Footnote 4 text<a href="#fnref:4" class="footnote-back-ref">↩</a></p></li>
|
||||
</ol>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
Another test with consecutive footnotes without a blank line separator:
|
||||
|
||||
```````````````````````````````` example
|
||||
Here is a footnote[^1]. And another one[^2]. And a third one[^3]. And a fourth[^4].
|
||||
|
||||
[^1]: Footnote 1 text
|
||||
[^2]: Footnote 2 text
|
||||
[^3]: Footnote 3 text
|
||||
[^4]: Footnote 4 text
|
||||
.
|
||||
<p>Here is a footnote<a id="fnref:1" href="#fn:1" class="footnote-ref"><sup>1</sup></a>. And another one<a id="fnref:2" href="#fn:2" class="footnote-ref"><sup>2</sup></a>. And a third one<a id="fnref:3" href="#fn:3" class="footnote-ref"><sup>3</sup></a>. And a fourth<a id="fnref:4" href="#fn:4" class="footnote-ref"><sup>4</sup></a>.</p>
|
||||
<div class="footnotes">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn:1">
|
||||
<p>Footnote 1 text<a href="#fnref:1" class="footnote-back-ref">↩</a></p></li>
|
||||
<li id="fn:2">
|
||||
<p>Footnote 2 text<a href="#fnref:2" class="footnote-back-ref">↩</a></p></li>
|
||||
<li id="fn:3">
|
||||
<p>Footnote 3 text<a href="#fnref:3" class="footnote-back-ref">↩</a></p></li>
|
||||
<li id="fn:4">
|
||||
<p>Footnote 4 text<a href="#fnref:4" class="footnote-back-ref">↩</a></p></li>
|
||||
</ol>
|
||||
</div>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -27,13 +27,13 @@ The following shows that attributes is attached to the current block or the prev
|
||||
This is a heading{#heading-link2}
|
||||
-----------------
|
||||
|
||||
This is a paragraph with an attached attributes {#myparagraph attached-bool-property}
|
||||
This is a paragraph with an attached attributes {#myparagraph attached-bool-property attached-bool-property2}
|
||||
.
|
||||
<h1 id="heading-link">This is a heading with an an attribute</h1>
|
||||
<h1 id="heading-link2">This is a heading</h1>
|
||||
<p><a href="http://google.com" id="a-link" class="myclass" data-lang="fr" data-value="This is a value">This is a link</a></p>
|
||||
<h2 id="heading-link2">This is a heading</h2>
|
||||
<p id="myparagraph" attached-bool-property>This is a paragraph with an attached attributes </p>
|
||||
<p id="myparagraph" attached-bool-property attached-bool-property2>This is a paragraph with an attached attributes </p>
|
||||
````````````````````````````````
|
||||
|
||||
The following shows that attributes can be attached to the next block if they are used inside a single line just preceding the block (and preceded by a blank line or beginning of a block container):
|
||||
|
||||
@@ -70,8 +70,8 @@ A regular row can continue a previous regular row when column separator `|` are
|
||||
+---------+---------+---------+
|
||||
| Col1 | Col2 | Col3 |
|
||||
| Col1a | Col2a | Col3a |
|
||||
| Col12 | Col3b |
|
||||
| Col123 |
|
||||
| Col1b | Col3b |
|
||||
| Col1c |
|
||||
.
|
||||
<table>
|
||||
<col style="width:33.33%">
|
||||
@@ -87,11 +87,11 @@ Col2a</td>
|
||||
Col3a</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">Col12</td>
|
||||
<td></td>
|
||||
<td colspan="2">Col1b</td>
|
||||
<td>Col3b</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">Col123</td>
|
||||
<td colspan="3">Col1c</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -184,3 +184,103 @@ Alignment might be specified on the first row using the character `:`:
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
A grid table may have cells spanning both columns and rows:
|
||||
|
||||
```````````````````````````````` example
|
||||
+---+---+---+
|
||||
| AAAAA | B |
|
||||
+---+---+ B +
|
||||
| D | E | B |
|
||||
+ D +---+---+
|
||||
| D | CCCCC |
|
||||
+---+---+---+
|
||||
.
|
||||
<table>
|
||||
<col style="width:33.33%">
|
||||
<col style="width:33.33%">
|
||||
<col style="width:33.33%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">AAAAA</td>
|
||||
<td rowspan="2">B
|
||||
B
|
||||
B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">D
|
||||
D
|
||||
D</td>
|
||||
<td>E</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">CCCCC</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
A grid table may have cells with both colspan and rowspan:
|
||||
|
||||
```````````````````````````````` example
|
||||
+---+---+---+
|
||||
| AAAAA | B |
|
||||
+ AAAAA +---+
|
||||
| AAAAA | C |
|
||||
+---+---+---+
|
||||
| D | E | F |
|
||||
+---+---+---+
|
||||
.
|
||||
<table>
|
||||
<col style="width:33.33%">
|
||||
<col style="width:33.33%">
|
||||
<col style="width:33.33%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2" rowspan="2">AAAAA
|
||||
AAAAA
|
||||
AAAAA</td>
|
||||
<td>B</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D</td>
|
||||
<td>E</td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
A grid table may not have irregularly shaped cells:
|
||||
|
||||
```````````````````````````````` example
|
||||
+---+---+---+
|
||||
| AAAAA | B |
|
||||
+ A +---+ B +
|
||||
| A | C | B |
|
||||
+---+---+---+
|
||||
| DDDDD | E |
|
||||
+---+---+---+
|
||||
.
|
||||
<p>+---+---+---+
|
||||
| AAAAA | B |
|
||||
+ A +---+ B +
|
||||
| A | C | B |
|
||||
+---+---+---+
|
||||
| DDDDD | E |
|
||||
+---+---+---+</p>
|
||||
````````````````````````````````
|
||||
|
||||
An empty `+` on a line should result in a simple empty list output:
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
+
|
||||
.
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
````````````````````````````````
|
||||
|
||||
77
src/Markdig.Tests/Specs/JiraLinks.md
Normal file
77
src/Markdig.Tests/Specs/JiraLinks.md
Normal file
@@ -0,0 +1,77 @@
|
||||
## Jira Links
|
||||
|
||||
The JiraLinks extension will automatically add links to JIRA issue items within your markdown, e.g. XX-1234. For this to happen, you must configure the extension when adding to the pipeline, e.g.
|
||||
|
||||
```
|
||||
var pipeline = new MarkdownPipelineBuilder()
|
||||
.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"))
|
||||
.Build();
|
||||
```
|
||||
|
||||
The rules for detecting a link are:
|
||||
|
||||
- The project key must be composed of onre or more capitalised ASCII letter `[A-Z]+`
|
||||
- A single hypen `-` must separate the project key and issue number.
|
||||
- The issue number is composed of 1 or more digits `[0, 9]+`
|
||||
- The reference must be preceeded by either `(` or whitespace or EOF.
|
||||
- The reference must be followed by either `)` or whitespace or EOF.
|
||||
|
||||
The following are valid examples:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a ABCD-123 issue
|
||||
.
|
||||
<p>This is a <a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a> issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a KIRA-1 issue
|
||||
.
|
||||
<p>This is a <a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a> issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a Z-1 issue
|
||||
.
|
||||
<p>This is a <a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a> issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
These are also valid links with `(` and `)`:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a (ABCD-123) issue
|
||||
.
|
||||
<p>This is a (<a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a>) issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a (KIRA-1) issue
|
||||
.
|
||||
<p>This is a (<a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a>) issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a (Z-1) issue
|
||||
.
|
||||
<p>This is a (<a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a>) issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
These are not valid links:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not aJIRA-123 issue
|
||||
.
|
||||
<p>This is not aJIRA-123 issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not JIRA-123a issue
|
||||
.
|
||||
<p>This is not JIRA-123a issue</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not JIRA- issue
|
||||
.
|
||||
<p>This is not JIRA- issue</p>
|
||||
````````````````````````````````
|
||||
@@ -38,7 +38,7 @@ Like for numbered list, a list can start with a different letter
|
||||
b. First item
|
||||
c. Second item
|
||||
.
|
||||
<ol type="a" start="b">
|
||||
<ol type="a" start="2">
|
||||
<li>First item</li>
|
||||
<li>Second item</li>
|
||||
</ol>
|
||||
@@ -100,8 +100,26 @@ Like for numbered list, a list can start with a different letter
|
||||
ii. First item
|
||||
iii. Second item
|
||||
.
|
||||
<ol type="i" start="ii">
|
||||
<ol type="i" start="2">
|
||||
<li>First item</li>
|
||||
<li>Second item</li>
|
||||
</ol>
|
||||
````````````````````````````````
|
||||
|
||||
Lists can be restarted, specifying the start point.
|
||||
|
||||
```````````````````````````````` example
|
||||
1. First item
|
||||
|
||||
Some text
|
||||
|
||||
2. Second item
|
||||
.
|
||||
<ol>
|
||||
<li>First item</li>
|
||||
</ol>
|
||||
<p>Some text</p>
|
||||
<ol start="2">
|
||||
<li>Second item</li>
|
||||
</ol>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -20,12 +20,47 @@ This is a $$math block$$
|
||||
<p>This is a <span class="math">math block</span></p>
|
||||
````````````````````````````````
|
||||
|
||||
The opening `$` and closing `$` is following the rules of the emphasis delimiter `_`:
|
||||
Newlines inside an inline math are not allowed:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not a $ math block $
|
||||
This is not a $$math
|
||||
block$$ and? this is a $$math block$$
|
||||
.
|
||||
<p>This is not a $ math block $</p>
|
||||
<p>This is not a $$math
|
||||
block$$ and? this is a <span class="math">math block</span></p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is not a $math
|
||||
block$ and? this is a $math block$
|
||||
.
|
||||
<p>This is not a $math
|
||||
block$ and? this is a <span class="math">math block</span></p>
|
||||
````````````````````````````````
|
||||
An opening `$` can be followed by a space if the closing is also preceded by a space `$`:
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a $ math block $
|
||||
.
|
||||
<p>This is a <span class="math">math block</span></p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a $ math block $ after
|
||||
.
|
||||
<p>This is a <span class="math">math block</span> after</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a $$ math block $$ after
|
||||
.
|
||||
<p>This is a <span class="math">math block</span> after</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a not $ math block$ because there is not a whitespace before the closing
|
||||
.
|
||||
<p>This is a not $ math block$ because there is not a whitespace before the closing</p>
|
||||
````````````````````````````````
|
||||
|
||||
For the opening `$` it requires a space or a punctuation before (but cannot be used within a word):
|
||||
@@ -68,6 +103,13 @@ This is a $$$math block$$$
|
||||
<p>This is a <span class="math">$math block$</span></p>
|
||||
````````````````````````````````
|
||||
|
||||
Regular text can come both before and after the math inline
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a $math block$ with text on both sides.
|
||||
.
|
||||
<p>This is a <span class="math">math block</span> with text on both sides.</p>
|
||||
````````````````````````````````
|
||||
A mathematic block takes precedence over standard emphasis `*` `_`:
|
||||
|
||||
```````````````````````````````` example
|
||||
@@ -75,6 +117,13 @@ This is *a $math* block$
|
||||
.
|
||||
<p>This is *a <span class="math">math* block</span></p>
|
||||
````````````````````````````````
|
||||
An opening $$ at the beginning of a line should not be interpreted as a Math block:
|
||||
|
||||
```````````````````````````````` example
|
||||
$$ math $$ starting at a line
|
||||
.
|
||||
<p><span class="math">math</span> starting at a line</p>
|
||||
````````````````````````````````
|
||||
|
||||
## Math Block
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ Allows to embed audio/video links to popular website:
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
.
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><video width="500" height="281" controls><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
|
||||
````````````````````````````````
|
||||
27
src/Markdig.Tests/Specs/NoHtmlSpecs.md
Normal file
27
src/Markdig.Tests/Specs/NoHtmlSpecs.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Extensions
|
||||
|
||||
## NoHTML
|
||||
|
||||
The extension DisableHtml allows to disable the parsing of HTML:
|
||||
|
||||
For inline HTML:
|
||||
|
||||
```````````````````````````````` example
|
||||
this is some text</td></tr>
|
||||
.
|
||||
<p>this is some text</td></tr></p>
|
||||
````````````````````````````````
|
||||
|
||||
For Block HTML:
|
||||
|
||||
```````````````````````````````` example
|
||||
<div>
|
||||
this is some text
|
||||
</div>
|
||||
.
|
||||
<p><div>
|
||||
this is some text
|
||||
</div></p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
@@ -38,18 +38,27 @@ a | b
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
While the following would be considered as a plain paragraph with a list item:
|
||||
The following is also considered as a table, even if the second line starts like a list:
|
||||
|
||||
```````````````````````````````` example
|
||||
a | b
|
||||
- | -
|
||||
0 | 1
|
||||
.
|
||||
<p>a | b</p>
|
||||
<ul>
|
||||
<li>| -
|
||||
0 | 1</li>
|
||||
</ul>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
A pipe table with only one header row is allowed:
|
||||
@@ -113,7 +122,7 @@ c no d
|
||||
c no d</p>
|
||||
````````````````````````````````
|
||||
|
||||
The number of columns in the first row determine the number of columns for the whole table. Any extra columns delimiter `|` for sub-sequent lines are converted to literal strings instead:
|
||||
If a row contains more column than the header row, it will still be added as a column:
|
||||
|
||||
```````````````````````````````` example
|
||||
a | b
|
||||
@@ -127,19 +136,24 @@ a | b
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1 | 2</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -205,6 +219,76 @@ Column delimiters `|` at the very beginning of a line or just before a line endi
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
A pipe may be present at both the beginning/ending of each line:
|
||||
|
||||
```````````````````````````````` example
|
||||
|a|b|
|
||||
|-|-|
|
||||
|0|1|
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
Or may be ommitted on one side:
|
||||
|
||||
```````````````````````````````` example
|
||||
a|b|
|
||||
-|-|
|
||||
0|1|
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
|a|b
|
||||
|-|-
|
||||
|0|1
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
|
||||
Single column table can be declared with lines starting only by a column delimiter:
|
||||
|
||||
```````````````````````````````` example
|
||||
@@ -266,7 +350,8 @@ The first row is considered as a **header row** if it is separated from the regu
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
The text alignment is defined by default to be left.
|
||||
The text alignment is defined by default to be center for header and left for cells. If the left alignment is applied, it will force the column heading to be left aligned.
|
||||
There is no way to define a different alignment for heading and cells (apart from the default).
|
||||
The text alignment can be changed by using the character `:` with the header column separator:
|
||||
|
||||
```````````````````````````````` example
|
||||
@@ -278,19 +363,19 @@ The text alignment can be changed by using the character `:` with the header col
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th style="text-align: left;">a</th>
|
||||
<th style="text-align: center;">b</th>
|
||||
<th style="text-align: right;">c</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td style="text-align: left;">0</td>
|
||||
<td style="text-align: center;">1</td>
|
||||
<td style="text-align: right;">2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td style="text-align: left;">3</td>
|
||||
<td style="text-align: center;">4</td>
|
||||
<td style="text-align: right;">5</td>
|
||||
</tr>
|
||||
@@ -298,6 +383,31 @@ The text alignment can be changed by using the character `:` with the header col
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
Test alignment with starting and ending pipes:
|
||||
|
||||
```````````````````````````````` example
|
||||
| abc | def | ghi |
|
||||
|:---:|-----|----:|
|
||||
| 1 | 2 | 3 |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: center;">abc</th>
|
||||
<th>def</th>
|
||||
<th style="text-align: right;">ghi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: center;">1</td>
|
||||
<td>2</td>
|
||||
<td style="text-align: right;">3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
The following example shows a non matching header column separator:
|
||||
|
||||
```````````````````````````````` example
|
||||
@@ -403,3 +513,70 @@ a | b
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
** Tests **
|
||||
|
||||
Tests trailing spaces after pipes
|
||||
|
||||
```````````````````````````````` example
|
||||
| abc | def |
|
||||
|---|---|
|
||||
| cde| ddd|
|
||||
| eee| fff|
|
||||
| fff | fffff |
|
||||
|gggg | ffff |
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>cde</td>
|
||||
<td>ddd</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>eee</td>
|
||||
<td>fff</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>fff</td>
|
||||
<td>fffff</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>gggg</td>
|
||||
<td>ffff</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
** Normalized columns count **
|
||||
|
||||
The tables are normalized to the maximum number of columns found in a table
|
||||
|
||||
|
||||
```````````````````````````````` example
|
||||
a | b
|
||||
-- | -
|
||||
0 | 1 | 2
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
@@ -83,29 +83,6 @@ They are' not matching 'quotes
|
||||
.
|
||||
<p>They are' not matching 'quotes</p>
|
||||
````````````````````````````````
|
||||
|
||||
Double quotes using ``` `` ``` are working if they match another `''` pair, and there is no other double quotes on the line (otherwise they would be parsed as a code span):
|
||||
|
||||
```````````````````````````````` example
|
||||
This is ``a double quote''
|
||||
.
|
||||
<p>This is “a double quote”</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is ``a code span''``
|
||||
.
|
||||
<p>This is <code>a code span''</code></p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
hello ``there```
|
||||
test
|
||||
.
|
||||
<p>hello “there”`
|
||||
test</p>
|
||||
````````````````````````````````
|
||||
|
||||
An emphasis starting inside left/right quotes will span over the right quote:
|
||||
|
||||
```````````````````````````````` example
|
||||
@@ -133,3 +110,36 @@ This is a en ellipsis...
|
||||
.
|
||||
<p>This is a en ellipsis…</p>
|
||||
````````````````````````````````
|
||||
|
||||
Check that a smartypants are not breaking pipetable parsing:
|
||||
|
||||
```````````````````````````````` example
|
||||
a | b
|
||||
-- | --
|
||||
0 | 1
|
||||
.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
````````````````````````````````
|
||||
|
||||
Check quotes and dash:
|
||||
|
||||
```````````````````````````````` example
|
||||
A "quote" with a ---
|
||||
.
|
||||
<p>A “quote” with a —</p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,24 +38,30 @@ SOFTWARE.
|
||||
<#@ import namespace="System.CodeDom.Compiler" #>
|
||||
<#@ output extension=".cs" #><#
|
||||
var specFiles = new KeyValuePair<string, string>[] {
|
||||
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/master/spec.txt", string.Empty),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("PipeTableSpecs.md"), "pipetables"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FootnotesSpecs.md"), "footnotes"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("GenericAttributesSpecs.md"), "attributes"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("EmphasisExtraSpecs.md"), "emphasisextra"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("HardlineBreakSpecs.md"), "hardlinebreak"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("GridTableSpecs.md"), "gridtables"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("CustomContainerSpecs.md"), "customcontainers+attributes"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("DefinitionListSpecs.md"), "definitionlists+attributes"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("EmojiSpecs.md"), "emojis"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AbbreviationSpecs.md"), "abbreviations"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("ListExtraSpecs.md"), "listextra"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+cites"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "math"),
|
||||
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/4ec06917c3a3632be4a935ffa0973092bd2621be/spec.txt", string.Empty), // 0.28
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("PipeTableSpecs.md"), "pipetables|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FootnotesSpecs.md"), "footnotes|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("GenericAttributesSpecs.md"), "attributes|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("EmphasisExtraSpecs.md"), "emphasisextras|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("HardlineBreakSpecs.md"), "hardlinebreak|advanced+hardlinebreak"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("GridTableSpecs.md"), "gridtables|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("CustomContainerSpecs.md"), "customcontainers+attributes|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("DefinitionListSpecs.md"), "definitionlists+attributes|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("EmojiSpecs.md"), "emojis|advanced+emojis"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AbbreviationSpecs.md"), "abbreviations|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("ListExtraSpecs.md"), "listextras|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+citations|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "mathematics|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("BootstrapSpecs.md"), "bootstrap+pipetables+figures+attributes"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("MediaSpecs.md"), "medias"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("SmartyPantsSpecs.md"), "smartypants"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AutoIdentifierSpecs.md"), "autoidentifiers"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("MediaSpecs.md"), "medialinks|advanced+medialinks"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("SmartyPantsSpecs.md"), "pipetables+smartypants|advanced+smartypants"), // Check with smartypants to make sure that it doesn't break pipetables
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AutoIdentifierSpecs.md"), "autoidentifiers|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("TaskListSpecs.md"), "tasklists|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("DiagramsSpecs.md"), "diagrams|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("NoHtmlSpecs.md"), "nohtml"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("YamlSpecs.md"), "yaml"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AutoLinks.md"), "autolinks|advanced"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("JiraLinks.md"), "jiralinks"),
|
||||
};
|
||||
var emptyLines = false;
|
||||
var displayEmptyLines = false;
|
||||
|
||||
29
src/Markdig.Tests/Specs/TaskListSpecs.md
Normal file
29
src/Markdig.Tests/Specs/TaskListSpecs.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Extensions
|
||||
|
||||
Adds support for task lists:
|
||||
|
||||
## TaskLists
|
||||
|
||||
A task list item consist of `[ ]` or `[x]` or `[X]` inside a list item (ordered or unordered)
|
||||
|
||||
```````````````````````````````` example
|
||||
- [ ] Item1
|
||||
- [x] Item2
|
||||
- [ ] Item3
|
||||
- Item4
|
||||
.
|
||||
<ul class="contains-task-list">
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item1</li>
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
|
||||
<li class="task-list-item"><input disabled="disabled" type="checkbox" /> Item3</li>
|
||||
<li>Item4</li>
|
||||
</ul>
|
||||
````````````````````````````````
|
||||
|
||||
A task is not recognized outside a list item:
|
||||
|
||||
```````````````````````````````` example
|
||||
[ ] This is not a task list
|
||||
.
|
||||
<p>[ ] This is not a task list</p>
|
||||
````````````````````````````````
|
||||
25
src/Markdig.Tests/Specs/TestEmphasisPlus.cs
Normal file
25
src/Markdig.Tests/Specs/TestEmphasisPlus.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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 NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestEmphasisPlus
|
||||
{
|
||||
[Test]
|
||||
public void StrongNormal()
|
||||
{
|
||||
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalStrongNormal()
|
||||
{
|
||||
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/Markdig.Tests/Specs/YamlSpecs.md
Normal file
103
src/Markdig.Tests/Specs/YamlSpecs.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Extensions
|
||||
|
||||
Adds support for YAML frontmatter parsing:
|
||||
|
||||
## YAML frontmatter discard
|
||||
|
||||
If a frontmatter is present, it will not be rendered:
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
---
|
||||
This is a text
|
||||
.
|
||||
<p>This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
But if a frontmatter doesn't happen on the first line, it will be parse as regular Markdown content
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a text1
|
||||
---
|
||||
this: is a frontmatter
|
||||
---
|
||||
This is a text2
|
||||
.
|
||||
<h2>This is a text1</h2>
|
||||
<h2>this: is a frontmatter</h2>
|
||||
<p>This is a text2</p>
|
||||
````````````````````````````````
|
||||
|
||||
It expects an exact 3 dashes `---`:
|
||||
|
||||
```````````````````````````````` example
|
||||
----
|
||||
this: is a frontmatter
|
||||
----
|
||||
This is a text
|
||||
.
|
||||
<hr />
|
||||
<h2>this: is a frontmatter</h2>
|
||||
<p>This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
It can end with three dots `...`:
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
...
|
||||
This is a text
|
||||
.
|
||||
<p>This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
It expects exactly three dots `...`:
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
....
|
||||
This is a text
|
||||
.
|
||||
````````````````````````````````
|
||||
|
||||
Front matter ends with the first line containing three dots `...` or three dashes `...`:
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
....
|
||||
|
||||
Hello
|
||||
---
|
||||
This is a text
|
||||
.
|
||||
<p>This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
It expects whitespace can exist after the leading characters
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
...
|
||||
This is a text
|
||||
.
|
||||
<p>This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
It expects whitespace can exist after the trailing characters
|
||||
|
||||
```````````````````````````````` example
|
||||
---
|
||||
this: is a frontmatter
|
||||
...
|
||||
This is a text
|
||||
.
|
||||
<p>This is a text</p>
|
||||
````````````````````````````````
|
||||
|
||||
|
||||
|
||||
36
src/Markdig.Tests/Specs/readme.md
Normal file
36
src/Markdig.Tests/Specs/readme.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Documentation Tests/Specs
|
||||
|
||||
You will find from the following links the supported extensions in markdig and their usage:
|
||||
|
||||
- 2 kind of tables:
|
||||
- [**Pipe tables**](PipeTableSpecs.md)
|
||||
- [**Grid tables**](GridTableSpecs.md)
|
||||
- [**Extra emphasis**](EmphasisExtraSpecs.md)
|
||||
- strike through `~~`,
|
||||
- Subscript `~`
|
||||
- Superscript `^`
|
||||
- Inserted `++`
|
||||
- Marked `==`
|
||||
- [**Special attributes**](GenericAttributesSpecs.md)
|
||||
- [**Definition lists**](DefinitionListSpecs.md)
|
||||
- [**Footnotes**](FootnotesSpecs.md)
|
||||
- [**Auto-identifiers**](AutoIdentifierSpecs.md)
|
||||
- [**Auto-links**](AutoLinks.md)
|
||||
- [**Task Lists**](TaskListSpecs.md)
|
||||
- [**Extra bullet lists**](ListExtraSpecs.md)
|
||||
- [**Media support**](MediaSpecs.md)
|
||||
- [**Abbreviations**](AbbreviationSpecs.md)
|
||||
- [**Citation**](FigureFooterAndCiteSpecs.md)
|
||||
- [**Custom containers**](CustomContainerSpecs.md)
|
||||
- [**Figures**](FigureFooterAndCiteSpecs.md)
|
||||
- [**Footers**](FigureFooterAndCiteSpecs.md)
|
||||
- [**Mathematics**](MathSpecs.md)/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
|
||||
- [**Soft lines as hard lines**](HardlineBreakSpecs.md)
|
||||
- [**Emoji**](EmojiSpecs.md)
|
||||
- [**SmartyPants**](SmartyPantsSpecs.md)
|
||||
- [**Bootstrap**](BootstrapSpecs.md)
|
||||
- [**Diagrams**](DiagramsSpecs.md)
|
||||
- [**YAML frontmatter**](YamlSpecs.md)
|
||||
- [**JIRA links**](JiraLinks.md)
|
||||
|
||||
> Notice that the links above are not yet the final documentation but are "specification" files used for testing the correctness of markdig for each extension
|
||||
96
src/Markdig.Tests/TestHtmlAttributes.cs
Normal file
96
src/Markdig.Tests/TestHtmlAttributes.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers.Html;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture()]
|
||||
public class TestHtmlAttributes
|
||||
{
|
||||
[Test]
|
||||
public void TestAddClass()
|
||||
{
|
||||
var attributes = new HtmlAttributes();
|
||||
attributes.AddClass("test");
|
||||
Assert.NotNull(attributes.Classes);
|
||||
Assert.AreEqual(new List<string>() { "test" }, attributes.Classes);
|
||||
|
||||
attributes.AddClass("test");
|
||||
Assert.AreEqual(1, attributes.Classes.Count);
|
||||
|
||||
attributes.AddClass("test1");
|
||||
Assert.AreEqual(new List<string>() { "test", "test1" }, attributes.Classes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddProperty()
|
||||
{
|
||||
var attributes = new HtmlAttributes();
|
||||
attributes.AddProperty("key1", "1");
|
||||
Assert.NotNull(attributes.Properties);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
|
||||
|
||||
attributes.AddPropertyIfNotExist("key1", "1");
|
||||
Assert.NotNull(attributes.Properties);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, attributes.Properties);
|
||||
|
||||
attributes.AddPropertyIfNotExist("key2", "2");
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, attributes.Properties);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCopyTo()
|
||||
{
|
||||
var from = new HtmlAttributes();
|
||||
from.AddClass("test");
|
||||
from.AddProperty("key1", "1");
|
||||
|
||||
var to = new HtmlAttributes();
|
||||
from.CopyTo(to);
|
||||
|
||||
Assert.True(ReferenceEquals(from.Classes, to.Classes));
|
||||
Assert.True(ReferenceEquals(from.Properties, to.Properties));
|
||||
|
||||
// From: Classes From: Properties To: Classes To: Properties
|
||||
// test1: null null null null
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.Null(to.Classes);
|
||||
Assert.Null(to.Properties);
|
||||
|
||||
// test2: ["test"] ["key1", "1"] null null
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
from.AddClass("test");
|
||||
from.AddProperty("key1", "1");
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1")}, to.Properties);
|
||||
|
||||
// test3: null null ["test"] ["key1", "1"]
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
to.AddClass("test");
|
||||
to.AddProperty("key1", "1");
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.AreEqual(new List<string>() { "test" }, to.Classes);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1") }, to.Properties);
|
||||
|
||||
// test4: ["test1"] ["key2", "2"] ["test"] ["key1", "1"]
|
||||
from = new HtmlAttributes();
|
||||
to = new HtmlAttributes();
|
||||
from.AddClass("test1");
|
||||
from.AddProperty("key2", "2");
|
||||
to.AddClass("test");
|
||||
to.AddProperty("key1", "1");
|
||||
from.CopyTo(to, false, false);
|
||||
Assert.AreEqual(new List<string>() { "test", "test1" }, to.Classes);
|
||||
Assert.AreEqual(new List<KeyValuePair<string, string>>() { new KeyValuePair<string, string>("key1", "1"), new KeyValuePair<string, string>("key2", "2") }, to.Properties);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/Markdig.Tests/TestLineReader.cs
Normal file
128
src/Markdig.Tests/TestLineReader.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
// 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.IO;
|
||||
using NUnit.Framework;
|
||||
using Markdig.Helpers;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test for <see cref="LineReader"/>.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class TestLineReader
|
||||
{
|
||||
[Test]
|
||||
public void TestEmpty()
|
||||
{
|
||||
var lineReader = new LineReader("");
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinesOnlyLf()
|
||||
{
|
||||
var lineReader = new LineReader("\n\n\n");
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(1, lineReader.SourcePosition);
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(2, lineReader.SourcePosition);
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinesOnlyCr()
|
||||
{
|
||||
var lineReader = new LineReader("\r\r\r");
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(1, lineReader.SourcePosition);
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(2, lineReader.SourcePosition);
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinesOnlyCrLf()
|
||||
{
|
||||
var lineReader = new LineReader("\r\n\r\n\r\n");
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(2, lineReader.SourcePosition);
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(4, lineReader.SourcePosition);
|
||||
Assert.AreEqual(string.Empty, lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoEndOfLine()
|
||||
{
|
||||
var lineReader = new LineReader("123");
|
||||
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLf()
|
||||
{
|
||||
var lineReader = new LineReader("123\n");
|
||||
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(4, lineReader.SourcePosition);
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLf2()
|
||||
{
|
||||
// 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(4, lineReader.SourcePosition);
|
||||
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCr()
|
||||
{
|
||||
var lineReader = new LineReader("123\r");
|
||||
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(4, lineReader.SourcePosition);
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCr2()
|
||||
{
|
||||
var lineReader = new LineReader("123\r456");
|
||||
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(4, lineReader.SourcePosition);
|
||||
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCrLf()
|
||||
{
|
||||
// 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(5, lineReader.SourcePosition);
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCrLf2()
|
||||
{
|
||||
var lineReader = new LineReader("123\r\n456");
|
||||
Assert.AreEqual("123", lineReader.ReadLine()?.ToString());
|
||||
Assert.AreEqual(5, lineReader.SourcePosition);
|
||||
Assert.AreEqual("456", lineReader.ReadLine()?.ToString());
|
||||
Assert.Null(lineReader.ReadLine()?.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System.Security;
|
||||
using NUnit.Framework;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
@@ -30,6 +32,18 @@ namespace Markdig.Tests
|
||||
Assert.AreEqual(')', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("http://google.com.")]
|
||||
[TestCase("http://google.com. ")]
|
||||
public void TestUrlTrailingFullStop(string uri)
|
||||
{
|
||||
var text = new StringSlice(uri);
|
||||
string link;
|
||||
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual('.', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUrlNestedParenthesis()
|
||||
{
|
||||
@@ -80,36 +94,52 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlAndTitle()
|
||||
{
|
||||
// 0 1 2 3
|
||||
// 0123456789012345678901234567890123456789
|
||||
var text = new StringSlice(@"(http://google.com 'this is a title')ABC");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual("this is a title", title);
|
||||
Assert.AreEqual(new SourceSpan(1, 17), linkSpan);
|
||||
Assert.AreEqual(new SourceSpan(19, 35), titleSpan);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUrlAndTitleEmpty()
|
||||
{
|
||||
// 01234
|
||||
var text = new StringSlice(@"(<>)A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual(new SourceSpan(1, 2), linkSpan);
|
||||
Assert.AreEqual(SourceSpan.Empty, titleSpan);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUrlAndTitleEmpty2()
|
||||
{
|
||||
// 012345
|
||||
var text = new StringSlice(@"( <> )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual(new SourceSpan(2, 3), linkSpan);
|
||||
Assert.AreEqual(SourceSpan.Empty, titleSpan);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
@@ -117,12 +147,18 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void TestUrlEmptyWithTitleWithMultipleSpaces()
|
||||
{
|
||||
// 0 1 2
|
||||
// 0123456789012345678901234567
|
||||
var text = new StringSlice(@"( <> 'toto' )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual("toto", title);
|
||||
Assert.AreEqual(new SourceSpan(4, 5), linkSpan);
|
||||
Assert.AreEqual(new SourceSpan(12, 17), titleSpan);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
@@ -132,50 +168,67 @@ namespace Markdig.Tests
|
||||
var text = new StringSlice(@"()A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
|
||||
Assert.AreEqual(string.Empty, link);
|
||||
Assert.AreEqual(string.Empty, title);
|
||||
Assert.AreEqual(SourceSpan.Empty, linkSpan);
|
||||
Assert.AreEqual(SourceSpan.Empty, titleSpan);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleLines()
|
||||
{
|
||||
var text = new StringSlice(@"(
|
||||
<http://google.com>
|
||||
'toto' )A");
|
||||
// 0 1 2 3
|
||||
// 01 2345678901234567890 1234567890123456789
|
||||
var text = new StringSlice("(\n<http://google.com>\n 'toto' )A");
|
||||
string link;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title));
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan));
|
||||
Assert.AreEqual("http://google.com", link);
|
||||
Assert.AreEqual("toto", title);
|
||||
Assert.AreEqual(new SourceSpan(2, 20), linkSpan);
|
||||
Assert.AreEqual(new SourceSpan(26, 31), titleSpan);
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLabelSimple()
|
||||
{
|
||||
// 01234
|
||||
var text = new StringSlice("[foo]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
SourceSpan labelSpan;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
|
||||
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
|
||||
Assert.AreEqual("foo", label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLabelEscape()
|
||||
{
|
||||
// 012345678
|
||||
var text = new StringSlice(@"[fo\[\]o]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
SourceSpan labelSpan;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
|
||||
Assert.AreEqual(new SourceSpan(1, 7), labelSpan);
|
||||
Assert.AreEqual(@"fo[]o", label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLabelEscape2()
|
||||
{
|
||||
// 0123
|
||||
var text = new StringSlice(@"[\]]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
SourceSpan labelSpan;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
|
||||
Assert.AreEqual(new SourceSpan(1, 2), labelSpan);
|
||||
Assert.AreEqual(@"]", label);
|
||||
}
|
||||
|
||||
@@ -194,23 +247,36 @@ namespace Markdig.Tests
|
||||
[Test]
|
||||
public void TestLabelWhitespaceCollapsedAndTrim()
|
||||
{
|
||||
// 0 1 2 3
|
||||
// 0123456789012345678901234567890123456789
|
||||
var text = new StringSlice(@"[ fo o z ]");
|
||||
string label;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label));
|
||||
SourceSpan labelSpan;
|
||||
Assert.True(LinkHelper.TryParseLabel(ref text, out label, out labelSpan));
|
||||
Assert.AreEqual(new SourceSpan(6, 17), labelSpan);
|
||||
Assert.AreEqual(@"fo o z", label);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestlLinkReferenceDefinitionSimple()
|
||||
{
|
||||
// 0 1 2 3
|
||||
// 0123456789012345678901234567890123456789
|
||||
var text = new StringSlice(@"[foo]: /toto 'title'");
|
||||
string label;
|
||||
string url;
|
||||
string title;
|
||||
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title));
|
||||
SourceSpan labelSpan;
|
||||
SourceSpan urlSpan;
|
||||
SourceSpan titleSpan;
|
||||
Assert.True(LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title, out labelSpan, out urlSpan, out titleSpan));
|
||||
Assert.AreEqual(@"foo", label);
|
||||
Assert.AreEqual(@"/toto", url);
|
||||
Assert.AreEqual(@"title", title);
|
||||
Assert.AreEqual(new SourceSpan(1, 3), labelSpan);
|
||||
Assert.AreEqual(new SourceSpan(7, 11), urlSpan);
|
||||
Assert.AreEqual(new SourceSpan(13, 19), titleSpan);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -245,5 +311,103 @@ namespace Markdig.Tests
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<ab"), out text, out isEmail));
|
||||
Assert.False(LinkHelper.TryParseAutolink(new StringSlice(@"<user@>"), out text, out isEmail));
|
||||
}
|
||||
|
||||
[TestCase("Header identifiers in HTML", "header-identifiers-in-html")]
|
||||
[TestCase("* Dogs*?--in *my* house?", "dogs-in-my-house")] // Not Pandoc equivalent: dogs--in...
|
||||
[TestCase("[HTML], [S5], or [RTF]?", "html-s5-or-rtf")]
|
||||
[TestCase("3. Applications", "applications")]
|
||||
[TestCase("33", "")]
|
||||
public void TestUrilizeNonAscii_Pandoc(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
|
||||
}
|
||||
|
||||
[TestCase("abc", "abc")]
|
||||
[TestCase("a-c", "a-c")]
|
||||
[TestCase("a c", "a-c")]
|
||||
[TestCase("a_c", "a_c")]
|
||||
[TestCase("a.c", "a.c")]
|
||||
[TestCase("a,c", "ac")]
|
||||
[TestCase("a--", "a")] // Not Pandoc-equivalent: a--
|
||||
[TestCase("a__", "a")] // Not Pandoc-equivalent: a__
|
||||
[TestCase("a..", "a")] // Not Pandoc-equivalent: a..
|
||||
[TestCase("a??", "a")]
|
||||
[TestCase("a ", "a")]
|
||||
[TestCase("a--d", "a-d")]
|
||||
[TestCase("a__d", "a_d")]
|
||||
[TestCase("a??d", "ad")]
|
||||
[TestCase("a d", "a-d")]
|
||||
[TestCase("a..d", "a.d")]
|
||||
[TestCase("-bc", "bc")]
|
||||
[TestCase("_bc", "bc")]
|
||||
[TestCase(" bc", "bc")]
|
||||
[TestCase("?bc", "bc")]
|
||||
[TestCase(".bc", "bc")]
|
||||
[TestCase("a-.-", "a")] // Not Pandoc equivalent: a-.-
|
||||
public void TestUrilizeOnlyAscii_Simple(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
|
||||
}
|
||||
|
||||
[TestCase("bær", "br")]
|
||||
[TestCase("bør", "br")]
|
||||
[TestCase("bΘr", "br")]
|
||||
[TestCase("四五", "")]
|
||||
public void TestUrilizeOnlyAscii_NonAscii(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
|
||||
}
|
||||
|
||||
[TestCase("bár", "bar")]
|
||||
[TestCase("àrrivé", "arrive")]
|
||||
public void TestUrilizeOnlyAscii_Normalization(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
|
||||
}
|
||||
|
||||
[TestCase("123", "")]
|
||||
[TestCase("1,-b", "b")]
|
||||
[TestCase("b1,-", "b1")] // Not Pandoc equivalent: b1-
|
||||
[TestCase("ab3", "ab3")]
|
||||
[TestCase("ab3de", "ab3de")]
|
||||
public void TestUrilizeOnlyAscii_Numeric(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, true));
|
||||
}
|
||||
|
||||
[TestCase("一二三四五", "一二三四五")]
|
||||
[TestCase("一,-b", "一-b")]
|
||||
public void TestUrilizeNonAscii_NonAsciiNumeric(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
|
||||
}
|
||||
|
||||
[TestCase("bær", "bær")]
|
||||
[TestCase("æ5el", "æ5el")]
|
||||
[TestCase("-æ5el", "æ5el")]
|
||||
[TestCase("-frø-", "frø")]
|
||||
[TestCase("-fr-ø", "fr-ø")]
|
||||
public void TestUrilizeNonAscii_Simple(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
|
||||
}
|
||||
|
||||
// Just to be sure, test for characters expressly forbidden in URI fragments:
|
||||
[TestCase("b#r", "br")]
|
||||
[TestCase("b%r", "br")] // Invalid except as an escape character
|
||||
[TestCase("b^r", "br")]
|
||||
[TestCase("b[r", "br")]
|
||||
[TestCase("b]r", "br")]
|
||||
[TestCase("b{r", "br")]
|
||||
[TestCase("b}r", "br")]
|
||||
[TestCase("b<r", "br")]
|
||||
[TestCase("b>r", "br")]
|
||||
[TestCase(@"b\r", "br")]
|
||||
[TestCase(@"b""r", "br")]
|
||||
[TestCase(@"Requirement 😀", "requirement")]
|
||||
public void TestUrilizeNonAscii_NonValidCharactersForFragments(string input, string expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, LinkHelper.Urilize(input, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/Markdig.Tests/TestOrderedList.cs
Normal file
43
src/Markdig.Tests/TestOrderedList.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestOrderedList
|
||||
{
|
||||
[Test]
|
||||
public void TestReplace()
|
||||
{
|
||||
var list = new OrderedList<ITest>
|
||||
{
|
||||
new A(),
|
||||
new B(),
|
||||
new C(),
|
||||
};
|
||||
|
||||
// Replacing B with D. Order should now be A, D, B.
|
||||
var result = list.Replace<B>(new D());
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(list.Count, Is.EqualTo(3));
|
||||
Assert.That(list[0], Is.InstanceOf<A>());
|
||||
Assert.That(list[1], Is.InstanceOf<D>());
|
||||
Assert.That(list[2], Is.InstanceOf<C>());
|
||||
|
||||
// Replacing B again should fail, as it's no longer in the list.
|
||||
Assert.That(list.Replace<B>(new D()), Is.False);
|
||||
}
|
||||
|
||||
#region Test fixtures
|
||||
private interface ITest { }
|
||||
private class A : ITest { }
|
||||
private class B : ITest { }
|
||||
private class C : ITest { }
|
||||
private class D : ITest { }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Markdig.Extensions.JiraLinks;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
@@ -14,43 +15,76 @@ namespace Markdig.Tests
|
||||
{
|
||||
foreach (var pipeline in GetPipeline(extensions))
|
||||
{
|
||||
Console.WriteLine($"Pipeline configured with extensions: {extensions}");
|
||||
// Uncomment this line to get more debug information for process inlines.
|
||||
//pipeline.DebugLog = Console.Out;
|
||||
var result = Markdown.ToHtml(inputText, pipeline);
|
||||
|
||||
result = Compact(result);
|
||||
expectedOutputText = Compact(expectedOutputText);
|
||||
|
||||
Console.WriteLine("```````````````````Source");
|
||||
Console.WriteLine(DisplaySpaceAndTabs(inputText));
|
||||
Console.WriteLine("```````````````````Result");
|
||||
Console.WriteLine(DisplaySpaceAndTabs(result));
|
||||
Console.WriteLine("```````````````````Expected");
|
||||
Console.WriteLine(DisplaySpaceAndTabs(expectedOutputText));
|
||||
Console.WriteLine("```````````````````");
|
||||
Console.WriteLine();
|
||||
TextAssert.AreEqual(expectedOutputText, result);
|
||||
Console.WriteLine($"Pipeline configured with extensions: {pipeline.Key}");
|
||||
TestSpec(inputText, expectedOutputText, pipeline.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MarkdownPipeline> GetPipeline(string extensionsGroupText)
|
||||
public static void TestSpec(string inputText, string expectedOutputText, MarkdownPipeline pipeline)
|
||||
{
|
||||
// Uncomment this line to get more debug information for process inlines.
|
||||
//pipeline.DebugLog = Console.Out;
|
||||
var result = Markdown.ToHtml(inputText, pipeline);
|
||||
|
||||
result = Compact(result);
|
||||
expectedOutputText = Compact(expectedOutputText);
|
||||
|
||||
Console.WriteLine("```````````````````Source");
|
||||
Console.WriteLine(DisplaySpaceAndTabs(inputText));
|
||||
Console.WriteLine("```````````````````Result");
|
||||
Console.WriteLine(DisplaySpaceAndTabs(result));
|
||||
Console.WriteLine("```````````````````Expected");
|
||||
Console.WriteLine(DisplaySpaceAndTabs(expectedOutputText));
|
||||
Console.WriteLine("```````````````````");
|
||||
Console.WriteLine();
|
||||
TextAssert.AreEqual(expectedOutputText, result);
|
||||
}
|
||||
|
||||
private static IEnumerable<KeyValuePair<string, MarkdownPipeline>> GetPipeline(string extensionsGroupText)
|
||||
{
|
||||
// For the standard case, we make sure that both the CommmonMark core and Extra/Advanced are CommonMark compliant!
|
||||
if (string.IsNullOrEmpty(extensionsGroupText))
|
||||
{
|
||||
yield return new MarkdownPipelineBuilder().Build();
|
||||
yield return new KeyValuePair<string, MarkdownPipeline>("default", new MarkdownPipelineBuilder().Build());
|
||||
|
||||
yield return new KeyValuePair<string, MarkdownPipeline>("advanced", new MarkdownPipelineBuilder() // Use similar to advanced extension without auto-identifier
|
||||
.UseAbbreviations()
|
||||
//.UseAutoIdentifiers()
|
||||
.UseCitations()
|
||||
.UseCustomContainers()
|
||||
.UseDefinitionLists()
|
||||
.UseEmphasisExtras()
|
||||
.UseFigures()
|
||||
.UseFooters()
|
||||
.UseFootnotes()
|
||||
.UseGridTables()
|
||||
.UseMathematics()
|
||||
.UseMediaLinks()
|
||||
.UsePipeTables()
|
||||
.UseListExtras()
|
||||
.UseGenericAttributes().Build());
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
var extensionGroups = extensionsGroupText.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var extensionsText in extensionGroups)
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder().Configure(extensionsText);
|
||||
yield return pipeline.Build();
|
||||
var builder = new MarkdownPipelineBuilder();
|
||||
builder.DebugLog = Console.Out;
|
||||
if (extensionsText == "jiralinks")
|
||||
{
|
||||
builder.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
|
||||
}
|
||||
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, builder.Build());
|
||||
}
|
||||
}
|
||||
|
||||
private static string DisplaySpaceAndTabs(string text)
|
||||
public static string DisplaySpaceAndTabs(string text)
|
||||
{
|
||||
// Output special characters to check correctly the results
|
||||
return text.Replace('\t', '→').Replace(' ', '·');
|
||||
@@ -59,7 +93,7 @@ namespace Markdig.Tests
|
||||
private static string Compact(string html)
|
||||
{
|
||||
// Normalize the output to make it compatible with CommonMark specs
|
||||
html = html.Replace("\r", "").Trim();
|
||||
html = html.Replace("\r\n", "\n").Replace(@"\r", @"\n").Trim();
|
||||
html = Regex.Replace(html, @"\s+</li>", "</li>");
|
||||
html = Regex.Replace(html, @"<li>\s+", "<li>");
|
||||
html = html.Normalize(NormalizationForm.FormKD);
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
@@ -9,6 +12,18 @@ namespace Markdig.Tests
|
||||
[TestFixture]
|
||||
public class TestPlayParser
|
||||
{
|
||||
[Test]
|
||||
public void TestListBug2()
|
||||
{
|
||||
TestParser.TestSpec("10.\t*test* – test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
|
||||
<li><p><em>test</em> – test</p>
|
||||
</li>
|
||||
<li><p><strong>test</strong> test</p>
|
||||
</li>
|
||||
</ol>
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimple()
|
||||
{
|
||||
@@ -21,12 +36,176 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
|
||||
//> titi toto
|
||||
//");
|
||||
|
||||
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtra());
|
||||
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviation().Build());
|
||||
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtras());
|
||||
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviations().Build());
|
||||
//File.WriteAllText("test.html", result, Encoding.UTF8);
|
||||
Console.WriteLine(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmptyLiteral()
|
||||
{
|
||||
var text = @"> *some text*
|
||||
> some other text";
|
||||
var doc = Markdown.Parse(text);
|
||||
|
||||
Assert.True(doc.Descendants().OfType<LiteralInline>().All(x => !x.Content.IsEmpty),
|
||||
"There should not have any empty literals");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelfPipeline1()
|
||||
{
|
||||
var text = @" <!--markdig:pipetables-->
|
||||
|
||||
a | b
|
||||
- | -
|
||||
0 | 1
|
||||
";
|
||||
TestParser.TestSpec(text, @"<!--markdig:pipetables-->
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
", "self");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestListBug()
|
||||
{
|
||||
// TODO: Add this test back to the CommonMark specs
|
||||
var text = @"- item1
|
||||
- item2
|
||||
- item3
|
||||
- item4";
|
||||
TestParser.TestSpec(text, @"<ul>
|
||||
<li>item1
|
||||
<ul>
|
||||
<li>item2
|
||||
<ul>
|
||||
<li>item3
|
||||
<ul>
|
||||
<li>item4</li>
|
||||
</ul></li>
|
||||
</ul></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestHtmlBug()
|
||||
{
|
||||
TestParser.TestSpec(@" # header1
|
||||
|
||||
<pre class='copy'>
|
||||
blabla
|
||||
</pre>
|
||||
|
||||
# header2
|
||||
", @"<h1>header1</h1>
|
||||
<pre class='copy'>
|
||||
blabla
|
||||
</pre>
|
||||
<h1>header2</h1>");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlh4Bug()
|
||||
{
|
||||
TestParser.TestSpec(@"<h4>foobar</h4>", @"<h4>foobar</h4>");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStandardUriEscape()
|
||||
{
|
||||
TestParser.TestSpec(@"", "<p><img src=\"你好.png\" alt=\"你好\" /></p>", "nonascii-noescape");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestBugAdvanced()
|
||||
{
|
||||
TestParser.TestSpec(@"`https://{domain}/callbacks`
|
||||
#### HEADING
|
||||
Paragraph
|
||||
", "<p><code>https://{domain}/callbacks</code></p>\n<h4 id=\"heading\">HEADING</h4>\n<p>Paragraph</p>", "advanced");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestBugEmphAttribute()
|
||||
{
|
||||
// https://github.com/lunet-io/markdig/issues/108
|
||||
TestParser.TestSpec(@"*test*{name=value}", "<p><em name=\"value\">test</em></p>", "advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBugPipeTables()
|
||||
{
|
||||
// https://github.com/lunet-io/markdig/issues/73
|
||||
TestParser.TestSpec(@"| abc | def |
|
||||
| --- | --- |
|
||||
| 1 | ~3 |
|
||||
", @"<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>abc</th>
|
||||
<th>def</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>~3</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>", "advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGridTableWithCustomAttributes() {
|
||||
|
||||
var input = @"
|
||||
{.table}
|
||||
+---+---+
|
||||
| a | b |
|
||||
+===+===+
|
||||
| 1 | 2 |
|
||||
+---+---+
|
||||
";
|
||||
|
||||
var expected = @"<table class=""table"">
|
||||
<col style=""width:50%"">
|
||||
<col style=""width:50%"">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>a</th>
|
||||
<th>b</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
";
|
||||
TestParser.TestSpec(input, expected, "advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSamePipelineAllExtensions()
|
||||
{
|
||||
|
||||
81
src/Markdig.Tests/TestPragmaLines.cs
Normal file
81
src/Markdig.Tests/TestPragmaLines.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Syntax;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestPragmaLines
|
||||
{
|
||||
[Test]
|
||||
public void TestFindClosest()
|
||||
{
|
||||
var doc = Markdown.Parse(
|
||||
"test1\n" + // 0
|
||||
"\n" + // 1
|
||||
"test2\n" + // 2
|
||||
"\n" + // 3
|
||||
"test3\n" + // 4
|
||||
"\n" + // 5
|
||||
"test4\n" + // 6
|
||||
"\n" + // 7
|
||||
"# Heading\n" + // 8
|
||||
"\n" + // 9
|
||||
"Long para\n" + // 10
|
||||
"on multiple\n" + // 11
|
||||
"lines\n" + // 12
|
||||
"to check that\n" + // 13
|
||||
"lines are\n" + // 14
|
||||
"correctly \n" + // 15
|
||||
"found\n" + // 16
|
||||
"\n" + // 17
|
||||
"- item1\n" + // 18
|
||||
"- item2\n" + // 19
|
||||
"- item3\n" + // 20
|
||||
"\n" + // 21
|
||||
"This is a last paragraph\n" // 22
|
||||
, new MarkdownPipelineBuilder().UsePragmaLines().Build());
|
||||
|
||||
foreach (var exact in new int[] {0, 2, 4, 6, 8, 10, 18, 19, 20, 22})
|
||||
{
|
||||
Assert.AreEqual(exact, doc.FindClosestLine(exact));
|
||||
}
|
||||
|
||||
Assert.AreEqual(22, doc.FindClosestLine(23));
|
||||
|
||||
Assert.AreEqual(10, doc.FindClosestLine(11));
|
||||
Assert.AreEqual(10, doc.FindClosestLine(12));
|
||||
Assert.AreEqual(10, doc.FindClosestLine(13));
|
||||
Assert.AreEqual(18, doc.FindClosestLine(14)); // > 50% of the paragraph, we switch to next
|
||||
Assert.AreEqual(18, doc.FindClosestLine(15));
|
||||
Assert.AreEqual(18, doc.FindClosestLine(16));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFindClosest1()
|
||||
{
|
||||
var text =
|
||||
"- item1\n" + // 0
|
||||
" - item11\n" + // 1
|
||||
" - item12\n" + // 2
|
||||
" - item121\n" + // 3
|
||||
" - item13\n" + // 4
|
||||
" - item131\n" + // 5
|
||||
" - item1311\n"; // 6
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder().UsePragmaLines().Build();
|
||||
var doc = Markdown.Parse(text, pipeline);
|
||||
|
||||
for (int exact = 0; exact < 7; exact++)
|
||||
{
|
||||
Assert.AreEqual(exact, doc.FindClosestLine(exact));
|
||||
}
|
||||
|
||||
Assert.AreEqual(6, doc.FindClosestLine(50));
|
||||
}
|
||||
}
|
||||
}
|
||||
791
src/Markdig.Tests/TestSourcePosition.cs
Normal file
791
src/Markdig.Tests/TestSourcePosition.cs
Normal file
@@ -0,0 +1,791 @@
|
||||
// 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;
|
||||
using System.Text;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Markdig.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test the precise source location of all Markdown elements, including extensions
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class TestSourcePosition
|
||||
{
|
||||
[Test]
|
||||
public void TestParagraph()
|
||||
{
|
||||
Check("0123456789", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-9
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParagraphAndNewLine()
|
||||
{
|
||||
Check("0123456789\n0123456789", @"
|
||||
paragraph ( 0, 0) 0-20
|
||||
literal ( 0, 0) 0-9
|
||||
linebreak ( 0,10) 10-10
|
||||
literal ( 1, 0) 11-20
|
||||
");
|
||||
|
||||
Check("0123456789\r\n0123456789", @"
|
||||
paragraph ( 0, 0) 0-21
|
||||
literal ( 0, 0) 0-9
|
||||
linebreak ( 0,10) 10-10
|
||||
literal ( 1, 0) 12-21
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParagraphNewLineAndSpaces()
|
||||
{
|
||||
// 0123 45678
|
||||
Check("012\n 345", @"
|
||||
paragraph ( 0, 0) 0-8
|
||||
literal ( 0, 0) 0-2
|
||||
linebreak ( 0, 3) 3-3
|
||||
literal ( 1, 2) 6-8
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestParagraph2()
|
||||
{
|
||||
Check("0123456789\n\n0123456789", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-9
|
||||
paragraph ( 2, 0) 12-21
|
||||
literal ( 2, 0) 12-21
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasis()
|
||||
{
|
||||
Check("012**3456789**", @"
|
||||
paragraph ( 0, 0) 0-13
|
||||
literal ( 0, 0) 0-2
|
||||
emphasis ( 0, 3) 3-13
|
||||
literal ( 0, 5) 5-11
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasis2()
|
||||
{
|
||||
// 01234567
|
||||
Check("01*2**3*", @"
|
||||
paragraph ( 0, 0) 0-7
|
||||
literal ( 0, 0) 0-1
|
||||
emphasis ( 0, 2) 2-7
|
||||
literal ( 0, 3) 3-3
|
||||
literal ( 0, 4) 4-5
|
||||
literal ( 0, 6) 6-6
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasis3()
|
||||
{
|
||||
// 0123456789
|
||||
Check("01**2***3*", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-1
|
||||
emphasis ( 0, 2) 2-6
|
||||
literal ( 0, 4) 4-4
|
||||
emphasis ( 0, 7) 7-9
|
||||
literal ( 0, 8) 8-8
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasisFalse()
|
||||
{
|
||||
Check("0123456789**0123", @"
|
||||
paragraph ( 0, 0) 0-15
|
||||
literal ( 0, 0) 0-9
|
||||
literal ( 0,10) 10-11
|
||||
literal ( 0,12) 12-15
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHeading()
|
||||
{
|
||||
// 012345
|
||||
Check("# 2345", @"
|
||||
heading ( 0, 0) 0-5
|
||||
literal ( 0, 2) 2-5
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHeadingWithEmphasis()
|
||||
{
|
||||
// 0123456789
|
||||
Check("# 23**45**", @"
|
||||
heading ( 0, 0) 0-9
|
||||
literal ( 0, 2) 2-3
|
||||
emphasis ( 0, 4) 4-9
|
||||
literal ( 0, 6) 6-7
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCodeSpan()
|
||||
{
|
||||
// 012345678
|
||||
Check("0123`456`", @"
|
||||
paragraph ( 0, 0) 0-8
|
||||
literal ( 0, 0) 0-3
|
||||
code ( 0, 4) 4-8
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLink()
|
||||
{
|
||||
// 0123456789
|
||||
Check("012[45](#)", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-2
|
||||
link ( 0, 3) 3-9
|
||||
literal ( 0, 4) 4-5
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkParts1()
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 3456789012345
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56)", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
Assert.AreEqual(new SourceSpan(12, 14), link.UrlSpan);
|
||||
Assert.AreEqual(SourceSpan.Empty, link.TitleSpan);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLinkParts2()
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 34567890123456789
|
||||
var link = Markdown.Parse("0\n\n01 [234](/56 'yo')", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
Assert.AreEqual(new SourceSpan(12, 14), link.UrlSpan);
|
||||
Assert.AreEqual(new SourceSpan(16, 19), link.TitleSpan);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestLinkParts3()
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 3456789012345
|
||||
var link = Markdown.Parse("0\n\n01", new MarkdownPipelineBuilder().UsePreciseSourceLocation().Build()).Descendants().OfType<LinkInline>().FirstOrDefault();
|
||||
Assert.NotNull(link);
|
||||
|
||||
Assert.AreEqual(new SourceSpan(5, 15), link.Span);
|
||||
Assert.AreEqual(new SourceSpan(7, 9), link.LabelSpan);
|
||||
Assert.AreEqual(new SourceSpan(12, 14), link.UrlSpan);
|
||||
Assert.AreEqual(SourceSpan.Empty, link.TitleSpan);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutolinkInline()
|
||||
{
|
||||
// 0123456789ABCD
|
||||
Check("01<http://yes>", @"
|
||||
paragraph ( 0, 0) 0-13
|
||||
literal ( 0, 0) 0-1
|
||||
autolink ( 0, 2) 2-13
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFencedCodeBlock()
|
||||
{
|
||||
// 012 3456 78 9ABC
|
||||
Check("01\n```\n3\n```\n", @"
|
||||
paragraph ( 0, 0) 0-1
|
||||
literal ( 0, 0) 0-1
|
||||
fencedcode ( 1, 0) 3-11
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlBlock()
|
||||
{
|
||||
// 012345 67 89ABCDE F 0
|
||||
Check("<div>\n0\n</div>\n\n1", @"
|
||||
html ( 0, 0) 0-13
|
||||
paragraph ( 4, 0) 16-16
|
||||
literal ( 4, 0) 16-16
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlBlock1()
|
||||
{
|
||||
// 0 1
|
||||
// 01 2 345678901 23
|
||||
Check("0\n\n<!--A-->\n1\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
html ( 2, 0) 3-10
|
||||
paragraph ( 3, 0) 12-12
|
||||
literal ( 3, 0) 12-12
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlComment()
|
||||
{
|
||||
// 0 1 2
|
||||
// 012345678901 234567890 1234
|
||||
Check("# 012345678\n<!--0-->\n123\n", @"
|
||||
heading ( 0, 0) 0-10
|
||||
literal ( 0, 2) 2-10
|
||||
html ( 1, 0) 12-19
|
||||
paragraph ( 2, 0) 21-23
|
||||
literal ( 2, 0) 21-23
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlInline()
|
||||
{
|
||||
// 0123456789
|
||||
Check("01<b>4</b>", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-1
|
||||
html ( 0, 2) 2-4
|
||||
literal ( 0, 5) 5-5
|
||||
html ( 0, 6) 6-9
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlInline1()
|
||||
{
|
||||
// 0
|
||||
// 0123456789
|
||||
Check("0<!--A-->1", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-0
|
||||
html ( 0, 1) 1-8
|
||||
literal ( 0, 9) 9-9
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestThematicBreak()
|
||||
{
|
||||
// 0123 4567
|
||||
Check("---\n---\n", @"
|
||||
thematicbreak ( 0, 0) 0-2
|
||||
thematicbreak ( 1, 0) 4-6
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuoteBlock()
|
||||
{
|
||||
// 0123456
|
||||
Check("> 2345\n", @"
|
||||
quote ( 0, 0) 0-5
|
||||
paragraph ( 0, 2) 2-5
|
||||
literal ( 0, 2) 2-5
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuoteBlockWithLines()
|
||||
{
|
||||
// 01234 56789A
|
||||
Check("> 01\n> 23\n", @"
|
||||
quote ( 0, 0) 0-9
|
||||
paragraph ( 0, 2) 2-9
|
||||
literal ( 0, 2) 2-3
|
||||
linebreak ( 0, 4) 4-4
|
||||
literal ( 1, 3) 8-9
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestQuoteBlockWithLazyContinuation()
|
||||
{
|
||||
// 01234 56
|
||||
Check("> 01\n23\n", @"
|
||||
quote ( 0, 0) 0-6
|
||||
paragraph ( 0, 2) 2-6
|
||||
literal ( 0, 2) 2-3
|
||||
linebreak ( 0, 4) 4-4
|
||||
literal ( 1, 0) 5-6
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestListBlock()
|
||||
{
|
||||
// 0123 4567
|
||||
Check("- 0\n- 1\n", @"
|
||||
list ( 0, 0) 0-6
|
||||
listitem ( 0, 0) 0-2
|
||||
paragraph ( 0, 2) 2-2
|
||||
literal ( 0, 2) 2-2
|
||||
listitem ( 1, 0) 4-6
|
||||
paragraph ( 1, 2) 6-6
|
||||
literal ( 1, 2) 6-6
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEscapeInline()
|
||||
{
|
||||
// 0123
|
||||
Check(@"\-\)", @"
|
||||
paragraph ( 0, 0) 0-3
|
||||
literal ( 0, 0) 0-1
|
||||
literal ( 0, 2) 2-3
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHtmlEntityInline()
|
||||
{
|
||||
// 01 23456789
|
||||
Check("0\n 1", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-0
|
||||
linebreak ( 0, 1) 1-1
|
||||
htmlentity ( 1, 0) 2-7
|
||||
literal ( 1, 6) 8-9
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAbbreviations()
|
||||
{
|
||||
Check("*[HTML]: Hypertext Markup Language\r\n\r\nLater in a text we are using HTML and it becomes an abbr tag HTML", @"
|
||||
paragraph ( 2, 0) 38-102
|
||||
container ( 2, 0) 38-102
|
||||
literal ( 2, 0) 38-66
|
||||
abbreviation ( 2,29) 67-70
|
||||
literal ( 2,33) 71-98
|
||||
abbreviation ( 2,61) 99-102
|
||||
", "abbreviations");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCitation()
|
||||
{
|
||||
// 0123 4 567 8
|
||||
Check("01 \"\"23\"\"", @"
|
||||
paragraph ( 0, 0) 0-8
|
||||
literal ( 0, 0) 0-2
|
||||
emphasis ( 0, 3) 3-8
|
||||
literal ( 0, 5) 5-6
|
||||
", "citations");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomContainer()
|
||||
{
|
||||
// 01 2345 678 9ABC DEF
|
||||
Check("0\n:::\n23\n:::\n45\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
customcontainer ( 1, 0) 2-11
|
||||
paragraph ( 2, 0) 6-7
|
||||
literal ( 2, 0) 6-7
|
||||
paragraph ( 4, 0) 13-14
|
||||
literal ( 4, 0) 13-14
|
||||
", "customcontainers");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefinitionList()
|
||||
{
|
||||
// 012 3456789A
|
||||
Check("a0\n: 1234", @"
|
||||
definitionlist ( 0, 0) 0-10
|
||||
definitionitem ( 1, 0) 3-10
|
||||
definitionterm ( 0, 0) 0-1
|
||||
literal ( 0, 0) 0-1
|
||||
paragraph ( 1, 4) 7-10
|
||||
literal ( 1, 4) 7-10
|
||||
", "definitionlists");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefinitionList2()
|
||||
{
|
||||
// 012 3456789AB CDEF01234
|
||||
Check("a0\n: 1234\n: 5678", @"
|
||||
definitionlist ( 0, 0) 0-20
|
||||
definitionitem ( 1, 0) 3-10
|
||||
definitionterm ( 0, 0) 0-1
|
||||
literal ( 0, 0) 0-1
|
||||
paragraph ( 1, 4) 7-10
|
||||
literal ( 1, 4) 7-10
|
||||
definitionitem ( 2, 4) 12-20
|
||||
paragraph ( 2, 5) 17-20
|
||||
literal ( 2, 5) 17-20
|
||||
", "definitionlists");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmoji()
|
||||
{
|
||||
// 01 2345
|
||||
Check("0\n :)\n", @"
|
||||
paragraph ( 0, 0) 0-4
|
||||
literal ( 0, 0) 0-0
|
||||
linebreak ( 0, 1) 1-1
|
||||
emoji ( 1, 1) 3-4
|
||||
", "emojis");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmphasisExtra()
|
||||
{
|
||||
// 0123456
|
||||
Check("0 ~~1~~", @"
|
||||
paragraph ( 0, 0) 0-6
|
||||
literal ( 0, 0) 0-1
|
||||
emphasis ( 0, 2) 2-6
|
||||
literal ( 0, 4) 4-4
|
||||
", "emphasisextras");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFigures()
|
||||
{
|
||||
// 01 2345 67 89AB
|
||||
Check("0\n^^^\n0\n^^^\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
figure ( 1, 0) 2-10
|
||||
paragraph ( 2, 0) 6-6
|
||||
literal ( 2, 0) 6-6
|
||||
", "figures");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFiguresCaption1()
|
||||
{
|
||||
// 01 234567 89 ABCD
|
||||
Check("0\n^^^ab\n0\n^^^\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
figure ( 1, 0) 2-12
|
||||
figurecaption ( 1, 3) 5-6
|
||||
literal ( 1, 3) 5-6
|
||||
paragraph ( 2, 0) 8-8
|
||||
literal ( 2, 0) 8-8
|
||||
", "figures");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFiguresCaption2()
|
||||
{
|
||||
// 01 2345 67 89ABCD
|
||||
Check("0\n^^^\n0\n^^^ab\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
figure ( 1, 0) 2-12
|
||||
paragraph ( 2, 0) 6-6
|
||||
literal ( 2, 0) 6-6
|
||||
figurecaption ( 3, 3) 11-12
|
||||
literal ( 3, 3) 11-12
|
||||
", "figures");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFooters()
|
||||
{
|
||||
// 01 234567 89ABCD
|
||||
Check("0\n^^ 12\n^^ 34\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
footer ( 1, 0) 2-12
|
||||
paragraph ( 1, 3) 5-12
|
||||
literal ( 1, 3) 5-6
|
||||
linebreak ( 1, 5) 7-7
|
||||
literal ( 2, 3) 11-12
|
||||
", "footers");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void TestAttributes()
|
||||
{
|
||||
// 0123456789
|
||||
Check("0123{#456}", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
attributes ( 0, 4) 4-9
|
||||
literal ( 0, 0) 0-3
|
||||
", "attributes");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttributesForHeading()
|
||||
{
|
||||
// 0123456789ABC
|
||||
Check("# 01 {#456}", @"
|
||||
heading ( 0, 0) 0-4
|
||||
attributes ( 0, 5) 5-10
|
||||
literal ( 0, 2) 2-3
|
||||
", "attributes");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMathematicsInline()
|
||||
{
|
||||
// 01 23456789AB
|
||||
Check("0\n012 $abcd$", @"
|
||||
paragraph ( 0, 0) 0-11
|
||||
literal ( 0, 0) 0-0
|
||||
linebreak ( 0, 1) 1-1
|
||||
literal ( 1, 0) 2-5
|
||||
math ( 1, 4) 6-11
|
||||
attributes ( 0, 0) 0--1
|
||||
", "mathematics");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSmartyPants()
|
||||
{
|
||||
// 01234567
|
||||
// 01 23456789
|
||||
Check("0\n2 <<45>>", @"
|
||||
paragraph ( 0, 0) 0-9
|
||||
literal ( 0, 0) 0-0
|
||||
linebreak ( 0, 1) 1-1
|
||||
literal ( 1, 0) 2-3
|
||||
smartypant ( 1, 2) 4-5
|
||||
literal ( 1, 4) 6-7
|
||||
smartypant ( 1, 6) 8-9
|
||||
", "smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSmartyPantsUnbalanced()
|
||||
{
|
||||
// 012345
|
||||
// 01 234567
|
||||
Check("0\n2 <<45", @"
|
||||
paragraph ( 0, 0) 0-7
|
||||
literal ( 0, 0) 0-0
|
||||
linebreak ( 0, 1) 1-1
|
||||
literal ( 1, 0) 2-3
|
||||
literal ( 1, 2) 4-5
|
||||
literal ( 1, 4) 6-7
|
||||
", "smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPipeTable()
|
||||
{
|
||||
// 0123 4567 89AB
|
||||
Check("a|b\n-|-\n0|1\n", @"
|
||||
table ( 0, 0) 0-10
|
||||
tablerow ( 0, 0) 0-2
|
||||
tablecell ( 0, 0) 0-0
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
tablecell ( 0, 2) 2-2
|
||||
paragraph ( 0, 2) 2-2
|
||||
literal ( 0, 2) 2-2
|
||||
tablerow ( 2, 0) 8-10
|
||||
tablecell ( 2, 0) 8-8
|
||||
paragraph ( 2, 0) 8-8
|
||||
literal ( 2, 0) 8-8
|
||||
tablecell ( 2, 2) 10-10
|
||||
paragraph ( 2, 2) 10-10
|
||||
literal ( 2, 2) 10-10
|
||||
", "pipetables");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPipeTable2()
|
||||
{
|
||||
// 01 2 3456 789A BCD
|
||||
Check("0\n\na|b\n-|-\n0|1\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
table ( 2, 0) 3-13
|
||||
tablerow ( 2, 0) 3-5
|
||||
tablecell ( 2, 0) 3-3
|
||||
paragraph ( 2, 0) 3-3
|
||||
literal ( 2, 0) 3-3
|
||||
tablecell ( 2, 2) 5-5
|
||||
paragraph ( 2, 2) 5-5
|
||||
literal ( 2, 2) 5-5
|
||||
tablerow ( 4, 0) 11-13
|
||||
tablecell ( 4, 0) 11-11
|
||||
paragraph ( 4, 0) 11-11
|
||||
literal ( 4, 0) 11-11
|
||||
tablecell ( 4, 2) 13-13
|
||||
paragraph ( 4, 2) 13-13
|
||||
literal ( 4, 2) 13-13
|
||||
", "pipetables");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIndentedCode()
|
||||
{
|
||||
// 01 2 345678 9ABCDE
|
||||
Check("0\n\n 0\n 1\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
code ( 2, 0) 3-13
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIndentedCodeAfterList()
|
||||
{
|
||||
// 0 1 2 3 4 5
|
||||
// 012345678901234567 8 901234567890123456 789012345678901234 56789
|
||||
Check("1) Some list item\n\n some code\n more code\n", @"
|
||||
list ( 0, 0) 0-53
|
||||
listitem ( 0, 0) 0-53
|
||||
paragraph ( 0, 3) 3-16
|
||||
literal ( 0, 3) 3-16
|
||||
code ( 2, 0) 19-53
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIndentedCodeWithTabs()
|
||||
{
|
||||
// 01 2 3 45 6 78
|
||||
Check("0\n\n\t0\n\t1\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
code ( 2, 0) 3-7
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestIndentedCodeWithMixedTabs()
|
||||
{
|
||||
// 01 2 34 56 78 9
|
||||
Check("0\n\n \t0\n \t1\n", @"
|
||||
paragraph ( 0, 0) 0-0
|
||||
literal ( 0, 0) 0-0
|
||||
code ( 2, 0) 3-9
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTabsInList()
|
||||
{
|
||||
// 012 34 567 89
|
||||
Check("- \t0\n- \t1\n", @"
|
||||
list ( 0, 0) 0-8
|
||||
listitem ( 0, 0) 0-3
|
||||
paragraph ( 0, 4) 3-3
|
||||
literal ( 0, 4) 3-3
|
||||
listitem ( 1, 0) 5-8
|
||||
paragraph ( 1, 4) 8-8
|
||||
literal ( 1, 4) 8-8
|
||||
");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDocument()
|
||||
{
|
||||
// L0 L0 L1L2 L3 L4 L5L6 L7L8
|
||||
// 0 10 20 30 40 50 60 70 80 90
|
||||
// 012345678901234567890 1 2345678901 2345678901 2345678901 2 345678901234567890123 4 5678901234567890123
|
||||
Check("# This is a document\n\n1) item 1\n2) item 2\n3) item 4\n\nWith an **emphasis**\n\n> and a blockquote\n", @"
|
||||
heading ( 0, 0) 0-19
|
||||
literal ( 0, 2) 2-19
|
||||
list ( 2, 0) 22-51
|
||||
listitem ( 2, 0) 22-30
|
||||
paragraph ( 2, 3) 25-30
|
||||
literal ( 2, 3) 25-30
|
||||
listitem ( 3, 0) 32-40
|
||||
paragraph ( 3, 3) 35-40
|
||||
literal ( 3, 3) 35-40
|
||||
listitem ( 4, 0) 42-51
|
||||
paragraph ( 4, 3) 45-50
|
||||
literal ( 4, 3) 45-50
|
||||
paragraph ( 6, 0) 53-72
|
||||
literal ( 6, 0) 53-60
|
||||
emphasis ( 6, 8) 61-72
|
||||
literal ( 6,10) 63-70
|
||||
quote ( 8, 0) 75-92
|
||||
paragraph ( 8, 2) 77-92
|
||||
literal ( 8, 2) 77-92
|
||||
");
|
||||
}
|
||||
|
||||
private static void Check(string text, string expectedResult, string extensions = null)
|
||||
{
|
||||
var pipelineBuilder = new MarkdownPipelineBuilder().UsePreciseSourceLocation();
|
||||
if (extensions != null)
|
||||
{
|
||||
pipelineBuilder.Configure(extensions);
|
||||
}
|
||||
var pipeline = pipelineBuilder.Build();
|
||||
|
||||
var document = Markdown.Parse(text, pipeline);
|
||||
|
||||
var build = new StringBuilder();
|
||||
foreach (var val in document.Descendants())
|
||||
{
|
||||
var name = GetTypeName(val.GetType());
|
||||
build.Append($"{name,-12} ({val.Line,2},{val.Column,2}) {val.Span.Start,2}-{val.Span.End}\n");
|
||||
var attributes = val.TryGetAttributes();
|
||||
if (attributes != null)
|
||||
{
|
||||
build.Append($"{"attributes",-12} ({attributes.Line,2},{attributes.Column,2}) {attributes.Span.Start,2}-{attributes.Span.End}\n");
|
||||
}
|
||||
}
|
||||
var result = build.ToString().Trim();
|
||||
|
||||
expectedResult = expectedResult.Trim();
|
||||
expectedResult = expectedResult.Replace("\r\n", "\n").Replace("\r", "\n");
|
||||
|
||||
if (expectedResult != result)
|
||||
{
|
||||
Console.WriteLine("```````````````````Source");
|
||||
Console.WriteLine(TestParser.DisplaySpaceAndTabs(text));
|
||||
Console.WriteLine("```````````````````Result");
|
||||
Console.WriteLine(result);
|
||||
Console.WriteLine("```````````````````Expected");
|
||||
Console.WriteLine(expectedResult);
|
||||
Console.WriteLine("```````````````````");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
TextAssert.AreEqual(expectedResult, result);
|
||||
}
|
||||
|
||||
private static string GetTypeName(Type type)
|
||||
{
|
||||
return type.Name.ToLowerInvariant()
|
||||
.Replace("block", string.Empty)
|
||||
.Replace("inline", string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"NUnit": "3.2.0"
|
||||
"NUnit": "3.2.0",
|
||||
"NUnit3TestAdapter": "3.2.0"
|
||||
}
|
||||
}
|
||||
36
src/Markdig.WebApp/Markdig.WebApp.csproj
Normal file
36
src/Markdig.WebApp/Markdig.WebApp.csproj
Normal file
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp1.0</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<AssemblyName>Markdig.WebApp</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackageId>Markdig.WebApp</PackageId>
|
||||
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
|
||||
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Views">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Markdig\Markdig.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>3cad9801-9976-46be-baca-f6d0d21fdc00</ProjectGuid>
|
||||
<RootNamespace>Markdig.WebApp</RootNamespace>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"version": "1.0.0-rc2-3002702",
|
||||
"type": "platform"
|
||||
},
|
||||
"Microsoft.ApplicationInsights.AspNetCore": "1.0.0-rc2-final",
|
||||
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
|
||||
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
|
||||
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
|
||||
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
|
||||
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
|
||||
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
|
||||
"Microsoft.Extensions.Logging": "1.0.0-rc2-final",
|
||||
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
|
||||
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
|
||||
"Markdig": "0.2.1"
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
|
||||
"version": "1.0.0-preview1-final",
|
||||
"imports": "portable-net45+win8+dnxcore50"
|
||||
}
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
"netcoreapp1.0": {
|
||||
"imports": [
|
||||
"dotnet5.6",
|
||||
"dnxcore50",
|
||||
"portable-net45+win8"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"buildOptions": {
|
||||
"emitEntryPoint": true,
|
||||
"preserveCompilationContext": true
|
||||
},
|
||||
|
||||
"runtimeOptions": {
|
||||
"gcServer": true
|
||||
},
|
||||
|
||||
"publishOptions": {
|
||||
"include": [
|
||||
"wwwroot",
|
||||
"Views",
|
||||
"appsettings.json",
|
||||
"web.config"
|
||||
]
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
|
||||
}
|
||||
}
|
||||
@@ -33,5 +33,10 @@ namespace Markdig.Extensions.Abbreviations
|
||||
/// The text associated to this label.
|
||||
/// </summary>
|
||||
public StringSlice Text;
|
||||
|
||||
/// <summary>
|
||||
/// The label span
|
||||
/// </summary>
|
||||
public SourceSpan LabelSpan;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace Markdig.Extensions.Abbreviations
|
||||
pipeline.BlockParsers.AddIfNotAlready<AbbreviationParser>();
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.Abbreviations
|
||||
@@ -19,7 +20,7 @@ namespace Markdig.Extensions.Abbreviations
|
||||
/// </summary>
|
||||
public AbbreviationParser()
|
||||
{
|
||||
OpeningCharacters = new[] {'*'};
|
||||
OpeningCharacters = new[] { '*' };
|
||||
}
|
||||
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
@@ -31,14 +32,16 @@ namespace Markdig.Extensions.Abbreviations
|
||||
|
||||
// A link must be of the form *[Some Text]: An abbreviation
|
||||
var slice = processor.Line;
|
||||
var startPosition = slice.Start;
|
||||
var c = slice.NextChar();
|
||||
if (c != '[')
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
SourceSpan labelSpan;
|
||||
string label;
|
||||
if (!LinkHelper.TryParseLabel(ref slice, out label))
|
||||
if (!LinkHelper.TryParseLabel(ref slice, out label, out labelSpan))
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
@@ -55,7 +58,11 @@ namespace Markdig.Extensions.Abbreviations
|
||||
var abbr = new Abbreviation(this)
|
||||
{
|
||||
Label = label,
|
||||
Text = slice, Line = processor.LineIndex, Column = processor.Column
|
||||
Text = slice,
|
||||
Span = new SourceSpan(startPosition, slice.End),
|
||||
Line = processor.LineIndex,
|
||||
Column = processor.Column,
|
||||
LabelSpan = labelSpan,
|
||||
};
|
||||
if (!processor.Document.HasAbbreviations())
|
||||
{
|
||||
@@ -83,7 +90,8 @@ namespace Markdig.Extensions.Abbreviations
|
||||
|
||||
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
|
||||
{
|
||||
var literal = (LiteralInline) processor.Inline;
|
||||
var literal = (LiteralInline)processor.Inline;
|
||||
var originalLiteral = literal;
|
||||
|
||||
ContainerInline container = null;
|
||||
|
||||
@@ -93,20 +101,9 @@ namespace Markdig.Extensions.Abbreviations
|
||||
for (int i = content.Start; i < content.End; i++)
|
||||
{
|
||||
string match;
|
||||
if (matcher.TryMatch(text, i, content.End - i + 1, out match))
|
||||
if (matcher.TryMatch(text, i, content.End - i + 1, out match) && IsValidAbbreviation(match, content, i))
|
||||
{
|
||||
// The word matched must be embraced by punctuation or whitespace or \0.
|
||||
var c = content.PeekCharExtra(i - 1);
|
||||
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var indexAfterMatch = i + match.Length;
|
||||
c = content.PeekCharExtra(indexAfterMatch);
|
||||
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We should have a match, but in case...
|
||||
Abbreviation abbr;
|
||||
@@ -118,19 +115,43 @@ namespace Markdig.Extensions.Abbreviations
|
||||
// If we don't have a container, create a new one
|
||||
if (container == null)
|
||||
{
|
||||
container = new ContainerInline();
|
||||
container = literal.Parent ??
|
||||
new ContainerInline
|
||||
{
|
||||
Span = originalLiteral.Span,
|
||||
Line = originalLiteral.Line,
|
||||
Column = originalLiteral.Column,
|
||||
};
|
||||
}
|
||||
|
||||
var abbrInline = new AbbreviationInline(abbr);
|
||||
int line;
|
||||
int column;
|
||||
var abbrInline = new AbbreviationInline(abbr)
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(i, out line, out column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
abbrInline.Span.End = abbrInline.Span.Start + match.Length - 1;
|
||||
|
||||
// Append the previous literal
|
||||
if (i > content.Start)
|
||||
{
|
||||
container.AppendChild(literal);
|
||||
// Truncate it before the abbreviation
|
||||
literal.Content.End = i - 1;
|
||||
if (literal.Parent == null)
|
||||
{
|
||||
container.AppendChild(literal);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
literal.Span.End = abbrInline.Span.Start - 1;
|
||||
// Truncate it before the abbreviation
|
||||
literal.Content.End = i - 1;
|
||||
|
||||
|
||||
// Appned the abbreviation
|
||||
container.AppendChild(abbrInline);
|
||||
|
||||
@@ -143,7 +164,12 @@ namespace Markdig.Extensions.Abbreviations
|
||||
}
|
||||
|
||||
// Process the remaining literal
|
||||
literal = new LiteralInline();
|
||||
literal = new LiteralInline()
|
||||
{
|
||||
Span = new SourceSpan(abbrInline.Span.End + 1, literal.Span.End),
|
||||
Line = line,
|
||||
Column = column + match.Length,
|
||||
};
|
||||
content.Start = indexAfterMatch;
|
||||
literal.Content = content;
|
||||
|
||||
@@ -153,14 +179,63 @@ namespace Markdig.Extensions.Abbreviations
|
||||
|
||||
if (container != null)
|
||||
{
|
||||
processor.Inline = container;
|
||||
// If we have a pending literal, we can add it
|
||||
if (literal != null)
|
||||
{
|
||||
container.AppendChild(literal);
|
||||
}
|
||||
processor.Inline = container;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsValidAbbreviation(string match, StringSlice content, int matchIndex)
|
||||
{
|
||||
// The word matched must be embraced by punctuation or whitespace or \0.
|
||||
var index = matchIndex - 1;
|
||||
while (index >= content.Start)
|
||||
{
|
||||
var c = content.PeekCharAbsolute(index);
|
||||
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.IsAlphaNumeric())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!c.IsAsciiPunctuation() || c.IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
// This will check if the next char at the end of the StringSlice is whitespace, punctuation or \0.
|
||||
var contentNew = content;
|
||||
contentNew.End = content.End + 1;
|
||||
index = matchIndex + match.Length;
|
||||
while (index <= contentNew.End)
|
||||
{
|
||||
var c = contentNew.PeekCharAbsolute(index);
|
||||
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.IsAlphaNumeric())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c.IsWhitespace())
|
||||
{
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
headingBlockParser.Closed -= HeadingBlockParser_Closed;
|
||||
headingBlockParser.Closed += HeadingBlockParser_Closed;
|
||||
}
|
||||
var paragraphBlockParser = pipeline.BlockParsers.Find<ParagraphBlockParser>();
|
||||
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
|
||||
if (paragraphBlockParser != null)
|
||||
{
|
||||
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
|
||||
@@ -59,7 +59,7 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -132,6 +132,13 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
return;
|
||||
}
|
||||
|
||||
// If id is already set, don't try to modify it
|
||||
var attributes = processor.Block.GetAttributes();
|
||||
if (attributes.Id != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a HtmlRenderer with
|
||||
stripRenderer.Render(headingBlock.Inline);
|
||||
var headingText = headingWriter.ToString();
|
||||
@@ -152,7 +159,7 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
headingBuffer.Length = 0;
|
||||
}
|
||||
|
||||
processor.Block.GetAttributes().Id = headingId;
|
||||
attributes.Id = headingId;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Markdig/Extensions/AutoLinks/AutoLinkExtension.cs
Normal file
29
src/Markdig/Extensions/AutoLinks/AutoLinkExtension.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.AutoLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to automatically create <see cref="LinkInline"/> when a link url http: or mailto: is found.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class AutoLinkExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
if (!pipeline.InlineParsers.Contains<AutoLinkParser>())
|
||||
{
|
||||
// Insert the parser before any other parsers
|
||||
pipeline.InlineParsers.Insert(0, new AutoLinkParser());
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
239
src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs
Normal file
239
src/Markdig/Extensions/AutoLinks/AutoLinkParser.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.AutoLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// The inline parser used to for autolinks.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
public class AutoLinkParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoLinkParser"/> class.
|
||||
/// </summary>
|
||||
public AutoLinkParser()
|
||||
{
|
||||
OpeningCharacters = new char[]
|
||||
{
|
||||
'h', // for http:// and https://
|
||||
'f', // for ftp://
|
||||
'm', // for mailto:
|
||||
'w', // for www.
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
// Previous char must be a whitespace or a punctuation
|
||||
var previousChar = slice.PeekCharExtra(-1);
|
||||
if (!previousChar.IsAsciiPunctuation() && !previousChar.IsWhiteSpaceOrZero())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<char> pendingEmphasis;
|
||||
// Check that an autolink is possible in the current context
|
||||
if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
// Precheck URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (!slice.MatchLowercase("ttp://", 1) && !slice.MatchLowercase("ttps://", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (!slice.MatchLowercase("tp://", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (!slice.MatchLowercase("ailto:", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (!slice.MatchLowercase("ww.", 1) || previousChar == '/') // We won't match http:/www. or /www.xxx
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
string link;
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out link))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
|
||||
if (pendingEmphasis != null)
|
||||
{
|
||||
for (int i = link.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pendingEmphasis.Contains(link[i]))
|
||||
{
|
||||
slice.Start--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < link.Length - 1)
|
||||
{
|
||||
link = link.Substring(0, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-check URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
if (string.Equals(link, "mailto:", StringComparison.OrdinalIgnoreCase) || !link.Contains("@"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
// We require at least two .
|
||||
if (link.Length <= "www.x.y".Length || link.IndexOf(".", 4, StringComparison.Ordinal) < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int line;
|
||||
int column;
|
||||
var inline = new LinkInline()
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(startPosition, out line, out column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Url = c == 'w' ? "http://" + link : link,
|
||||
IsClosed = true,
|
||||
};
|
||||
inline.Span.End = inline.Span.Start + link.Length - 1;
|
||||
inline.UrlSpan = inline.Span;
|
||||
inline.AppendChild(new LiteralInline()
|
||||
{
|
||||
Span = inline.Span,
|
||||
Line = line,
|
||||
Column = column,
|
||||
Content = new StringSlice(slice.Text, startPosition, startPosition + link.Length - 1),
|
||||
IsClosed = true
|
||||
});
|
||||
processor.Inline = inline;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, out List<char> pendingEmphasis)
|
||||
{
|
||||
pendingEmphasis = null;
|
||||
|
||||
// Case where there is a pending HtmlInline <a>
|
||||
var currentInline = processor.Inline;
|
||||
while (currentInline != null)
|
||||
{
|
||||
var htmlInline = currentInline as HtmlInline;
|
||||
if (htmlInline != null)
|
||||
{
|
||||
// If we have a </a> we don't expect nested <a>
|
||||
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// If there is a pending <a>, we can't allow a link
|
||||
if (htmlInline.Tag.StartsWith("<a", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check previous sibling and parents in the tree
|
||||
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
|
||||
}
|
||||
|
||||
// Check that we don't have any pending brackets opened (where we could have a possible markdown link)
|
||||
// NOTE: This assume that [ and ] are used for links, otherwise autolink will not work properly
|
||||
currentInline = processor.Inline;
|
||||
int countBrackets = 0;
|
||||
while (currentInline != null)
|
||||
{
|
||||
var linkDelimiterInline = currentInline as LinkDelimiterInline;
|
||||
if (linkDelimiterInline != null && linkDelimiterInline.IsActive)
|
||||
{
|
||||
if (linkDelimiterInline.Type == DelimiterType.Open)
|
||||
{
|
||||
countBrackets++;
|
||||
}
|
||||
else if (linkDelimiterInline.Type == DelimiterType.Close)
|
||||
{
|
||||
countBrackets--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Record all pending characters for emphasis
|
||||
var emphasisDelimiter = currentInline as EmphasisDelimiterInline;
|
||||
if (emphasisDelimiter != null)
|
||||
{
|
||||
if (pendingEmphasis == null)
|
||||
{
|
||||
// Not optimized for GC, but we don't expect this case much
|
||||
pendingEmphasis = new List<char>();
|
||||
}
|
||||
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
|
||||
{
|
||||
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
|
||||
}
|
||||
}
|
||||
}
|
||||
currentInline = currentInline.Parent;
|
||||
}
|
||||
|
||||
return countBrackets <= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Markdig.Extensions.Bootstrap
|
||||
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@ using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html.Inlines;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.Cites
|
||||
namespace Markdig.Extensions.Citations
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension for cite ""...""
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class CiteExtension : IMarkdownExtension
|
||||
public class CitationExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
@@ -24,7 +24,7 @@ namespace Markdig.Extensions.Cites
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
@@ -38,7 +38,7 @@ namespace Markdig.Extensions.CustomContainers
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -30,6 +30,8 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
var startPosition = processor.Start;
|
||||
|
||||
var column = processor.ColumnBeforeIndent;
|
||||
processor.NextChar();
|
||||
processor.ParseIndent();
|
||||
@@ -49,8 +51,7 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
}
|
||||
|
||||
var previousParent = paragraphBlock.Parent;
|
||||
var indexOfParagraph = previousParent.IndexOf(paragraphBlock);
|
||||
var currentDefinitionList = indexOfParagraph - 1 >= 0 ? previousParent[indexOfParagraph - 1] as DefinitionList : null;
|
||||
var currentDefinitionList = GetCurrentDefinitionList(paragraphBlock, previousParent);
|
||||
|
||||
processor.Discard(paragraphBlock);
|
||||
|
||||
@@ -62,16 +63,22 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
|
||||
if (currentDefinitionList == null)
|
||||
{
|
||||
currentDefinitionList = new DefinitionList(this);
|
||||
currentDefinitionList = new DefinitionList(this)
|
||||
{
|
||||
Span = new SourceSpan(paragraphBlock.Span.Start, processor.Line.End),
|
||||
Column = paragraphBlock.Column,
|
||||
Line = paragraphBlock.Line,
|
||||
};
|
||||
previousParent.Add(currentDefinitionList);
|
||||
}
|
||||
|
||||
var definitionItem = new DefinitionItem(this)
|
||||
{
|
||||
Column = processor.Column,
|
||||
Line = processor.LineIndex,
|
||||
Column = column,
|
||||
Span = new SourceSpan(startPosition, processor.Line.End),
|
||||
OpeningCharacter = processor.CurrentChar,
|
||||
};
|
||||
currentDefinitionList.Add(definitionItem);
|
||||
|
||||
for (int i = 0; i < paragraphBlock.Lines.Count; i++)
|
||||
{
|
||||
@@ -80,17 +87,34 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
{
|
||||
Column = paragraphBlock.Column,
|
||||
Line = line.Line,
|
||||
Span = new SourceSpan(paragraphBlock.Span.Start, paragraphBlock.Span.End),
|
||||
IsOpen = false
|
||||
};
|
||||
term.AppendLine(ref line.Slice, line.Column, line.Line);
|
||||
term.AppendLine(ref line.Slice, line.Column, line.Line, line.Position);
|
||||
definitionItem.Add(term);
|
||||
}
|
||||
|
||||
currentDefinitionList.Add(definitionItem);
|
||||
processor.Open(definitionItem);
|
||||
|
||||
// Update the end position
|
||||
currentDefinitionList.UpdateSpanEnd(processor.Line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
private static DefinitionList GetCurrentDefinitionList(ParagraphBlock paragraphBlock, ContainerBlock previousParent)
|
||||
{
|
||||
var index = previousParent.IndexOf(paragraphBlock) - 1;
|
||||
if (index < 0) return null;
|
||||
var lastBlock = previousParent[index];
|
||||
if (lastBlock is BlankLineBlock)
|
||||
{
|
||||
lastBlock = previousParent[index - 1];
|
||||
previousParent.RemoveAt(index);
|
||||
}
|
||||
return lastBlock as DefinitionList;
|
||||
}
|
||||
|
||||
public override BlockState TryContinue(BlockProcessor processor, Block block)
|
||||
{
|
||||
var definitionItem = (DefinitionItem)block;
|
||||
@@ -100,11 +124,13 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
var list = (DefinitionList)definitionItem.Parent;
|
||||
var lastBlankLine = definitionItem.LastChild as BlankLineBlock;
|
||||
|
||||
// Check if we have another definition list
|
||||
if (Array.IndexOf(OpeningCharacters, processor.CurrentChar) >= 0)
|
||||
{
|
||||
var startPosition = processor.Start;
|
||||
var column = processor.ColumnBeforeIndent;
|
||||
processor.NextChar();
|
||||
processor.ParseIndent();
|
||||
@@ -118,6 +144,8 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
{
|
||||
definitionItem.RemoveAt(definitionItem.Count - 1);
|
||||
}
|
||||
|
||||
list.Span.End = list.LastChild.Span.End;
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
@@ -126,10 +154,11 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
processor.GoToColumn(column + 4);
|
||||
}
|
||||
|
||||
var list = (DefinitionList) definitionItem.Parent;
|
||||
processor.Close(definitionItem);
|
||||
var nextDefinitionItem = new DefinitionItem(this)
|
||||
{
|
||||
Span = new SourceSpan(startPosition, processor.Line.End),
|
||||
Line = processor.LineIndex,
|
||||
Column = processor.Column,
|
||||
OpeningCharacter = processor.CurrentChar,
|
||||
};
|
||||
@@ -161,6 +190,7 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
definitionItem.RemoveAt(definitionItem.Count - 1);
|
||||
}
|
||||
|
||||
list.Span.End = list.LastChild.Span.End;
|
||||
return BlockState.Break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
{
|
||||
if (!hasOpendd)
|
||||
{
|
||||
renderer.Write("<dd>");
|
||||
renderer.Write("<dd").WriteAttributes(definitionItem).Write(">");
|
||||
countdd = 0;
|
||||
hasOpendd = true;
|
||||
}
|
||||
|
||||
31
src/Markdig/Extensions/Diagrams/DiagramExtension.cs
Normal file
31
src/Markdig/Extensions/Diagrams/DiagramExtension.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
|
||||
namespace Markdig.Extensions.Diagrams
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to allow diagrams.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class DiagramExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
{
|
||||
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
|
||||
// TODO: Add other well known diagram languages
|
||||
codeRenderer.BlocksAsDiv.Add("mermaid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Markdig.Extensions.Emoji
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Markdig.Extensions.Emoji
|
||||
/// </summary>
|
||||
public Dictionary<string, string> SmileyToEmoji { get; }
|
||||
|
||||
public override void Initialize(InlineProcessor processor)
|
||||
public override void Initialize()
|
||||
{
|
||||
var firstChars = new HashSet<char>();
|
||||
var textToMatch = new HashSet<string>();
|
||||
@@ -67,17 +67,28 @@ namespace Markdig.Extensions.Emoji
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
string match;
|
||||
|
||||
// Previous char must be a space
|
||||
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to match an existing emoji
|
||||
var startPosition = slice.Start;
|
||||
if (!textMatchHelper.TryMatch(slice.Text, slice.Start, slice.Length, out match))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have a smiley, we decode it to emoji
|
||||
string emoji;
|
||||
if (!SmileyToEmoji.TryGetValue(match, out emoji))
|
||||
{
|
||||
emoji = match;
|
||||
}
|
||||
|
||||
// Decode the eomji to unicode
|
||||
string unicode;
|
||||
if (!EmojiToUnicode.TryGetValue(emoji, out unicode))
|
||||
{
|
||||
@@ -89,7 +100,19 @@ namespace Markdig.Extensions.Emoji
|
||||
slice.Start += match.Length;
|
||||
|
||||
// Push the EmojiInline
|
||||
processor.Inline = new EmojiInline(unicode) {Match = match};
|
||||
int line;
|
||||
int column;
|
||||
processor.Inline = new EmojiInline(unicode)
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(startPosition, out line, out column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Match = match
|
||||
};
|
||||
processor.Inline.Span.End = processor.Inline.Span.Start + match.Length - 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -968,7 +991,16 @@ namespace Markdig.Extensions.Emoji
|
||||
{":large_orange_diamond:", "🔶"},
|
||||
{":large_blue_diamond:", "🔷"},
|
||||
{":small_orange_diamond:", "🔸"},
|
||||
{":small_blue_diamond:", "🔹"}
|
||||
{":small_blue_diamond:", "🔹"},
|
||||
|
||||
// Custom additions
|
||||
{ ":custom_arrow_left:", "←"},
|
||||
{ ":custom_arrow_right:", "→"},
|
||||
{ ":custom_arrow_left_right:", "↔"},
|
||||
|
||||
{ ":custom_arrow_left_strong:", "⇐"},
|
||||
{ ":custom_arrow_right_strong:", "⇒"},
|
||||
{ ":custom_arrow_left_right_strong:", "⇔"},
|
||||
};
|
||||
|
||||
SmileyToEmojiDefault = new Dictionary<string, string>()
|
||||
@@ -1038,6 +1070,15 @@ namespace Markdig.Extensions.Emoji
|
||||
{":-$", ":unamused:"},
|
||||
{";)", ":wink:"},
|
||||
{";-)", ":wink:"},
|
||||
|
||||
// Custom arrows
|
||||
{"<-", ":custom_arrow_left:" },
|
||||
{"->", ":custom_arrow_rigth:" },
|
||||
{"<->", ":custom_arrow_left_rigth:" },
|
||||
|
||||
{"<=", ":custom_arrow_left_strong:" },
|
||||
{"=>", ":custom_arrow_rigth_strong:" },
|
||||
{"<=>", ":custom_arrow_left_rigth_strong:" },
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -7,7 +7,7 @@ using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html.Inlines;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.EmphasisExtra
|
||||
namespace Markdig.Extensions.EmphasisExtras
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension for strikethrough, subscript, superscript, inserted and marked.
|
||||
@@ -85,7 +85,7 @@ namespace Markdig.Extensions.EmphasisExtra
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Markdig.Extensions.EmphasisExtra
|
||||
namespace Markdig.Extensions.EmphasisExtras
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for enabling support for extra emphasis.
|
||||
@@ -30,7 +30,9 @@ namespace Markdig.Extensions.Figures
|
||||
|
||||
// Match fenced char
|
||||
int count = 0;
|
||||
var column = processor.Column;
|
||||
var line = processor.Line;
|
||||
var startPosition = line.Start;
|
||||
char c = line.CurrentChar;
|
||||
var matchChar = c;
|
||||
while (c != '\0')
|
||||
@@ -51,6 +53,8 @@ namespace Markdig.Extensions.Figures
|
||||
|
||||
var figure = new Figure(this)
|
||||
{
|
||||
Span = new SourceSpan(startPosition, line.End),
|
||||
Line = processor.LineIndex,
|
||||
Column = processor.Column,
|
||||
OpeningCharacter = matchChar,
|
||||
OpeningCharacterCount = count
|
||||
@@ -59,8 +63,14 @@ namespace Markdig.Extensions.Figures
|
||||
line.TrimStart();
|
||||
if (!line.IsEmpty)
|
||||
{
|
||||
var caption = new FigureCaption(this) {IsOpen = false};
|
||||
caption.AppendLine(ref line, line.Start, processor.LineIndex);
|
||||
var caption = new FigureCaption(this)
|
||||
{
|
||||
Span = new SourceSpan(line.Start, line.End),
|
||||
Line = processor.LineIndex,
|
||||
Column = column + line.Start - startPosition,
|
||||
IsOpen = false
|
||||
};
|
||||
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
|
||||
figure.Add(caption);
|
||||
}
|
||||
processor.NewBlocks.Push(figure);
|
||||
@@ -76,8 +86,10 @@ namespace Markdig.Extensions.Figures
|
||||
var matchChar = figure.OpeningCharacter;
|
||||
var c = processor.CurrentChar;
|
||||
|
||||
var column = processor.Column;
|
||||
// Match if we have a closing fence
|
||||
var line = processor.Line;
|
||||
var startPosition = line.Start;
|
||||
while (c == matchChar)
|
||||
{
|
||||
c = line.NextChar();
|
||||
@@ -91,11 +103,19 @@ namespace Markdig.Extensions.Figures
|
||||
line.TrimStart();
|
||||
if (!line.IsEmpty)
|
||||
{
|
||||
var caption = new FigureCaption(this) {IsOpen = false};
|
||||
caption.AppendLine(ref line, line.Start, processor.LineIndex);
|
||||
var caption = new FigureCaption(this)
|
||||
{
|
||||
Span = new SourceSpan(line.Start, line.End),
|
||||
Line = processor.LineIndex,
|
||||
Column = column + line.Start - startPosition,
|
||||
IsOpen = false
|
||||
};
|
||||
caption.AppendLine(ref line, caption.Column, processor.LineIndex, processor.CurrentLineStartPosition);
|
||||
figure.Add(caption);
|
||||
}
|
||||
|
||||
figure.UpdateSpanEnd(line.End);
|
||||
|
||||
// Don't keep the last line
|
||||
return BlockState.BreakDiscard;
|
||||
}
|
||||
@@ -103,6 +123,8 @@ namespace Markdig.Extensions.Figures
|
||||
// Reset the indentation to the column before the indent
|
||||
processor.GoToColumn(processor.ColumnBeforeIndent);
|
||||
|
||||
figure.UpdateSpanEnd(line.End);
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Extensions.Cites;
|
||||
using Markdig.Extensions.Footers;
|
||||
using Markdig.Renderers;
|
||||
|
||||
@@ -30,7 +29,7 @@ namespace Markdig.Extensions.Figures
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Markdig.Extensions.Footers
|
||||
}
|
||||
|
||||
var column = processor.Column;
|
||||
var startPosition = processor.Start;
|
||||
|
||||
// A footer
|
||||
// A Footer marker consists of 0-3 spaces of initial indent, plus (a) the characters ^^ together with a following space, or (b) a double character ^^ not followed by a space.
|
||||
@@ -44,7 +45,13 @@ namespace Markdig.Extensions.Footers
|
||||
{
|
||||
processor.NextColumn();
|
||||
}
|
||||
processor.NewBlocks.Push(new FooterBlock(this) { OpeningCharacter = openingChar, Column = column});
|
||||
processor.NewBlocks.Push(new FooterBlock(this)
|
||||
{
|
||||
Span = new SourceSpan(startPosition, processor.Line.End),
|
||||
OpeningCharacter = openingChar,
|
||||
Column = column,
|
||||
Line = processor.LineIndex,
|
||||
});
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -60,19 +67,22 @@ namespace Markdig.Extensions.Footers
|
||||
// A footer
|
||||
// A Footer marker consists of 0-3 spaces of initial indent, plus (a) the characters ^^ together with a following space, or (b) a double character ^^ not followed by a space.
|
||||
var c = processor.CurrentChar;
|
||||
var result = BlockState.Continue;
|
||||
if (c != quote.OpeningCharacter || processor.PeekChar(1) != c)
|
||||
{
|
||||
return processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
|
||||
result = processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
|
||||
}
|
||||
|
||||
processor.NextChar(); // Skip ^^ char (1st)
|
||||
c = processor.NextChar(); // Skip ^^ char (2nd)
|
||||
if (c.IsSpace())
|
||||
else
|
||||
{
|
||||
processor.NextChar(); // Skip following space
|
||||
processor.NextChar(); // Skip ^^ char (1st)
|
||||
c = processor.NextChar(); // Skip ^^ char (2nd)
|
||||
if (c.IsSpace())
|
||||
{
|
||||
processor.NextChar(); // Skip following space
|
||||
}
|
||||
block.UpdateSpanEnd(processor.Line.End);
|
||||
}
|
||||
|
||||
return BlockState.Continue;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Markdig.Extensions.Footers
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -34,6 +34,11 @@ namespace Markdig.Extensions.Footnotes
|
||||
/// </summary>
|
||||
public List<FootnoteLink> Links { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The label span
|
||||
/// </summary>
|
||||
public SourceSpan LabelSpan;
|
||||
|
||||
internal bool IsLastLineEmpty { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Markdig.Extensions.Footnotes
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -26,9 +26,14 @@ namespace Markdig.Extensions.Footnotes
|
||||
}
|
||||
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
{
|
||||
return TryOpen(processor, false);
|
||||
}
|
||||
|
||||
private BlockState TryOpen(BlockProcessor processor, bool isContinue)
|
||||
{
|
||||
// We expect footnote to appear only at document level and not indented more than a code indent block
|
||||
if (processor.IsCodeIndent || processor.CurrentContainer.GetType() != typeof(MarkdownDocument) )
|
||||
if (processor.IsCodeIndent || (!isContinue && processor.CurrentContainer.GetType() != typeof(MarkdownDocument)) || (isContinue && !(processor.CurrentContainer is Footnote)))
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
@@ -36,19 +41,24 @@ namespace Markdig.Extensions.Footnotes
|
||||
var saved = processor.Column;
|
||||
string label;
|
||||
int start = processor.Start;
|
||||
if (!LinkHelper.TryParseLabel(ref processor.Line, false, out label) || !label.StartsWith("^") || processor.CurrentChar != ':')
|
||||
SourceSpan labelSpan;
|
||||
if (!LinkHelper.TryParseLabel(ref processor.Line, false, out label, out labelSpan) || !label.StartsWith("^") || processor.CurrentChar != ':')
|
||||
{
|
||||
processor.GoToColumn(saved);
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
|
||||
// Advance the column
|
||||
int deltaColumn = processor.Start - start;
|
||||
processor.Column = processor.Column + deltaColumn;
|
||||
|
||||
processor.NextChar(); // Skip ':'
|
||||
|
||||
var footnote = new Footnote(this) {Label = label};
|
||||
var footnote = new Footnote(this)
|
||||
{
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
};
|
||||
|
||||
// Maintain a list of all footnotes at document level
|
||||
var footnotes = processor.Document.GetData(DocumentKey) as FootnoteGroup;
|
||||
@@ -83,9 +93,23 @@ namespace Markdig.Extensions.Footnotes
|
||||
return BlockState.ContinueDiscard;
|
||||
}
|
||||
|
||||
if (footnote.IsLastLineEmpty && processor.Start == 0)
|
||||
if (processor.Column == 0)
|
||||
{
|
||||
return BlockState.Break;
|
||||
if (footnote.IsLastLineEmpty)
|
||||
{
|
||||
// Close the current footnote
|
||||
processor.Close(footnote);
|
||||
|
||||
// Parse any opening footnote
|
||||
return TryOpen(processor);
|
||||
}
|
||||
|
||||
// Make sure that consecutive footnotes without a blanklines are parsed correctly
|
||||
if (TryOpen(processor, true) == BlockState.Continue)
|
||||
{
|
||||
processor.Close(footnote);
|
||||
return BlockState.Continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
footnote.IsLastLineEmpty = false;
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -46,17 +46,25 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
// Try to find if there is any attributes { in the info string on the first line of a FencedCodeBlock
|
||||
if (line.Start < line.End)
|
||||
{
|
||||
var indexOfAttributes = line.Text.LastIndexOf('{', line.End);
|
||||
int indexOfAttributes = line.IndexOf('{');
|
||||
if (indexOfAttributes >= 0)
|
||||
{
|
||||
// Work on a copy
|
||||
var copy = line;
|
||||
copy.Start = indexOfAttributes;
|
||||
var startOfAttributes = copy.Start;
|
||||
HtmlAttributes attributes;
|
||||
if (GenericAttributesParser.TryParse(ref copy, out attributes))
|
||||
{
|
||||
var htmlAttributes = block.GetAttributes();
|
||||
attributes.CopyTo(htmlAttributes);
|
||||
|
||||
// Update position for HtmlAttributes
|
||||
htmlAttributes.Line = processor.LineIndex;
|
||||
htmlAttributes.Column = startOfAttributes - processor.CurrentLineStartPosition; // This is not accurate with tabs!
|
||||
htmlAttributes.Span.Start = startOfAttributes;
|
||||
htmlAttributes.Span.End = copy.Start - 1;
|
||||
|
||||
line.End = indexOfAttributes - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
HtmlAttributes attributes;
|
||||
var startPosition = slice.Start;
|
||||
if (TryParse(ref slice, out attributes))
|
||||
{
|
||||
var inline = processor.Inline;
|
||||
@@ -58,12 +59,20 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
{
|
||||
objectToAttach = parent[indexOfParagraph + 1];
|
||||
// We can remove the paragraph as it is empty
|
||||
parent.RemoveAt(indexOfParagraph);
|
||||
paragraph.RemoveAfterProcessInlines = true;
|
||||
}
|
||||
}
|
||||
|
||||
var currentHtmlAttributes = objectToAttach.GetAttributes();
|
||||
attributes.CopyTo(currentHtmlAttributes);
|
||||
attributes.CopyTo(currentHtmlAttributes, true, false);
|
||||
|
||||
// Update the position of the attributes
|
||||
int line;
|
||||
int column;
|
||||
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out line, out column);
|
||||
currentHtmlAttributes.Line = line;
|
||||
currentHtmlAttributes.Column = column;
|
||||
currentHtmlAttributes.Span.End = currentHtmlAttributes.Span.Start + slice.Start - startPosition - 1;
|
||||
|
||||
// We don't set the processor.Inline as we don't want to add attach attributes to a particular entity
|
||||
return true;
|
||||
@@ -162,9 +171,10 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
|
||||
// Skip any whitespaces
|
||||
line.TrimStart();
|
||||
c = line.CurrentChar;
|
||||
|
||||
// Handle boolean properties that are not followed by =
|
||||
if ((hasSpace && (line.CurrentChar == '.' || line.CurrentChar == '#' || IsStartAttributeName(line.CurrentChar))) || line.CurrentChar == '}')
|
||||
if ((hasSpace && (c == '.' || c == '#' || IsStartAttributeName(c))) || c == '}')
|
||||
{
|
||||
if (properties == null)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Markdig.Extensions.Hardlines
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
31
src/Markdig/Extensions/JiraLinks/JiraLink.cs
Normal file
31
src/Markdig/Extensions/JiraLinks/JiraLink.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System.Diagnostics;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.JiraLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Model for a JIRA link item
|
||||
/// </summary>
|
||||
[DebuggerDisplay("{ProjectKey}-{Issue}")]
|
||||
public class JiraLink : LinkInline
|
||||
{
|
||||
public JiraLink()
|
||||
{
|
||||
IsClosed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JIRA Project Key
|
||||
/// </summary>
|
||||
public StringSlice ProjectKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// JIRA Issue Number
|
||||
/// </summary>
|
||||
public StringSlice Issue { get; set; }
|
||||
}
|
||||
}
|
||||
37
src/Markdig/Extensions/JiraLinks/JiraLinkExtension.cs
Normal file
37
src/Markdig/Extensions/JiraLinks/JiraLinkExtension.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using Markdig.Parsers.Inlines;
|
||||
using Markdig.Renderers;
|
||||
|
||||
namespace Markdig.Extensions.JiraLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple inline parser extension for Markdig to find, and
|
||||
/// automatically add links to JIRA issue numbers.
|
||||
/// </summary>
|
||||
public class JiraLinkExtension : IMarkdownExtension
|
||||
{
|
||||
private readonly JiraLinkOptions _options;
|
||||
|
||||
public JiraLinkExtension(JiraLinkOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
if (!pipeline.InlineParsers.Contains<JiraLinkInlineParser>())
|
||||
{
|
||||
// Insert the parser before the link inline parser
|
||||
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new JiraLinkInlineParser(_options));
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
// Nothing to setup, JiraLinks used a normal LinkInlineRenderer
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
115
src/Markdig/Extensions/JiraLinks/JiraLinkInlineParser.cs
Normal file
115
src/Markdig/Extensions/JiraLinks/JiraLinkInlineParser.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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.Text;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.JiraLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds and replaces JIRA links inline
|
||||
/// </summary>
|
||||
public class JiraLinkInlineParser : InlineParser
|
||||
{
|
||||
private readonly JiraLinkOptions _options;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public JiraLinkInlineParser(JiraLinkOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_baseUrl = _options.GetUrl();
|
||||
//look for uppercase chars at the start (for the project key)
|
||||
OpeningCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
|
||||
}
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
// Allow preceding whitespace or `(`
|
||||
var pc = slice.PeekCharExtra(-1);
|
||||
if (!pc.IsWhiteSpaceOrZero() && pc != '(')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var current = slice.CurrentChar;
|
||||
|
||||
var startKey = slice.Start;
|
||||
var endKey = slice.Start;
|
||||
|
||||
//read as many uppercase characters as required - project key
|
||||
while (current.IsAlphaUpper())
|
||||
{
|
||||
endKey = slice.Start;
|
||||
current = slice.NextChar();
|
||||
}
|
||||
|
||||
//require a '-' between key and issue number
|
||||
if (!current.Equals('-'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
current = slice.NextChar(); // skip -
|
||||
|
||||
//read as many numbers as required - issue number
|
||||
if (!current.IsDigit())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startIssue = slice.Start;
|
||||
var endIssue = slice.Start;
|
||||
|
||||
while (current.IsDigit())
|
||||
{
|
||||
endIssue = slice.Start;
|
||||
current = slice.NextChar();
|
||||
}
|
||||
|
||||
if (!current.IsWhiteSpaceOrZero() && current != ')') //can be followed only by a whitespace or `)`
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int line;
|
||||
int column;
|
||||
|
||||
var jiraLink = new JiraLink() //create the link at the relevant position
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(slice.Start, out line, out column)
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Issue = new StringSlice(slice.Text, startIssue, endIssue),
|
||||
ProjectKey = new StringSlice(slice.Text, startKey, endKey),
|
||||
};
|
||||
jiraLink.Span.End = jiraLink.Span.Start + (endIssue - startKey);
|
||||
|
||||
// Builds the Url
|
||||
var builder = new StringBuilder();
|
||||
builder.Append(_baseUrl).Append('/').Append(jiraLink.ProjectKey).Append('-').Append(jiraLink.Issue);
|
||||
jiraLink.Url = builder.ToString();
|
||||
|
||||
// Builds the Label
|
||||
builder.Length = 0;
|
||||
builder.Append(jiraLink.ProjectKey).Append('-').Append(jiraLink.Issue);
|
||||
jiraLink.AppendChild(new LiteralInline(builder.ToString()));
|
||||
|
||||
if (_options.OpenInNewWindow)
|
||||
{
|
||||
jiraLink.GetAttributes().AddProperty("target", "blank");
|
||||
}
|
||||
|
||||
processor.Inline = jiraLink;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
57
src/Markdig/Extensions/JiraLinks/JiraLinkOptions.cs
Normal file
57
src/Markdig/Extensions/JiraLinks/JiraLinkOptions.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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.Text;
|
||||
|
||||
namespace Markdig.Extensions.JiraLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Available options for replacing JIRA links
|
||||
/// </summary>
|
||||
public class JiraLinkOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The base Url (e.g. `https://mycompany.atlassian.net`)
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The base path after the base url (default is `/browse`)
|
||||
/// </summary>
|
||||
public string BasePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the link open in a new window when clicked
|
||||
/// </summary>
|
||||
public bool OpenInNewWindow { get; set; }
|
||||
|
||||
public JiraLinkOptions(string baseUrl)
|
||||
{
|
||||
OpenInNewWindow = true; //default
|
||||
BaseUrl = baseUrl;
|
||||
BasePath = "/browse";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the full url composed of the <see cref="BaseUrl"/> and <see cref="BasePath"/> with no trailing `/`
|
||||
/// </summary>
|
||||
public virtual string GetUrl()
|
||||
{
|
||||
var url = new StringBuilder();
|
||||
var baseUrl = BaseUrl;
|
||||
if (baseUrl != null)
|
||||
{
|
||||
url.Append(baseUrl.TrimEnd('/'));
|
||||
}
|
||||
|
||||
url.Append("/");
|
||||
|
||||
if (BasePath != null)
|
||||
{
|
||||
url.Append(BasePath.Trim('/'));
|
||||
}
|
||||
|
||||
return url.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers;
|
||||
|
||||
namespace Markdig.Extensions.ListExtra
|
||||
namespace Markdig.Extensions.ListExtras
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension for adding new type of list items (a., A., i., I.)
|
||||
@@ -22,7 +22,7 @@ namespace Markdig.Extensions.ListExtra
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
|
||||
namespace Markdig.Extensions.ListExtra
|
||||
namespace Markdig.Extensions.ListExtras
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Parser that adds supports for parsing alpha/roman list items (e.g: `a)` or `a.` or `ii.` or `II.`)
|
||||
/// </summary>
|
||||
@@ -52,7 +54,7 @@ namespace Markdig.Extensions.ListExtra
|
||||
c = state.NextChar();
|
||||
}
|
||||
|
||||
result.OrderedStart = state.Line.Text.Substring(startChar, endChar - startChar + 1);
|
||||
result.OrderedStart = CharHelper.RomanToArabic(state.Line.Text.Substring(startChar, endChar - startChar + 1)).ToString();
|
||||
result.BulletType = isRomanLow ? 'i' : 'I';
|
||||
result.DefaultOrderedStart = isRomanLow ? "i" : "I";
|
||||
}
|
||||
@@ -60,7 +62,7 @@ namespace Markdig.Extensions.ListExtra
|
||||
{
|
||||
// otherwise we expect a regular alpha lettered list with a single character.
|
||||
var isUpper = c.IsAlphaUpper();
|
||||
result.OrderedStart = c.ToString();
|
||||
result.OrderedStart = (Char.ToUpperInvariant(c) - 64).ToString();
|
||||
result.BulletType = isUpper ? 'A' : 'a';
|
||||
result.DefaultOrderedStart = isUpper ? "A" : "a";
|
||||
state.NextChar();
|
||||
@@ -1,15 +1,18 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.Mathematics
|
||||
{
|
||||
/// <summary>
|
||||
/// The block parser for a <see cref="MathBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.FencedBlockParserBase{Markdig.Extensions.Mathematics.MathBlock}" />
|
||||
/// <seealso cref="MathBlock" />
|
||||
public class MathBlockParser : FencedBlockParserBase<MathBlock>
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,6 +25,8 @@ namespace Markdig.Extensions.Mathematics
|
||||
MinimumMatchCount = 2;
|
||||
MaximumMatchCount = 2;
|
||||
|
||||
InfoParser = NoInfoParser;
|
||||
|
||||
DefaultClass = "math";
|
||||
|
||||
// We don't need a prefix
|
||||
@@ -39,5 +44,19 @@ namespace Markdig.Extensions.Mathematics
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
private static bool NoInfoParser(BlockProcessor state, ref StringSlice line, IFencedBlock fenced)
|
||||
{
|
||||
var c = line.CurrentChar;
|
||||
for (int i = line.Start; i <= line.End; i++)
|
||||
{
|
||||
c = line.Text[i];
|
||||
if (!c.IsSpaceOrTab())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Markdig.Extensions.Mathematics
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.Mathematics
|
||||
{
|
||||
@@ -12,7 +13,7 @@ namespace Markdig.Extensions.Mathematics
|
||||
/// An inline parser for <see cref="MathInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
/// <seealso cref="Markdig.Parsers.IDelimiterProcessor" />
|
||||
/// <seealso cref="IPostInlineProcessor" />
|
||||
public class MathInlineParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
@@ -38,6 +39,8 @@ namespace Markdig.Extensions.Mathematics
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
|
||||
// Match the opened $ or $$
|
||||
int openDollars = 1; // we have at least a $
|
||||
var c = slice.NextChar();
|
||||
@@ -47,11 +50,18 @@ namespace Markdig.Extensions.Mathematics
|
||||
c = slice.NextChar();
|
||||
}
|
||||
|
||||
bool canOpen;
|
||||
bool canClose;
|
||||
// Check that opening $/$$ is correct, using the same heuristics than for emphasis delimiters
|
||||
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
|
||||
if (!canOpen)
|
||||
bool openPrevIsPunctuation;
|
||||
bool openPrevIsWhiteSpace;
|
||||
bool openNextIsPunctuation;
|
||||
bool openNextIsWhiteSpace;
|
||||
bool openNextIsDigit = c.IsDigit();
|
||||
pc.CheckUnicodeCategory(out openPrevIsWhiteSpace, out openPrevIsPunctuation);
|
||||
c.CheckUnicodeCategory(out openNextIsWhiteSpace, out openNextIsPunctuation);
|
||||
|
||||
// Check that opening $/$$ is correct, using the different heuristics than for emphasis delimiters
|
||||
// If a $/$$ is not preceded by a whitespace or punctuation, or followed by a digit
|
||||
// this is a not a math block
|
||||
if ((!openPrevIsWhiteSpace && !openPrevIsPunctuation) || openNextIsDigit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -59,31 +69,60 @@ namespace Markdig.Extensions.Mathematics
|
||||
bool isMatching = false;
|
||||
int closeDollars = 0;
|
||||
|
||||
// Eat any leading spaces
|
||||
while (c.IsSpaceOrTab())
|
||||
{
|
||||
c = slice.NextChar();
|
||||
}
|
||||
|
||||
var start = slice.Start;
|
||||
var end = 0;
|
||||
|
||||
pc = match;
|
||||
var lastWhiteSpace = -1;
|
||||
while (c != '\0')
|
||||
{
|
||||
// Count new '\n'
|
||||
if (c == '\n')
|
||||
// Don't allow newline in an inline math expression
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
processor.LocalLineIndex++;
|
||||
processor.LineIndex++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't process sticks if we have a '\' as a previous char
|
||||
if (pc != '\\' )
|
||||
{
|
||||
while (c == match)
|
||||
// Record continous whitespaces at the end
|
||||
if (c.IsSpaceOrTab())
|
||||
{
|
||||
closeDollars++;
|
||||
c = slice.NextChar();
|
||||
if (lastWhiteSpace < 0)
|
||||
{
|
||||
lastWhiteSpace = slice.Start;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasClosingDollars = c == match;
|
||||
if (hasClosingDollars)
|
||||
{
|
||||
while (c == match)
|
||||
{
|
||||
closeDollars++;
|
||||
c = slice.NextChar();
|
||||
}
|
||||
}
|
||||
|
||||
if (closeDollars >= openDollars)
|
||||
{
|
||||
break;
|
||||
if (closeDollars >= openDollars)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lastWhiteSpace = -1;
|
||||
if (hasClosingDollars)
|
||||
{
|
||||
pc = match;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pc = match;
|
||||
}
|
||||
|
||||
if (closeDollars > 0)
|
||||
@@ -99,23 +138,45 @@ namespace Markdig.Extensions.Mathematics
|
||||
|
||||
if (closeDollars >= openDollars)
|
||||
{
|
||||
// Check that closing $/$$ is correct
|
||||
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
|
||||
if (!canClose || c.IsDigit())
|
||||
bool closePrevIsPunctuation;
|
||||
bool closePrevIsWhiteSpace;
|
||||
bool closeNextIsPunctuation;
|
||||
bool closeNextIsWhiteSpace;
|
||||
pc.CheckUnicodeCategory(out closePrevIsWhiteSpace, out closePrevIsPunctuation);
|
||||
c.CheckUnicodeCategory(out closeNextIsWhiteSpace, out closeNextIsPunctuation);
|
||||
|
||||
// A closing $/$$ should be followed by at least a punctuation or a whitespace
|
||||
// and if the character after an openning $/$$ was a whitespace, it should be
|
||||
// a whitespace as well for the character preceding the closing of $/$$
|
||||
if ((!closeNextIsPunctuation && !closeNextIsWhiteSpace) || (openNextIsWhiteSpace != closePrevIsWhiteSpace))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (closePrevIsWhiteSpace && lastWhiteSpace > 0)
|
||||
{
|
||||
end = lastWhiteSpace + openDollars - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
end = slice.Start - 1;
|
||||
}
|
||||
|
||||
// Create a new MathInline
|
||||
int line;
|
||||
int column;
|
||||
var inline = new MathInline()
|
||||
{
|
||||
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.End)),
|
||||
Line = line,
|
||||
Column = column,
|
||||
Delimiter = match,
|
||||
DelimiterCount = openDollars,
|
||||
Content = slice
|
||||
};
|
||||
inline.Content.Start = start;
|
||||
// We substract the end to the number of opening $ to keep inside the block the additionals $
|
||||
inline.Content.End = inline.Content.End - openDollars;
|
||||
inline.Content.End = end - openDollars;
|
||||
|
||||
// Add the default class if necessary
|
||||
if (DefaultClass != null)
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Renderers.Html.Inlines;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.Medias
|
||||
namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension for extending image Markdown links in case a video or an audio file is linked and output proper link.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class MediaExtension : IMarkdownExtension
|
||||
public class MediaLinkExtension : IMarkdownExtension
|
||||
{
|
||||
public MediaExtension() : this(new MediaOptions())
|
||||
public MediaLinkExtension() : this(new MediaOptions())
|
||||
{
|
||||
}
|
||||
|
||||
public MediaExtension(MediaOptions options)
|
||||
public MediaLinkExtension(MediaOptions options)
|
||||
{
|
||||
Options = options ?? new MediaOptions();
|
||||
}
|
||||
@@ -30,7 +31,7 @@ namespace Markdig.Extensions.Medias
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
@@ -49,7 +50,8 @@ namespace Markdig.Extensions.Medias
|
||||
if (linkInline.IsImage && linkInline.Url != null)
|
||||
{
|
||||
Uri uri;
|
||||
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri))
|
||||
// Only process absolute Uri
|
||||
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri) && uri.IsAbsoluteUri)
|
||||
{
|
||||
var htmlAttributes = new HtmlAttributes();
|
||||
var fromAttributes = linkInline.TryGetAttributes();
|
||||
@@ -113,7 +115,6 @@ namespace Markdig.Extensions.Medias
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
|
||||
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
|
||||
renderer.Write("</iframe>");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
// 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;
|
||||
|
||||
namespace Markdig.Extensions.Medias
|
||||
namespace Markdig.Extensions.MediaLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for the <see cref="MediaExtension"/>.
|
||||
/// Options for the <see cref="MediaLinkExtension"/>.
|
||||
/// </summary>
|
||||
public class MediaOptions
|
||||
{
|
||||
34
src/Markdig/Extensions/NoRefLinks/NoFollowLinksExtension.cs
Normal file
34
src/Markdig/Extensions/NoRefLinks/NoFollowLinksExtension.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.NoRefLinks
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to automatically render rel=nofollow to all links in an HTML output.
|
||||
/// </summary>
|
||||
public class NoFollowLinksExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var linkRenderer = renderer.ObjectRenderers.Find<LinkInlineRenderer>();
|
||||
if (linkRenderer != null)
|
||||
{
|
||||
linkRenderer.AutoRelNoFollow = true;
|
||||
}
|
||||
|
||||
var autolinkRenderer = renderer.ObjectRenderers.Find<AutolinkInlineRenderer>();
|
||||
if (autolinkRenderer != null)
|
||||
{
|
||||
autolinkRenderer.AutoRelNoFollow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Renderers;
|
||||
|
||||
namespace Markdig.Extensions.NonAsciiNoEscape
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension that will disable URI escape with % characters for non-US-ASCII characters in order to workaround a bug under IE/Edge with local file links containing non US-ASCII chars. DO NOT USE OTHERWISE.
|
||||
/// </summary>
|
||||
public class NonAsciiNoEscapeExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
{
|
||||
htmlRenderer.UseNonAsciiNoEscape = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/Markdig/Extensions/PragmaLines/PragmaLineExtension.cs
Normal file
79
src/Markdig/Extensions/PragmaLines/PragmaLineExtension.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.PragmaLines
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to a span for each line containing the original line id (using id = pragma-line#line_number_zero_based)
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
public class PragmaLineExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
|
||||
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
|
||||
{
|
||||
int index = 0;
|
||||
AddPragmas(document, ref index);
|
||||
}
|
||||
|
||||
private static void AddPragmas(Block block, ref int index)
|
||||
{
|
||||
var attribute = block.GetAttributes();
|
||||
var pragmaId = GetPragmaId(block);
|
||||
if ( attribute.Id == null)
|
||||
{
|
||||
attribute.Id = pragmaId;
|
||||
}
|
||||
else if (block.Parent != null)
|
||||
{
|
||||
var heading = block as HeadingBlock;
|
||||
|
||||
// If we have a heading, we will try to add the tag inside it
|
||||
// otherwise we will add it just before
|
||||
var tag = $"<a id=\"{pragmaId}\"></a>";
|
||||
if (heading?.Inline?.FirstChild != null)
|
||||
{
|
||||
heading.Inline.FirstChild.InsertBefore(new HtmlInline() { Tag = tag });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
block.Parent.Insert(index, new HtmlBlock(null) { Lines = new StringLineGroup(tag) });
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
var container = block as ContainerBlock;
|
||||
if (container != null)
|
||||
{
|
||||
for (int i = 0; i < container.Count; i++)
|
||||
{
|
||||
var subBlock = container[i];
|
||||
AddPragmas(subBlock, ref i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPragmaId(Block block)
|
||||
{
|
||||
return $"pragma-line-{block.Line}";
|
||||
}
|
||||
}
|
||||
}
|
||||
99
src/Markdig/Extensions/SelfPipeline/SelfPipelineExtension.cs
Normal file
99
src/Markdig/Extensions/SelfPipeline/SelfPipelineExtension.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Markdig.Renderers;
|
||||
|
||||
namespace Markdig.Extensions.SelfPipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to enable SelfPipeline, to configure a Markdown parsing/convertion to HTML automatically
|
||||
/// from an embedded special tag in the input text <code><!--markdig:extensions--></code> where extensions is a string
|
||||
/// that specifies the extensions to use for the pipeline as exposed by <see cref="MarkdownExtensions.Configure"/> extension method
|
||||
/// on the <see cref="MarkdownPipelineBuilder"/>. This extension will invalidate all other extensions and will override them.
|
||||
/// </summary>
|
||||
public sealed class SelfPipelineExtension : IMarkdownExtension
|
||||
{
|
||||
public const string DefaultTag = "markdig";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SelfPipelineExtension"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tag">The matching start tag.</param>
|
||||
/// <param name="defaultExtensions">The default extensions.</param>
|
||||
/// <exception cref="System.ArgumentException">Tag cannot contain `<` or `>` characters</exception>
|
||||
public SelfPipelineExtension(string tag = null, string defaultExtensions = null)
|
||||
{
|
||||
tag = tag?.Trim();
|
||||
tag = string.IsNullOrEmpty(tag) ? DefaultTag : tag;
|
||||
if (tag.IndexOfAny(new []{'<', '>'}) >= 0)
|
||||
{
|
||||
throw new ArgumentException("Tag cannot contain `<` or `>` characters", nameof(tag));
|
||||
}
|
||||
|
||||
if (defaultExtensions != null)
|
||||
{
|
||||
// Check that this default pipeline is supported
|
||||
// Will throw an ArgumentInvalidException if not
|
||||
new MarkdownPipelineBuilder().Configure(defaultExtensions);
|
||||
}
|
||||
DefaultExtensions = defaultExtensions;
|
||||
SelfPipelineHintTagStart = "<!--" + tag + ":";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default pipeline to configure if no tag was found in the input text. Default is <c>null</c> (core pipeline).
|
||||
/// </summary>
|
||||
public string DefaultExtensions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the self pipeline hint tag start that will be matched.
|
||||
/// </summary>
|
||||
public string SelfPipelineHintTagStart { get; }
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
// Make sure that this pipeline has only one extension (itself)
|
||||
if (pipeline.Extensions.Count > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The SelfPipeline extension cannot be configured with other extensions");
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pipeline automatically configured from an input markdown based on the presence of the configuration tag.
|
||||
/// </summary>
|
||||
/// <param name="inputText">The input text.</param>
|
||||
/// <returns>The pipeline configured from the input</returns>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public MarkdownPipeline CreatePipelineFromInput(string inputText)
|
||||
{
|
||||
if (inputText == null) throw new ArgumentNullException(nameof(inputText));
|
||||
|
||||
var builder = new MarkdownPipelineBuilder();
|
||||
string defaultConfig = DefaultExtensions;
|
||||
var indexOfSelfPipeline = inputText.IndexOf(SelfPipelineHintTagStart, StringComparison.OrdinalIgnoreCase);
|
||||
if (indexOfSelfPipeline >= 0)
|
||||
{
|
||||
var optionStart = indexOfSelfPipeline + SelfPipelineHintTagStart.Length;
|
||||
var endOfTag = inputText.IndexOf("-->", optionStart, StringComparison.OrdinalIgnoreCase);
|
||||
if (endOfTag >= 0)
|
||||
{
|
||||
defaultConfig = inputText.Substring(optionStart, endOfTag - optionStart).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(defaultConfig))
|
||||
{
|
||||
builder.Configure(defaultConfig);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,14 @@ namespace Markdig.Extensions.SmartyPants
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
if (!pipeline.InlineParsers.Contains<SmaryPantsInlineParser>())
|
||||
if (!pipeline.InlineParsers.Contains<SmartyPantsInlineParser>())
|
||||
{
|
||||
// Insert the parser after the code span parser
|
||||
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(new SmaryPantsInlineParser());
|
||||
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(new SmartyPantsInlineParser());
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.SmartyPants
|
||||
@@ -11,14 +12,14 @@ namespace Markdig.Extensions.SmartyPants
|
||||
/// <summary>
|
||||
/// The inline parser for SmartyPants.
|
||||
/// </summary>
|
||||
public class SmaryPantsInlineParser : InlineParser
|
||||
public class SmartyPantsInlineParser : InlineParser, IPostInlineProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SmaryPantsInlineParser"/> class.
|
||||
/// Initializes a new instance of the <see cref="SmartyPantsInlineParser"/> class.
|
||||
/// </summary>
|
||||
public SmaryPantsInlineParser()
|
||||
public SmartyPantsInlineParser()
|
||||
{
|
||||
OpeningCharacters = new[] {'`', '\'', '"', '<', '>', '.', '-'};
|
||||
OpeningCharacters = new[] {'\'', '"', '<', '>', '.', '-'};
|
||||
}
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
@@ -30,6 +31,8 @@ namespace Markdig.Extensions.SmartyPants
|
||||
// " “ ” “ ” 'left-double-quote', 'right-double-quote'
|
||||
// << >> « » « » 'left-angle-quote', 'right-angle-quote'
|
||||
// ... … … 'ellipsis'
|
||||
|
||||
// Special case: – and — are handle as a PostProcess step to avoid conflicts with pipetables header separator row
|
||||
// -- – – 'ndash'
|
||||
// --- — — 'mdash'
|
||||
|
||||
@@ -37,18 +40,13 @@ namespace Markdig.Extensions.SmartyPants
|
||||
var c = slice.CurrentChar;
|
||||
var openingChar = c;
|
||||
|
||||
var startingPosition = slice.Start;
|
||||
|
||||
// undefined first
|
||||
var type = (SmartyPantType) 0;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '`':
|
||||
if (slice.PeekChar(1) == '`')
|
||||
{
|
||||
slice.NextChar();
|
||||
type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines
|
||||
}
|
||||
break;
|
||||
case '\'':
|
||||
type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines
|
||||
if (slice.PeekChar(1) == '\'')
|
||||
@@ -81,12 +79,9 @@ namespace Markdig.Extensions.SmartyPants
|
||||
case '-':
|
||||
if (slice.NextChar() == '-')
|
||||
{
|
||||
type = SmartyPantType.Dash2;
|
||||
if (slice.PeekChar(1) == '-')
|
||||
{
|
||||
slice.NextChar();
|
||||
type = SmartyPantType.Dash3;
|
||||
}
|
||||
var quotePants = GetOrCreateState(processor);
|
||||
quotePants.HasDash = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -161,20 +156,23 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
|
||||
// Create the SmartyPant inline
|
||||
int line;
|
||||
int column;
|
||||
var pant = new SmartyPant()
|
||||
{
|
||||
Span = {Start = processor.GetSourcePosition(startingPosition, out line, out column)},
|
||||
Line = line,
|
||||
Column = column,
|
||||
OpeningCharacter = openingChar,
|
||||
Type = type
|
||||
};
|
||||
pant.Span.End = pant.Span.Start + slice.Start - startingPosition - 1;
|
||||
|
||||
// We will check in a post-process step for balanaced open/close quotes
|
||||
if (postProcess)
|
||||
{
|
||||
var quotePants = processor.ParserStates[Index] as List<SmartyPant>;
|
||||
if (quotePants == null)
|
||||
{
|
||||
processor.ParserStates[Index] = quotePants = new List<SmartyPant>();
|
||||
}
|
||||
var quotePants = GetOrCreateState(processor);
|
||||
|
||||
// Register only if we don't have yet any quotes
|
||||
if (quotePants.Count == 0)
|
||||
{
|
||||
@@ -187,11 +185,21 @@ namespace Markdig.Extensions.SmartyPants
|
||||
return true;
|
||||
}
|
||||
|
||||
private ListSmartyPants GetOrCreateState(InlineProcessor processor)
|
||||
{
|
||||
var quotePants = processor.ParserStates[Index] as ListSmartyPants;
|
||||
if (quotePants == null)
|
||||
{
|
||||
processor.ParserStates[Index] = quotePants = new ListSmartyPants();
|
||||
}
|
||||
return quotePants;
|
||||
}
|
||||
|
||||
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline inline)
|
||||
{
|
||||
processor.Block.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
|
||||
|
||||
var pants = (List<SmartyPant>) processor.ParserStates[Index];
|
||||
var pants = (ListSmartyPants) processor.ParserStates[Index];
|
||||
|
||||
// We only change quote into left or right quotes if we find proper balancing
|
||||
var previousIndices = new int[3] {-1, -1, -1};
|
||||
@@ -241,14 +249,19 @@ namespace Markdig.Extensions.SmartyPants
|
||||
{
|
||||
if (quote.Type == expectedRightQuote)
|
||||
{
|
||||
// Replace all intermediate unmatched left or right SmartyPants to there literal equivalent
|
||||
// Replace all intermediate unmatched left or right SmartyPants to their literal equivalent
|
||||
pants.RemoveAt(i);
|
||||
i--;
|
||||
for (int j = i; j > previousIndex; j--)
|
||||
{
|
||||
var toReplace = pants[j];
|
||||
pants.RemoveAt(j);
|
||||
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString()));
|
||||
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString())
|
||||
{
|
||||
Span = toReplace.Span,
|
||||
Line = toReplace.Line,
|
||||
Column = toReplace.Column,
|
||||
});
|
||||
i--;
|
||||
}
|
||||
|
||||
@@ -266,10 +279,102 @@ namespace Markdig.Extensions.SmartyPants
|
||||
// If we have any quotes lefts, replace them by there literal equivalent
|
||||
foreach (var quote in pants)
|
||||
{
|
||||
quote.ReplaceBy(new LiteralInline(quote.ToString()));
|
||||
quote.ReplaceBy(new LiteralInline(quote.ToString())
|
||||
{
|
||||
Span = quote.Span,
|
||||
Line = quote.Line,
|
||||
Column = quote.Column,
|
||||
});
|
||||
}
|
||||
|
||||
pants.Clear();
|
||||
}
|
||||
|
||||
bool IPostInlineProcessor.PostProcess(InlineProcessor state, Inline root, Inline lastChild, int postInlineProcessorIndex,
|
||||
bool isFinalProcessing)
|
||||
{
|
||||
// Don't try to process anything if there are no dash
|
||||
var quotePants = state.ParserStates[Index] as ListSmartyPants;
|
||||
if (quotePants == null || !quotePants.HasDash)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var child = root;
|
||||
var pendingContainers = new Stack<Inline>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (child != null)
|
||||
{
|
||||
var next = child.NextSibling;
|
||||
|
||||
if (child is LiteralInline)
|
||||
{
|
||||
var literal = (LiteralInline) child;
|
||||
|
||||
var startIndex = 0;
|
||||
|
||||
var indexOfDash = literal.Content.IndexOf("--", startIndex);
|
||||
if (indexOfDash >= 0)
|
||||
{
|
||||
var type = SmartyPantType.Dash2;
|
||||
if (literal.Content.PeekCharAbsolute(indexOfDash + 2) == '-')
|
||||
{
|
||||
type = SmartyPantType.Dash3;
|
||||
}
|
||||
var nextContent = literal.Content;
|
||||
var originalSpan = literal.Span;
|
||||
literal.Span.End -= literal.Content.End - indexOfDash + 1;
|
||||
literal.Content.End = indexOfDash - 1;
|
||||
nextContent.Start = indexOfDash + (type == SmartyPantType.Dash2 ? 2 : 3);
|
||||
|
||||
var pant = new SmartyPant()
|
||||
{
|
||||
Span = new SourceSpan(literal.Content.End + 1, nextContent.Start - 1),
|
||||
Line = literal.Line,
|
||||
Column = literal.Column,
|
||||
OpeningCharacter = '-',
|
||||
Type = type
|
||||
};
|
||||
literal.InsertAfter(pant);
|
||||
|
||||
var postLiteral = new LiteralInline()
|
||||
{
|
||||
Span = new SourceSpan(pant.Span.End + 1, originalSpan.End),
|
||||
Line = literal.Line,
|
||||
Column = literal.Column,
|
||||
Content = nextContent
|
||||
};
|
||||
pant.InsertAfter(postLiteral);
|
||||
|
||||
// Use the pending literal to proceed further
|
||||
next = postLiteral;
|
||||
}
|
||||
}
|
||||
else if (child is ContainerInline)
|
||||
{
|
||||
pendingContainers.Push(((ContainerInline)child).FirstChild);
|
||||
}
|
||||
|
||||
child = next;
|
||||
}
|
||||
if (pendingContainers.Count > 0)
|
||||
{
|
||||
child = pendingContainers.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private class ListSmartyPants : List<SmartyPant>
|
||||
{
|
||||
public bool HasDash { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlTableRenderer>())
|
||||
|
||||
@@ -1,6 +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.Collections.Generic;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax;
|
||||
|
||||
@@ -10,7 +13,7 @@ namespace Markdig.Extensions.Tables
|
||||
{
|
||||
public GridTableParser()
|
||||
{
|
||||
OpeningCharacters = new[] {'+'};
|
||||
OpeningCharacters = new[] { '+' };
|
||||
}
|
||||
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
@@ -22,64 +25,51 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
|
||||
var line = processor.Line;
|
||||
|
||||
// A grid table must start with a line like this:
|
||||
// + ------------- + ------------ + ---------------------------------------- +
|
||||
// Spaces are optional
|
||||
|
||||
GridTableState tableState = null;
|
||||
|
||||
// Match the first row that should be of the minimal form: +---------------
|
||||
var c = line.CurrentChar;
|
||||
while (true)
|
||||
var lineStart = line.Start;
|
||||
while (c == '+')
|
||||
{
|
||||
if (c == '+')
|
||||
var columnStart = line.Start;
|
||||
line.NextChar();
|
||||
line.TrimStart();
|
||||
|
||||
// if we have reached the end of the line, exit
|
||||
c = line.CurrentChar;
|
||||
if (c == 0)
|
||||
{
|
||||
var startCharacter = line.Start;
|
||||
line.NextChar();
|
||||
if (line.IsEmptyOrWhitespace())
|
||||
{
|
||||
if (tableState == null)
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
TableColumnAlign align;
|
||||
if (TableHelper.ParseColumnHeader(ref line, '-', out align))
|
||||
{
|
||||
if (tableState == null)
|
||||
{
|
||||
tableState = new GridTableState()
|
||||
{
|
||||
Start = processor.Column,
|
||||
ExpectRow = true,
|
||||
};
|
||||
}
|
||||
tableState.AddColumn(startCharacter, line.Start - 1, align);
|
||||
|
||||
c = line.CurrentChar;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If we have any other characters, this is an invalid line
|
||||
return BlockState.None;
|
||||
// Parse a column alignment
|
||||
TableColumnAlign? columnAlign;
|
||||
if (!TableHelper.ParseColumnHeader(ref line, '-', out columnAlign))
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
tableState = tableState ?? new GridTableState { Start = processor.Start, ExpectRow = true };
|
||||
tableState.AddColumn(columnStart - lineStart, line.Start - lineStart, columnAlign);
|
||||
|
||||
c = line.CurrentChar;
|
||||
}
|
||||
|
||||
if (c != 0 || tableState == null)
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
// Store the line (if we need later to build a ParagraphBlock because the GridTable was in fact invalid)
|
||||
tableState.AddLine(ref processor.Line);
|
||||
|
||||
// Create the grid table
|
||||
var table = new Table(this);
|
||||
|
||||
table.SetData(typeof(GridTableState), tableState);
|
||||
|
||||
|
||||
// Calculate the total width of all columns
|
||||
int totalWidth = 0;
|
||||
foreach (var columnSlice in tableState.ColumnSlices)
|
||||
{
|
||||
totalWidth += columnSlice.End - columnSlice.Start;
|
||||
totalWidth += columnSlice.End - columnSlice.Start - 1;
|
||||
}
|
||||
|
||||
// Store the column width and alignment
|
||||
@@ -88,8 +78,8 @@ namespace Markdig.Extensions.Tables
|
||||
var columnDefinition = new TableColumnDefinition
|
||||
{
|
||||
// Column width proportional to the total width
|
||||
Width = (float)(columnSlice.End - columnSlice.Start) * 100.0f / totalWidth,
|
||||
Alignment = columnSlice.Align,
|
||||
Width = (float)(columnSlice.End - columnSlice.Start - 1) * 100.0f / totalWidth,
|
||||
Alignment = columnSlice.Align
|
||||
};
|
||||
table.ColumnDefinitions.Add(columnDefinition);
|
||||
}
|
||||
@@ -101,253 +91,276 @@ namespace Markdig.Extensions.Tables
|
||||
|
||||
public override BlockState TryContinue(BlockProcessor processor, Block block)
|
||||
{
|
||||
var gridTable = (Table) block;
|
||||
var gridTable = (Table)block;
|
||||
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
|
||||
|
||||
// We expect to start at the same
|
||||
if (processor.Start == tableState.Start)
|
||||
tableState.AddLine(ref processor.Line);
|
||||
if (processor.CurrentChar == '+')
|
||||
{
|
||||
var columns = tableState.ColumnSlices;
|
||||
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
|
||||
columnSlice.CurrentColumnSpan = 0;
|
||||
}
|
||||
|
||||
if (processor.CurrentChar == '+')
|
||||
{
|
||||
var result = ParseRowSeparator(processor, tableState, gridTable);
|
||||
if (result != BlockState.None)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
else if (processor.CurrentChar == '|')
|
||||
{
|
||||
var line = processor.Line;
|
||||
|
||||
// | ------------- | ------------ | ---------------------------------------- |
|
||||
// Calculate the colspan for the new row
|
||||
int columnIndex = -1;
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
if (line.PeekCharExtra(columnSlice.Start) == '|')
|
||||
{
|
||||
columnIndex++;
|
||||
}
|
||||
if (columnIndex >= 0)
|
||||
{
|
||||
columns[columnIndex].CurrentColumnSpan++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the colspan of the current row is the same than the previous row
|
||||
bool continueRow = true;
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
|
||||
{
|
||||
continueRow = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the current row doesn't continue the previous row (col span are different)
|
||||
// Close the previous row
|
||||
if (!continueRow)
|
||||
{
|
||||
TerminateLastRow(processor, tableState, gridTable, false);
|
||||
}
|
||||
|
||||
for (int i = 0; i < columns.Count;)
|
||||
{
|
||||
var column = columns[i];
|
||||
var nextColumnIndex = i + column.CurrentColumnSpan;
|
||||
// If the span is 0, we exit
|
||||
if (nextColumnIndex == i)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
|
||||
|
||||
var sliceForCell = line;
|
||||
sliceForCell.Start = column.Start + 1;
|
||||
if (nextColumn != null)
|
||||
{
|
||||
sliceForCell.End = nextColumn.Start - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var columnEnd = columns[columns.Count - 1].End;
|
||||
// If there is a `|` exactly at the expected end of the table row, we cut the line
|
||||
// otherwise we allow to have the last cell of a row to be open for longer cell content
|
||||
if (line.PeekCharExtra(columnEnd + 1) == '|')
|
||||
{
|
||||
sliceForCell.End = columnEnd;
|
||||
}
|
||||
}
|
||||
sliceForCell.TrimEnd();
|
||||
|
||||
// Process the content of the cell
|
||||
column.BlockProcessor.LineIndex = processor.LineIndex;
|
||||
column.BlockProcessor.ProcessLine(sliceForCell);
|
||||
|
||||
// Go to next column
|
||||
i = nextColumnIndex;
|
||||
}
|
||||
|
||||
return BlockState.ContinueDiscard;
|
||||
}
|
||||
return HandleNewRow(processor, tableState, gridTable);
|
||||
}
|
||||
|
||||
TerminateLastRow(processor, tableState, gridTable, true);
|
||||
|
||||
// If we don't have a row, it means that only the header was valid
|
||||
// So we need to remove the grid table, and create a ParagraphBlock
|
||||
// with the 2 slices
|
||||
if (gridTable.Count == 0)
|
||||
if (processor.CurrentChar == '|')
|
||||
{
|
||||
var parser = processor.Parsers.Find<ParagraphBlockParser>();
|
||||
// Discard the grid table
|
||||
var parent = gridTable.Parent;
|
||||
processor.Discard(gridTable);
|
||||
var paragraphBlock = new ParagraphBlock(parser)
|
||||
{
|
||||
Lines = tableState.Lines,
|
||||
};
|
||||
parent.Add(paragraphBlock);
|
||||
processor.Open(paragraphBlock);
|
||||
return HandleContents(processor, tableState, gridTable);
|
||||
}
|
||||
TerminateCurrentRow(processor, tableState, gridTable, true);
|
||||
// If the table is not valid we need to remove the grid table,
|
||||
// and create a ParagraphBlock with the slices
|
||||
if (!gridTable.IsValid())
|
||||
{
|
||||
Undo(processor, tableState, gridTable);
|
||||
}
|
||||
|
||||
return BlockState.Break;
|
||||
}
|
||||
|
||||
private BlockState HandleNewRow(BlockProcessor processor, GridTableState tableState, Table gridTable)
|
||||
{
|
||||
bool isHeaderRow, hasRowSpan;
|
||||
var columns = tableState.ColumnSlices;
|
||||
SetRowSpanState(columns, processor.Line, out isHeaderRow, out hasRowSpan);
|
||||
SetColumnSpanState(columns, processor.Line);
|
||||
TerminateCurrentRow(processor, tableState, gridTable, false);
|
||||
if (isHeaderRow)
|
||||
{
|
||||
for (int i = 0; i < gridTable.Count; i++)
|
||||
{
|
||||
var row = (TableRow)gridTable[i];
|
||||
row.IsHeader = true;
|
||||
}
|
||||
}
|
||||
tableState.StartRowGroup = gridTable.Count;
|
||||
if (hasRowSpan)
|
||||
{
|
||||
HandleContents(processor, tableState, gridTable);
|
||||
}
|
||||
return BlockState.ContinueDiscard;
|
||||
}
|
||||
|
||||
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
|
||||
{
|
||||
var lineStart = line.Start;
|
||||
isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
|
||||
hasRowSpan = false;
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
if (columnSlice.CurrentCell != null)
|
||||
{
|
||||
line.Start = lineStart + columnSlice.Start + 1;
|
||||
line.End = lineStart + columnSlice.End - 1;
|
||||
line.Trim();
|
||||
if (line.IsEmptyOrWhitespace() || !IsRowSeperator(line))
|
||||
{
|
||||
hasRowSpan = true;
|
||||
columnSlice.CurrentCell.RowSpan++;
|
||||
columnSlice.CurrentCell.AllowClose = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnSlice.CurrentCell.AllowClose = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRowSeperator(StringSlice slice)
|
||||
{
|
||||
while (slice.Length > 0)
|
||||
{
|
||||
if (slice.CurrentChar != '-' && slice.CurrentChar != '=' && slice.CurrentChar != ':')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
slice.NextChar();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TerminateCurrentRow(BlockProcessor processor, GridTableState tableState, Table gridTable, bool isLastRow)
|
||||
{
|
||||
var columns = tableState.ColumnSlices;
|
||||
TableRow currentRow = null;
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
var columnSlice = columns[i];
|
||||
if (columnSlice.CurrentCell != null)
|
||||
{
|
||||
if (currentRow == null)
|
||||
{
|
||||
currentRow = new TableRow();
|
||||
}
|
||||
// If this cell does not already belong to a row
|
||||
if (columnSlice.CurrentCell.Parent == null)
|
||||
{
|
||||
currentRow.Add(columnSlice.CurrentCell);
|
||||
}
|
||||
// If the cell is not going to span through to the next row
|
||||
if (columnSlice.CurrentCell.AllowClose)
|
||||
{
|
||||
columnSlice.BlockProcessor.Close(columnSlice.CurrentCell);
|
||||
}
|
||||
}
|
||||
|
||||
// Renew the block parser processor (or reset it for the last row)
|
||||
if (columnSlice.BlockProcessor != null && (columnSlice.CurrentCell == null || columnSlice.CurrentCell.AllowClose))
|
||||
{
|
||||
columnSlice.BlockProcessor.ReleaseChild();
|
||||
columnSlice.BlockProcessor = isLastRow ? null : processor.CreateChild();
|
||||
}
|
||||
|
||||
// Create or erase the cell
|
||||
if (isLastRow || columnSlice.CurrentColumnSpan == 0 || (columnSlice.CurrentCell != null && columnSlice.CurrentCell.AllowClose))
|
||||
{
|
||||
// We don't need the cell anymore if we have a last row
|
||||
// Or the cell has a columnspan == 0
|
||||
// And the cell does not have to be kept open to span rows
|
||||
columnSlice.CurrentCell = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRow != null && currentRow.Count > 0)
|
||||
{
|
||||
gridTable.Add(currentRow);
|
||||
}
|
||||
}
|
||||
|
||||
private BlockState HandleContents(BlockProcessor processor, GridTableState tableState, Table gridTable)
|
||||
{
|
||||
var isRowLine = processor.CurrentChar == '+';
|
||||
var columns = tableState.ColumnSlices;
|
||||
var line = processor.Line;
|
||||
SetColumnSpanState(columns, line);
|
||||
if (!isRowLine && !CanContinueRow(columns))
|
||||
{
|
||||
TerminateCurrentRow(processor, tableState, gridTable, false);
|
||||
}
|
||||
for (int i = 0; i < columns.Count;)
|
||||
{
|
||||
var columnSlice = columns[i];
|
||||
var nextColumnIndex = i + columnSlice.CurrentColumnSpan;
|
||||
// If the span is 0, we exit
|
||||
if (nextColumnIndex == i)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
|
||||
|
||||
var sliceForCell = line;
|
||||
sliceForCell.Start = line.Start + columnSlice.Start + 1;
|
||||
if (nextColumn != null)
|
||||
{
|
||||
sliceForCell.End = line.Start + nextColumn.Start - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var columnEnd = columns[columns.Count - 1].End;
|
||||
var columnEndChar = line.PeekCharExtra(columnEnd);
|
||||
// If there is a `|` (or a `+` in the case that we are dealing with a row line
|
||||
// with spanned contents) exactly at the expected end of the table row, we cut the line
|
||||
// otherwise we allow to have the last cell of a row to be open for longer cell content
|
||||
if (columnEndChar == '|' || (isRowLine && columnEndChar == '+'))
|
||||
{
|
||||
sliceForCell.End = line.Start + columnEnd - 1;
|
||||
}
|
||||
else if (line.PeekCharExtra(line.End) == '|')
|
||||
{
|
||||
sliceForCell.End = line.End - 1;
|
||||
}
|
||||
}
|
||||
sliceForCell.TrimEnd();
|
||||
|
||||
if (!isRowLine || !IsRowSeperator(sliceForCell))
|
||||
{
|
||||
if (columnSlice.CurrentCell == null)
|
||||
{
|
||||
columnSlice.CurrentCell = new TableCell(this)
|
||||
{
|
||||
ColumnSpan = columnSlice.CurrentColumnSpan,
|
||||
ColumnIndex = i
|
||||
};
|
||||
|
||||
if (columnSlice.BlockProcessor == null)
|
||||
{
|
||||
columnSlice.BlockProcessor = processor.CreateChild();
|
||||
}
|
||||
|
||||
// Ensure that the BlockParser is aware that the TableCell is the top-level container
|
||||
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
|
||||
}
|
||||
// Process the content of the cell
|
||||
columnSlice.BlockProcessor.LineIndex = processor.LineIndex;
|
||||
columnSlice.BlockProcessor.ProcessLine(sliceForCell);
|
||||
}
|
||||
|
||||
// Go to next column
|
||||
i = nextColumnIndex;
|
||||
}
|
||||
return BlockState.ContinueDiscard;
|
||||
}
|
||||
|
||||
private static void SetColumnSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line)
|
||||
{
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
|
||||
columnSlice.CurrentColumnSpan = 0;
|
||||
}
|
||||
// | ------------- | ------------ | ---------------------------------------- |
|
||||
// Calculate the colspan for the new row
|
||||
int columnIndex = -1;
|
||||
for (int i = 0; i < columns.Count; i++)
|
||||
{
|
||||
var columnSlice = columns[i];
|
||||
var peek = line.PeekChar(columnSlice.Start);
|
||||
if (peek == '|' || peek == '+')
|
||||
{
|
||||
columnIndex = i;
|
||||
}
|
||||
if (columnIndex >= 0)
|
||||
{
|
||||
columns[columnIndex].CurrentColumnSpan++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanContinueRow(List<GridTableState.ColumnSlice> columns)
|
||||
{
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Undo(BlockProcessor processor, GridTableState tableState, Table gridTable)
|
||||
{
|
||||
var parser = processor.Parsers.FindExact<ParagraphBlockParser>();
|
||||
// Discard the grid table
|
||||
var parent = gridTable.Parent;
|
||||
processor.Discard(gridTable);
|
||||
var paragraphBlock = new ParagraphBlock(parser)
|
||||
{
|
||||
Lines = tableState.Lines,
|
||||
};
|
||||
parent.Add(paragraphBlock);
|
||||
processor.Open(paragraphBlock);
|
||||
}
|
||||
|
||||
public override bool Close(BlockProcessor processor, Block block)
|
||||
{
|
||||
// Work only on Table, not on TableCell
|
||||
var gridTable = block as Table;
|
||||
if (gridTable != null)
|
||||
{
|
||||
var tableState = (GridTableState) block.GetData(typeof (GridTableState));
|
||||
TerminateLastRow(processor, tableState, gridTable, true);
|
||||
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
|
||||
TerminateCurrentRow(processor, tableState, gridTable, true);
|
||||
if (!gridTable.IsValid())
|
||||
{
|
||||
Undo(processor, tableState, gridTable);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private BlockState ParseRowSeparator(BlockProcessor state, GridTableState tableState, Table gridTable)
|
||||
{
|
||||
// A grid table must start with a line like this:
|
||||
// + ------------- + ------------ + ---------------------------------------- +
|
||||
// Spaces are optional
|
||||
|
||||
var line = state.Line;
|
||||
var c = line.CurrentChar;
|
||||
bool isFirst = true;
|
||||
var delimiterChar = '\0';
|
||||
while (true)
|
||||
{
|
||||
if (c == '+')
|
||||
{
|
||||
line.NextChar();
|
||||
if (line.IsEmptyOrWhitespace())
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
TableColumnAlign align;
|
||||
if (TableHelper.ParseColumnHeaderDetect(ref line, ref delimiterChar, out align))
|
||||
{
|
||||
isFirst = false;
|
||||
c = line.CurrentChar;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have any other characters, this is an invalid line
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
// If we have an header row
|
||||
var isHeader = delimiterChar == '=';
|
||||
|
||||
// Terminate the current row
|
||||
TerminateLastRow(state, tableState, gridTable, false);
|
||||
|
||||
// If we had a header row separator, we can mark all rows since last row separator
|
||||
// to be header rows
|
||||
if (isHeader)
|
||||
{
|
||||
for (int i = tableState.StartRowGroup; i < gridTable.Count; i++)
|
||||
{
|
||||
var row = (TableRow) gridTable[i];
|
||||
row.IsHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Makr the next start row group continue on the next row
|
||||
tableState.StartRowGroup = gridTable.Count;
|
||||
|
||||
// We don't keep the line
|
||||
return BlockState.ContinueDiscard;
|
||||
}
|
||||
|
||||
private void TerminateLastRow(BlockProcessor state, GridTableState tableState, Table gridTable, bool isLastRow)
|
||||
{
|
||||
var columns = tableState.ColumnSlices;
|
||||
TableRow currentRow = null;
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
if (columnSlice.CurrentCell != null)
|
||||
{
|
||||
if (currentRow == null)
|
||||
{
|
||||
currentRow = new TableRow();
|
||||
}
|
||||
currentRow.Add(columnSlice.CurrentCell);
|
||||
columnSlice.BlockProcessor.Close(columnSlice.CurrentCell);
|
||||
}
|
||||
|
||||
// Renew the block parser processor (or reset it for the last row)
|
||||
if (columnSlice.BlockProcessor != null)
|
||||
{
|
||||
columnSlice.BlockProcessor.ReleaseChild();
|
||||
columnSlice.BlockProcessor = isLastRow ? null : state.CreateChild();
|
||||
}
|
||||
|
||||
// Create or erase the cell
|
||||
if (isLastRow || columnSlice.CurrentColumnSpan == 0)
|
||||
{
|
||||
// We don't need the cell anymore if we have a last row
|
||||
// Or the cell has a columnspan == 0
|
||||
columnSlice.CurrentCell = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Else we can create a new cell
|
||||
columnSlice.CurrentCell = new TableCell(this)
|
||||
{
|
||||
ColumnSpan = columnSlice.CurrentColumnSpan
|
||||
};
|
||||
|
||||
if (columnSlice.BlockProcessor == null)
|
||||
{
|
||||
columnSlice.BlockProcessor = state.CreateChild();
|
||||
}
|
||||
|
||||
// Ensure that the BlockParser is aware that the TableCell is the top-level container
|
||||
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRow != null)
|
||||
{
|
||||
gridTable.Add(currentRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Markdig.Extensions.Tables
|
||||
Lines.Add(line);
|
||||
}
|
||||
|
||||
public void AddColumn(int start, int end, TableColumnAlign align)
|
||||
public void AddColumn(int start, int end, TableColumnAlign? align)
|
||||
{
|
||||
if (ColumnSlices == null)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace Markdig.Extensions.Tables
|
||||
|
||||
public int End { get; set; }
|
||||
|
||||
public TableColumnAlign Align { get; set; }
|
||||
public TableColumnAlign? Align { get; set; }
|
||||
|
||||
public int CurrentColumnSpan { get; set; }
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Renderers.Html;
|
||||
|
||||
@@ -38,7 +39,9 @@ namespace Markdig.Extensions.Tables
|
||||
{
|
||||
foreach (var tableColumnDefinition in table.ColumnDefinitions)
|
||||
{
|
||||
renderer.WriteLine($"<col style=\"width:{Math.Round(tableColumnDefinition.Width*100)/100}%\">");
|
||||
var width = Math.Round(tableColumnDefinition.Width*100)/100;
|
||||
var widthValue = string.Format(CultureInfo.InvariantCulture, "{0:0.##}", width);
|
||||
renderer.WriteLine($"<col style=\"width:{widthValue}%\">");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,17 +81,31 @@ namespace Markdig.Extensions.Tables
|
||||
{
|
||||
renderer.Write($" colspan=\"{cell.ColumnSpan}\"");
|
||||
}
|
||||
|
||||
if (table.ColumnDefinitions != null && i < table.ColumnDefinitions.Count)
|
||||
if (cell.RowSpan != 1)
|
||||
{
|
||||
switch (table.ColumnDefinitions[i].Alignment)
|
||||
renderer.Write($" rowspan=\"{cell.RowSpan}\"");
|
||||
}
|
||||
if (table.ColumnDefinitions != null)
|
||||
{
|
||||
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
|
||||
? i
|
||||
: cell.ColumnIndex;
|
||||
columnIndex = columnIndex >= table.ColumnDefinitions.Count ? table.ColumnDefinitions.Count - 1 : columnIndex;
|
||||
var alignment = table.ColumnDefinitions[columnIndex].Alignment;
|
||||
if (alignment.HasValue)
|
||||
{
|
||||
case TableColumnAlign.Center:
|
||||
renderer.Write(" style=\"text-align: center;\"");
|
||||
break;
|
||||
case TableColumnAlign.Right:
|
||||
renderer.Write(" style=\"text-align: right;\"");
|
||||
break;
|
||||
switch (alignment)
|
||||
{
|
||||
case TableColumnAlign.Center:
|
||||
renderer.Write(" style=\"text-align: center;\"");
|
||||
break;
|
||||
case TableColumnAlign.Right:
|
||||
renderer.Write(" style=\"text-align: right;\"");
|
||||
break;
|
||||
case TableColumnAlign.Left:
|
||||
renderer.Write(" style=\"text-align: left;\"");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
renderer.WriteAttributes(cell);
|
||||
@@ -101,7 +118,7 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
renderer.Write(cell);
|
||||
renderer.ImplicitParagraph = previousImplicitParagraph;
|
||||
|
||||
|
||||
renderer.WriteLine(row.IsHeader ? "</th>" : "</td>");
|
||||
}
|
||||
renderer.WriteLine("</tr>");
|
||||
|
||||
69
src/Markdig/Extensions/Tables/PipeTableBlockParser.cs
Normal file
69
src/Markdig/Extensions/Tables/PipeTableBlockParser.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.Tables
|
||||
{
|
||||
/// <summary>
|
||||
/// This block parsers for pipe tables is used to by-pass list items that could start by a single '-'
|
||||
/// and would disallow to detect a pipe tables at inline parsing time, so we are basically forcing a line
|
||||
/// that starts by a '-' and have at least a '|' (and have optional spaces) and is a continuation of a
|
||||
/// paragraph.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.BlockParser" />
|
||||
public class PipeTableBlockParser : BlockParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PipeTableBlockParser"/> class.
|
||||
/// </summary>
|
||||
public PipeTableBlockParser()
|
||||
{
|
||||
OpeningCharacters = new[] {'-'};
|
||||
}
|
||||
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
{
|
||||
// Only if we have already a paragraph
|
||||
var paragraph = processor.CurrentBlock as ParagraphBlock;
|
||||
if (processor.IsCodeIndent || paragraph == null)
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
// We require at least a pipe (and we allow only : - | and space characters)
|
||||
var line = processor.Line;
|
||||
var countPipe = 0;
|
||||
while (true)
|
||||
{
|
||||
var c = line.NextChar();
|
||||
if (c == '\0')
|
||||
{
|
||||
if (countPipe > 0)
|
||||
{
|
||||
// Mark the paragraph as open (important, otherwise we would have an infinite loop)
|
||||
paragraph.AppendLine(ref processor.Line, processor.Column, processor.LineIndex, processor.Line.Start);
|
||||
paragraph.IsOpen = true;
|
||||
return BlockState.BreakDiscard;
|
||||
}
|
||||
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
if (c.IsSpace() || c == '-' || c == '|' || c == ':')
|
||||
{
|
||||
if (c == '|')
|
||||
{
|
||||
countPipe++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return BlockState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ namespace Markdig.Extensions.Tables
|
||||
/// The delimiter used to separate the columns of a pipe table.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Syntax.Inlines.DelimiterInline" />
|
||||
public class PiprTableDelimiterInline : DelimiterInline
|
||||
public class PipeTableDelimiterInline : DelimiterInline
|
||||
{
|
||||
public PiprTableDelimiterInline(InlineParser parser) : base(parser)
|
||||
public PipeTableDelimiterInline(InlineParser parser) : base(parser)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -28,13 +28,20 @@ namespace Markdig.Extensions.Tables
|
||||
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
// Pipe tables require precise source location
|
||||
pipeline.PreciseSourceLocation = true;
|
||||
if (!pipeline.BlockParsers.Contains<PipeTableBlockParser>())
|
||||
{
|
||||
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());
|
||||
}
|
||||
var lineBreakParser = pipeline.InlineParsers.FindExact<LineBreakInlineParser>();
|
||||
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
|
||||
{
|
||||
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(Options));
|
||||
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(lineBreakParser, Options));
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlTableRenderer>())
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user