mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-04 05:44:50 +00:00
Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
fd493865cf | ||
|
|
d43947af45 | ||
|
|
ab04ac3e00 | ||
|
|
547c00eb5a | ||
|
|
795d002ed0 | ||
|
|
987357ef5a | ||
|
|
613a1d97fb | ||
|
|
874170bc1a | ||
|
|
1c1e56aebe | ||
|
|
ed18d3fa25 | ||
|
|
33a6a39c34 | ||
|
|
a1228a1e1c |
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<solution>
|
||||
<add key="disableSourceControlIntegration" value="true" />
|
||||
</solution>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
<add key="dotnet-core" value="http://myget.org/F/dotnet-core" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -1,150 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">$(MSBuildProjectDirectory)\..\</SolutionDir>
|
||||
|
||||
<!-- Enable the restore command to run before builds -->
|
||||
<RestorePackages Condition=" '$(RestorePackages)' == '' ">false</RestorePackages>
|
||||
|
||||
<!-- Property that enables building a package from a project -->
|
||||
<BuildPackage Condition=" '$(BuildPackage)' == '' ">false</BuildPackage>
|
||||
|
||||
<!-- Determines if package restore consent is required to restore packages -->
|
||||
<RequireRestoreConsent Condition=" '$(RequireRestoreConsent)' != 'false' ">true</RequireRestoreConsent>
|
||||
|
||||
<!-- Download NuGet.exe if it does not already exist -->
|
||||
<DownloadNuGetExe Condition=" '$(DownloadNuGetExe)' == '' ">true</DownloadNuGetExe>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(PackageSources)' == '' ">
|
||||
<!-- Package sources used to restore packages. By default will used the registered sources under %APPDATA%\NuGet\NuGet.Config -->
|
||||
<!--
|
||||
<PackageSource Include="https://nuget.org/api/v2/" />
|
||||
<PackageSource Include="https://my-nuget-source/nuget/" />
|
||||
-->
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
|
||||
<!-- Windows specific commands -->
|
||||
<NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
|
||||
<PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT'">
|
||||
<!-- We need to launch nuget.exe with the mono command if we're not on windows -->
|
||||
<NuGetToolsPath>$(SolutionDir).nuget</NuGetToolsPath>
|
||||
<PackagesConfig>packages.config</PackagesConfig>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- NuGet command -->
|
||||
<NuGetExePath Condition=" '$(NuGetExePath)' == '' ">$(NuGetToolsPath)\nuget.exe</NuGetExePath>
|
||||
<PackageSources Condition=" $(PackageSources) == '' ">@(PackageSource)</PackageSources>
|
||||
|
||||
<NuGetCommand Condition=" '$(OS)' == 'Windows_NT'">"$(NuGetExePath)"</NuGetCommand>
|
||||
<NuGetCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(NuGetExePath)</NuGetCommand>
|
||||
|
||||
<PackageOutputDir Condition="$(PackageOutputDir) == ''">$(TargetDir.Trim('\\'))</PackageOutputDir>
|
||||
|
||||
<RequireConsentSwitch Condition=" $(RequireRestoreConsent) == 'true' ">-RequireConsent</RequireConsentSwitch>
|
||||
<!-- Commands -->
|
||||
<RestoreCommand>$(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir) "</RestoreCommand>
|
||||
<BuildCommand>$(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols</BuildCommand>
|
||||
|
||||
<!-- We need to ensure packages are restored prior to assembly resolve -->
|
||||
<ResolveReferencesDependsOn Condition="$(RestorePackages) == 'true'">
|
||||
RestorePackages;
|
||||
$(ResolveReferencesDependsOn);
|
||||
</ResolveReferencesDependsOn>
|
||||
|
||||
<!-- Make the build depend on restore packages -->
|
||||
<BuildDependsOn Condition="$(BuildPackage) == 'true'">
|
||||
$(BuildDependsOn);
|
||||
BuildPackage;
|
||||
</BuildDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CheckPrerequisites">
|
||||
<!-- Raise an error if we're unable to locate nuget.exe -->
|
||||
<Error Condition="'$(DownloadNuGetExe)' != 'true' AND !Exists('$(NuGetExePath)')" Text="Unable to locate '$(NuGetExePath)'" />
|
||||
<SetEnvironmentVariable EnvKey="VisualStudioVersion" EnvValue="$(VisualStudioVersion)" Condition=" '$(VisualStudioVersion)' != '' AND '$(OS)' == 'Windows_NT' " />
|
||||
<!--
|
||||
Take advantage of MsBuild's build dependency tracking to make sure that we only ever download nuget.exe once.
|
||||
This effectively acts as a lock that makes sure that the download operation will only happen once and all
|
||||
parallel builds will have to wait for it to complete.
|
||||
-->
|
||||
<MsBuild Targets="_DownloadNuGet" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_DownloadNuGet">
|
||||
<DownloadNuGet OutputFilename="$(NuGetExePath)" Condition=" '$(DownloadNuGetExe)' == 'true' AND !Exists('$(NuGetExePath)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
Condition="'$(OS)' != 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
|
||||
<Exec Command="$(RestoreCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition="'$(OS)' == 'Windows_NT' And Exists('$(PackagesConfig)')" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildPackage" DependsOnTargets="CheckPrerequisites">
|
||||
<Exec Command="$(BuildCommand)"
|
||||
Condition=" '$(OS)' != 'Windows_NT' " />
|
||||
|
||||
<Exec Command="$(BuildCommand)"
|
||||
LogStandardErrorAsError="true"
|
||||
Condition=" '$(OS)' == 'Windows_NT' " />
|
||||
</Target>
|
||||
|
||||
<UsingTask TaskName="DownloadNuGet" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<OutputFilename ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Reference Include="System.Core" />
|
||||
<Using Namespace="System" />
|
||||
<Using Namespace="System.IO" />
|
||||
<Using Namespace="System.Net" />
|
||||
<Using Namespace="Microsoft.Build.Framework" />
|
||||
<Using Namespace="Microsoft.Build.Utilities" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
OutputFilename = Path.GetFullPath(OutputFilename);
|
||||
|
||||
Log.LogMessage("Downloading latest version of NuGet.exe...");
|
||||
WebClient webClient = new WebClient();
|
||||
webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFilename);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
|
||||
<UsingTask TaskName="SetEnvironmentVariable" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
|
||||
<ParameterGroup>
|
||||
<EnvKey ParameterType="System.String" Required="true" />
|
||||
<EnvValue ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Using Namespace="System" />
|
||||
<Code Type="Fragment" Language="cs">
|
||||
<![CDATA[
|
||||
try {
|
||||
Environment.SetEnvironmentVariable(EnvKey, EnvValue, System.EnvironmentVariableTarget.Process);
|
||||
}
|
||||
catch {
|
||||
}
|
||||
]]>
|
||||
</Code>
|
||||
</Task>
|
||||
</UsingTask>
|
||||
</Project>
|
||||
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 |
122
readme.md
122
readme.md
@@ -1,9 +1,13 @@
|
||||
# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/)
|
||||
|
||||
<img align="right" width="160px" height="160px" src="img/markdig.png">
|
||||
|
||||
Markdig is a fast, powerfull, [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
|
||||
@@ -14,20 +18,21 @@ Markdig is a fast, powerfull, [CommonMark](http://commonmark.org/) compliant, ex
|
||||
- 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](http://pandoc.org/README.html#pipe_tables))
|
||||
- **Grid tables** (inspired from [Pandoc](http://pandoc.org/README.html#grid_tables))
|
||||
- **Extra emphasis** (inspired from [Pandoc](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
|
||||
- **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/))
|
||||
- strike through `~~`,
|
||||
- Subscript `~`
|
||||
- Superscript `^`
|
||||
- Inserted `++`
|
||||
- Marked `==`
|
||||
- **Special attributes** or attached HTML attributes (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
|
||||
- **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](http://pandoc.org/README.html#extension-auto_identifiers)
|
||||
- **Auto-identifiers** for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
|
||||
- **Task Lists** inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments).
|
||||
- **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))
|
||||
@@ -42,6 +47,13 @@ Markdig is a fast, powerfull, [CommonMark](http://commonmark.org/) compliant, ex
|
||||
- **Bootstrap** class (to output bootstrap class)
|
||||
- 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!
|
||||
|
||||
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/)
|
||||
@@ -57,11 +69,11 @@ 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 all extensions (except Emoji)
|
||||
In order to activate most of all advanced extensions (except Emoji, SoftLine as HarLine and SmartyPants)
|
||||
|
||||
```csharp
|
||||
// Configure the pipeline with all extensions active
|
||||
var pipeline = new MarkdownPipelineBuilder().UseAllExtensions().Build();
|
||||
// Configure the pipeline with all advanced extensions active
|
||||
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
|
||||
var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
|
||||
```
|
||||
|
||||
@@ -76,24 +88,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**, Extremelly 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 +133,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,15 +162,10 @@ 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 |
|
||||
```
|
||||
|
||||
## Credits
|
||||
@@ -150,7 +174,9 @@ Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.ne
|
||||
|
||||
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)
|
||||
|
||||
## Author
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@
|
||||
<DependentUpon>Specs.tt</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="TestHtmlHelper.cs" />
|
||||
<Compile Include="TestLineReader.cs" />
|
||||
<Compile Include="TestLinkHelper.cs" />
|
||||
<Compile Include="TestSourcePosition.cs" />
|
||||
<Compile Include="TestStringSliceList.cs" />
|
||||
<Compile Include="TestPlayParser.cs" />
|
||||
<Compile Include="TextAssert.cs" />
|
||||
@@ -81,6 +83,7 @@
|
||||
<None Include="Specs\GridTableSpecs.md" />
|
||||
<None Include="Specs\HardlineBreakSpecs.md" />
|
||||
<None Include="Specs\BootstrapSpecs.md" />
|
||||
<None Include="Specs\TaskListSpecs.md" />
|
||||
<None Include="Specs\SmartyPantsSpecs.md" />
|
||||
<None Include="Specs\MediaSpecs.md" />
|
||||
<None Include="Specs\MathSpecs.md" />
|
||||
|
||||
@@ -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:
|
||||
@@ -205,6 +214,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
|
||||
|
||||
@@ -98,6 +98,14 @@ 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
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Markdig.Tests
|
||||
// articles, slide shows, letters, and lecture notes.
|
||||
//
|
||||
// What distinguishes Markdown from many other lightweight markup
|
||||
// syntaxes, which are often easier to write, is its readibility.
|
||||
// syntaxes, which are often easier to write, is its readability.
|
||||
// As Gruber writes:
|
||||
//
|
||||
// > The overriding design goal for Markdown's formatting syntax is
|
||||
@@ -16358,7 +16358,7 @@ namespace Markdig.Tests
|
||||
TestParser.TestSpec("a | b\n-- | -\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
// 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:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
@@ -16374,14 +16374,23 @@ namespace Markdig.Tests
|
||||
// 0 | 1
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <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>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("a | b\n- | -\n0 | 1", "<p>a | b</p>\n<ul>\n<li>| -\n0 | 1</li>\n</ul>", "pipetables");
|
||||
TestParser.TestSpec("a | b\n- | -\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
// A pipe table with only one header row is allowed:
|
||||
@@ -16622,7 +16631,7 @@ namespace Markdig.Tests
|
||||
TestParser.TestSpec(" a | b |\n-- | --\n| 0 | 1\n| 2 | 3 |\n 4 | 5 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>2</td>\n<td>3</td>\n</tr>\n<tr>\n<td>4</td>\n<td>5</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
// Single column table can be declared with lines starting only by a column delimiter:
|
||||
// A pipe may be present at both the beginning/ending of each line:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
@@ -16633,6 +16642,110 @@ namespace Markdig.Tests
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
// |a|b|
|
||||
// |-|-|
|
||||
// |0|1|
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <table>
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>a</th>
|
||||
// <th>b</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td>0</td>
|
||||
// <td>1</td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("|a|b|\n|-|-|\n|0|1|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
// Or may be ommitted on one side:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example011()
|
||||
{
|
||||
// Example 11
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
// a|b|
|
||||
// -|-|
|
||||
// 0|1|
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <table>
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>a</th>
|
||||
// <th>b</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td>0</td>
|
||||
// <td>1</td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 11, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("a|b|\n-|-|\n0|1|", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example012()
|
||||
{
|
||||
// Example 12
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
// |a|b
|
||||
// |-|-
|
||||
// |0|1
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <table>
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th>a</th>
|
||||
// <th>b</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td>0</td>
|
||||
// <td>1</td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 12, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("|a|b\n|-|-\n|0|1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
// Single column table can be declared with lines starting only by a column delimiter:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example013()
|
||||
{
|
||||
// Example 13
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
// | a
|
||||
// | --
|
||||
// | b
|
||||
@@ -16655,7 +16768,7 @@ namespace Markdig.Tests
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 13, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("| a\n| --\n| b\n| c ", "<table>\n<thead>\n<tr>\n<th>a</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>b</td>\n</tr>\n<tr>\n<td>c</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16672,9 +16785,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example011()
|
||||
public void Example014()
|
||||
{
|
||||
// Example 11
|
||||
// Example 14
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16703,7 +16816,7 @@ namespace Markdig.Tests
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 11, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 14, "Extensions Pipe Table");
|
||||
TestParser.TestSpec(" a | b \n-------|-------\n 0 | 1 \n 2 | 3 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n<tr>\n<td>2</td>\n<td>3</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16713,9 +16826,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example012()
|
||||
public void Example015()
|
||||
{
|
||||
// Example 12
|
||||
// Example 15
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16747,7 +16860,7 @@ namespace Markdig.Tests
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 12, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 15, "Extensions Pipe Table");
|
||||
TestParser.TestSpec(" a | b | c \n:------|:-------:| ----:\n 0 | 1 | 2 \n 3 | 4 | 5 ", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th style=\"text-align: center;\">b</th>\n<th style=\"text-align: right;\">c</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td style=\"text-align: center;\">1</td>\n<td style=\"text-align: right;\">2</td>\n</tr>\n<tr>\n<td>3</td>\n<td style=\"text-align: center;\">4</td>\n<td style=\"text-align: right;\">5</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16756,9 +16869,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example013()
|
||||
public void Example016()
|
||||
{
|
||||
// Example 13
|
||||
// Example 16
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16773,7 +16886,7 @@ namespace Markdig.Tests
|
||||
// 0 | 1
|
||||
// 2 | 3</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 13, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 16, "Extensions Pipe Table");
|
||||
TestParser.TestSpec(" a | b\n-------|---x---\n 0 | 1\n 2 | 3 ", "<p>a | b\n-------|---x---\n0 | 1\n2 | 3</p> ", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16784,9 +16897,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example014()
|
||||
public void Example017()
|
||||
{
|
||||
// Example 14
|
||||
// Example 17
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16815,7 +16928,7 @@ namespace Markdig.Tests
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 14, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 17, "Extensions Pipe Table");
|
||||
TestParser.TestSpec(" *a* | b\n----- |-----\n 0 | _1_\n _2 | 3* ", "<table>\n<thead>\n<tr>\n<th><em>a</em></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td><em>1</em></td>\n</tr>\n<tr>\n<td>_2</td>\n<td>3*</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16826,9 +16939,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example015()
|
||||
public void Example018()
|
||||
{
|
||||
// Example 15
|
||||
// Example 18
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16838,7 +16951,7 @@ namespace Markdig.Tests
|
||||
// Should be rendered as:
|
||||
// <p>a | b <code>0 |</code></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 15, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 18, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("a | b `\n0 | ` ", "<p>a | b <code>0 |</code></p> ", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16849,9 +16962,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example016()
|
||||
public void Example019()
|
||||
{
|
||||
// Example 16
|
||||
// Example 19
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16875,7 +16988,7 @@ namespace Markdig.Tests
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 16, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 19, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("a <a href=\"\" title=\"|\"></a> | b\n-- | --\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a <a href=\"\" title=\"|\"></a></th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -16886,9 +16999,9 @@ namespace Markdig.Tests
|
||||
public partial class TestExtensionsPipeTable
|
||||
{
|
||||
[Test]
|
||||
public void Example017()
|
||||
public void Example020()
|
||||
{
|
||||
// Example 17
|
||||
// Example 20
|
||||
// Section: Extensions Pipe Table
|
||||
//
|
||||
// The following CommonMark:
|
||||
@@ -16912,7 +17025,7 @@ namespace Markdig.Tests
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 17, "Extensions Pipe Table");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 20, "Extensions Pipe Table");
|
||||
TestParser.TestSpec("a | b\n-- | --\n[This is a link with a | inside the label](http://google.com) | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"http://google.com\">This is a link with a | inside the label</a></td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables");
|
||||
}
|
||||
}
|
||||
@@ -17088,7 +17201,7 @@ namespace Markdig.Tests
|
||||
// <p>The following text <del>is deleted</del></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Strikethrough");
|
||||
TestParser.TestSpec("The following text ~~is deleted~~", "<p>The following text <del>is deleted</del></p>", "emphasis_extra");
|
||||
TestParser.TestSpec("The following text ~~is deleted~~", "<p>The following text <del>is deleted</del></p>", "emphasisextras");
|
||||
}
|
||||
}
|
||||
// ## Superscript and Subscript
|
||||
@@ -17110,7 +17223,7 @@ namespace Markdig.Tests
|
||||
// <p>H<sub>2</sub>O is a liquid. 2<sup>10</sup> is 1024</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Superscript and Subscript");
|
||||
TestParser.TestSpec("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>", "emphasis_extra");
|
||||
TestParser.TestSpec("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>", "emphasisextras");
|
||||
}
|
||||
}
|
||||
// ## Inserted
|
||||
@@ -17132,7 +17245,7 @@ namespace Markdig.Tests
|
||||
// <p><ins>Inserted text</ins></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Inserted");
|
||||
TestParser.TestSpec("++Inserted text++", "<p><ins>Inserted text</ins></p>", "emphasis_extra");
|
||||
TestParser.TestSpec("++Inserted text++", "<p><ins>Inserted text</ins></p>", "emphasisextras");
|
||||
}
|
||||
}
|
||||
// ## Marked
|
||||
@@ -17154,7 +17267,7 @@ namespace Markdig.Tests
|
||||
// <p><mark>Marked text</mark></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Marked");
|
||||
TestParser.TestSpec("==Marked text==", "<p><mark>Marked text</mark></p>", "emphasis_extra");
|
||||
TestParser.TestSpec("==Marked text==", "<p><mark>Marked text</mark></p>", "emphasisextras");
|
||||
}
|
||||
}
|
||||
// # Extensions
|
||||
@@ -17991,7 +18104,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Ordered list with alpha letter");
|
||||
TestParser.TestSpec("a. First item\nb. Second item\nc. Last item", "<ol type=\"a\">\n<li>First item</li>\n<li>Second item</li>\n<li>Last item</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("a. First item\nb. Second item\nc. Last item", "<ol type=\"a\">\n<li>First item</li>\n<li>Second item</li>\n<li>Last item</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// It works also for uppercase alpha:
|
||||
@@ -18017,7 +18130,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Ordered list with alpha letter");
|
||||
TestParser.TestSpec("A. First item\nB. Second item\nC. Last item", "<ol type=\"A\">\n<li>First item</li>\n<li>Second item</li>\n<li>Last item</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("A. First item\nB. Second item\nC. Last item", "<ol type=\"A\">\n<li>First item</li>\n<li>Second item</li>\n<li>Last item</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// Like for numbered list, a list can start with a different letter
|
||||
@@ -18041,7 +18154,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Ordered list with alpha letter");
|
||||
TestParser.TestSpec("b. First item\nc. Second item", "<ol type=\"a\" start=\"b\">\n<li>First item</li>\n<li>Second item</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("b. First item\nc. Second item", "<ol type=\"a\" start=\"b\">\n<li>First item</li>\n<li>Second item</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// A different type of list will break the existing list:
|
||||
@@ -18069,7 +18182,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Ordered list with alpha letter");
|
||||
TestParser.TestSpec("a. First item1\nb. Second item\nA. First item2", "<ol type=\"a\">\n<li>First item1</li>\n<li>Second item</li>\n</ol>\n<ol type=\"A\">\n<li>First item2</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("a. First item1\nb. Second item\nA. First item2", "<ol type=\"a\">\n<li>First item1</li>\n<li>Second item</li>\n</ol>\n<ol type=\"A\">\n<li>First item2</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// ## Ordered list with roman letter
|
||||
@@ -18099,7 +18212,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Ordered list with roman letter");
|
||||
TestParser.TestSpec("i. First item\nii. Second item\niii. Third item\niv. Last item", "<ol type=\"i\">\n<li>First item</li>\n<li>Second item</li>\n<li>Third item</li>\n<li>Last item</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("i. First item\nii. Second item\niii. Third item\niv. Last item", "<ol type=\"i\">\n<li>First item</li>\n<li>Second item</li>\n<li>Third item</li>\n<li>Last item</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// It works also for uppercase alpha:
|
||||
@@ -18127,7 +18240,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Ordered list with roman letter");
|
||||
TestParser.TestSpec("I. First item\nII. Second item\nIII. Third item\nIV. Last item", "<ol type=\"I\">\n<li>First item</li>\n<li>Second item</li>\n<li>Third item</li>\n<li>Last item</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("I. First item\nII. Second item\nIII. Third item\nIV. Last item", "<ol type=\"I\">\n<li>First item</li>\n<li>Second item</li>\n<li>Third item</li>\n<li>Last item</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// Like for numbered list, a list can start with a different letter
|
||||
@@ -18151,7 +18264,7 @@ namespace Markdig.Tests
|
||||
// </ol>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 7, "Extensions Ordered list with roman letter");
|
||||
TestParser.TestSpec("ii. First item\niii. Second item", "<ol type=\"i\" start=\"ii\">\n<li>First item</li>\n<li>Second item</li>\n</ol>", "list_extra");
|
||||
TestParser.TestSpec("ii. First item\niii. Second item", "<ol type=\"i\" start=\"ii\">\n<li>First item</li>\n<li>Second item</li>\n</ol>", "listextras");
|
||||
}
|
||||
}
|
||||
// # Extensions
|
||||
@@ -18182,7 +18295,7 @@ namespace Markdig.Tests
|
||||
// </figure>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Figures");
|
||||
TestParser.TestSpec("^^^\nThis is a figure\n^^^ This is a *caption*", "<figure>\n<p>This is a figure</p>\n<figcaption>This is a <em>caption</em></figcaption>\n</figure>", "figures+footers+cites");
|
||||
TestParser.TestSpec("^^^\nThis is a figure\n^^^ This is a *caption*", "<figure>\n<p>This is a figure</p>\n<figcaption>This is a <em>caption</em></figcaption>\n</figure>", "figures+footers+citations");
|
||||
}
|
||||
}
|
||||
// ## Footers
|
||||
@@ -18206,7 +18319,7 @@ namespace Markdig.Tests
|
||||
// multi-line</footer>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Footers");
|
||||
TestParser.TestSpec("^^ This is a footer\n^^ multi-line", "<footer>This is a footer\nmulti-line</footer>", "figures+footers+cites");
|
||||
TestParser.TestSpec("^^ This is a footer\n^^ multi-line", "<footer>This is a footer\nmulti-line</footer>", "figures+footers+citations");
|
||||
}
|
||||
}
|
||||
// ## Cite
|
||||
@@ -18228,7 +18341,7 @@ namespace Markdig.Tests
|
||||
// <p>This is a <cite>citation of someone</cite></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Cite");
|
||||
TestParser.TestSpec("This is a \"\"citation of someone\"\"", "<p>This is a <cite>citation of someone</cite></p>", "figures+footers+cites");
|
||||
TestParser.TestSpec("This is a \"\"citation of someone\"\"", "<p>This is a <cite>citation of someone</cite></p>", "figures+footers+citations");
|
||||
}
|
||||
}
|
||||
// # Extensions
|
||||
@@ -18254,7 +18367,7 @@ namespace Markdig.Tests
|
||||
// <p>This is a <span class="math">math block</span></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is a $math block$", "<p>This is a <span class=\"math\">math block</span></p>", "math");
|
||||
TestParser.TestSpec("This is a $math block$", "<p>This is a <span class=\"math\">math block</span></p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// Or by `$$...$$` embracing it by:
|
||||
@@ -18274,7 +18387,7 @@ namespace Markdig.Tests
|
||||
// <p>This is a <span class="math">math block</span></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is a $$math block$$", "<p>This is a <span class=\"math\">math block</span></p>", "math");
|
||||
TestParser.TestSpec("This is a $$math block$$", "<p>This is a <span class=\"math\">math block</span></p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// The opening `$` and closing `$` is following the rules of the emphasis delimiter `_`:
|
||||
@@ -18294,7 +18407,7 @@ namespace Markdig.Tests
|
||||
// <p>This is not a $ math block $</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is not a $ math block $", "<p>This is not a $ math block $</p>", "math");
|
||||
TestParser.TestSpec("This is not a $ math block $", "<p>This is not a $ math block $</p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// For the opening `$` it requires a space or a punctuation before (but cannot be used within a word):
|
||||
@@ -18314,7 +18427,7 @@ namespace Markdig.Tests
|
||||
// <p>This is not a m$ath block$</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is not a m$ath block$", "<p>This is not a m$ath block$</p>", "math");
|
||||
TestParser.TestSpec("This is not a m$ath block$", "<p>This is not a m$ath block$</p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word):
|
||||
@@ -18334,7 +18447,7 @@ namespace Markdig.Tests
|
||||
// <p>This is not a $math bloc$k</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is not a $math bloc$k", "<p>This is not a $math bloc$k</p>", "math");
|
||||
TestParser.TestSpec("This is not a $math bloc$k", "<p>This is not a $math bloc$k</p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// For the closing `$` it requires a space after or a punctuation (but cannot be preceded by a space and cannot be used within a word):
|
||||
@@ -18354,7 +18467,7 @@ namespace Markdig.Tests
|
||||
// <p>This is should not match a 16$ or a $15</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is should not match a 16$ or a $15", "<p>This is should not match a 16$ or a $15</p>", "math");
|
||||
TestParser.TestSpec("This is should not match a 16$ or a $15", "<p>This is should not match a 16$ or a $15</p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// A `$` can be escaped between a math inline block by using the escape `\\`
|
||||
@@ -18374,7 +18487,7 @@ namespace Markdig.Tests
|
||||
// <p>This is a <span class="math">math \$ block</span></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 7, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is a $math \\$ block$", "<p>This is a <span class=\"math\">math \\$ block</span></p>", "math");
|
||||
TestParser.TestSpec("This is a $math \\$ block$", "<p>This is a <span class=\"math\">math \\$ block</span></p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// At most, two `$` will be matched for the opening and closing:
|
||||
@@ -18394,7 +18507,7 @@ namespace Markdig.Tests
|
||||
// <p>This is a <span class="math">$math block$</span></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 8, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is a $$$math block$$$", "<p>This is a <span class=\"math\">$math block$</span></p>", "math");
|
||||
TestParser.TestSpec("This is a $$$math block$$$", "<p>This is a <span class=\"math\">$math block$</span></p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// A mathematic block takes precedence over standard emphasis `*` `_`:
|
||||
@@ -18414,7 +18527,7 @@ namespace Markdig.Tests
|
||||
// <p>This is *a <span class="math">math* block</span></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 9, "Extensions Math Inline");
|
||||
TestParser.TestSpec("This is *a $math* block$", "<p>This is *a <span class=\"math\">math* block</span></p>", "math");
|
||||
TestParser.TestSpec("This is *a $math* block$", "<p>This is *a <span class=\"math\">math* block</span></p>", "mathematics");
|
||||
}
|
||||
}
|
||||
// ## Math Block
|
||||
@@ -18446,7 +18559,7 @@ namespace Markdig.Tests
|
||||
// </div>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Math Block");
|
||||
TestParser.TestSpec("$$\n\\begin{equation}\n \\int_0^\\infty \\frac{x^3}{e^x-1}\\,dx = \\frac{\\pi^4}{15}\n \\label{eq:sample}\n\\end{equation}\n$$", "<div class=\"math\">\\begin{equation}\n \\int_0^\\infty \\frac{x^3}{e^x-1}\\,dx = \\frac{\\pi^4}{15}\n \\label{eq:sample}\n\\end{equation}\n</div>", "math");
|
||||
TestParser.TestSpec("$$\n\\begin{equation}\n \\int_0^\\infty \\frac{x^3}{e^x-1}\\,dx = \\frac{\\pi^4}{15}\n \\label{eq:sample}\n\\end{equation}\n$$", "<div class=\"math\">\\begin{equation}\n \\int_0^\\infty \\frac{x^3}{e^x-1}\\,dx = \\frac{\\pi^4}{15}\n \\label{eq:sample}\n\\end{equation}\n</div>", "mathematics");
|
||||
}
|
||||
}
|
||||
// # Extensions
|
||||
@@ -18583,7 +18696,7 @@ namespace Markdig.Tests
|
||||
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Media links");
|
||||
TestParser.TestSpec("\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen></iframe></p>", "medias");
|
||||
TestParser.TestSpec("\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen></iframe></p>", "medialinks");
|
||||
}
|
||||
}
|
||||
// # Extensions
|
||||
@@ -18863,7 +18976,6 @@ namespace Markdig.Tests
|
||||
TestParser.TestSpec("This is ``a code span''`` ", "<p>This is <code>a code span''</code></p>", "smartypants");
|
||||
}
|
||||
}
|
||||
// An emphasis starting inside left/right quotes will span over the right quote:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsSmartyPantsQuotes
|
||||
{
|
||||
@@ -18874,35 +18986,38 @@ namespace Markdig.Tests
|
||||
// Section: Extensions SmartyPants Quotes
|
||||
//
|
||||
// The following CommonMark:
|
||||
// This is "a *text" with an emphasis*
|
||||
// hello ``there```
|
||||
// test
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is “a <em>text” with an emphasis</em></p>
|
||||
// <p>hello “there”`
|
||||
// test</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 15, "Extensions SmartyPants Quotes");
|
||||
TestParser.TestSpec("This is \"a *text\" with an emphasis*", "<p>This is “a <em>text” with an emphasis</em></p>", "smartypants");
|
||||
TestParser.TestSpec("hello ``there```\ntest", "<p>hello “there”`\ntest</p>", "smartypants");
|
||||
}
|
||||
}
|
||||
// ## SmartyPants Separators
|
||||
// An emphasis starting inside left/right quotes will span over the right quote:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsSmartyPantsSeparators
|
||||
public partial class TestExtensionsSmartyPantsQuotes
|
||||
{
|
||||
[Test]
|
||||
public void Example016()
|
||||
{
|
||||
// Example 16
|
||||
// Section: Extensions SmartyPants Separators
|
||||
// Section: Extensions SmartyPants Quotes
|
||||
//
|
||||
// The following CommonMark:
|
||||
// This is a -- text
|
||||
// This is "a *text" with an emphasis*
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a – text</p>
|
||||
// <p>This is “a <em>text” with an emphasis</em></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 16, "Extensions SmartyPants Separators");
|
||||
TestParser.TestSpec("This is a -- text", "<p>This is a – text</p>", "smartypants");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 16, "Extensions SmartyPants Quotes");
|
||||
TestParser.TestSpec("This is \"a *text\" with an emphasis*", "<p>This is “a <em>text” with an emphasis</em></p>", "smartypants");
|
||||
}
|
||||
}
|
||||
// ## SmartyPants Separators
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsSmartyPantsSeparators
|
||||
{
|
||||
@@ -18913,13 +19028,13 @@ namespace Markdig.Tests
|
||||
// Section: Extensions SmartyPants Separators
|
||||
//
|
||||
// The following CommonMark:
|
||||
// This is a --- text
|
||||
// This is a -- text
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a — text</p>
|
||||
// <p>This is a – text</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 17, "Extensions SmartyPants Separators");
|
||||
TestParser.TestSpec("This is a --- text", "<p>This is a — text</p>", "smartypants");
|
||||
TestParser.TestSpec("This is a -- text", "<p>This is a – text</p>", "smartypants");
|
||||
}
|
||||
}
|
||||
[TestFixture]
|
||||
@@ -18932,12 +19047,31 @@ namespace Markdig.Tests
|
||||
// Section: Extensions SmartyPants Separators
|
||||
//
|
||||
// The following CommonMark:
|
||||
// This is a --- text
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a — text</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 18, "Extensions SmartyPants Separators");
|
||||
TestParser.TestSpec("This is a --- text", "<p>This is a — text</p>", "smartypants");
|
||||
}
|
||||
}
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsSmartyPantsSeparators
|
||||
{
|
||||
[Test]
|
||||
public void Example019()
|
||||
{
|
||||
// Example 19
|
||||
// Section: Extensions SmartyPants Separators
|
||||
//
|
||||
// The following CommonMark:
|
||||
// This is a en ellipsis...
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a en ellipsis…</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 18, "Extensions SmartyPants Separators");
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 19, "Extensions SmartyPants Separators");
|
||||
TestParser.TestSpec("This is a en ellipsis...", "<p>This is a en ellipsis…</p>", "smartypants");
|
||||
}
|
||||
}
|
||||
@@ -19160,4 +19294,58 @@ namespace Markdig.Tests
|
||||
TestParser.TestSpec("[With a new text][This is a heading]\n# This is a heading", "<p><a href=\"#this-is-a-heading\">With a new text</a></p>\n<h1 id=\"this-is-a-heading\">This is a heading</h1>", "autoidentifiers");
|
||||
}
|
||||
}
|
||||
// # Extensions
|
||||
//
|
||||
// Adds support for task lists:
|
||||
//
|
||||
// ## TaskLists
|
||||
//
|
||||
// A task list item consist of `[ ]` or `[x]` or `[X]` inside a list item (ordered or unordered)
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsTaskLists
|
||||
{
|
||||
[Test]
|
||||
public void Example001()
|
||||
{
|
||||
// Example 1
|
||||
// Section: Extensions TaskLists
|
||||
//
|
||||
// The following CommonMark:
|
||||
// - [ ] Item1
|
||||
// - [x] Item2
|
||||
// - [ ] Item3
|
||||
// - Item4
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <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>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions TaskLists");
|
||||
TestParser.TestSpec("- [ ] Item1\n- [x] Item2\n- [ ] Item3\n- Item4", "<ul class=\"contains-task-list\">\n<li class=\"task-list-item\"><input disabled=\"disabled\" type=\"checkbox\" /> Item1</li>\n<li class=\"task-list-item\"><input disabled=\"disabled\" type=\"checkbox\" checked=\"checked\" /> Item2</li>\n<li class=\"task-list-item\"><input disabled=\"disabled\" type=\"checkbox\" /> Item3</li>\n<li>Item4</li>\n</ul>", "tasklists");
|
||||
}
|
||||
}
|
||||
// A task is not recognized outside a list item:
|
||||
[TestFixture]
|
||||
public partial class TestExtensionsTaskLists
|
||||
{
|
||||
[Test]
|
||||
public void Example002()
|
||||
{
|
||||
// Example 2
|
||||
// Section: Extensions TaskLists
|
||||
//
|
||||
// The following CommonMark:
|
||||
// [ ] This is not a task list
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>[ ] This is not a task list</p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions TaskLists");
|
||||
TestParser.TestSpec("[ ] This is not a task list", "<p>[ ] This is not a task list</p>", "tasklists");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +42,21 @@ SOFTWARE.
|
||||
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"), "emphasis_extra"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("EmphasisExtraSpecs.md"), "emphasisextras"),
|
||||
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"), "list_extra"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+cites"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "math"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("ListExtraSpecs.md"), "listextras"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+citations"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "mathematics"),
|
||||
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("MediaSpecs.md"), "medialinks"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("SmartyPantsSpecs.md"), "smartypants"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("AutoIdentifierSpecs.md"), "autoidentifiers"),
|
||||
new KeyValuePair<string, string>(Host.ResolvePath("TaskListSpecs.md"), "tasklists"),
|
||||
};
|
||||
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>
|
||||
````````````````````````````````
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,36 +80,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 +133,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 +154,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 +233,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]
|
||||
|
||||
@@ -14,10 +14,10 @@ namespace Markdig.Tests
|
||||
{
|
||||
foreach (var pipeline in GetPipeline(extensions))
|
||||
{
|
||||
Console.WriteLine($"Pipeline configured with extensions: {extensions}");
|
||||
Console.WriteLine($"Pipeline configured with extensions: {pipeline.Key}");
|
||||
// Uncomment this line to get more debug information for process inlines.
|
||||
//pipeline.DebugLog = Console.Out;
|
||||
var result = Markdown.ToHtml(inputText, pipeline);
|
||||
var result = Markdown.ToHtml(inputText, pipeline.Value);
|
||||
|
||||
result = Compact(result);
|
||||
expectedOutputText = Compact(expectedOutputText);
|
||||
@@ -34,90 +34,42 @@ namespace Markdig.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MarkdownPipeline> GetPipeline(string extensionsGroupText)
|
||||
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();
|
||||
foreach (var extension in extensionsText.Split(new[] { '+' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
switch (extension.ToLowerInvariant())
|
||||
{
|
||||
case "pipetables":
|
||||
pipeline.UsePipeTable();
|
||||
break;
|
||||
case "emphasis_extra":
|
||||
pipeline.UseEmphasisExtra();
|
||||
break;
|
||||
case "list_extra":
|
||||
pipeline.UseListExtra();
|
||||
break;
|
||||
case "hardlinebreak":
|
||||
pipeline.UseSoftlineBreakAsHardlineBreak();
|
||||
break;
|
||||
case "footnotes":
|
||||
pipeline.UseFootnotes();
|
||||
break;
|
||||
case "footers":
|
||||
pipeline.UseFooter();
|
||||
break;
|
||||
case "cites":
|
||||
pipeline.UseCite();
|
||||
break;
|
||||
case "attributes":
|
||||
pipeline.UseGenericAttributes();
|
||||
break;
|
||||
case "gridtables":
|
||||
pipeline.UseGridTable();
|
||||
break;
|
||||
case "abbreviations":
|
||||
pipeline.UseAbbreviation();
|
||||
break;
|
||||
case "emojis":
|
||||
pipeline.UseEmojiAndSmiley();
|
||||
break;
|
||||
case "definitionlists":
|
||||
pipeline.UseDefinitionList();
|
||||
break;
|
||||
case "customcontainers":
|
||||
pipeline.UseCustomContainer();
|
||||
break;
|
||||
case "figures":
|
||||
pipeline.UseFigure();
|
||||
break;
|
||||
case "math":
|
||||
pipeline.UseMath();
|
||||
break;
|
||||
case "bootstrap":
|
||||
pipeline.UseBootstrap();
|
||||
break;
|
||||
case "medias":
|
||||
pipeline.UseMedia();
|
||||
break;
|
||||
case "smartypants":
|
||||
pipeline.UseSmartyPants();
|
||||
break;
|
||||
case "autoidentifiers":
|
||||
pipeline.UseAutoIdentifier();
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine($"Unsupported extension: {extension}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
yield return pipeline.Build();
|
||||
var pipeline = new MarkdownPipelineBuilder().Configure(extensionsText);
|
||||
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, pipeline.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(' ', '·');
|
||||
|
||||
@@ -21,8 +21,8 @@ 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);
|
||||
}
|
||||
@@ -30,7 +30,7 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
|
||||
[Test]
|
||||
public void TestSamePipelineAllExtensions()
|
||||
{
|
||||
var pipeline = new MarkdownPipelineBuilder().UseAllExtensions().Build();
|
||||
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
|
||||
|
||||
// Reuse the same pipeline
|
||||
var result1 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
|
||||
|
||||
780
src/Markdig.Tests/TestSourcePosition.cs
Normal file
780
src/Markdig.Tests/TestSourcePosition.cs
Normal file
@@ -0,0 +1,780 @@
|
||||
// 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-4
|
||||
literal ( 0, 3) 3-3
|
||||
emphasis ( 0, 5) 5-7
|
||||
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()
|
||||
{
|
||||
// 01234567
|
||||
Check("0 1", @"
|
||||
paragraph ( 0, 0) 0-7
|
||||
literal ( 0, 0) 0-0
|
||||
htmlentity ( 0, 1) 1-6
|
||||
literal ( 0, 7) 7-7
|
||||
");
|
||||
}
|
||||
|
||||
[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, 4) 7-13
|
||||
");
|
||||
}
|
||||
|
||||
[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, 4) 4-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, 4) 5-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);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/Markdig.WebApp/ApiController.cs
Normal file
48
src/Markdig.WebApp/ApiController.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Markdig.WebApp
|
||||
{
|
||||
public class ApiController : Controller
|
||||
{
|
||||
// GET api/to_html?text=xxx&extensions=advanced
|
||||
[Route("api/to_html")]
|
||||
[HttpGet()]
|
||||
public object Get([FromQuery] string text, [FromQuery] string extension)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
text = string.Empty;
|
||||
}
|
||||
|
||||
if (text.Length > 1000)
|
||||
{
|
||||
text = text.Substring(0, 1000);
|
||||
}
|
||||
|
||||
var pipeline = new MarkdownPipelineBuilder().Configure(extension).Build();
|
||||
var result = Markdown.ToHtml(text, pipeline);
|
||||
|
||||
return new {name = "markdig", html = result, version = Markdown.Version};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new { name = "markdig", html = "exception: " + GetPrettyMessageFromException(ex), version = Markdown.Version };
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPrettyMessageFromException(Exception exception)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
while (exception != null)
|
||||
{
|
||||
builder.Append(exception.Message);
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Markdig.WebApp/Markdig.WebApp.xproj
Normal file
19
src/Markdig.WebApp/Markdig.WebApp.xproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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>
|
||||
25
src/Markdig.WebApp/Program.cs
Normal file
25
src/Markdig.WebApp/Program.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace Markdig.WebApp
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = new WebHostBuilder()
|
||||
.UseKestrel()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseIISIntegration()
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
# Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
# Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
[cmdletbinding(SupportsShouldProcess=$true)]
|
||||
param($publishProperties=@{}, $packOutput, $pubProfilePath, $nugetUrl)
|
||||
|
||||
# to learn more about this file visit https://go.microsoft.com/fwlink/?LinkId=524327
|
||||
$publishModuleVersion = '1.1.0'
|
||||
|
||||
function Get-PublishModulePath{
|
||||
[cmdletbinding()]
|
||||
param()
|
||||
process{
|
||||
$keysToCheck = @('hklm:\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\{0}',
|
||||
'hklm:\SOFTWARE\Microsoft\VisualStudio\{0}',
|
||||
'hklm:\SOFTWARE\Wow6432Node\Microsoft\VWDExpress\{0}',
|
||||
'hklm:\SOFTWARE\Microsoft\VWDExpress\{0}'
|
||||
)
|
||||
$versions = @('14.0', '15.0')
|
||||
|
||||
[string]$publishModulePath=$null
|
||||
:outer foreach($keyToCheck in $keysToCheck){
|
||||
foreach($version in $versions){
|
||||
if(Test-Path ($keyToCheck -f $version) ){
|
||||
$vsInstallPath = (Get-itemproperty ($keyToCheck -f $version) -Name InstallDir -ErrorAction SilentlyContinue | select -ExpandProperty InstallDir -ErrorAction SilentlyContinue)
|
||||
|
||||
if($vsInstallPath){
|
||||
$installedPublishModulePath = "{0}Extensions\Microsoft\Web Tools\Publish\Scripts\{1}\" -f $vsInstallPath, $publishModuleVersion
|
||||
if(!(Test-Path $installedPublishModulePath)){
|
||||
$vsInstallPath = $vsInstallPath + 'VWDExpress'
|
||||
$installedPublishModulePath = "{0}Extensions\Microsoft\Web Tools\Publish\Scripts\{1}\" -f $vsInstallPath, $publishModuleVersion
|
||||
}
|
||||
if(Test-Path $installedPublishModulePath){
|
||||
$publishModulePath = $installedPublishModulePath
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$publishModulePath
|
||||
}
|
||||
}
|
||||
|
||||
$publishModulePath = Get-PublishModulePath
|
||||
|
||||
$defaultPublishSettings = New-Object psobject -Property @{
|
||||
LocalInstallDir = $publishModulePath
|
||||
}
|
||||
|
||||
function Enable-PackageDownloader{
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
$toolsDir = "$env:LOCALAPPDATA\Microsoft\Web Tools\Publish\package-downloader-$publishModuleVersion\",
|
||||
$pkgDownloaderDownloadUrl = 'https://go.microsoft.com/fwlink/?LinkId=524325') # package-downloader.psm1
|
||||
process{
|
||||
if(get-module package-downloader){
|
||||
remove-module package-downloader | Out-Null
|
||||
}
|
||||
|
||||
if(!(get-module package-downloader)){
|
||||
if(!(Test-Path $toolsDir)){ New-Item -Path $toolsDir -ItemType Directory -WhatIf:$false }
|
||||
|
||||
$expectedPath = (Join-Path ($toolsDir) 'package-downloader.psm1')
|
||||
if(!(Test-Path $expectedPath)){
|
||||
'Downloading [{0}] to [{1}]' -f $pkgDownloaderDownloadUrl,$expectedPath | Write-Verbose
|
||||
(New-Object System.Net.WebClient).DownloadFile($pkgDownloaderDownloadUrl, $expectedPath)
|
||||
}
|
||||
|
||||
if(!$expectedPath){throw ('Unable to download package-downloader.psm1')}
|
||||
|
||||
'importing module [{0}]' -f $expectedPath | Write-Output
|
||||
Import-Module $expectedPath -DisableNameChecking -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Enable-PublishModule{
|
||||
[cmdletbinding()]
|
||||
param()
|
||||
process{
|
||||
if(get-module publish-module){
|
||||
remove-module publish-module | Out-Null
|
||||
}
|
||||
|
||||
if(!(get-module publish-module)){
|
||||
$localpublishmodulepath = Join-Path $defaultPublishSettings.LocalInstallDir 'publish-module.psm1'
|
||||
if(Test-Path $localpublishmodulepath){
|
||||
'importing module [publish-module="{0}"] from local install dir' -f $localpublishmodulepath | Write-Verbose
|
||||
Import-Module $localpublishmodulepath -DisableNameChecking -Force
|
||||
$true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
if (!(Enable-PublishModule)){
|
||||
Enable-PackageDownloader
|
||||
Enable-NuGetModule -name 'publish-module' -version $publishModuleVersion -nugetUrl $nugetUrl
|
||||
}
|
||||
|
||||
'Calling Publish-AspNet' | Write-Verbose
|
||||
# call Publish-AspNet to perform the publish operation
|
||||
Publish-AspNet -publishProperties $publishProperties -packOutput $packOutput -pubProfilePath $pubProfilePath
|
||||
}
|
||||
catch{
|
||||
"An error occurred during publish.`n{0}" -f $_.Exception.Message | Write-Error
|
||||
}
|
||||
28
src/Markdig.WebApp/Properties/launchSettings.json
Normal file
28
src/Markdig.WebApp/Properties/launchSettings.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:65396/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Markdig.WebApp": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:5000/api/to_html?text=yes",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/Markdig.WebApp/Startup.cs
Normal file
56
src/Markdig.WebApp/Startup.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Markdig.WebApp
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IHostingEnvironment env)
|
||||
{
|
||||
var builder = new ConfigurationBuilder()
|
||||
.SetBasePath(env.ContentRootPath)
|
||||
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
|
||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
|
||||
|
||||
if (env.IsEnvironment("Development"))
|
||||
{
|
||||
// This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.
|
||||
builder.AddApplicationInsightsSettings(developerMode: true);
|
||||
}
|
||||
|
||||
builder.AddEnvironmentVariables();
|
||||
Configuration = builder.Build();
|
||||
}
|
||||
|
||||
public IConfigurationRoot Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Add framework services.
|
||||
services.AddApplicationInsightsTelemetry(Configuration);
|
||||
|
||||
services.AddMvc();
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
|
||||
{
|
||||
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
|
||||
loggerFactory.AddDebug();
|
||||
|
||||
app.UseApplicationInsightsRequestTelemetry();
|
||||
|
||||
app.UseApplicationInsightsExceptionTelemetry();
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/Markdig.WebApp/appsettings.json
Normal file
10
src/Markdig.WebApp/appsettings.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/Markdig.WebApp/project.json
Normal file
58
src/Markdig.WebApp/project.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"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%" ]
|
||||
}
|
||||
}
|
||||
14
src/Markdig.WebApp/web.config
Normal file
14
src/Markdig.WebApp/web.config
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
|
||||
<!--
|
||||
Configure your application settings in appsettings.json. Learn more at http://go.microsoft.com/fwlink/?LinkId=786380
|
||||
-->
|
||||
|
||||
<system.webServer>
|
||||
<handlers>
|
||||
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
|
||||
</handlers>
|
||||
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="false"/>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
{
|
||||
@@ -84,6 +91,7 @@ namespace Markdig.Extensions.Abbreviations
|
||||
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
|
||||
{
|
||||
var literal = (LiteralInline) processor.Inline;
|
||||
var originalLiteral = literal;
|
||||
|
||||
ContainerInline container = null;
|
||||
|
||||
@@ -96,13 +104,13 @@ namespace Markdig.Extensions.Abbreviations
|
||||
if (matcher.TryMatch(text, i, content.End - i + 1, out match))
|
||||
{
|
||||
// The word matched must be embraced by punctuation or whitespace or \0.
|
||||
var c = content.PeekCharExtra(i - 1);
|
||||
var c = content.PeekCharAbsolute(i - 1);
|
||||
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var indexAfterMatch = i + match.Length;
|
||||
c = content.PeekCharExtra(indexAfterMatch);
|
||||
c = content.PeekCharAbsolute(indexAfterMatch);
|
||||
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
|
||||
{
|
||||
continue;
|
||||
@@ -118,15 +126,33 @@ namespace Markdig.Extensions.Abbreviations
|
||||
// If we don't have a container, create a new one
|
||||
if (container == null)
|
||||
{
|
||||
container = new ContainerInline();
|
||||
container = 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);
|
||||
|
||||
literal.Span.End = abbrInline.Span.Start - 1;
|
||||
// Truncate it before the abbreviation
|
||||
literal.Content.End = i - 1;
|
||||
}
|
||||
@@ -143,7 +169,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,12 +184,11 @@ 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -30,6 +30,8 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
var startPosition = processor.Start;
|
||||
|
||||
var column = processor.ColumnBeforeIndent;
|
||||
processor.NextChar();
|
||||
processor.ParseIndent();
|
||||
@@ -62,16 +64,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,14 +88,18 @@ 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.Span.End = processor.Line.End;
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -100,11 +112,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 +132,8 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
{
|
||||
definitionItem.RemoveAt(definitionItem.Count - 1);
|
||||
}
|
||||
|
||||
list.Span.End = list.LastChild.Span.End;
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
@@ -126,10 +142,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 +178,7 @@ namespace Markdig.Extensions.DefinitionLists
|
||||
definitionItem.RemoveAt(definitionItem.Count - 1);
|
||||
}
|
||||
|
||||
list.Span.End = list.LastChild.Span.End;
|
||||
return BlockState.Break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ namespace Markdig.Extensions.Emoji
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
string match;
|
||||
var startPosition = slice.Start;
|
||||
if (!textMatchHelper.TryMatch(slice.Text, slice.Start, slice.Length, out match))
|
||||
{
|
||||
return false;
|
||||
@@ -89,7 +90,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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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.Span.End = 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.Span.End = 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,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.Span.End = processor.Line.End;
|
||||
}
|
||||
|
||||
return BlockState.Continue;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ 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;
|
||||
@@ -48,7 +49,11 @@ namespace Markdig.Extensions.Footnotes
|
||||
|
||||
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,7 +88,7 @@ namespace Markdig.Extensions.Footnotes
|
||||
return BlockState.ContinueDiscard;
|
||||
}
|
||||
|
||||
if (footnote.IsLastLineEmpty && processor.Start == 0)
|
||||
if (footnote.IsLastLineEmpty && processor.Column == 0)
|
||||
{
|
||||
return BlockState.Break;
|
||||
}
|
||||
|
||||
@@ -52,11 +52,19 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
// 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;
|
||||
@@ -65,6 +66,14 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
var currentHtmlAttributes = objectToAttach.GetAttributes();
|
||||
attributes.CopyTo(currentHtmlAttributes);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -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.)
|
||||
@@ -5,7 +5,7 @@
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
|
||||
namespace Markdig.Extensions.ListExtra
|
||||
namespace Markdig.Extensions.ListExtras
|
||||
{
|
||||
/// <summary>
|
||||
/// Parser that adds supports for parsing alpha/roman list items (e.g: `a)` or `a.` or `ii.` or `II.`)
|
||||
@@ -5,6 +5,7 @@
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Markdig.Extensions.Mathematics
|
||||
{
|
||||
@@ -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();
|
||||
@@ -63,13 +66,6 @@ namespace Markdig.Extensions.Mathematics
|
||||
pc = match;
|
||||
while (c != '\0')
|
||||
{
|
||||
// Count new '\n'
|
||||
if (c == '\n')
|
||||
{
|
||||
processor.LocalLineIndex++;
|
||||
processor.LineIndex++;
|
||||
}
|
||||
|
||||
// Don't process sticks if we have a '\' as a previous char
|
||||
if (pc != '\\' )
|
||||
{
|
||||
@@ -107,8 +103,13 @@ namespace Markdig.Extensions.Mathematics
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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
|
||||
{
|
||||
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(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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,8 @@ namespace Markdig.Extensions.SmartyPants
|
||||
var c = slice.CurrentChar;
|
||||
var openingChar = c;
|
||||
|
||||
var startingPosition = slice.Start;
|
||||
|
||||
// undefined first
|
||||
var type = (SmartyPantType) 0;
|
||||
|
||||
@@ -161,11 +163,17 @@ 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)
|
||||
@@ -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,7 +279,12 @@ 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();
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Markdig.Extensions.Tables
|
||||
|
||||
GridTableState tableState = null;
|
||||
var c = line.CurrentChar;
|
||||
var startPosition = processor.Start;
|
||||
while (true)
|
||||
{
|
||||
if (c == '+')
|
||||
@@ -51,11 +52,11 @@ namespace Markdig.Extensions.Tables
|
||||
{
|
||||
tableState = new GridTableState()
|
||||
{
|
||||
Start = processor.Column,
|
||||
Start = processor.Start,
|
||||
ExpectRow = true,
|
||||
};
|
||||
}
|
||||
tableState.AddColumn(startCharacter, line.Start - 1, align);
|
||||
tableState.AddColumn(startCharacter - startPosition, line.Start - 1 - startPosition, align);
|
||||
|
||||
c = line.CurrentChar;
|
||||
continue;
|
||||
@@ -105,7 +106,7 @@ namespace Markdig.Extensions.Tables
|
||||
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
|
||||
|
||||
// We expect to start at the same
|
||||
if (processor.Start == tableState.Start)
|
||||
//if (processor.Start == tableState.Start)
|
||||
{
|
||||
var columns = tableState.ColumnSlices;
|
||||
|
||||
@@ -172,10 +173,10 @@ namespace Markdig.Extensions.Tables
|
||||
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
|
||||
|
||||
var sliceForCell = line;
|
||||
sliceForCell.Start = column.Start + 1;
|
||||
sliceForCell.Start = line.Start + column.Start + 1;
|
||||
if (nextColumn != null)
|
||||
{
|
||||
sliceForCell.End = nextColumn.Start - 1;
|
||||
sliceForCell.End = line.Start + nextColumn.Start - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -184,7 +185,7 @@ namespace Markdig.Extensions.Tables
|
||||
// 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.End = line.Start + columnEnd;
|
||||
}
|
||||
}
|
||||
sliceForCell.TrimEnd();
|
||||
@@ -208,7 +209,7 @@ namespace Markdig.Extensions.Tables
|
||||
// with the 2 slices
|
||||
if (gridTable.Count == 0)
|
||||
{
|
||||
var parser = processor.Parsers.Find<ParagraphBlockParser>();
|
||||
var parser = processor.Parsers.FindExact<ParagraphBlockParser>();
|
||||
// Discard the grid table
|
||||
var parent = gridTable.Parent;
|
||||
processor.Discard(gridTable);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,12 @@ 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());
|
||||
}
|
||||
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
|
||||
{
|
||||
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(Options));
|
||||
|
||||
@@ -55,14 +55,22 @@ namespace Markdig.Extensions.Tables
|
||||
// tracking other delimiters on following lines
|
||||
var tableState = processor.ParserStates[Index] as TableState;
|
||||
bool isFirstLineEmpty = false;
|
||||
|
||||
|
||||
int globalLineIndex;
|
||||
int column;
|
||||
var position = processor.GetSourcePosition(slice.Start, out globalLineIndex, out column);
|
||||
var localLineIndex = globalLineIndex - processor.LineIndex;
|
||||
|
||||
if (tableState == null)
|
||||
{
|
||||
|
||||
// A table could be preceded by an empty line or a line containing an inline
|
||||
// that has not been added to the stack, so we consider this as a valid
|
||||
// start for a table. Typically, with this, we can have an attributes {...}
|
||||
// starting on the first line of a pipe table, even if the first line
|
||||
// doesn't have a pipe
|
||||
if (processor.Inline != null &&(processor.LocalLineIndex > 0 || c == '\n'))
|
||||
if (processor.Inline != null && (localLineIndex > 0 || c == '\n'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -92,14 +100,20 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
else
|
||||
{
|
||||
processor.Inline = new PiprTableDelimiterInline(this) { LocalLineIndex = processor.LocalLineIndex };
|
||||
var deltaLine = processor.LocalLineIndex - tableState.LineIndex;
|
||||
processor.Inline = new PiprTableDelimiterInline(this)
|
||||
{
|
||||
Span = new SourceSpan(position, position),
|
||||
Line = globalLineIndex,
|
||||
Column = column,
|
||||
LocalLineIndex = localLineIndex
|
||||
};
|
||||
var deltaLine = localLineIndex - tableState.LineIndex;
|
||||
if (deltaLine > 0)
|
||||
{
|
||||
tableState.IsInvalidTable = true;
|
||||
}
|
||||
tableState.LineHasPipe = true;
|
||||
tableState.LineIndex = processor.LocalLineIndex;
|
||||
tableState.LineIndex = localLineIndex;
|
||||
slice.NextChar(); // Skip the `|` character
|
||||
|
||||
tableState.ColumnAndLineDelimiters.Add(processor.Inline);
|
||||
@@ -185,7 +199,7 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
|
||||
// Continue
|
||||
if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe || tableState.LineIndex != state.LocalLineIndex)
|
||||
if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe ) //|| tableState.LineIndex != state.LocalLineIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -220,6 +234,12 @@ namespace Markdig.Extensions.Tables
|
||||
column = ((PiprTableDelimiterInline)column).FirstChild;
|
||||
}
|
||||
|
||||
// TODO: This is not accurate for the table
|
||||
table.Span.Start = column.Span.Start;
|
||||
table.Span.End = column.Span.End;
|
||||
table.Line = column.Line;
|
||||
table.Column = column.Column;
|
||||
|
||||
int lastIndex = 0;
|
||||
for (int i = 0; i < delimiters.Count; i++)
|
||||
{
|
||||
@@ -229,8 +249,7 @@ namespace Markdig.Extensions.Tables
|
||||
var beforeDelimiter = delimiter?.PreviousSibling;
|
||||
var nextLineColumn = delimiter?.NextSibling;
|
||||
|
||||
var row = new TableRow();
|
||||
table.Add(row);
|
||||
TableRow row = null;
|
||||
|
||||
for (int j = lastIndex; j <= i; j++)
|
||||
{
|
||||
@@ -254,20 +273,52 @@ namespace Markdig.Extensions.Tables
|
||||
continue;
|
||||
}
|
||||
|
||||
var columnContainer = new ContainerInline();
|
||||
var cellContainer = new ContainerInline();
|
||||
var item = column;
|
||||
var isFirstItem = true;
|
||||
TrimStart(item);
|
||||
while (item != null && !IsLine(item) && !(item is PiprTableDelimiterInline))
|
||||
{
|
||||
var nextSibling = item.NextSibling;
|
||||
item.Remove();
|
||||
columnContainer.AppendChild(item);
|
||||
cellContainer.AppendChild(item);
|
||||
if (isFirstItem)
|
||||
{
|
||||
cellContainer.Line = item.Line;
|
||||
cellContainer.Column = item.Column;
|
||||
cellContainer.Span.Start = item.Span.Start;
|
||||
isFirstItem = false;
|
||||
}
|
||||
cellContainer.Span.End = item.Span.End;
|
||||
item = nextSibling;
|
||||
}
|
||||
|
||||
var tableCell = new TableCell();
|
||||
var tableParagraph = new ParagraphBlock() {Inline = columnContainer};
|
||||
var tableParagraph = new ParagraphBlock()
|
||||
{
|
||||
Span = cellContainer.Span,
|
||||
Line = cellContainer.Line,
|
||||
Column = cellContainer.Column,
|
||||
Inline = cellContainer
|
||||
};
|
||||
|
||||
var tableCell = new TableCell()
|
||||
{
|
||||
Span = cellContainer.Span,
|
||||
Line = cellContainer.Line,
|
||||
Column = cellContainer.Column,
|
||||
};
|
||||
|
||||
tableCell.Add(tableParagraph);
|
||||
|
||||
if (row == null)
|
||||
{
|
||||
row = new TableRow()
|
||||
{
|
||||
Span = cellContainer.Span,
|
||||
Line = cellContainer.Line,
|
||||
Column = cellContainer.Column,
|
||||
};
|
||||
}
|
||||
row.Add(tableCell);
|
||||
cells.Add(tableCell);
|
||||
|
||||
@@ -291,6 +342,11 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
}
|
||||
|
||||
if (row != null)
|
||||
{
|
||||
table.Add(row);
|
||||
}
|
||||
|
||||
TrimEnd(beforeDelimiter);
|
||||
|
||||
if (delimiter != null)
|
||||
@@ -388,8 +444,8 @@ namespace Markdig.Extensions.Tables
|
||||
}
|
||||
|
||||
// Check the left side of a `|` delimiter
|
||||
TableColumnAlign align;
|
||||
if (!ParseHeaderString(delimiter.PreviousSibling, out align))
|
||||
TableColumnAlign align = TableColumnAlign.Left;
|
||||
if (delimiter.PreviousSibling != null && !ParseHeaderString(delimiter.PreviousSibling, out align))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
35
src/Markdig/Extensions/TaskLists/HtmlTaskListRenderer.cs
Normal file
35
src/Markdig/Extensions/TaskLists/HtmlTaskListRenderer.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// 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;
|
||||
|
||||
namespace Markdig.Extensions.TaskLists
|
||||
{
|
||||
/// <summary>
|
||||
/// A HTML renderer for a <see cref="TaskList"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{TaskList}" />
|
||||
public class HtmlTaskListRenderer : HtmlObjectRenderer<TaskList>
|
||||
{
|
||||
protected override void Write(HtmlRenderer renderer, TaskList obj)
|
||||
{
|
||||
if (renderer.EnableHtmlForInline)
|
||||
{
|
||||
renderer.Write("<input").WriteAttributes(obj).Write(" disabled=\"disabled\" type=\"checkbox\"");
|
||||
if (obj.Checked)
|
||||
{
|
||||
renderer.Write(" checked=\"checked\"");
|
||||
}
|
||||
renderer.Write(" />");
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Write('[');
|
||||
renderer.Write(obj.Checked ? "x" : " ");
|
||||
renderer.Write(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Markdig/Extensions/TaskLists/TaskList.cs
Normal file
17
src/Markdig/Extensions/TaskLists/TaskList.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Extensions.TaskLists
|
||||
{
|
||||
/// <summary>
|
||||
/// An inline for TaskList.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("TaskList {Checked}")]
|
||||
public class TaskList : LeafInline
|
||||
{
|
||||
public bool Checked { get; set; }
|
||||
}
|
||||
}
|
||||
33
src/Markdig/Extensions/TaskLists/TaskListExtension.cs
Normal file
33
src/Markdig/Extensions/TaskLists/TaskListExtension.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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.TaskLists
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to enable TaskList.
|
||||
/// </summary>
|
||||
public class TaskListExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
if (!pipeline.InlineParsers.Contains<TaskListInlineParser>())
|
||||
{
|
||||
// Insert the parser after the code span parser
|
||||
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new TaskListInlineParser());
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
{
|
||||
htmlRenderer.ObjectRenderers.AddIfNotAlready<HtmlTaskListRenderer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/Markdig/Extensions/TaskLists/TaskListInlineParser.cs
Normal file
90
src/Markdig/Extensions/TaskLists/TaskListInlineParser.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// 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.TaskLists
|
||||
{
|
||||
/// <summary>
|
||||
/// The inline parser for SmartyPants.
|
||||
/// </summary>
|
||||
public class TaskListInlineParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskListInlineParser"/> class.
|
||||
/// </summary>
|
||||
public TaskListInlineParser()
|
||||
{
|
||||
OpeningCharacters = new[] {'['};
|
||||
ListClass = "contains-task-list";
|
||||
ListItemClass = "task-list-item";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list class used for a task list.
|
||||
/// </summary>
|
||||
public string ListClass { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list item class used for a task list.
|
||||
/// </summary>
|
||||
public string ListItemClass { get; set; }
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
// A tasklist is either
|
||||
// [ ]
|
||||
// or [x] or [X]
|
||||
|
||||
var listItemBlock = processor.Block.Parent as ListItemBlock;
|
||||
|
||||
if (listItemBlock == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startingPosition = slice.Start;
|
||||
var c = slice.NextChar();
|
||||
if (!c.IsSpace() && c != 'x' && c != 'X')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (slice.NextChar() != ']')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Skip last ]
|
||||
slice.NextChar();
|
||||
|
||||
// Create the TaskList
|
||||
int line;
|
||||
int column;
|
||||
var taskItem = new TaskList()
|
||||
{
|
||||
Span = { Start = processor.GetSourcePosition(startingPosition, out line, out column)},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Checked = !c.IsSpace()
|
||||
};
|
||||
taskItem.Span.End = taskItem.Span.Start + 2;
|
||||
processor.Inline = taskItem;
|
||||
|
||||
// Add proper class for task list
|
||||
if (!string.IsNullOrEmpty(ListItemClass))
|
||||
{
|
||||
listItemBlock.GetAttributes().AddClass(ListItemClass);
|
||||
}
|
||||
|
||||
var listBlock = (ListBlock) listItemBlock.Parent;
|
||||
if (!string.IsNullOrEmpty(ListClass))
|
||||
{
|
||||
listBlock.GetAttributes().AddClass(ListClass);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/Markdig/Helpers/LineReader.cs
Normal file
71
src/Markdig/Helpers/LineReader.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A line reader from a <see cref="TextReader"/> that can provide precise source position
|
||||
/// </summary>
|
||||
public struct LineReader
|
||||
{
|
||||
private readonly string text;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LineReader"/> class.
|
||||
/// </summary>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">bufferSize cannot be <= 0</exception>
|
||||
public LineReader(string text)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
this.text = text;
|
||||
SourcePosition = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the char position of the line. Valid for the next line before calling <see cref="ReadLine"/>.
|
||||
/// </summary>
|
||||
public int SourcePosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads a new line from the underlying <see cref="TextReader"/> and update the <see cref="SourcePosition"/> for the next line.
|
||||
/// </summary>
|
||||
/// <returns>A new line or null if the end of <see cref="TextReader"/> has been reached</returns>
|
||||
public StringSlice? ReadLine()
|
||||
{
|
||||
if (SourcePosition >= text.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var startPosition = SourcePosition;
|
||||
var position = SourcePosition;
|
||||
var slice = new StringSlice(text, startPosition, startPosition);
|
||||
for (;position < text.Length; position++)
|
||||
{
|
||||
var c = text[position];
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
slice.End = position - 1;
|
||||
if (c == '\r' && position + 1 < text.Length && text[position + 1] == '\n')
|
||||
{
|
||||
position++;
|
||||
}
|
||||
position++;
|
||||
SourcePosition = position;
|
||||
return slice;
|
||||
}
|
||||
}
|
||||
|
||||
slice.End = position - 1;
|
||||
SourcePosition = position;
|
||||
|
||||
return slice;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,10 +297,24 @@ namespace Markdig.Helpers
|
||||
|
||||
public static bool TryParseInlineLink(StringSlice text, out string link, out string title)
|
||||
{
|
||||
return TryParseInlineLink(ref text, out link, out title);
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
return TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseInlineLink(StringSlice text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)
|
||||
{
|
||||
return TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseInlineLink(ref StringSlice text, out string link, out string title)
|
||||
{
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
return TryParseInlineLink(ref text, out link, out title, out linkSpan, out titleSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseInlineLink(ref StringSlice text, out string link, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan)
|
||||
{
|
||||
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
|
||||
// 2. optional whitespace, TODO: specs: is it whitespace or multiple whitespaces?
|
||||
@@ -313,14 +327,25 @@ namespace Markdig.Helpers
|
||||
link = null;
|
||||
title = null;
|
||||
|
||||
linkSpan = SourceSpan.Empty;
|
||||
titleSpan = SourceSpan.Empty;
|
||||
|
||||
// 1. An inline link consists of a link text followed immediately by a left parenthesis (,
|
||||
if (c == '(')
|
||||
{
|
||||
text.NextChar();
|
||||
text.TrimStart();
|
||||
|
||||
var pos = text.Start;
|
||||
if (TryParseUrl(ref text, out link))
|
||||
{
|
||||
linkSpan.Start = pos;
|
||||
linkSpan.End = text.Start - 1;
|
||||
if (linkSpan.End < linkSpan.Start)
|
||||
{
|
||||
linkSpan = SourceSpan.Empty;
|
||||
}
|
||||
|
||||
int spaceCount;
|
||||
text.TrimStart(out spaceCount);
|
||||
var hasWhiteSpaces = spaceCount > 0;
|
||||
@@ -333,12 +358,19 @@ namespace Markdig.Helpers
|
||||
else if (hasWhiteSpaces)
|
||||
{
|
||||
c = text.CurrentChar;
|
||||
pos = text.Start;
|
||||
if (c == ')')
|
||||
{
|
||||
isValid = true;
|
||||
}
|
||||
else if (TryParseTitle(ref text, out title))
|
||||
{
|
||||
titleSpan.Start = pos;
|
||||
titleSpan.End = text.Start - 1;
|
||||
if (titleSpan.End < titleSpan.Start)
|
||||
{
|
||||
titleSpan = SourceSpan.Empty;
|
||||
}
|
||||
text.TrimStart();
|
||||
c = text.CurrentChar;
|
||||
|
||||
@@ -582,12 +614,25 @@ namespace Markdig.Helpers
|
||||
return TryParseLinkReferenceDefinition(ref text, out label, out url, out title);
|
||||
}
|
||||
|
||||
public static bool TryParseLinkReferenceDefinition<T>(ref T text, out string label, out string url,
|
||||
out string title) where T : ICharIterator
|
||||
public static bool TryParseLinkReferenceDefinition<T>(ref T text, out string label, out string url, out string title)
|
||||
where T : ICharIterator
|
||||
{
|
||||
SourceSpan labelSpan;
|
||||
SourceSpan urlSpan;
|
||||
SourceSpan titleSpan;
|
||||
return TryParseLinkReferenceDefinition(ref text, out label, out url, out title, out labelSpan, out urlSpan,
|
||||
out titleSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseLinkReferenceDefinition<T>(ref T text, out string label, out string url, out string title, out SourceSpan labelSpan, out SourceSpan urlSpan, out SourceSpan titleSpan) where T : ICharIterator
|
||||
{
|
||||
url = null;
|
||||
title = null;
|
||||
if (!TryParseLabel(ref text, out label))
|
||||
|
||||
urlSpan = SourceSpan.Empty;
|
||||
titleSpan = SourceSpan.Empty;
|
||||
|
||||
if (!TryParseLabel(ref text, out label, out labelSpan))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -601,10 +646,13 @@ namespace Markdig.Helpers
|
||||
|
||||
// Skip any whitespaces before the url
|
||||
text.TrimStart();
|
||||
|
||||
urlSpan.Start = text.Start;
|
||||
if (!TryParseUrl(ref text, out url) || string.IsNullOrEmpty(url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
urlSpan.End = text.Start - 1;
|
||||
|
||||
var saved = text;
|
||||
int newLineCount;
|
||||
@@ -612,8 +660,10 @@ namespace Markdig.Helpers
|
||||
var c = text.CurrentChar;
|
||||
if (c == '\'' || c == '"' || c == '(')
|
||||
{
|
||||
titleSpan.Start = text.Start;
|
||||
if (TryParseTitle(ref text, out title))
|
||||
{
|
||||
titleSpan.End = text.Start - 1;
|
||||
// If we have a title, it requires a whitespace after the url
|
||||
if (!hasWhiteSpaces)
|
||||
{
|
||||
@@ -662,24 +712,41 @@ namespace Markdig.Helpers
|
||||
|
||||
public static bool TryParseLabel<T>(T lines, out string label) where T : ICharIterator
|
||||
{
|
||||
return TryParseLabel(ref lines, false, out label);
|
||||
SourceSpan labelSpan;
|
||||
return TryParseLabel(ref lines, false, out label, out labelSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseLabel<T>(T lines, out string label, out SourceSpan labelSpan) where T : ICharIterator
|
||||
{
|
||||
return TryParseLabel(ref lines, false, out label, out labelSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseLabel<T>(ref T lines, out string label) where T : ICharIterator
|
||||
{
|
||||
return TryParseLabel(ref lines, false, out label);
|
||||
SourceSpan labelSpan;
|
||||
return TryParseLabel(ref lines, false, out label, out labelSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseLabel<T>(ref T lines, bool allowEmpty, out string label) where T : ICharIterator
|
||||
|
||||
public static bool TryParseLabel<T>(ref T lines, out string label, out SourceSpan labelSpan) where T : ICharIterator
|
||||
{
|
||||
return TryParseLabel(ref lines, false, out label, out labelSpan);
|
||||
}
|
||||
|
||||
public static bool TryParseLabel<T>(ref T lines, bool allowEmpty, out string label, out SourceSpan labelSpan) where T : ICharIterator
|
||||
{
|
||||
label = null;
|
||||
char c = lines.CurrentChar;
|
||||
labelSpan = SourceSpan.Empty;
|
||||
if (c != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var buffer = StringBuilderCache.Local();
|
||||
|
||||
var startLabel = -1;
|
||||
var endLabel = -1;
|
||||
|
||||
bool hasEscape = false;
|
||||
bool previousWhitespace = true;
|
||||
bool hasNonWhiteSpace = false;
|
||||
@@ -719,11 +786,19 @@ namespace Markdig.Helpers
|
||||
break;
|
||||
}
|
||||
buffer.Length = i;
|
||||
endLabel--;
|
||||
}
|
||||
|
||||
// Only valid if buffer is less than 1000 characters
|
||||
if (buffer.Length <= 999)
|
||||
{
|
||||
labelSpan.Start = startLabel;
|
||||
labelSpan.End = endLabel;
|
||||
if (labelSpan.Start > labelSpan.End)
|
||||
{
|
||||
labelSpan = SourceSpan.Empty;
|
||||
}
|
||||
|
||||
label = buffer.ToString();
|
||||
isValid = true;
|
||||
}
|
||||
@@ -741,6 +816,10 @@ namespace Markdig.Helpers
|
||||
|
||||
if (!hasEscape && c == '\\')
|
||||
{
|
||||
if (startLabel < 0)
|
||||
{
|
||||
startLabel = lines.Start;
|
||||
}
|
||||
hasEscape = true;
|
||||
}
|
||||
else
|
||||
@@ -749,6 +828,11 @@ namespace Markdig.Helpers
|
||||
|
||||
if (!previousWhitespace || !isWhitespace)
|
||||
{
|
||||
if (startLabel < 0)
|
||||
{
|
||||
startLabel = lines.Start;
|
||||
}
|
||||
endLabel = lines.Start;
|
||||
buffer.Append(c);
|
||||
if (!isWhitespace)
|
||||
{
|
||||
|
||||
@@ -23,11 +23,12 @@ namespace Markdig.Helpers
|
||||
/// <param name="slice">The slice.</param>
|
||||
/// <param name="line">The line.</param>
|
||||
/// <param name="column">The column.</param>
|
||||
public StringLine(StringSlice slice, int line, int column)
|
||||
public StringLine(StringSlice slice, int line, int column, int position)
|
||||
{
|
||||
Slice = slice;
|
||||
Line = line;
|
||||
Column = column;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,11 +37,12 @@ namespace Markdig.Helpers
|
||||
/// <param name="slice">The slice.</param>
|
||||
/// <param name="line">The line.</param>
|
||||
/// <param name="column">The column.</param>
|
||||
public StringLine(ref StringSlice slice, int line, int column)
|
||||
public StringLine(ref StringSlice slice, int line, int column, int position)
|
||||
{
|
||||
Slice = slice;
|
||||
Line = line;
|
||||
Column = column;
|
||||
Position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,6 +55,11 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
public int Line;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the start of this line within the original source code
|
||||
/// </summary>
|
||||
public int Position;
|
||||
|
||||
/// <summary>
|
||||
/// The column position.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Markdig.Extensions.Tables;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
@@ -108,15 +110,11 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <param name="lineOffsets">The position of the `\n` line offsets from the beginning of the returned slice.</param>
|
||||
/// <returns>A single slice concatenating the lines of this instance</returns>
|
||||
public StringSlice ToSlice(List<int> lineOffsets = null)
|
||||
public StringSlice ToSlice(List<LineOffset> lineOffsets = null)
|
||||
{
|
||||
// Optimization case when no lines
|
||||
if (Count == 0)
|
||||
{
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(1);
|
||||
}
|
||||
return new StringSlice(string.Empty);
|
||||
}
|
||||
|
||||
@@ -125,22 +123,24 @@ namespace Markdig.Helpers
|
||||
{
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(Lines[0].Slice.End + 1);
|
||||
lineOffsets.Add(new LineOffset(Lines[0].Position, Lines[0].Column, Lines[0].Slice.Start - Lines[0].Position, Lines[0].Slice.Start, Lines[0].Slice.End + 1));
|
||||
}
|
||||
return Lines[0];
|
||||
}
|
||||
|
||||
// Else use a builder
|
||||
var builder = StringBuilderCache.Local();
|
||||
int previousStartOfLine = 0;
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(builder.Length + 1); // Add 1 for \n and 1 for next line
|
||||
lineOffsets.Add(new LineOffset(Lines[i - 1].Position, Lines[i - 1].Column, Lines[i - 1].Slice.Start - Lines[i - 1].Position, previousStartOfLine, builder.Length));
|
||||
}
|
||||
builder.Append('\n');
|
||||
previousStartOfLine = builder.Length;
|
||||
}
|
||||
if (!Lines[i].Slice.IsEmpty)
|
||||
{
|
||||
@@ -149,7 +149,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
if (lineOffsets != null)
|
||||
{
|
||||
lineOffsets.Add(builder.Length); // Add 1 for \0
|
||||
lineOffsets.Add(new LineOffset(Lines[Count - 1].Position, Lines[Count - 1].Column, Lines[Count - 1].Slice.Start - Lines[Count - 1].Position, previousStartOfLine, builder.Length));
|
||||
}
|
||||
var str = builder.ToString();
|
||||
builder.Length = 0;
|
||||
@@ -265,5 +265,27 @@ namespace Markdig.Helpers
|
||||
return hasSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LineOffset
|
||||
{
|
||||
public LineOffset(int linePosition, int column, int offset, int start, int end)
|
||||
{
|
||||
LinePosition = linePosition;
|
||||
Column = column;
|
||||
Offset = offset;
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
|
||||
public readonly int LinePosition;
|
||||
|
||||
public readonly int Column;
|
||||
|
||||
public readonly int Offset;
|
||||
|
||||
public readonly int Start;
|
||||
|
||||
public readonly int End;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,16 @@ namespace Markdig.Helpers
|
||||
return index >= Start && index <= End ? Text[index] : (char) 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a character at the specified offset from the current beginning of the string, without taking into account <see cref="Start"/> and <see cref="End"/>
|
||||
/// </summary>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekCharAbsolute(int index)
|
||||
{
|
||||
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a character at the specified offset from the current begining of the slice
|
||||
/// without using the range <see cref="Start"/> or <see cref="End"/>, returns `\0` if outside the <see cref="Text"/>.
|
||||
@@ -180,19 +190,31 @@ namespace Markdig.Helpers
|
||||
return i == text.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the specified text within this slice.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns><c>true</c> if the text was found; <c>false</c> otherwise</returns>
|
||||
public bool Search(string text, out int index)
|
||||
{
|
||||
return Search(text, 0, out index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches the specified text within this slice.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text was found; <c>false</c> otherwise</returns>
|
||||
public bool Search(string text, int offset = 0)
|
||||
public bool Search(string text, int offset, out int index)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
for (int i = Start; i <= end; i++)
|
||||
index = Start + offset;
|
||||
for (int i = index; i <= end; i ++)
|
||||
{
|
||||
if (Match(text, End, i))
|
||||
if (Match(text, End, i - Start))
|
||||
{
|
||||
index = i + text.Length;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -205,13 +227,15 @@ namespace Markdig.Helpers
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text was found; <c>false</c> otherwise</returns>
|
||||
public bool SearchLowercase(string text, int offset = 0)
|
||||
public bool SearchLowercase(string text, out int endOfIndex)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
endOfIndex = 0;
|
||||
for (int i = Start; i <= end; i++)
|
||||
{
|
||||
if (MatchLowercase(text, End, i))
|
||||
{
|
||||
endOfIndex = i + text.Length;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>8A58A7E2-627C-4F41-933F-5AC92ADFAB48</ProjectGuid>
|
||||
<RootNamespace>Markdig</RootNamespace>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\Bin</OutputPath>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
|
||||
<TypeScriptCompileBlocked>True</TypeScriptCompileBlocked>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
@@ -12,7 +12,7 @@ namespace Markdig
|
||||
/// <summary>
|
||||
/// Provides methods for parsing a Markdown string to a syntax tree and converting it to other formats.
|
||||
/// </summary>
|
||||
public static class Markdown
|
||||
public static partial class Markdown
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a Markdown string to HTML.
|
||||
@@ -24,35 +24,22 @@ namespace Markdig
|
||||
public static string ToHtml(string markdown, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
var reader = new StringReader(markdown);
|
||||
return ToHtml(reader, pipeline) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Markdown string to HTML.
|
||||
/// </summary>
|
||||
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
|
||||
/// <param name="pipeline">The pipeline used for the conversion.</param>
|
||||
/// <returns>The result of the conversion</returns>
|
||||
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
|
||||
public static string ToHtml(TextReader reader, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
var writer = new StringWriter();
|
||||
ToHtml(reader, writer, pipeline);
|
||||
ToHtml(markdown, writer, pipeline);
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Markdown string to HTML.
|
||||
/// Converts a Markdown string to HTML and output to the specified writer.
|
||||
/// </summary>
|
||||
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
|
||||
/// <param name="markdown">A Markdown text.</param>
|
||||
/// <param name="writer">The destination <see cref="TextWriter"/> that will receive the result of the conversion.</param>
|
||||
/// <param name="pipeline">The pipeline used for the conversion.</param>
|
||||
/// <returns>The Markdown document that has been parsed</returns>
|
||||
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
|
||||
public static void ToHtml(TextReader reader, TextWriter writer, MarkdownPipeline pipeline = null)
|
||||
public static MarkdownDocument ToHtml(string markdown, TextWriter writer, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
|
||||
@@ -60,25 +47,27 @@ namespace Markdig
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
var document = Parse(reader, pipeline);
|
||||
var document = Parse(markdown, pipeline);
|
||||
renderer.Render(document);
|
||||
writer.Flush();
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Markdown string using a custom <see cref="IMarkdownRenderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
|
||||
/// <param name="markdown">A Markdown text.</param>
|
||||
/// <param name="renderer">The renderer to convert Markdown to.</param>
|
||||
/// <param name="pipeline">The pipeline used for the conversion.</param>
|
||||
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
|
||||
public static object Convert(TextReader reader, IMarkdownRenderer renderer, MarkdownPipeline pipeline = null)
|
||||
/// <exception cref="System.ArgumentNullException">if markdown or writer variable are null</exception>
|
||||
public static object Convert(string markdown, IMarkdownRenderer renderer, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
|
||||
var document = Parse(reader, pipeline);
|
||||
var document = Parse(markdown, pipeline);
|
||||
pipeline.Setup(renderer);
|
||||
return renderer.Render(document);
|
||||
}
|
||||
@@ -92,22 +81,22 @@ namespace Markdig
|
||||
public static MarkdownDocument Parse(string markdown)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
return Parse(new StringReader(markdown));
|
||||
return Parse(markdown, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified markdown into an AST <see cref="MarkdownDocument"/>
|
||||
/// </summary>
|
||||
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
|
||||
/// <param name="markdown">The markdown text.</param>
|
||||
/// <param name="pipeline">The pipeline used for the parsing.</param>
|
||||
/// <returns>An AST Markdown document</returns>
|
||||
/// <exception cref="System.ArgumentNullException">if reader variable is null</exception>
|
||||
public static MarkdownDocument Parse(TextReader reader, MarkdownPipeline pipeline = null)
|
||||
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
|
||||
public static MarkdownDocument Parse(string markdown, MarkdownPipeline pipeline)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
|
||||
return MarkdownParser.Parse(reader, pipeline);
|
||||
return MarkdownParser.Parse(markdown, pipeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,28 @@
|
||||
// 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.Extensions.Abbreviations;
|
||||
using Markdig.Extensions.AutoIdentifiers;
|
||||
using Markdig.Extensions.Bootstrap;
|
||||
using Markdig.Extensions.Cites;
|
||||
using Markdig.Extensions.Citations;
|
||||
using Markdig.Extensions.CustomContainers;
|
||||
using Markdig.Extensions.DefinitionLists;
|
||||
using Markdig.Extensions.Emoji;
|
||||
using Markdig.Extensions.EmphasisExtra;
|
||||
using Markdig.Extensions.EmphasisExtras;
|
||||
using Markdig.Extensions.Figures;
|
||||
using Markdig.Extensions.Footers;
|
||||
using Markdig.Extensions.Footnotes;
|
||||
using Markdig.Extensions.GenericAttributes;
|
||||
using Markdig.Extensions.Hardlines;
|
||||
using Markdig.Extensions.ListExtra;
|
||||
using Markdig.Extensions.ListExtras;
|
||||
using Markdig.Extensions.Mathematics;
|
||||
using Markdig.Extensions.Medias;
|
||||
using Markdig.Extensions.MediaLinks;
|
||||
using Markdig.Extensions.PragmaLines;
|
||||
using Markdig.Extensions.SmartyPants;
|
||||
using Markdig.Extensions.Tables;
|
||||
using Markdig.Extensions.TaskLists;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Parsers.Inlines;
|
||||
|
||||
@@ -30,38 +34,70 @@ namespace Markdig
|
||||
public static class MarkdownExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses all extensions except the Emoji.
|
||||
/// Uses all extensions except the BootStrap, Emoji, SmartyPants and soft line as hard line breaks extensions.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseAllExtensions(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseAdvancedExtensions(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
return pipeline
|
||||
.UseAbbreviation()
|
||||
.UseAutoIdentifier()
|
||||
.UseBootstrap()
|
||||
.UseCite()
|
||||
.UseCustomContainer()
|
||||
.UseDefinitionList()
|
||||
.UseEmphasisExtra()
|
||||
.UseFigure()
|
||||
.UseFooter()
|
||||
.UseAbbreviations()
|
||||
.UseAutoIdentifiers()
|
||||
.UseCitations()
|
||||
.UseCustomContainers()
|
||||
.UseDefinitionLists()
|
||||
.UseEmphasisExtras()
|
||||
.UseFigures()
|
||||
.UseFooters()
|
||||
.UseFootnotes()
|
||||
.UseGridTable()
|
||||
.UseMath()
|
||||
.UseMedia()
|
||||
.UsePipeTable()
|
||||
.UseSoftlineBreakAsHardlineBreak()
|
||||
.UseSmartyPants()
|
||||
.UseGridTables()
|
||||
.UseMathematics()
|
||||
.UseMediaLinks()
|
||||
.UsePipeTables()
|
||||
.UseListExtras()
|
||||
.UseTaskLists()
|
||||
.UseGenericAttributes(); // Must be last as it is one parser that is modifying other parsers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses pragma lines to output span with an id containing the line number (pragma-line#line_number_zero_based`)
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UsePragmaLines(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<PragmaLineExtension>();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses precise source code location (useful for syntax highlighting).
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UsePreciseSourceLocation(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.PreciseSourceLocation = true;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the task list extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseTaskLists(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<TaskListExtension>();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the custom container extension.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseCustomContainer(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseCustomContainers(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<CustomContainerExtension>();
|
||||
return pipeline;
|
||||
@@ -75,11 +111,11 @@ namespace Markdig
|
||||
/// <returns>
|
||||
/// The modified pipeline
|
||||
/// </returns>
|
||||
public static MarkdownPipelineBuilder UseMedia(this MarkdownPipelineBuilder pipeline, MediaOptions options = null)
|
||||
public static MarkdownPipelineBuilder UseMediaLinks(this MarkdownPipelineBuilder pipeline, MediaOptions options = null)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<MediaExtension>())
|
||||
if (!pipeline.Extensions.Contains<MediaLinkExtension>())
|
||||
{
|
||||
pipeline.Extensions.Add(new MediaExtension(options));
|
||||
pipeline.Extensions.Add(new MediaLinkExtension(options));
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
@@ -92,7 +128,7 @@ namespace Markdig
|
||||
/// <returns>
|
||||
/// The modified pipeline
|
||||
/// </returns>
|
||||
public static MarkdownPipelineBuilder UseAutoIdentifier(this MarkdownPipelineBuilder pipeline, AutoIdentifierOptions options = AutoIdentifierOptions.Default)
|
||||
public static MarkdownPipelineBuilder UseAutoIdentifiers(this MarkdownPipelineBuilder pipeline, AutoIdentifierOptions options = AutoIdentifierOptions.Default)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<AutoIdentifierExtension>())
|
||||
{
|
||||
@@ -134,7 +170,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseMath(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseMathematics(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<MathExtension>();
|
||||
return pipeline;
|
||||
@@ -145,7 +181,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseFigure(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseFigures(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<FigureExtension>();
|
||||
return pipeline;
|
||||
@@ -156,7 +192,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseAbbreviation(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseAbbreviations(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<AbbreviationExtension>();
|
||||
return pipeline;
|
||||
@@ -167,7 +203,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseDefinitionList(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseDefinitionLists(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<DefinitionListExtension>();
|
||||
return pipeline;
|
||||
@@ -181,7 +217,7 @@ namespace Markdig
|
||||
/// <returns>
|
||||
/// The modified pipeline
|
||||
/// </returns>
|
||||
public static MarkdownPipelineBuilder UsePipeTable(this MarkdownPipelineBuilder pipeline, PipeTableOptions options = null)
|
||||
public static MarkdownPipelineBuilder UsePipeTables(this MarkdownPipelineBuilder pipeline, PipeTableOptions options = null)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<PipeTableExtension>())
|
||||
{
|
||||
@@ -195,7 +231,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseGridTable(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseGridTables(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<GridTableExtension>();
|
||||
return pipeline;
|
||||
@@ -207,9 +243,9 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseCite(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseCitations(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<CiteExtension>();
|
||||
pipeline.Extensions.AddIfNotAlready<CitationExtension>();
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
@@ -218,7 +254,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder UseFooter(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseFooters(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<FooterExtension>();
|
||||
return pipeline;
|
||||
@@ -254,7 +290,7 @@ namespace Markdig
|
||||
/// <returns>
|
||||
/// The modified pipeline
|
||||
/// </returns>
|
||||
public static MarkdownPipelineBuilder UseEmphasisExtra(this MarkdownPipelineBuilder pipeline, EmphasisExtraOptions options = EmphasisExtraOptions.Default)
|
||||
public static MarkdownPipelineBuilder UseEmphasisExtras(this MarkdownPipelineBuilder pipeline, EmphasisExtraOptions options = EmphasisExtraOptions.Default)
|
||||
{
|
||||
if (!pipeline.Extensions.Contains<EmphasisExtraExtension>())
|
||||
{
|
||||
@@ -270,7 +306,7 @@ namespace Markdig
|
||||
/// <returns>
|
||||
/// The modified pipeline
|
||||
/// </returns>
|
||||
public static MarkdownPipelineBuilder UseListExtra(this MarkdownPipelineBuilder pipeline)
|
||||
public static MarkdownPipelineBuilder UseListExtras(this MarkdownPipelineBuilder pipeline)
|
||||
{
|
||||
pipeline.Extensions.AddIfNotAlready<ListExtraExtension>();
|
||||
return pipeline;
|
||||
@@ -318,5 +354,92 @@ namespace Markdig
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the pipeline using a string that defines the extensions to activate.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">The pipeline (e.g: advanced for <see cref="UseAdvancedExtensions"/>, pipetables+gridtables for <see cref="UsePipeTables"/> and <see cref="UseGridTables"/></param>
|
||||
/// <param name="extensions">The extensions to activate as a string</param>
|
||||
/// <returns>The modified pipeline</returns>
|
||||
public static MarkdownPipelineBuilder Configure(this MarkdownPipelineBuilder pipeline, string extensions)
|
||||
{
|
||||
if (extensions == null)
|
||||
{
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
foreach (var extension in extensions.Split(new[] { '+' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
switch (extension.ToLowerInvariant())
|
||||
{
|
||||
case "advanced":
|
||||
pipeline.UseAdvancedExtensions();
|
||||
break;
|
||||
case "pipetables":
|
||||
pipeline.UsePipeTables();
|
||||
break;
|
||||
case "emphasisextras":
|
||||
pipeline.UseEmphasisExtras();
|
||||
break;
|
||||
case "listextras":
|
||||
pipeline.UseListExtras();
|
||||
break;
|
||||
case "hardlinebreak":
|
||||
pipeline.UseSoftlineBreakAsHardlineBreak();
|
||||
break;
|
||||
case "footnotes":
|
||||
pipeline.UseFootnotes();
|
||||
break;
|
||||
case "footers":
|
||||
pipeline.UseFooters();
|
||||
break;
|
||||
case "citations":
|
||||
pipeline.UseCitations();
|
||||
break;
|
||||
case "attributes":
|
||||
pipeline.UseGenericAttributes();
|
||||
break;
|
||||
case "gridtables":
|
||||
pipeline.UseGridTables();
|
||||
break;
|
||||
case "abbreviations":
|
||||
pipeline.UseAbbreviations();
|
||||
break;
|
||||
case "emojis":
|
||||
pipeline.UseEmojiAndSmiley();
|
||||
break;
|
||||
case "definitionlists":
|
||||
pipeline.UseDefinitionLists();
|
||||
break;
|
||||
case "customcontainers":
|
||||
pipeline.UseCustomContainers();
|
||||
break;
|
||||
case "figures":
|
||||
pipeline.UseFigures();
|
||||
break;
|
||||
case "mathematics":
|
||||
pipeline.UseMathematics();
|
||||
break;
|
||||
case "bootstrap":
|
||||
pipeline.UseBootstrap();
|
||||
break;
|
||||
case "medialinks":
|
||||
pipeline.UseMediaLinks();
|
||||
break;
|
||||
case "smartypants":
|
||||
pipeline.UseSmartyPants();
|
||||
break;
|
||||
case "autoidentifiers":
|
||||
pipeline.UseAutoIdentifiers();
|
||||
break;
|
||||
case "tasklists":
|
||||
pipeline.UseTaskLists();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"unknown extension {extension}");
|
||||
}
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ namespace Markdig
|
||||
DebugLog = debugLog;
|
||||
DocumentProcessed = documentProcessed;
|
||||
}
|
||||
|
||||
internal bool PreciseSourceLocation { get; set; }
|
||||
|
||||
internal OrderedList<IMarkdownExtension> Extensions { get; }
|
||||
|
||||
internal BlockParserList BlockParsers { get; }
|
||||
@@ -44,7 +47,11 @@ namespace Markdig
|
||||
|
||||
internal ProcessDocumentDelegate DocumentProcessed;
|
||||
|
||||
internal void Setup(IMarkdownRenderer renderer)
|
||||
/// <summary>
|
||||
/// Allows to setup a <see cref="IMarkdownRenderer"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The markdown renderer to setup</param>
|
||||
public void Setup(IMarkdownRenderer renderer)
|
||||
{
|
||||
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
|
||||
foreach (var extension in Extensions)
|
||||
|
||||
@@ -73,6 +73,11 @@ namespace Markdig
|
||||
/// </summary>
|
||||
public StringBuilderCache StringBuilderCache { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to enable precise source location (slower parsing but accurate position for block and inline elements)
|
||||
/// </summary>
|
||||
public bool PreciseSourceLocation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the debug log.
|
||||
/// </summary>
|
||||
@@ -111,7 +116,9 @@ namespace Markdig
|
||||
extension.Setup(this);
|
||||
}
|
||||
|
||||
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions), new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog, GetDocumentProcessed);
|
||||
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions),
|
||||
new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog,
|
||||
GetDocumentProcessed) {PreciseSourceLocation = PreciseSourceLocation};
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,11 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
public StringSlice Line;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current line start position.
|
||||
/// </summary>
|
||||
public int CurrentLineStartPosition { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the line in the source text.
|
||||
/// </summary>
|
||||
@@ -260,12 +265,10 @@ namespace Markdig.Parsers
|
||||
public void GoToColumn(int newColumn)
|
||||
{
|
||||
// Optimized path when we are moving above the previous start of indent
|
||||
if (newColumn > ColumnBeforeIndent)
|
||||
if (newColumn >= ColumnBeforeIndent)
|
||||
{
|
||||
Line.Start = StartBeforeIndent;
|
||||
Column = ColumnBeforeIndent;
|
||||
ColumnBeforeIndent = 0;
|
||||
StartBeforeIndent = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -367,6 +370,8 @@ namespace Markdig.Parsers
|
||||
/// <param name="newLine">The new line.</param>
|
||||
public void ProcessLine(StringSlice newLine)
|
||||
{
|
||||
CurrentLineStartPosition = newLine.Start;
|
||||
|
||||
ContinueProcessingLine = true;
|
||||
|
||||
ResetLine(newLine);
|
||||
@@ -548,7 +553,7 @@ namespace Markdig.Parsers
|
||||
ContinueProcessingLine = false;
|
||||
if (!result.IsDiscard())
|
||||
{
|
||||
leaf.AppendLine(ref Line, Column, LineIndex);
|
||||
leaf.AppendLine(ref Line, Column, LineIndex, CurrentLineStartPosition);
|
||||
}
|
||||
|
||||
if (NewBlocks.Count > 0)
|
||||
@@ -585,8 +590,16 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
private void TryOpenBlocks()
|
||||
{
|
||||
int previousStart = -1;
|
||||
while (ContinueProcessingLine)
|
||||
{
|
||||
// Security check so that the parser can't go into a crazy infinite loop if one extension is messing
|
||||
if (previousStart == Start)
|
||||
{
|
||||
throw new InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse blocks at line [{LineIndex}] with line [{Line}]");
|
||||
}
|
||||
previousStart = Start;
|
||||
|
||||
// Eat indent spaces before checking the character
|
||||
ParseIndent();
|
||||
|
||||
@@ -671,7 +684,7 @@ namespace Markdig.Parsers
|
||||
|
||||
if (!result.IsDiscard())
|
||||
{
|
||||
paragraph.AppendLine(ref Line, Column, LineIndex);
|
||||
paragraph.AppendLine(ref Line, Column, LineIndex, CurrentLineStartPosition);
|
||||
}
|
||||
|
||||
// We have just found a lazy continuation for a paragraph, early exit
|
||||
@@ -724,7 +737,7 @@ namespace Markdig.Parsers
|
||||
{
|
||||
if (!result.IsDiscard())
|
||||
{
|
||||
leaf.AppendLine(ref Line, Column, LineIndex);
|
||||
leaf.AppendLine(ref Line, Column, LineIndex, CurrentLineStartPosition);
|
||||
}
|
||||
|
||||
if (newBlocks.Count > 0)
|
||||
|
||||
@@ -128,6 +128,8 @@ namespace Markdig.Parsers
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
var startPosition = processor.Start;
|
||||
|
||||
// Match fenced char
|
||||
int count = 0;
|
||||
var line = processor.Line;
|
||||
@@ -157,6 +159,8 @@ namespace Markdig.Parsers
|
||||
fenced.Column = processor.Column;
|
||||
fenced.FencedChar = matchChar;
|
||||
fenced.FencedCharCount = count;
|
||||
fenced.Span.Start = startPosition;
|
||||
fenced.Span.End = line.Start;
|
||||
};
|
||||
|
||||
// Try to parse any attached attributes
|
||||
@@ -212,6 +216,8 @@ namespace Markdig.Parsers
|
||||
// The line must contain only fence opening character followed only by whitespaces.
|
||||
if (count <=0 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
|
||||
{
|
||||
block.Span.End = line.Start - 1;
|
||||
|
||||
// Don't keep the last line
|
||||
return BlockState.BreakDiscard;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Markdig.Parsers
|
||||
// opening sequence.
|
||||
var column = processor.Column;
|
||||
var line = processor.Line;
|
||||
var sourcePosition = line.Start;
|
||||
var c = line.CurrentChar;
|
||||
var matchingChar = c;
|
||||
|
||||
@@ -64,14 +65,15 @@ namespace Markdig.Parsers
|
||||
if (leadingCount > 0 && leadingCount <= 6 && (c.IsSpace() || c == '\0'))
|
||||
{
|
||||
// Move to the content
|
||||
processor.Line.Start = line.Start + 1;
|
||||
var headingBlock = new HeadingBlock(this)
|
||||
{
|
||||
HeaderChar = matchingChar,
|
||||
Level = leadingCount,
|
||||
Column = column
|
||||
Column = column,
|
||||
Span = { Start = sourcePosition }
|
||||
};
|
||||
processor.NewBlocks.Push(headingBlock);
|
||||
processor.GoToColumn(column + leadingCount + 1);
|
||||
|
||||
// Gives a chance to parse attributes
|
||||
if (TryParseAttributes != null)
|
||||
@@ -116,6 +118,9 @@ namespace Markdig.Parsers
|
||||
}
|
||||
}
|
||||
|
||||
// Setup the source end position of this element
|
||||
headingBlock.Span.End = processor.Line.End;
|
||||
|
||||
// We expect a single line, so don't continue
|
||||
return BlockState.Break;
|
||||
}
|
||||
|
||||
@@ -47,18 +47,19 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
var line = state.Line;
|
||||
var startPosition = line.Start;
|
||||
line.NextChar();
|
||||
var result = TryParseTagType16(state, line, state.ColumnBeforeIndent);
|
||||
var result = TryParseTagType16(state, line, state.ColumnBeforeIndent, startPosition);
|
||||
|
||||
// HTML blocks of type 7 cannot interrupt a paragraph:
|
||||
if (result == BlockState.None && !(state.CurrentBlock is ParagraphBlock))
|
||||
{
|
||||
result = TryParseTagType7(state, line, state.ColumnBeforeIndent);
|
||||
result = TryParseTagType7(state, line, state.ColumnBeforeIndent, startPosition);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private BlockState TryParseTagType7(BlockProcessor state, StringSlice line, int startColumn)
|
||||
private BlockState TryParseTagType7(BlockProcessor state, StringSlice line, int startColumn, int startPosition)
|
||||
{
|
||||
var builder = StringBuilderCache.Local();
|
||||
var c = line.CurrentChar;
|
||||
@@ -84,7 +85,7 @@ namespace Markdig.Parsers
|
||||
|
||||
if (hasOnlySpaces)
|
||||
{
|
||||
result = CreateHtmlBlock(state, HtmlBlockType.NonInterruptingBlock, startColumn);
|
||||
result = CreateHtmlBlock(state, HtmlBlockType.NonInterruptingBlock, startColumn, startPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ namespace Markdig.Parsers
|
||||
return result;
|
||||
}
|
||||
|
||||
private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn)
|
||||
private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn, int startPosition)
|
||||
{
|
||||
char c;
|
||||
c = line.CurrentChar;
|
||||
@@ -101,15 +102,15 @@ namespace Markdig.Parsers
|
||||
c = line.NextChar();
|
||||
if (c == '-' && line.PeekChar(1) == '-')
|
||||
{
|
||||
return CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn); // group 2
|
||||
return CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition); // group 2
|
||||
}
|
||||
if (c.IsAlphaUpper())
|
||||
{
|
||||
return CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn); // group 4
|
||||
return CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn, startPosition); // group 4
|
||||
}
|
||||
if (c == '[' && line.Match("CDATA[", 1))
|
||||
{
|
||||
return CreateHtmlBlock(state, HtmlBlockType.CData, startColumn); // group 5
|
||||
return CreateHtmlBlock(state, HtmlBlockType.CData, startColumn, startPosition); // group 5
|
||||
}
|
||||
|
||||
return BlockState.None;
|
||||
@@ -117,7 +118,7 @@ namespace Markdig.Parsers
|
||||
|
||||
if (c == '?')
|
||||
{
|
||||
return CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn); // group 3
|
||||
return CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn, startPosition); // group 3
|
||||
}
|
||||
|
||||
var hasLeadingClose = c == '/';
|
||||
@@ -164,10 +165,10 @@ namespace Markdig.Parsers
|
||||
{
|
||||
return BlockState.None;
|
||||
}
|
||||
return CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn);
|
||||
return CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn, startPosition);
|
||||
}
|
||||
|
||||
return CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn);
|
||||
return CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn, startPosition);
|
||||
}
|
||||
|
||||
private BlockState MatchEnd(BlockProcessor state, HtmlBlock htmlBlock)
|
||||
@@ -177,58 +178,77 @@ namespace Markdig.Parsers
|
||||
// Early exit if it is not starting by an HTML tag
|
||||
var line = state.Line;
|
||||
var c = line.CurrentChar;
|
||||
var result = BlockState.Continue;
|
||||
int endof;
|
||||
switch (htmlBlock.Type)
|
||||
{
|
||||
case HtmlBlockType.Comment:
|
||||
if (line.Search("-->"))
|
||||
if (line.Search("-->", out endof))
|
||||
{
|
||||
return BlockState.Break;
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.CData:
|
||||
if (line.Search("]]>"))
|
||||
if (line.Search("]]>", out endof))
|
||||
{
|
||||
return BlockState.Break;
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.ProcessingInstruction:
|
||||
if (line.Search("?>"))
|
||||
if (line.Search("?>", out endof))
|
||||
{
|
||||
return BlockState.Break;
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.DocumentType:
|
||||
if (line.Search(">"))
|
||||
if (line.Search(">", out endof))
|
||||
{
|
||||
return BlockState.Break;
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.ScriptPreOrStyle:
|
||||
if (line.SearchLowercase("</script>") || line.SearchLowercase("</pre>") || line.SearchLowercase("</style>"))
|
||||
if (line.SearchLowercase("</script>", out endof) || line.SearchLowercase("</pre>", out endof) || line.SearchLowercase("</style>", out endof))
|
||||
{
|
||||
return BlockState.Break;
|
||||
htmlBlock.Span.End = endof - 1;
|
||||
result = BlockState.Break;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.InterruptingBlock:
|
||||
if (state.IsBlankLine)
|
||||
{
|
||||
return BlockState.BreakDiscard;
|
||||
result = BlockState.BreakDiscard;
|
||||
}
|
||||
break;
|
||||
case HtmlBlockType.NonInterruptingBlock:
|
||||
if (state.IsBlankLine)
|
||||
{
|
||||
return BlockState.BreakDiscard;
|
||||
result = BlockState.BreakDiscard;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return BlockState.Continue;
|
||||
// Update only if we don't have a break discard
|
||||
if (result != BlockState.BreakDiscard)
|
||||
{
|
||||
htmlBlock.Span.End = line.End;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private BlockState CreateHtmlBlock(BlockProcessor state, HtmlBlockType type, int startColumn)
|
||||
private BlockState CreateHtmlBlock(BlockProcessor state, HtmlBlockType type, int startColumn, int startPosition)
|
||||
{
|
||||
state.NewBlocks.Push(new HtmlBlock(this) {Column = startColumn, Type = type});
|
||||
state.NewBlocks.Push(new HtmlBlock(this)
|
||||
{
|
||||
Column = startColumn,
|
||||
Type = type,
|
||||
// By default, setup to the end of line
|
||||
Span = new SourceSpan(startPosition, startPosition + state.Line.End)
|
||||
});
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,15 @@ namespace Markdig.Parsers
|
||||
|
||||
public override BlockState TryOpen(BlockProcessor processor)
|
||||
{
|
||||
var startPosition = processor.Line.Start;
|
||||
var result = TryContinue(processor, null);
|
||||
if (result == BlockState.Continue)
|
||||
{
|
||||
processor.NewBlocks.Push(new CodeBlock(this) { Column = processor.Column });
|
||||
processor.NewBlocks.Push(new CodeBlock(this)
|
||||
{
|
||||
Column = processor.Column,
|
||||
Span = new SourceSpan(startPosition, processor.Line.End)
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -37,10 +42,14 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
// If we don't have a blank line, we reset to the indent
|
||||
if (processor.Indent >= 4)
|
||||
if (processor.Indent > 4)
|
||||
{
|
||||
processor.GoToCodeIndent();
|
||||
}
|
||||
if (block != null)
|
||||
{
|
||||
block.Span.End = processor.Line.End;
|
||||
}
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,9 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
public class InlineProcessor
|
||||
{
|
||||
private readonly List<int> lineOffsets;
|
||||
private readonly List<StringLineGroup.LineOffset> lineOffsets;
|
||||
private int previousSliceOffset;
|
||||
private int previousLineIndexForSliceOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InlineProcessor" /> class.
|
||||
@@ -34,7 +36,7 @@ namespace Markdig.Parsers
|
||||
/// <param name="inlineCreated">The inline created event.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// </exception>
|
||||
public InlineProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, InlineParserList parsers)
|
||||
public InlineProcessor(StringBuilderCache stringBuilders, MarkdownDocument document, InlineParserList parsers, bool preciseSourcelocation)
|
||||
{
|
||||
if (stringBuilders == null) throw new ArgumentNullException(nameof(stringBuilders));
|
||||
if (document == null) throw new ArgumentNullException(nameof(document));
|
||||
@@ -42,7 +44,8 @@ namespace Markdig.Parsers
|
||||
StringBuilders = stringBuilders;
|
||||
Document = document;
|
||||
Parsers = parsers;
|
||||
lineOffsets = new List<int>();
|
||||
PreciseSourceLocation = preciseSourcelocation;
|
||||
lineOffsets = new List<StringLineGroup.LineOffset>();
|
||||
Parsers.Initialize(this);
|
||||
ParserStates = new object[Parsers.Count];
|
||||
LiteralInlineParser = new LiteralInlineParser();
|
||||
@@ -53,6 +56,11 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
public LeafBlock Block { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to provide precise source location.
|
||||
/// </summary>
|
||||
public bool PreciseSourceLocation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the new block to replace the block being processed.
|
||||
/// </summary>
|
||||
@@ -86,12 +94,7 @@ namespace Markdig.Parsers
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the line from the begining of the document being processed.
|
||||
/// </summary>
|
||||
public int LineIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the local line from the beginning of the block being processed.
|
||||
/// </summary>
|
||||
public int LocalLineIndex { get; set; }
|
||||
public int LineIndex { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parser states that can be used by <see cref="InlineParser"/> using their <see cref="InlineParser.Index"/> property.
|
||||
@@ -108,12 +111,68 @@ namespace Markdig.Parsers
|
||||
/// </summary>
|
||||
public LiteralInlineParser LiteralInlineParser { get; }
|
||||
|
||||
|
||||
public int GetSourcePosition(int sliceOffset)
|
||||
{
|
||||
int column;
|
||||
int lineIndex;
|
||||
return GetSourcePosition(sliceOffset, out lineIndex, out column);
|
||||
}
|
||||
|
||||
public SourceSpan GetSourcePositionFromLocalSpan(SourceSpan span)
|
||||
{
|
||||
if (span.IsEmpty)
|
||||
{
|
||||
return SourceSpan.Empty;
|
||||
}
|
||||
|
||||
int column;
|
||||
int lineIndex;
|
||||
return new SourceSpan(GetSourcePosition(span.Start, out lineIndex, out column), GetSourcePosition(span.End, out lineIndex, out column));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source position for the specified offset within the current slice.
|
||||
/// </summary>
|
||||
/// <param name="sliceOffset">The slice offset.</param>
|
||||
/// <returns>The source position</returns>
|
||||
public int GetSourcePosition(int sliceOffset, out int lineIndex, out int column)
|
||||
{
|
||||
column = 0;
|
||||
lineIndex = sliceOffset >= previousSliceOffset ? previousLineIndexForSliceOffset : 0;
|
||||
int position = 0;
|
||||
if (PreciseSourceLocation)
|
||||
{
|
||||
for (; lineIndex < lineOffsets.Count; lineIndex++)
|
||||
{
|
||||
var lineOffset = lineOffsets[lineIndex];
|
||||
if (sliceOffset <= lineOffset.End)
|
||||
{
|
||||
// Use the beginning of the line as a previous slice offset
|
||||
// (since it is on the same line)
|
||||
previousSliceOffset = lineOffsets[lineIndex].Start;
|
||||
var delta = sliceOffset - previousSliceOffset;
|
||||
column = lineOffsets[lineIndex].Column + delta;
|
||||
position = lineOffset.LinePosition + delta + lineOffsets[lineIndex].Offset;
|
||||
previousLineIndexForSliceOffset = lineIndex;
|
||||
|
||||
// Return an absolute line index
|
||||
lineIndex = lineIndex + LineIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the inline of the specified <see cref="LeafBlock"/>.
|
||||
/// </summary>
|
||||
/// <param name="leafBlock">The leaf block.</param>
|
||||
public void ProcessInlineLeaf(LeafBlock leafBlock)
|
||||
{
|
||||
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
|
||||
|
||||
// clear parser states
|
||||
Array.Clear(ParserStates, 0, ParserStates.Length);
|
||||
|
||||
@@ -124,21 +183,24 @@ namespace Markdig.Parsers
|
||||
BlockNew = null;
|
||||
LineIndex = leafBlock.Line;
|
||||
|
||||
previousSliceOffset = 0;
|
||||
previousLineIndexForSliceOffset = 0;
|
||||
lineOffsets.Clear();
|
||||
LocalLineIndex = 0;
|
||||
var text = leafBlock.Lines.ToSlice(lineOffsets);
|
||||
leafBlock.Lines = new StringLineGroup();
|
||||
|
||||
int previousStart = -1;
|
||||
|
||||
while (!text.IsEmpty)
|
||||
{
|
||||
var c = text.CurrentChar;
|
||||
|
||||
// Update line index
|
||||
if (text.Start >= lineOffsets[LocalLineIndex])
|
||||
// Security check so that the parser can't go into a crazy infinite loop if one extension is messing
|
||||
if (previousStart == text.Start)
|
||||
{
|
||||
LineIndex++;
|
||||
LocalLineIndex++;
|
||||
throw new InvalidOperationException($"The parser is in an invalid infinite loop while trying to parse inlines for block [{leafBlock.GetType().Name}] at position ({leafBlock.ToPositionText()}");
|
||||
}
|
||||
previousStart = text.Start;
|
||||
|
||||
var c = text.CurrentChar;
|
||||
|
||||
var textSaved = text;
|
||||
var parsers = Parsers.GetParsersForOpeningCharacter(c);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Parsers.Inlines
|
||||
@@ -31,9 +32,18 @@ namespace Markdig.Parsers.Inlines
|
||||
string link;
|
||||
bool isEmail;
|
||||
var saved = slice;
|
||||
int line;
|
||||
int column;
|
||||
if (LinkHelper.TryParseAutolink(ref slice, out link, out isEmail))
|
||||
{
|
||||
processor.Inline = new AutolinkInline() {IsEmail = isEmail, Url = link};
|
||||
processor.Inline = new AutolinkInline()
|
||||
{
|
||||
IsEmail = isEmail,
|
||||
Url = link,
|
||||
Span = new SourceSpan(processor.GetSourcePosition(saved.Start, out line, out column), processor.GetSourcePosition(slice.Start - 1)),
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
}
|
||||
else if (EnableHtmlParsing)
|
||||
{
|
||||
@@ -44,7 +54,13 @@ namespace Markdig.Parsers.Inlines
|
||||
return false;
|
||||
}
|
||||
|
||||
processor.Inline = new HtmlInline() { Tag = htmlTag };
|
||||
processor.Inline = new HtmlInline()
|
||||
{
|
||||
Tag = htmlTag,
|
||||
Span = new SourceSpan(processor.GetSourcePosition(saved.Start, out line, out column), processor.GetSourcePosition(slice.Start - 1)),
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Parsers.Inlines
|
||||
@@ -29,6 +30,8 @@ namespace Markdig.Parsers.Inlines
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
|
||||
// Match the opened sticks
|
||||
var c = slice.CurrentChar;
|
||||
while (c == match)
|
||||
@@ -47,13 +50,13 @@ namespace Markdig.Parsers.Inlines
|
||||
// The contents of the code span are the characters between the two backtick strings, with leading and trailing spaces and line endings removed, and whitespace collapsed to single spaces.
|
||||
var pc = ' ';
|
||||
|
||||
int newLinesFound = 0;
|
||||
while (c != '\0')
|
||||
{
|
||||
// Transform '\n' into a single space
|
||||
if (c == '\n')
|
||||
{
|
||||
processor.LocalLineIndex++;
|
||||
processor.LineIndex++;
|
||||
newLinesFound++;
|
||||
c = ' ';
|
||||
}
|
||||
|
||||
@@ -98,10 +101,15 @@ namespace Markdig.Parsers.Inlines
|
||||
builder.Length--;
|
||||
}
|
||||
}
|
||||
int line;
|
||||
int column;
|
||||
processor.Inline = new CodeInline()
|
||||
{
|
||||
Delimiter = match,
|
||||
Content = builder.ToString()
|
||||
Content = builder.ToString(),
|
||||
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.Start - 1)),
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
isMatching = true;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,8 @@ namespace Markdig.Parsers.Inlines
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
|
||||
int delimiterCount = 0;
|
||||
char c;
|
||||
do
|
||||
@@ -177,10 +179,15 @@ namespace Markdig.Parsers.Inlines
|
||||
delimiterType |= DelimiterType.Close;
|
||||
}
|
||||
|
||||
int line;
|
||||
int column;
|
||||
var delimiter = new EmphasisDelimiterInline(this, emphasisDesc)
|
||||
{
|
||||
DelimiterCount = delimiterCount,
|
||||
Type = delimiterType,
|
||||
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(slice.Start - 1)),
|
||||
Column = column,
|
||||
Line = line,
|
||||
};
|
||||
|
||||
processor.Inline = delimiter;
|
||||
@@ -241,6 +248,24 @@ namespace Markdig.Parsers.Inlines
|
||||
IsDouble = isStrong
|
||||
};
|
||||
|
||||
// Update position for emphasis
|
||||
var openDelimitercount = openDelimiter.DelimiterCount;
|
||||
var closeDelimitercount = closeDelimiter.DelimiterCount;
|
||||
var delimiterDelta = isStrong ? 2 : 1;
|
||||
|
||||
emphasis.Span.Start = openDelimiter.Span.Start;
|
||||
emphasis.Line = openDelimiter.Line;
|
||||
emphasis.Column = openDelimiter.Column;
|
||||
emphasis.Span.End = closeDelimiter.Span.End - closeDelimitercount + delimiterDelta;
|
||||
|
||||
openDelimiter.Span.Start += delimiterDelta;
|
||||
openDelimiter.Column += delimiterDelta;
|
||||
closeDelimiter.Span.Start += delimiterDelta;
|
||||
closeDelimiter.Column += delimiterDelta;
|
||||
|
||||
openDelimiter.DelimiterCount -= delimiterDelta;
|
||||
closeDelimiter.DelimiterCount -= delimiterDelta;
|
||||
|
||||
var embracer = (ContainerInline)openDelimiter;
|
||||
|
||||
// Go down to the first emphasis with a lower level
|
||||
@@ -267,9 +292,6 @@ namespace Markdig.Parsers.Inlines
|
||||
// Embrace all delimiters
|
||||
embracer.EmbraceChildrenBy(emphasis);
|
||||
|
||||
openDelimiter.DelimiterCount -= isStrong ? 2 : 1;
|
||||
closeDelimiter.DelimiterCount -= isStrong ? 2 : 1;
|
||||
|
||||
// Remove any intermediate emphasis
|
||||
for (int k = i - 1; k >= openDelimiterIndex + 1; k--)
|
||||
{
|
||||
@@ -277,7 +299,10 @@ namespace Markdig.Parsers.Inlines
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice(literalDelimiter.ToLiteral()),
|
||||
IsClosed = true
|
||||
IsClosed = true,
|
||||
Span = literalDelimiter.Span,
|
||||
Line = literalDelimiter.Line,
|
||||
Column = literalDelimiter.Column
|
||||
};
|
||||
|
||||
literalDelimiter.ReplaceBy(literal);
|
||||
@@ -327,7 +352,10 @@ namespace Markdig.Parsers.Inlines
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice(closeDelimiter.ToLiteral()),
|
||||
IsClosed = true
|
||||
IsClosed = true,
|
||||
Span = closeDelimiter.Span,
|
||||
Line = closeDelimiter.Line,
|
||||
Column = closeDelimiter.Column
|
||||
};
|
||||
|
||||
closeDelimiter.ReplaceBy(literal);
|
||||
@@ -351,7 +379,10 @@ namespace Markdig.Parsers.Inlines
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice(delimiter.ToLiteral()),
|
||||
IsClosed = true
|
||||
IsClosed = true,
|
||||
Span = delimiter.Span,
|
||||
Line = delimiter.Line,
|
||||
Column = delimiter.Column
|
||||
};
|
||||
|
||||
delimiter.ReplaceBy(literal);
|
||||
|
||||
@@ -19,11 +19,21 @@ namespace Markdig.Parsers.Inlines
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
var startPosition = slice.Start;
|
||||
// Go to escape character
|
||||
var c = slice.NextChar();
|
||||
int line;
|
||||
int column;
|
||||
if (c.IsAsciiPunctuation())
|
||||
{
|
||||
processor.Inline = new LiteralInline() {Content = new StringSlice(new string(c, 1))};
|
||||
processor.Inline = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice(new string(c, 1)),
|
||||
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
processor.Inline.Span.End = processor.Inline.Span.Start + 1;
|
||||
slice.NextChar();
|
||||
return true;
|
||||
}
|
||||
@@ -31,7 +41,14 @@ namespace Markdig.Parsers.Inlines
|
||||
// A backslash at the end of the line is a [hard line break]:
|
||||
if (c == '\n')
|
||||
{
|
||||
processor.Inline = new HardlineBreakInline();
|
||||
processor.Inline = new LineBreakInline()
|
||||
{
|
||||
IsHard = true,
|
||||
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
processor.Inline.Span.End = processor.Inline.Span.Start + 1;
|
||||
slice.NextChar();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Parsers.Inlines
|
||||
@@ -24,6 +25,7 @@ namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
string entityName;
|
||||
int entityValue;
|
||||
var startPosition = slice.Start;
|
||||
int match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
|
||||
if (match == 0)
|
||||
{
|
||||
@@ -44,10 +46,15 @@ namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
var matched = slice;
|
||||
matched.End = match - 1;
|
||||
int line;
|
||||
int column;
|
||||
processor.Inline = new HtmlEntityInline()
|
||||
{
|
||||
Original = matched,
|
||||
Transcoded = new StringSlice(literal)
|
||||
Transcoded = new StringSlice(literal),
|
||||
Span = new SourceSpan(processor.GetSourcePosition(startPosition, out line, out column), processor.GetSourcePosition(matched.End + 1)),
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
slice.Start = slice.Start + match;
|
||||
return true;
|
||||
|
||||
@@ -8,7 +8,7 @@ using Markdig.Syntax.Inlines;
|
||||
namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// An inline parser for <see cref="SoftlineBreakInline"/> and <see cref="HardlineBreakInline"/>.
|
||||
/// An inline parser for <see cref="LineBreakInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
public class LineBreakInlineParser : InlineParser
|
||||
@@ -34,10 +34,20 @@ namespace Markdig.Parsers.Inlines
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
var hasDoubleSpacesBefore = slice.PeekCharExtra(-1).IsSpace() && slice.PeekCharExtra(-2).IsSpace();
|
||||
slice.NextChar(); // Skip \n
|
||||
|
||||
processor.Inline = !EnableSoftAsHard && (slice.Start == 0 || !hasDoubleSpacesBefore) ? (Inline)new SoftlineBreakInline() : new HardlineBreakInline();
|
||||
int line;
|
||||
int column;
|
||||
processor.Inline = new LineBreakInline
|
||||
{
|
||||
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column)},
|
||||
IsHard = EnableSoftAsHard || (slice.Start != 0 && hasDoubleSpacesBefore),
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
processor.Inline.Span.End = processor.Inline.Span.Start;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ namespace Markdig.Parsers.Inlines
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
|
||||
int line;
|
||||
int column;
|
||||
var startPosition = processor.GetSourcePosition(slice.Start, out line, out column);
|
||||
|
||||
bool isImage = false;
|
||||
if (c == '!')
|
||||
{
|
||||
@@ -47,8 +51,9 @@ namespace Markdig.Parsers.Inlines
|
||||
var saved = slice;
|
||||
string label;
|
||||
|
||||
SourceSpan labelSpan;
|
||||
// If the label is followed by either a ( or a [, this is not a shortcut
|
||||
if (LinkHelper.TryParseLabel(ref slice, out label))
|
||||
if (LinkHelper.TryParseLabel(ref slice, out label, out labelSpan))
|
||||
{
|
||||
if (!processor.Document.ContainsLinkReferenceDefinition(label))
|
||||
{
|
||||
@@ -63,7 +68,11 @@ namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
Type = DelimiterType.Open,
|
||||
Label = label,
|
||||
IsImage = isImage
|
||||
LabelSpan = processor.GetSourcePositionFromLocalSpan(labelSpan),
|
||||
IsImage = isImage,
|
||||
Span = new SourceSpan(startPosition, processor.GetSourcePosition(slice.Start - 1)),
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
return true;
|
||||
|
||||
@@ -86,7 +95,7 @@ namespace Markdig.Parsers.Inlines
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ProcessLinkReference(InlineProcessor state, string label, bool isImage, Inline child = null)
|
||||
private bool ProcessLinkReference(InlineProcessor state, string label, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
|
||||
{
|
||||
bool isValidLink = false;
|
||||
LinkReferenceDefinition linkRef;
|
||||
@@ -96,7 +105,7 @@ namespace Markdig.Parsers.Inlines
|
||||
// Try to use a callback directly defined on the LinkReferenceDefinition
|
||||
if (linkRef.CreateLinkInline != null)
|
||||
{
|
||||
link = linkRef.CreateLinkInline(state, linkRef, child);
|
||||
link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild);
|
||||
}
|
||||
|
||||
// Create a default link if the callback was not found
|
||||
@@ -107,19 +116,30 @@ namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
Url = HtmlHelper.Unescape(linkRef.Url),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title),
|
||||
IsImage = isImage,
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
IsImage = parent.IsImage,
|
||||
Reference = linkRef,
|
||||
Span = new SourceSpan(parent.Span.Start, endPosition),
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
}
|
||||
|
||||
var containerLink = link as ContainerInline;
|
||||
if (containerLink != null)
|
||||
{
|
||||
var child = parent.FirstChild;
|
||||
if (child == null)
|
||||
{
|
||||
child = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice(label),
|
||||
IsClosed = true
|
||||
IsClosed = true,
|
||||
// Not exact but we leave it like this
|
||||
Span = parent.Span,
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
containerLink.AppendChild(child);
|
||||
}
|
||||
@@ -175,7 +195,10 @@ namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
inlineState.Inline = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice("[")
|
||||
Content = new StringSlice("["),
|
||||
Span = openParent.Span,
|
||||
Line = openParent.Line,
|
||||
Column = openParent.Column,
|
||||
};
|
||||
openParent.ReplaceBy(inlineState.Inline);
|
||||
return false;
|
||||
@@ -192,7 +215,9 @@ namespace Markdig.Parsers.Inlines
|
||||
case '(':
|
||||
string url;
|
||||
string title;
|
||||
if (LinkHelper.TryParseInlineLink(ref text, out url, out title))
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
if (LinkHelper.TryParseInlineLink(ref text, out url, out title, out linkSpan, out titleSpan))
|
||||
{
|
||||
// Inline Link
|
||||
var link = new LinkInline()
|
||||
@@ -200,6 +225,12 @@ namespace Markdig.Parsers.Inlines
|
||||
Url = HtmlHelper.Unescape(url),
|
||||
Title = HtmlHelper.Unescape(title),
|
||||
IsImage = openParent.IsImage,
|
||||
LabelSpan = openParent.LabelSpan,
|
||||
UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
|
||||
TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan),
|
||||
Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start -1)),
|
||||
Line = openParent.Line,
|
||||
Column = openParent.Column,
|
||||
};
|
||||
|
||||
openParent.ReplaceBy(link);
|
||||
@@ -224,13 +255,17 @@ namespace Markdig.Parsers.Inlines
|
||||
break;
|
||||
default:
|
||||
|
||||
var labelSpan = SourceSpan.Empty;
|
||||
string label = null;
|
||||
bool isLabelSpanLocal = true;
|
||||
// Handle Collapsed links
|
||||
if (text.CurrentChar == '[')
|
||||
{
|
||||
if (text.PeekChar(1) == ']')
|
||||
{
|
||||
label = openParent.Label;
|
||||
labelSpan = openParent.LabelSpan;
|
||||
isLabelSpanLocal = false;
|
||||
text.NextChar(); // Skip [
|
||||
text.NextChar(); // Skip ]
|
||||
}
|
||||
@@ -240,10 +275,14 @@ namespace Markdig.Parsers.Inlines
|
||||
label = openParent.Label;
|
||||
}
|
||||
|
||||
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label))
|
||||
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
|
||||
{
|
||||
if (ProcessLinkReference(inlineState, label, openParent.IsImage,
|
||||
openParent.FirstChild))
|
||||
if (isLabelSpanLocal)
|
||||
{
|
||||
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
|
||||
}
|
||||
|
||||
if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
|
||||
{
|
||||
// Remove the open parent
|
||||
openParent.Remove();
|
||||
@@ -267,6 +306,7 @@ namespace Markdig.Parsers.Inlines
|
||||
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Span = openParent.Span,
|
||||
Content = new StringSlice(openParent.IsImage ? "![" : "[")
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Parsers.Inlines
|
||||
@@ -33,6 +34,10 @@ namespace Markdig.Parsers.Inlines
|
||||
{
|
||||
var text = slice.Text;
|
||||
|
||||
int line;
|
||||
int column;
|
||||
var startPosition = processor.GetSourcePosition(slice.Start, out line, out column);
|
||||
|
||||
// Sligthly faster to perform our own search for opening characters
|
||||
var nextStart = processor.Parsers.IndexOfOpeningCharacter(text, slice.Start + 1, slice.End);
|
||||
//var nextStart = str.IndexOfAny(processor.SpecialCharacters, slice.Start + 1, slice.Length - 1);
|
||||
@@ -59,7 +64,15 @@ namespace Markdig.Parsers.Inlines
|
||||
}
|
||||
|
||||
// The LiteralInlineParser is always matching (at least an empty string)
|
||||
processor.Inline = length > 0 ? new LiteralInline {Content = new StringSlice(slice.Text, slice.Start, slice.Start + length - 1)} : new LiteralInline();
|
||||
var endPosition = slice.Start + length - 1;
|
||||
processor.Inline = new LiteralInline()
|
||||
{
|
||||
Content = length > 0 ? new StringSlice(slice.Text, slice.Start, endPosition) : StringSlice.Empty,
|
||||
Span = new SourceSpan(startPosition, processor.GetSourcePosition(endPosition)),
|
||||
Line = line,
|
||||
Column = column,
|
||||
};
|
||||
|
||||
slice.Start = nextStart;
|
||||
|
||||
// Call only PostMatch if necessary
|
||||
|
||||
@@ -152,6 +152,9 @@ namespace Markdig.Parsers
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
// Update list-item source end position
|
||||
listItem.Span.End = state.Line.End;
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -170,6 +173,9 @@ namespace Markdig.Parsers
|
||||
state.GoToColumn(columWidth);
|
||||
}
|
||||
|
||||
// Update list-item source end position
|
||||
listItem.Span.End = state.Line.End;
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -189,6 +195,8 @@ namespace Markdig.Parsers
|
||||
|
||||
var initColumnBeforeIndent = state.ColumnBeforeIndent;
|
||||
var initColumn = state.Column;
|
||||
var sourcePosition = state.Start;
|
||||
var sourceEndPosition = state.Line.End;
|
||||
|
||||
var c = state.CurrentChar;
|
||||
var itemParser = mapItemParsers[c];
|
||||
@@ -249,7 +257,8 @@ namespace Markdig.Parsers
|
||||
var newListItem = new ListItemBlock(this)
|
||||
{
|
||||
Column = initColumn,
|
||||
ColumnWidth = columnWidth
|
||||
ColumnWidth = columnWidth,
|
||||
Span = new SourceSpan(sourcePosition, sourceEndPosition)
|
||||
};
|
||||
state.NewBlocks.Push(newListItem);
|
||||
|
||||
@@ -276,6 +285,7 @@ namespace Markdig.Parsers
|
||||
var newList = new ListBlock(this)
|
||||
{
|
||||
Column = initColumn,
|
||||
Span = new SourceSpan(sourcePosition, sourceEndPosition),
|
||||
IsOrdered = isOrdered,
|
||||
BulletType = listInfo.BulletType,
|
||||
OrderedDelimiter = listInfo.OrderedDelimiter,
|
||||
@@ -342,6 +352,12 @@ namespace Markdig.Parsers
|
||||
isLastListItem = false;
|
||||
}
|
||||
|
||||
// Update end-position for the list
|
||||
if (listBlock.Count > 0)
|
||||
{
|
||||
listBlock.Span.End = listBlock[listBlock.Count - 1].Span.End;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,19 +26,24 @@ namespace Markdig.Parsers
|
||||
private readonly InlineProcessor inlineProcessor;
|
||||
private readonly MarkdownDocument document;
|
||||
private readonly ProcessDocumentDelegate documentProcessed;
|
||||
private readonly bool preciseSourceLocation;
|
||||
|
||||
private LineReader lineReader;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarkdownParser" /> class.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader.</param>
|
||||
/// <param name="text">The reader.</param>
|
||||
/// <param name="pipeline">The pipeline.</param>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// </exception>
|
||||
private MarkdownParser(TextReader reader, MarkdownPipeline pipeline)
|
||||
private MarkdownParser(string text, MarkdownPipeline pipeline)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
|
||||
Reader = reader;
|
||||
text = FixupZero(text);
|
||||
lineReader = new LineReader(text);
|
||||
preciseSourceLocation = pipeline.PreciseSourceLocation;
|
||||
|
||||
// Initialize the pipeline
|
||||
var stringBuilderCache = pipeline.StringBuilderCache ?? new StringBuilderCache();
|
||||
@@ -53,7 +58,7 @@ namespace Markdig.Parsers
|
||||
// Initialize the inline parsers
|
||||
var inlineParserList = new InlineParserList();
|
||||
inlineParserList.AddRange(pipeline.InlineParsers);
|
||||
inlineProcessor = new InlineProcessor(stringBuilderCache, document, inlineParserList)
|
||||
inlineProcessor = new InlineProcessor(stringBuilderCache, document, inlineParserList, pipeline.PreciseSourceLocation)
|
||||
{
|
||||
DebugLog = pipeline.DebugLog
|
||||
};
|
||||
@@ -64,25 +69,20 @@ namespace Markdig.Parsers
|
||||
/// <summary>
|
||||
/// Parses the specified markdown into an AST <see cref="MarkdownDocument"/>
|
||||
/// </summary>
|
||||
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
|
||||
/// <param name="text">A Markdown text</param>
|
||||
/// <param name="pipeline">The pipeline used for the parsing.</param>
|
||||
/// <returns>An AST Markdown document</returns>
|
||||
/// <exception cref="System.ArgumentNullException">if reader variable is null</exception>
|
||||
public static MarkdownDocument Parse(TextReader reader, MarkdownPipeline pipeline = null)
|
||||
public static MarkdownDocument Parse(string text, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (reader == null) throw new ArgumentNullException(nameof(reader));
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
|
||||
// Perform the parsing
|
||||
var markdownParser = new MarkdownParser(reader, pipeline);
|
||||
var markdownParser = new MarkdownParser(text, pipeline);
|
||||
return markdownParser.Parse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the text reader used.
|
||||
/// </summary>
|
||||
private TextReader Reader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses the current <see cref="Reader"/> into a Markdown <see cref="MarkdownDocument"/>.
|
||||
/// </summary>
|
||||
@@ -101,17 +101,15 @@ namespace Markdig.Parsers
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// TODO: A TextReader doesn't allow to precisely track position in file due to line endings
|
||||
var lineText = Reader.ReadLine();
|
||||
|
||||
// Get the precise position of the begining of the line
|
||||
var lineText = lineReader.ReadLine();
|
||||
|
||||
// If this is the end of file and the last line is empty
|
||||
if (lineText == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
lineText = FixupZero(lineText);
|
||||
|
||||
blockProcessor.ProcessLine(new StringSlice(lineText));
|
||||
blockProcessor.ProcessLine(lineText.Value);
|
||||
}
|
||||
blockProcessor.CloseAll(true);
|
||||
}
|
||||
@@ -122,29 +120,7 @@ namespace Markdig.Parsers
|
||||
/// <param name="text">The text to secure.</param>
|
||||
private string FixupZero(string text)
|
||||
{
|
||||
int startPos = 0;
|
||||
int nextZero;
|
||||
StringBuilder newLine = null;
|
||||
while ((nextZero = text.IndexOf('\0', startPos)) >= 0)
|
||||
{
|
||||
if (newLine == null)
|
||||
{
|
||||
newLine = StringBuilderCache.Local();
|
||||
}
|
||||
newLine.Append(text, startPos, nextZero - startPos);
|
||||
newLine.Append(CharHelper.ZeroSafeChar);
|
||||
startPos = nextZero + 1;
|
||||
}
|
||||
|
||||
if (newLine == null)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
newLine.Append(text, startPos, text.Length - startPos);
|
||||
|
||||
var result = newLine.ToString();
|
||||
newLine.Length = 0;
|
||||
return result;
|
||||
return text.Replace('\0', CharHelper.ZeroSafeChar);
|
||||
}
|
||||
|
||||
private class ContainerItemCache : DefaultObjectCache<ContainerItem>
|
||||
|
||||
@@ -20,7 +20,11 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
// We continue trying to match by default
|
||||
processor.NewBlocks.Push(new ParagraphBlock(this) {Column = processor.Column});
|
||||
processor.NewBlocks.Push(new ParagraphBlock(this)
|
||||
{
|
||||
Column = processor.Column,
|
||||
Span = new SourceSpan(processor.Line.Start, processor.Line.End)
|
||||
});
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -35,6 +39,8 @@ namespace Markdig.Parsers
|
||||
{
|
||||
return TryParseSetexHeading(processor, block);
|
||||
}
|
||||
|
||||
block.Span.End = processor.Line.End;
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -121,6 +127,7 @@ namespace Markdig.Parsers
|
||||
var heading = new HeadingBlock(this)
|
||||
{
|
||||
Column = paragraph.Column,
|
||||
Span = new SourceSpan(paragraph.Span.Start, line.Start),
|
||||
Level = level,
|
||||
Lines = paragraph.Lines,
|
||||
};
|
||||
@@ -132,6 +139,8 @@ namespace Markdig.Parsers
|
||||
return BlockState.BreakDiscard;
|
||||
}
|
||||
|
||||
block.Span.End = state.Line.End;
|
||||
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
var column = processor.Column;
|
||||
var sourcePosition = processor.Start;
|
||||
|
||||
// 5.1 Block quotes
|
||||
// A block quote marker consists of 0-3 spaces of initial indent, plus (a) the character > together with a following space, or (b) a single character > not followed by a space.
|
||||
@@ -37,7 +38,12 @@ namespace Markdig.Parsers
|
||||
{
|
||||
processor.NextColumn();
|
||||
}
|
||||
processor.NewBlocks.Push(new QuoteBlock(this) {QuoteChar = quoteChar, Column = column});
|
||||
processor.NewBlocks.Push(new QuoteBlock(this)
|
||||
{
|
||||
QuoteChar = quoteChar,
|
||||
Column = column,
|
||||
Span = new SourceSpan(sourcePosition, processor.Line.End),
|
||||
});
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
@@ -55,6 +61,7 @@ namespace Markdig.Parsers
|
||||
var c = processor.CurrentChar;
|
||||
if (c != quote.QuoteChar)
|
||||
{
|
||||
block.Span.End = processor.Start - 1;
|
||||
return processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
|
||||
}
|
||||
|
||||
@@ -64,7 +71,18 @@ namespace Markdig.Parsers
|
||||
processor.NextChar(); // Skip following space
|
||||
}
|
||||
|
||||
block.Span.End = processor.Line.End;
|
||||
return BlockState.Continue;
|
||||
}
|
||||
|
||||
public override bool Close(BlockProcessor processor, Block block)
|
||||
{
|
||||
var quoteBlock = block as QuoteBlock;
|
||||
if (quoteBlock?.LastChild != null)
|
||||
{
|
||||
quoteBlock.Span.End = quoteBlock.LastChild.Span.End;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,8 @@ namespace Markdig.Parsers
|
||||
return BlockState.None;
|
||||
}
|
||||
|
||||
var startPosition = processor.Start;
|
||||
|
||||
var line = processor.Line;
|
||||
|
||||
// 4.1 Thematic breaks
|
||||
@@ -83,7 +85,11 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
// Push a new block
|
||||
processor.NewBlocks.Push(new ThematicBreakBlock(this) { Column = processor.Column });
|
||||
processor.NewBlocks.Push(new ThematicBreakBlock(this)
|
||||
{
|
||||
Column = processor.Column,
|
||||
Span = new SourceSpan(startPosition, line.End)
|
||||
});
|
||||
return BlockState.BreakDiscard;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,13 @@ using System.Reflection;
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
[assembly: AssemblyVersion("0.2.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.2.0.0")]
|
||||
[assembly: AssemblyVersion(Markdig.Markdown.Version)]
|
||||
[assembly: AssemblyFileVersion(Markdig.Markdown.Version)]
|
||||
|
||||
namespace Markdig
|
||||
{
|
||||
public static partial class Markdown
|
||||
{
|
||||
public const string Version = "0.5.6";
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Markdig.Renderers.Html
|
||||
/// <summary>
|
||||
/// Attached HTML attributes to a <see cref="MarkdownObject"/>.
|
||||
/// </summary>
|
||||
public class HtmlAttributes
|
||||
public class HtmlAttributes : MarkdownObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HtmlAttributes"/> class.
|
||||
@@ -44,9 +44,14 @@ namespace Markdig.Renderers.Html
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (Classes == null)
|
||||
{
|
||||
Classes = new List<string>(2); // Use half list compare to default capacity (4), as we don't expect lots of classes
|
||||
Classes = new List<string>(2);
|
||||
// Use half list compare to default capacity (4), as we don't expect lots of classes
|
||||
}
|
||||
|
||||
if (!Classes.Contains(name))
|
||||
{
|
||||
Classes.Add(name);
|
||||
}
|
||||
Classes.Add(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Markdig.Renderers.Html.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A HTML renderer for a <see cref="HardlineBreakInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Syntax.Inlines.HardlineBreakInline}" />
|
||||
public class HardlineBreakInlineRenderer : HtmlObjectRenderer<HardlineBreakInline>
|
||||
{
|
||||
protected override void Write(HtmlRenderer renderer, HardlineBreakInline obj)
|
||||
{
|
||||
if (renderer.EnableHtmlForInline)
|
||||
{
|
||||
renderer.WriteLine("<br />");
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Write(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,21 @@ using Markdig.Syntax.Inlines;
|
||||
namespace Markdig.Renderers.Html.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A HTML renderer for a <see cref="SoftlineBreakInline"/>.
|
||||
/// A HTML renderer for a <see cref="LineBreakInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Syntax.Inlines.SoftlineBreakInline}" />
|
||||
public class SoftlineBreakInlineRenderer : HtmlObjectRenderer<SoftlineBreakInline>
|
||||
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Syntax.Inlines.LineBreakInline}" />
|
||||
public class LineBreakInlineRenderer : HtmlObjectRenderer<LineBreakInline>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to render this softline break as a HTML hardline break tag (<br />)
|
||||
/// </summary>
|
||||
public bool RenderAsHardlineBreak { get; set; }
|
||||
|
||||
protected override void Write(HtmlRenderer renderer, SoftlineBreakInline obj)
|
||||
protected override void Write(HtmlRenderer renderer, LineBreakInline obj)
|
||||
{
|
||||
if (renderer.EnableHtmlForInline)
|
||||
{
|
||||
if (RenderAsHardlineBreak)
|
||||
if (obj.IsHard || RenderAsHardlineBreak)
|
||||
{
|
||||
renderer.WriteLine("<br />");
|
||||
}
|
||||
@@ -32,7 +32,9 @@ namespace Markdig.Renderers.Html
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.WriteLine("<ul>");
|
||||
renderer.Write("<ul");
|
||||
renderer.WriteAttributes(listBlock);
|
||||
renderer.WriteLine(">");
|
||||
}
|
||||
foreach (var item in listBlock)
|
||||
{
|
||||
|
||||
@@ -39,8 +39,7 @@ namespace Markdig.Renderers
|
||||
ObjectRenderers.Add(new CodeInlineRenderer());
|
||||
ObjectRenderers.Add(new DelimiterInlineRenderer());
|
||||
ObjectRenderers.Add(new EmphasisInlineRenderer());
|
||||
ObjectRenderers.Add(new HardlineBreakInlineRenderer());
|
||||
ObjectRenderers.Add(new SoftlineBreakInlineRenderer());
|
||||
ObjectRenderers.Add(new LineBreakInlineRenderer());
|
||||
ObjectRenderers.Add(new HtmlInlineRenderer());
|
||||
ObjectRenderers.Add(new HtmlEntityInlineRenderer());
|
||||
ObjectRenderers.Add(new LinkInlineRenderer());
|
||||
|
||||
@@ -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;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
@@ -11,6 +13,16 @@ namespace Markdig.Renderers
|
||||
/// </summary>
|
||||
public interface IMarkdownRenderer
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when before writing an object.
|
||||
/// </summary>
|
||||
event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteBefore;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when after writing an object.
|
||||
/// </summary>
|
||||
event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteAfter;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object renderers that will render <see cref="Block"/> and <see cref="Inline"/> elements.
|
||||
/// </summary>
|
||||
|
||||
@@ -35,6 +35,16 @@ namespace Markdig.Renderers
|
||||
|
||||
public bool IsLastInContainer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when before writing an object.
|
||||
/// </summary>
|
||||
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteBefore;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when after writing an object.
|
||||
/// </summary>
|
||||
public event Action<IMarkdownRenderer, MarkdownObject> ObjectWriteAfter;
|
||||
|
||||
/// <summary>
|
||||
/// Writes the children of the specified <see cref="ContainerBlock"/>.
|
||||
/// </summary>
|
||||
@@ -105,6 +115,10 @@ namespace Markdig.Renderers
|
||||
|
||||
var objectType = obj.GetType();
|
||||
|
||||
// Calls before writing an object
|
||||
var writeBefore = ObjectWriteBefore;
|
||||
writeBefore?.Invoke(this, obj);
|
||||
|
||||
// Handle regular renderers
|
||||
IMarkdownObjectRenderer renderer = previousObjectType == objectType ? previousRenderer : null;
|
||||
if (renderer == null && !renderersPerType.TryGetValue(objectType, out renderer))
|
||||
@@ -142,6 +156,10 @@ namespace Markdig.Renderers
|
||||
|
||||
previousObjectType = objectType;
|
||||
previousRenderer = renderer;
|
||||
|
||||
// Calls after writing an object
|
||||
var writeAfter = ObjectWriteAfter;
|
||||
writeAfter?.Invoke(this, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ namespace Markdig.Renderers
|
||||
{
|
||||
if (writer == null) throw new ArgumentNullException(nameof(writer));
|
||||
this.Writer = writer;
|
||||
// By default we output a newline with '\n' only even on Windows platforms
|
||||
Writer.NewLine = "\n";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,16 +23,6 @@ namespace Markdig.Syntax
|
||||
IsBreakable = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text column this instance was declared (zero-based).
|
||||
/// </summary>
|
||||
public int Column { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the text line this instance was declared (zero-based).
|
||||
/// </summary>
|
||||
public int Line { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent of this container. May be null.
|
||||
/// </summary>
|
||||
|
||||
@@ -68,6 +68,11 @@ namespace Markdig.Syntax
|
||||
}
|
||||
children[Count++] = item;
|
||||
item.Parent = this;
|
||||
|
||||
if (item.Span.End > Span.End)
|
||||
{
|
||||
Span.End = item.Span.End;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int min)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Markdig.Syntax.Inlines
|
||||
{
|
||||
/// <summary>
|
||||
/// A hard line break (Section 6.9 CommonMark specs).
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Syntax.Inlines.LeafInline" />
|
||||
[DebuggerDisplay("<br >/")]
|
||||
public class HardlineBreakInline : LineBreakInline
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user