Compare commits

...

101 Commits

Author SHA1 Message Date
Alexandre Mutel
d14f277c7b Bump version to 0.5.3 2016-06-19 11:37:26 +09:00
Alexandre Mutel
593bf08b92 Fix bug in pipetables with a trailing | on the header row separator (issue #10) 2016-06-19 11:37:12 +09:00
Alexandre Mutel
85f631f868 Bump version to 0.5.2 2016-06-17 13:58:31 +09:00
Alexandre Mutel
5ad964bcb6 Rename MarkdownObject.SourceSpan to Span 2016-06-17 13:56:09 +09:00
Alexandre Mutel
137a404bdc Add property handling of precise position for links 2016-06-17 13:45:43 +09:00
Alexandre Mutel
5204ec758a Add version used for benchmarks. Add docfx Microsoft.DocAsCode.MarkdownLite for comparison 2016-06-17 13:15:32 +09:00
Alexandre Mutel
6f4fb69c62 Add precise source location for title, span for inlines/footnote/abbreviations (#8) 2016-06-17 09:34:46 +09:00
Alexandre Mutel
0a1b37c965 Breaking change: Add SourceSpan and replace SourceStartPosition and SourceEndPosition 2016-06-16 23:45:18 +09:00
Alexandre Mutel
90bdafb05a Update link to babelmark3 2016-06-16 13:35:45 +09:00
Alexandre Mutel
8cc668ae6d Update link to babelmark3 https 2016-06-16 13:22:23 +09:00
Alexandre Mutel
1787dc4590 Update readme.md with link to article 2016-06-16 12:43:32 +09:00
Alexandre Mutel
0a9cc8fcd7 Fix typo 2016-06-16 12:31:31 +09:00
Alexandre Mutel
10c06daf5d Make benchmark results shorter 2016-06-16 12:26:28 +09:00
Alexandre Mutel
a262e42980 Update readme and benchmarks 2016-06-16 12:24:01 +09:00
Alexandre Mutel
4cd3d045d1 Add tests precise position with text containing tabs 2016-06-16 10:03:04 +09:00
Alexandre Mutel
92357576b1 Add test precise position for indented code 2016-06-16 09:54:07 +09:00
Alexandre Mutel
d45f67f8c2 Add protection against infinite loops if an extension was messing the parsing of blocks or inlines 2016-06-16 09:22:45 +09:00
Alexandre Mutel
2571cdffee Add tasklists to readme.md 2016-06-16 06:43:19 +09:00
Alexandre Mutel
c31cb6da27 Bump version to 0.5.1 2016-06-16 06:40:22 +09:00
Alexandre Mutel
5503929d15 Add support for task lists (issue #7) 2016-06-16 06:39:20 +09:00
Alexandre Mutel
ca32dda1fe Bump version to 0.5.0 2016-06-15 21:15:03 +09:00
Alexandre Mutel
12111e0b63 Update the readme with latest version 2016-06-15 21:13:19 +09:00
Alexandre Mutel
bdd46c0fc0 Merge branch 'precise_position' 2016-06-15 21:12:32 +09:00
Alexandre Mutel
daf4c8fe86 Activate only calculation for precise location when PreciseSourceLocation is setup on the pipeline 2016-06-15 21:11:34 +09:00
Alexandre Mutel
bd2c2aff9c Add credits to CommonMark.NET 2016-06-15 18:19:26 +09:00
Alexandre Mutel
921f75e1f3 Add precise position for pipetables 2016-06-15 17:02:50 +09:00
Alexandre Mutel
44a3b85f0b Add precise position for smarty pants 2016-06-15 16:36:43 +09:00
Alexandre Mutel
f9e827395b Add precise position for mathematics extension 2016-06-15 16:19:20 +09:00
Alexandre Mutel
64a9a80774 Add test precise position for HtmlAttributes 2016-06-15 16:08:46 +09:00
Alexandre Mutel
e10594391d Add test source position for footers 2016-06-15 15:46:41 +09:00
Alexandre Mutel
60eb03a221 Add test precise position for figure captions 2016-06-15 15:37:43 +09:00
Alexandre Mutel
c0a0f10af0 Add test precise location for figures 2016-06-15 15:27:08 +09:00
Alexandre Mutel
56e1ed0e25 Add test precise location for emphasis extras 2016-06-15 15:26:47 +09:00
Alexandre Mutel
a9f33cbca6 Add test precise position for emojis 2016-06-15 15:26:31 +09:00
Alexandre Mutel
6f1d39e1bb Add precise position for definition lists 2016-06-15 15:01:41 +09:00
Alexandre Mutel
838f7c5598 Fix eol for TestSourcePosition for abbreviations 2016-06-15 14:04:59 +09:00
Alexandre Mutel
0f54cc5927 Add support for precise positions for abbreviations 2016-06-15 13:54:37 +09:00
Alexandre Mutel
12745f70cf Add test position for HtmlEntity inline 2016-06-15 12:11:31 +09:00
Alexandre Mutel
442737767f Add test for escape inline 2016-06-15 12:03:03 +09:00
Alexandre Mutel
61ac46e467 Add test for ListBlock 2016-06-15 12:02:52 +09:00
Alexandre Mutel
85550580d5 Add tests for QuoteBlock position 2016-06-15 12:02:35 +09:00
Alexandre Mutel
ed69ac5fe0 Add test for thematic break position 2016-06-15 11:25:01 +09:00
Alexandre Mutel
0aec5a5783 Improve html block test 2016-06-15 11:20:48 +09:00
Alexandre Mutel
c1885fe31b Add test for HtmlBlock position 2016-06-15 11:16:57 +09:00
Alexandre Mutel
c2270a2b3a Add test for code span, link, html inline, autolink, fenced code block 2016-06-15 11:16:31 +09:00
Alexandre Mutel
3e60515bb3 Fix offset for lines within a StringLineGroup 2016-06-15 11:15:53 +09:00
Alexandre Mutel
8550c13688 Add precise position for heading 2016-06-15 10:05:08 +09:00
Alexandre Mutel
3aa65694aa Improve InlineProcessor.GetSourcePosition 2016-06-15 00:33:06 +09:00
Alexandre Mutel
52403687db Improve handling of position for core elements. Add Line and Column for inline elements. 2016-06-15 00:22:28 +09:00
Alexandre Mutel
3e83409cf4 Cleanup code with source position for BlockProcessor 2016-06-14 21:18:16 +09:00
Alexandre Mutel
9b051955bd Switch to string only parsing instead of TextReader to avoid allocations of line. Use StringSlice instead on the whole input for each line. 2016-06-14 21:00:18 +09:00
Alexandre Mutel
35c8126add Add MarkdownObject.SourceStartPosition and SourceEndPosition and start to fill this for all core syntax 2016-06-14 17:05:49 +09:00
Alexandre Mutel
67e1c8ce7f Rename images to img 2016-06-13 22:44:04 +09:00
Alexandre Mutel
6e5fbda8e5 Bump version to 0.4.0 2016-06-13 22:42:39 +09:00
Alexandre Mutel
90e7ccd80a Allow a pipe table header to be separated by a single -|- instead of at least -- | - 2016-06-13 22:40:25 +09:00
Alexandre Mutel
c09b3eedd2 Rename several extensions adding a 's' at the end to make them uniform and coherent 2016-06-13 15:56:44 +09:00
Alexandre Mutel
9441e8a04b Use Replace of whole IndexOf/StringBuilder code 2016-06-13 13:35:52 +09:00
Alexandre Mutel
301dc70ab4 Update readme 2016-06-10 22:24:57 +09:00
Alexandre Mutel
959d6db62f Update logo 2016-06-10 21:30:16 +09:00
Alexandre Mutel
38a7410e4b Update Markdown logo 2016-06-10 21:28:15 +09:00
Alexandre Mutel
b941a58ad0 Fix project.json 2016-06-10 17:16:37 +09:00
Alexandre Mutel
3fa20fa92f Update logo 2016-06-10 17:10:11 +09:00
Alexandre Mutel
5dcd4ea4aa Update logo for nuget. Bump version to 0.3.3 2016-06-10 17:03:49 +09:00
Alexandre Mutel
9c03683913 Add logo to readme 2016-06-10 17:01:17 +09:00
Alexandre Mutel
2fed1b3ebf Add markdig logo 2016-06-10 16:56:23 +09:00
Alexandre Mutel
9da3eef65f Bump to 0.3.2 2016-06-10 06:20:44 +09:00
Alexandre Mutel
0b4fe3f02f Modify tests to run default and advanced together on the core CommonMark specs to make sure the Advanced mode is compatible with the core specs (except for the AutoIdentifier that we are disabling) 2016-06-10 06:19:14 +09:00
Alexandre Mutel
96b39e1856 Fix exception when extension Media was active. Make sure that we only process absolute Uri for media 2016-06-10 06:18:19 +09:00
Alexandre Mutel
6d90f517cc Fix BlockProcessor.GoToColumn that was messing indent 2016-06-10 06:17:47 +09:00
Alexandre Mutel
c17630e3b6 Add link to babelmark3 2016-06-09 14:00:06 +09:00
Alexandre Mutel
6eecfe2edc Bump version to 0.3.1 2016-06-09 10:13:53 +09:00
Alexandre Mutel
9abeb97394 Remove Bootstrap and add ListExtra to the UseAdvancedExtensions 2016-06-09 10:13:39 +09:00
Alexandre Mutel
fd493865cf Bump version to 0.3 2016-06-08 17:26:16 +09:00
Alexandre Mutel
d43947af45 Breaking change: rename UseAllExtensions to UseAdvancedExtensions. Add pipeline.Configure(string) method 2016-06-08 17:26:07 +09:00
Alexandre Mutel
ab04ac3e00 Add new test that was crashing the CodeInlineParser 2016-06-08 16:09:17 +09:00
Alexandre Mutel
547c00eb5a Remove SoftlineBreakAsHarlineBreak and SmartyPants by default for AllExtensions 2016-06-08 16:08:22 +09:00
Alexandre Mutel
795d002ed0 Fix issue with CodeInlineParser that was incrementing local line index even without matching, resulting in an exception IndexOutOfRange 2016-06-08 16:08:01 +09:00
Alexandre Mutel
987357ef5a Render always with '\n' only instead of platform dependent EOF 2016-06-08 16:07:21 +09:00
Alexandre Mutel
613a1d97fb Remove NuGet.Config no longer used asof dotnet rc2 2016-06-06 12:07:08 +09:00
Alexandre Mutel
874170bc1a Add Markdig.WebApp ASP.NET Core to provide API for BabelMark2 test at http://johnmacfarlane.net/babelmark2 2016-06-06 10:28:09 +09:00
Alexandre Mutel
1c1e56aebe Add Markdown.Version API. Bump to 0.2.1 2016-06-06 10:13:03 +09:00
Alexandre Mutel
ed18d3fa25 Update links from readme 2016-06-01 22:40:40 +09:00
Alexandre Mutel
33a6a39c34 Fix links to pandoc grid and pipe tables extensions 2016-06-01 22:38:14 +09:00
Alexandre Mutel
a1228a1e1c Add missing update to Markdig.xproj 2016-06-01 22:37:57 +09:00
Alexandre Mutel
d9f1c8c818 Update readme with MarkdownPipelineBuilder 2016-05-31 06:57:42 +09:00
Alexandre Mutel
60350dc9bf Fix MarkdownPipeline not being immutable by introducing a MarkdownPipelineBuilder (issue #5). Bump to version 0.2.0 (dev) 2016-05-31 06:55:40 +09:00
Alexandre Mutel
dbaed0a2bb Add credit for BenchmarkDotNet 2016-05-30 22:47:11 +09:00
Alexandre Mutel
0aa26aeef1 Bump version to 0.1.1 2016-05-30 22:34:07 +09:00
Alexandre Mutel
09beb2f867 Make sure that an emphasis is not added twice (issue #4) 2016-05-30 22:33:50 +09:00
Alexandre Mutel
fb559af72b Fix wrong dependencies in project.json (issue #3) 2016-05-30 22:32:56 +09:00
Alexandre Mutel
124b4d6e6e Split usage for better readability in readme 2016-05-26 15:20:51 +09:00
Alexandre Mutel
4a2d270db1 Update link to license. Update comments on other Markdown implems 2016-05-26 15:10:17 +09:00
Alexandre Mutel
feac9d4125 Reorder items in extension lists based on importance. Fix usage 2016-05-26 14:34:09 +09:00
Alexandre Mutel
5e77d7b38e Add link to AppVeyor build 2016-05-26 10:40:21 +09:00
Alexandre Mutel
9f64dcc868 Add info about disabling features 2016-05-26 09:37:00 +09:00
Alexandre Mutel
7964bd0160 Add support for disabling HTML tag parsing 2016-05-26 09:31:56 +09:00
Alexandre Mutel
191c1e896e Update benchmarks with accurate GC 2016-05-26 06:25:51 +09:00
Alexandre Mutel
4bdf1d4d18 Fix link to extensions 2016-05-26 00:17:47 +09:00
Alexandre Mutel
080dee270d Add benchmarks 2016-05-26 00:04:25 +09:00
Alexandre Mutel
7ae36a3794 Fix link to MarkdownPipeline 2016-05-25 23:48:17 +09:00
Alexandre Mutel
f43d5be0e7 Add credits section 2016-05-25 23:33:58 +09:00
124 changed files with 3931 additions and 941 deletions

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
img/BenchmarkMemory.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
img/markdig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

131
img/markdig.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
img/markdig64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

171
readme.md
View File

@@ -1,9 +1,13 @@
# Markdig [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/)
# Markdig [![Build status](https://ci.appveyor.com/api/projects/status/hk391x8jcskxt1u8?svg=true)](https://ci.appveyor.com/project/xoofx/markdig) [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](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
@@ -13,74 +17,167 @@ Markdig is a fast, powerfull, [CommonMark](http://commonmark.org/) compliant, ex
- Includes all the core elements of CommonMark:
- including GFM fenced code blocks.
- **Extensible** architecture
- Built-in with **18+ extensions**, including:
- **Abbreviations** (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
- **Auto-identifiers** for headings (similar to [Pandoc](http://pandoc.org/README.html#extension-auto_identifiers)
- **Bootstrap** class (to output bootstrap class)
- **Citation** text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
- **Custom containers** similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
- **Definition lists** (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- **Emoji** support (inspired from [Markdown-it](https://markdown-it.github.io/))
- **Extra emphasis** (inspired from [Pandoc](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- Even the core Markdown/CommonMark parsing is pluggable, so it allows to disable builtin Markdown/Commonmark parsing (e.g [Disable HTML parsing](https://github.com/lunet-io/markdig/blob/7964bd0160d4c18e4155127a4c863d61ebd8944a/src/Markdig/MarkdownExtensions.cs#L306)) or change behaviour (e.g change matching `#` of a headers with `@`)
- Built-in with **20+ extensions**, including:
- 2 kind of tables:
- **Pipe tables** (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 - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- **Definition lists** (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- **Footnotes** (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- **Auto-identifiers** for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
- **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))
- **Citation** text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
- **Custom containers** similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
- **Figures** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
- **Footers** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
- **Footnotes** (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- **Special attributes** or attached HTML attributes (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- **Soft lines as hard lines**
- **Extra bullet lists**, supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
- **Mathematics**/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- **Embed player** for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
- **Soft lines as hard lines**
- **Emoji** support (inspired from [Markdown-it](https://markdown-it.github.io/))
- **SmartyPants** (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- Tables:
- **Pipe tables** (inspired from Kramdown/[PanDoc](http://pandoc.org/README.html#pipe_tables))
- **Grid tables** (inspired from [Pandoc](http://pandoc.org/README.html#grid_tables))
- **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: [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/)
## Usage
The main entry point for the API is the `Markdown` class:
The main entry point for the API is the `Markdig.Markdown` class:
By default, without any options, Markdig is using the plain CommonMark parser:
```csharp
var result = Markdown.ToHtml("This is a text with some **emphasis**");
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
var result = Markdown.ToHtml("This is a text with some **emphasis**", new MarkdownPipeline().UseAllExtensions());
// 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);
```
You can have a look at the [MarkdownPipeline](https://github.com/lunet-io/markdig/blob/src/Markdig/MarkdownPipeline.cs) file that describes all actionable extensions.
You can have a look at the [MarkdownExtensions](https://github.com/lunet-io/markdig/blob/master/src/Markdig/MarkdownExtensions.cs) that describes all actionable extensions (by modifying the MarkdownPipeline)
## License
This software is released under the [BSD-Clause 2 license](http://opensource.org/licenses/BSD-2-Clause).
This software is released under the [BSD-Clause 2 license](https://github.com/lunet-io/markdig/blob/master/license.txt).
## Benchmarking
This is an early preview of the benchmarking against various implementations:
**C implementations**:
- [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
**.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:
![BenchMark CPU Time](img/BenchmarkCPU.png)
![BenchMark Memory](img/BenchmarkMemory.png)
### Performance for x86:
```
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
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
Type=Program Mode=SingleRun LaunchCount=2
WarmupCount=2 TargetCount=10
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:
```
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
Frequency=3319351 ticks, Resolution=301.2637 ns, Timer=TSC
HostCLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT]
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.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
Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.net/) for the CommonMark specs and all the people involved in making Markdown a better standard!
This project would not have been possible without this huge foundation.
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
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
Alexandre MUTEL aka [xoofx](http://xoofx.com)

View File

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

View File

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

View File

@@ -98,6 +98,14 @@ This is ``a code span''``
<p>This is <code>a code span''</code></p>
````````````````````````````````
```````````````````````````````` example
hello ``there```
test
.
<p>hello &ldquo;there&rdquo;`
test</p>
````````````````````````````````
An emphasis starting inside left/right quotes will span over the right quote:
```````````````````````````````` example

View File

@@ -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("![Video1](https://www.youtube.com/watch?v=mswPy5bt3TQ)\n\n![Video2](https://vimeo.com/8607834)", "<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("![Video1](https://www.youtube.com/watch?v=mswPy5bt3TQ)\n\n![Video2](https://vimeo.com/8607834)", "<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 &ldquo;a <em>text&rdquo; with an emphasis</em></p>
// <p>hello &ldquo;there&rdquo;`
// 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 &ldquo;a <em>text&rdquo; with an emphasis</em></p>", "smartypants");
TestParser.TestSpec("hello ``there```\ntest", "<p>hello &ldquo;there&rdquo;`\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 &ndash; text</p>
// <p>This is &ldquo;a <em>text&rdquo; 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 &ndash; 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 &ldquo;a <em>text&rdquo; 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 &mdash; text</p>
// <p>This is a &ndash; 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 &mdash; text</p>", "smartypants");
TestParser.TestSpec("This is a -- text", "<p>This is a &ndash; 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 &mdash; 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 &mdash; 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&hellip;</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&hellip;</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>
// <li><input disabled="disabled" type="checkbox" /> Item1</li>
// <li><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
// <li><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>\n<li><input disabled=\"disabled\" type=\"checkbox\" /> Item1</li>\n<li><input disabled=\"disabled\" type=\"checkbox\" checked=\"checked\" /> Item2</li>\n<li><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");
}
}
}

View File

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

View 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>
<li><input disabled="disabled" type="checkbox" /> Item1</li>
<li><input disabled="disabled" type="checkbox" checked="checked" /> Item2</li>
<li><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>
````````````````````````````````

View 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());
}
}
}

View File

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

View File

@@ -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 MarkdownPipeline();
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 MarkdownPipeline();
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;
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(' ', '·');

View File

@@ -21,40 +21,53 @@ 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 MarkdownPipeline().UseAbbreviation());
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtras());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviations().Build());
//File.WriteAllText("test.html", result, Encoding.UTF8);
Console.WriteLine(result);
}
// Test for emoji and smileys
// var text = @" This is a test with a :) and a :angry: smiley";
[Test]
public void TestSamePipelineAllExtensions()
{
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
// Reuse the same pipeline
var result1 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
var result2 = Markdown.ToHtml("This is a \"\"citation\"\"", pipeline);
Assert.AreEqual("<p>This is a <cite>citation</cite></p>", result1.Trim());
Assert.AreEqual(result1, result2);
}
// Test for emoji and smileys
// var text = @" This is a test with a :) and a :angry: smiley";
// Test for definition lists:
//
// var text = @"
//Term 1
//: This is a definition item
// With a paragraph
// > This is a block quote
// Test for definition lists:
//
// var text = @"
//Term 1
//: This is a definition item
// With a paragraph
// > This is a block quote
// - This is a list
// - item2
// - This is a list
// - item2
// ```java
// Test
// ```java
// Test
// ```
// ```
// And a lazy line
//: This ia another definition item
// And a lazy line
//: This ia another definition item
//Term2
//Term3 *with some inline*
//: This is another definition for term2
//";
//Term2
//Term3 *with some inline*
//: This is another definition for term2
//";
// Test for grid table

View File

@@ -0,0 +1,735 @@
// 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![234](/56)", 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 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 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 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&nbsp;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);
}
}
}

View 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();
}
}
}

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

View 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();
}
}
}

View File

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

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

View 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();
}
}
}

View File

@@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View 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%" ]
}
}

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

View File

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

View File

@@ -12,18 +12,18 @@ namespace Markdig.Extensions.Abbreviations
/// <seealso cref="Markdig.IMarkdownExtension" />
public class AbbreviationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
pipeline.BlockParsers.AddIfNotAlready<AbbreviationParser>();
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
if (htmlRenderer != null)
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlAbbreviationRenderer>())
{
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlAbbreviationRenderer());
}
// Must be inserted before CodeBlockRenderer
htmlRenderer.ObjectRenderers.Insert(0, new HtmlAbbreviationRenderer());
}
}
}

View File

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

View File

@@ -41,7 +41,7 @@ namespace Markdig.Extensions.AutoIdentifiers
};
}
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
var headingBlockParser = pipeline.BlockParsers.Find<HeadingBlockParser>();
if (headingBlockParser != null)
@@ -50,7 +50,7 @@ namespace Markdig.Extensions.AutoIdentifiers
headingBlockParser.Closed -= HeadingBlockParser_Closed;
headingBlockParser.Closed += HeadingBlockParser_Closed;
}
var paragraphBlockParser = pipeline.BlockParsers.Find<ParagraphBlockParser>();
var paragraphBlockParser = pipeline.BlockParsers.FindExact<ParagraphBlockParser>();
if (paragraphBlockParser != null)
{
// Install a hook on the ParagraphBlockParser when a HeadingBlock is actually processed as a Setex heading
@@ -59,6 +59,10 @@ namespace Markdig.Extensions.AutoIdentifiers
}
}
public void Setup(IMarkdownRenderer renderer)
{
}
/// <summary>
/// Process on a new <see cref="HeadingBlock"/>
/// </summary>

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -14,13 +15,17 @@ namespace Markdig.Extensions.Bootstrap
/// <seealso cref="Markdig.IMarkdownExtension" />
public class BootstrapExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Make sure we don't have a delegate twice
pipeline.DocumentProcessed -= PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += PipelineOnDocumentProcessed;
}
public void Setup(IMarkdownRenderer renderer)
{
}
private static void PipelineOnDocumentProcessed(MarkdownDocument document)
{
foreach(var node in document.Descendants())

View File

@@ -7,36 +7,33 @@ 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(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
var parser = pipeline.InlineParsers.FindExact<EmphasisInlineParser>();
if (parser != null)
if (parser != null && !parser.HasEmphasisChar('"'))
{
foreach (var emphasis in parser.EmphasisDescriptors)
{
if (emphasis.Character == '"')
{
return;
}
}
parser.EmphasisDescriptors.Add(new EmphasisDescriptor('"', 2, 2, false));
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
// Extend the rendering here.
var emphasisRenderer = htmlRenderer.ObjectRenderers.FindExact<EmphasisInlineRenderer>();
var emphasisRenderer = renderer.ObjectRenderers.FindExact<EmphasisInlineRenderer>();
if (emphasisRenderer != null)
{
// TODO: Use an ordered list instead as we don't know if this specific GetTag has been already added
var previousTag = emphasisRenderer.GetTag;
emphasisRenderer.GetTag = inline => GetTag(inline) ?? previousTag(inline);
}

View File

@@ -13,7 +13,7 @@ namespace Markdig.Extensions.CustomContainers
/// <seealso cref="Markdig.IMarkdownExtension" />
public class CustomContainerExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<CustomContainerParser>())
{
@@ -23,7 +23,7 @@ namespace Markdig.Extensions.CustomContainers
// Plug the inline parser for CustomContainerInline
var inlineParser = pipeline.InlineParsers.Find<EmphasisInlineParser>();
if (inlineParser != null)
if (inlineParser != null && !inlineParser.HasEmphasisChar(':'))
{
inlineParser.EmphasisDescriptors.Add(new EmphasisDescriptor(':', 2, 2, true));
var previousCreateEmphasisInline = inlineParser.CreateEmphasisInline;
@@ -36,8 +36,11 @@ namespace Markdig.Extensions.CustomContainers
return previousCreateEmphasisInline?.Invoke(emphasisChar, strong);
};
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlCustomContainerRenderer>())
@@ -51,6 +54,7 @@ namespace Markdig.Extensions.CustomContainers
htmlRenderer.ObjectRenderers.Insert(0, new HtmlCustomContainerInlineRenderer());
}
}
}
}
}

View File

@@ -11,15 +11,18 @@ namespace Markdig.Extensions.DefinitionLists
/// <seealso cref="Markdig.IMarkdownExtension" />
public class DefinitionListExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<DefinitionListParser>())
{
// Insert the parser before any other parsers
pipeline.BlockParsers.Insert(0, new DefinitionListParser());
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlDefinitionListRenderer>())

View File

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

View File

@@ -2,6 +2,8 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
namespace Markdig.Extensions.Emoji
{
/// <summary>
@@ -10,7 +12,7 @@ namespace Markdig.Extensions.Emoji
/// <seealso cref="Markdig.IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())
{
@@ -18,5 +20,9 @@ namespace Markdig.Extensions.Emoji
pipeline.InlineParsers.Insert(0, new EmojiParser());
}
}
public void Setup(IMarkdownRenderer renderer)
{
}
}
}

View File

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

View File

@@ -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.
@@ -29,7 +29,7 @@ namespace Markdig.Extensions.EmphasisExtra
/// </summary>
public EmphasisExtraOptions Options { get; }
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
var parser = pipeline.InlineParsers.FindExact<EmphasisInlineParser>();
if (parser != null)
@@ -83,8 +83,11 @@ namespace Markdig.Extensions.EmphasisExtra
parser.EmphasisDescriptors.Add(new EmphasisDescriptor('=', 2, 2, true));
}
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
// Extend the rendering here.

View File

@@ -4,7 +4,7 @@
using System;
namespace Markdig.Extensions.EmphasisExtra
namespace Markdig.Extensions.EmphasisExtras
{
/// <summary>
/// Options for enabling support for extra emphasis.

View File

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

View File

@@ -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;
@@ -14,7 +13,7 @@ namespace Markdig.Extensions.Figures
/// <seealso cref="Markdig.IMarkdownExtension" />
public class FigureExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<FigureBlockParser>())
{
@@ -28,7 +27,11 @@ namespace Markdig.Extensions.Figures
pipeline.BlockParsers.Insert(0, new FigureBlockParser());
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
}
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
htmlRenderer.ObjectRenderers.AddIfNotAlready<HtmlFigureRenderer>();

View File

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

View File

@@ -13,7 +13,7 @@ namespace Markdig.Extensions.Footers
/// <seealso cref="Markdig.IMarkdownExtension" />
public class FooterExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<FooterBlockParser>())
{
@@ -27,8 +27,11 @@ namespace Markdig.Extensions.Footers
pipeline.BlockParsers.Insert(0, new FooterBlockParser());
}
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
htmlRenderer.ObjectRenderers.AddIfNotAlready(new HtmlFooterBlockRenderer());

View File

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

View File

@@ -11,15 +11,18 @@ namespace Markdig.Extensions.Footnotes
/// <seealso cref="Markdig.IMarkdownExtension" />
public class FootnoteExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<FootnoteParser>())
{
// Insert the parser before any other parsers
pipeline.BlockParsers.Insert(0, new FootnoteParser());
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
htmlRenderer.ObjectRenderers.AddIfNotAlready(new HtmlFootnoteGroupRenderer());

View File

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

View File

@@ -5,6 +5,7 @@
using System;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
@@ -18,7 +19,7 @@ namespace Markdig.Extensions.GenericAttributes
/// <seealso cref="Markdig.IMarkdownExtension" />
public class GenericAttributesExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<GenericAttributesParser>())
{
@@ -36,6 +37,10 @@ namespace Markdig.Extensions.GenericAttributes
}
}
public void Setup(IMarkdownRenderer renderer)
{
}
private bool TryProcessAttributesForHeading(BlockProcessor processor, ref StringSlice line, IBlock block)
{
// Try to find if there is any attributes { in the info string on the first line of a FencedCodeBlock
@@ -47,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;
}

View File

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

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
namespace Markdig.Extensions.Hardlines
{
@@ -12,7 +13,7 @@ namespace Markdig.Extensions.Hardlines
/// <seealso cref="Markdig.IMarkdownExtension" />
public class SoftlineBreakAsHardlineExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Simply modify the LineBreakInlineParser
// TODO: We might want more options (like pandoc)
@@ -22,5 +23,9 @@ namespace Markdig.Extensions.Hardlines
parser.EnableSoftAsHard = true;
}
}
public void Setup(IMarkdownRenderer renderer)
{
}
}
}

View File

@@ -3,8 +3,9 @@
// See the license.txt file in the project root for more information.
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.)
@@ -12,7 +13,7 @@ namespace Markdig.Extensions.ListExtra
/// <seealso cref="Markdig.IMarkdownExtension" />
public class ListExtraExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
var parser = pipeline.BlockParsers.Find<ListBlockParser>();
if (parser != null)
@@ -20,5 +21,9 @@ namespace Markdig.Extensions.ListExtra
parser.ItemParsers.AddIfNotAlready<ListExtraItemParser>();
}
}
public void Setup(IMarkdownRenderer renderer)
{
}
}
}

View File

@@ -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.`)

View File

@@ -13,7 +13,7 @@ namespace Markdig.Extensions.Mathematics
/// <seealso cref="Markdig.IMarkdownExtension" />
public class MathExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Adds the inline parser
if (!pipeline.InlineParsers.Contains<MathInlineParser>())
@@ -27,8 +27,11 @@ namespace Markdig.Extensions.Mathematics
// Insert before EmphasisInlineParser to take precedence
pipeline.BlockParsers.Insert(0, new MathBlockParser());
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlMathInlineRenderer>())

View File

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

View File

@@ -1,34 +1,39 @@
// 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();
}
public MediaOptions Options { get; }
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
}
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
var inlineRenderer = htmlRenderer.ObjectRenderers.FindExact<LinkInlineRenderer>();
@@ -45,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();

View File

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

View File

@@ -26,15 +26,18 @@ namespace Markdig.Extensions.SmartyPants
/// </summary>
public SmartyPantOptions Options { get; }
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<SmaryPantsInlineParser>())
{
// Insert the parser after the code span parser
pipeline.InlineParsers.InsertAfter<CodeInlineParser>(new SmaryPantsInlineParser());
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null)
{
if (!htmlRenderer.ObjectRenderers.Contains<HtmlSmartyPantRenderer>())

View File

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

View File

@@ -11,14 +11,17 @@ namespace Markdig.Extensions.Tables
/// <seealso cref="Markdig.IMarkdownExtension" />
public class GridTableExtension : IMarkdownExtension
{
public void Setup(MarkdownPipeline pipeline)
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<GridTableParser>())
{
pipeline.BlockParsers.Insert(0, new GridTableParser());
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlTableRenderer>())
{
htmlRenderer.ObjectRenderers.Add(new HtmlTableRenderer());

View File

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

View 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;
}
}
}
}

View File

@@ -26,14 +26,23 @@ namespace Markdig.Extensions.Tables
/// </summary>
public PipeTableOptions Options { get; }
public void Setup(MarkdownPipeline pipeline)
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));
}
}
var htmlRenderer = pipeline.Renderer as HtmlRenderer;
public void Setup(IMarkdownRenderer renderer)
{
var htmlRenderer = renderer as HtmlRenderer;
if (htmlRenderer != null && !htmlRenderer.ObjectRenderers.Contains<HtmlTableRenderer>())
{
htmlRenderer.ObjectRenderers.Add(new HtmlTableRenderer());

View File

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

View 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(']');
}
}
}
}

View 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; }
}
}

View 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>();
}
}
}
}

View File

@@ -0,0 +1,62 @@
// 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.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[] {'['};
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// A tasklist is either
// [ ]
// or [x] or [X]
if (!(processor.Block.Parent is ListItemBlock))
{
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;
return true;
}
}
}

View 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 &lt;= 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;
}
}
}

View File

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

View File

@@ -14,6 +14,14 @@ namespace Markdig.Helpers
/// <remarks>We use a typed list and don't use extension methods because it would pollute all list implemts and the top level namespace.</remarks>
public class OrderedList<T> : List<T>
{
public OrderedList()
{
}
public OrderedList(IEnumerable<T> collection) : base(collection)
{
}
public bool InsertBefore<TElement>(T element) where TElement : T
{
if (element == null) throw new ArgumentNullException(nameof(element));

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers;
namespace Markdig
{
/// <summary>
@@ -12,6 +15,12 @@ namespace Markdig
/// Setups this extension for the specified pipeline.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
void Setup(MarkdownPipeline pipeline);
void Setup(MarkdownPipelineBuilder pipeline);
/// <summary>
/// Setups this extension for the specified renderer.
/// </summary>
/// <param name="renderer">The renderer.</param>
void Setup(IMarkdownRenderer renderer);
}
}

View File

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

View File

@@ -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,63 +24,49 @@ 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.
/// </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>
/// <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 void 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 MarkdownPipeline();
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
// We override the renderer with our own writer
pipeline.Renderer = new HtmlRenderer(writer);
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer);
var document = Parse(reader, pipeline);
pipeline.Renderer.Render(document);
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
}
/// <summary>
/// Converts a Markdown string using a custom <see cref="IMarkdownRenderer"/> specified in the <see cref="MarkdownPipeline.Renderer"/>.
/// 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, 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));
pipeline = pipeline ?? new MarkdownPipeline();
if (pipeline.Renderer == null)
{
throw new InvalidOperationException("The property MarkdownPipeline.Renderer cannot be null");
}
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);
return pipeline.Renderer.Render(document);
var document = Parse(markdown, pipeline);
pipeline.Setup(renderer);
return renderer.Render(document);
}
/// <summary>
@@ -92,22 +78,22 @@ namespace Markdig
public static MarkdownDocument Parse(string markdown)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
return Parse(new StringReader(markdown), new MarkdownPipeline());
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));
pipeline = pipeline ?? new MarkdownPipeline();
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
return MarkdownParser.Parse(reader, pipeline);
return MarkdownParser.Parse(markdown, pipeline);
}
}
}

View File

@@ -1,24 +1,29 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using 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.SmartyPants;
using Markdig.Extensions.Tables;
using Markdig.Extensions.TaskLists;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
namespace Markdig
{
@@ -28,38 +33,59 @@ 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 MarkdownPipeline UseAllExtensions(this MarkdownPipeline 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 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 MarkdownPipeline UseCustomContainer(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseCustomContainers(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<CustomContainerExtension>();
return pipeline;
@@ -73,11 +99,11 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipeline UseMedia(this MarkdownPipeline 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;
}
@@ -90,7 +116,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipeline UseAutoIdentifier(this MarkdownPipeline pipeline, AutoIdentifierOptions options = AutoIdentifierOptions.Default)
public static MarkdownPipelineBuilder UseAutoIdentifiers(this MarkdownPipelineBuilder pipeline, AutoIdentifierOptions options = AutoIdentifierOptions.Default)
{
if (!pipeline.Extensions.Contains<AutoIdentifierExtension>())
{
@@ -107,7 +133,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipeline UseSmartyPants(this MarkdownPipeline pipeline, SmartyPantOptions options = null)
public static MarkdownPipelineBuilder UseSmartyPants(this MarkdownPipelineBuilder pipeline, SmartyPantOptions options = null)
{
if (!pipeline.Extensions.Contains<SmartyPantsExtension>())
{
@@ -121,7 +147,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseBootstrap(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseBootstrap(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<BootstrapExtension>();
return pipeline;
@@ -132,7 +158,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseMath(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseMathematics(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<MathExtension>();
return pipeline;
@@ -143,7 +169,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseFigure(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseFigures(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<FigureExtension>();
return pipeline;
@@ -154,7 +180,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseAbbreviation(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseAbbreviations(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<AbbreviationExtension>();
return pipeline;
@@ -165,7 +191,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseDefinitionList(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseDefinitionLists(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<DefinitionListExtension>();
return pipeline;
@@ -179,7 +205,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipeline UsePipeTable(this MarkdownPipeline pipeline, PipeTableOptions options = null)
public static MarkdownPipelineBuilder UsePipeTables(this MarkdownPipelineBuilder pipeline, PipeTableOptions options = null)
{
if (!pipeline.Extensions.Contains<PipeTableExtension>())
{
@@ -193,7 +219,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseGridTable(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseGridTables(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<GridTableExtension>();
return pipeline;
@@ -205,9 +231,9 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseCite(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseCitations(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<CiteExtension>();
pipeline.Extensions.AddIfNotAlready<CitationExtension>();
return pipeline;
}
@@ -216,7 +242,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseFooter(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseFooters(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<FooterExtension>();
return pipeline;
@@ -227,7 +253,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseFootnotes(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseFootnotes(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<FootnoteExtension>();
return pipeline;
@@ -238,7 +264,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseSoftlineBreakAsHardlineBreak(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseSoftlineBreakAsHardlineBreak(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<SoftlineBreakAsHardlineExtension>();
return pipeline;
@@ -252,7 +278,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipeline UseEmphasisExtra(this MarkdownPipeline pipeline, EmphasisExtraOptions options = EmphasisExtraOptions.Default)
public static MarkdownPipelineBuilder UseEmphasisExtras(this MarkdownPipelineBuilder pipeline, EmphasisExtraOptions options = EmphasisExtraOptions.Default)
{
if (!pipeline.Extensions.Contains<EmphasisExtraExtension>())
{
@@ -268,7 +294,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipeline UseListExtra(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseListExtras(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<ListExtraExtension>();
return pipeline;
@@ -279,7 +305,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseGenericAttributes(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseGenericAttributes(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<GenericAttributesExtension>();
return pipeline;
@@ -290,10 +316,118 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipeline UseEmojiAndSmiley(this MarkdownPipeline pipeline)
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<EmojiExtension>();
return pipeline;
}
/// <summary>
/// This will disable the HTML support in the markdown processor (for constraint/safe parsing).
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder DisableHtml(this MarkdownPipelineBuilder pipeline)
{
var parser = pipeline.BlockParsers.Find<HtmlBlockParser>();
if (parser != null)
{
pipeline.BlockParsers.Remove(parser);
}
var inlineParser = pipeline.InlineParsers.Find<AutolineInlineParser>();
if (inlineParser != null)
{
inlineParser.EnableHtmlParsing = false;
}
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;
}
}
}

View File

@@ -5,104 +5,54 @@ using System;
using System.IO;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
namespace Markdig
{
/// <summary>
/// This class allows to modify the pipeline to parse and render a Markdown document.
/// This class is the Markdown pipeline build from a <see cref="MarkdownPipelineBuilder"/>.
/// </summary>
public class MarkdownPipeline
{
// This class is immutable
/// <summary>
/// Initializes a new instance of the <see cref="MarkdownPipeline" /> class.
/// </summary>
public MarkdownPipeline()
internal MarkdownPipeline(OrderedList<IMarkdownExtension> extensions, BlockParserList blockParsers, InlineParserList inlineParsers, StringBuilderCache cache, TextWriter debugLog, ProcessDocumentDelegate documentProcessed)
{
if (blockParsers == null) throw new ArgumentNullException(nameof(blockParsers));
if (inlineParsers == null) throw new ArgumentNullException(nameof(inlineParsers));
// Add all default parsers
BlockParsers = new BlockParserList()
{
new ThematicBreakParser(),
new HeadingBlockParser(),
new QuoteBlockParser(),
new ListBlockParser(),
new HtmlBlockParser(),
new FencedCodeBlockParser(),
new IndentedCodeBlockParser(),
new ParagraphBlockParser(),
};
InlineParsers = new InlineParserList()
{
new HtmlEntityParser(),
new LinkInlineParser(),
new EscapeInlineParser(),
new EmphasisInlineParser(),
new CodeInlineParser(),
new AutolineInlineParser(),
new LineBreakInlineParser(),
};
Extensions = new OrderedList<IMarkdownExtension>();
Renderer = new HtmlRenderer(new StringWriter());
StringBuilderCache = new StringBuilderCache();
Extensions = extensions;
BlockParsers = blockParsers;
InlineParsers = inlineParsers;
StringBuilderCache = cache;
DebugLog = debugLog;
DocumentProcessed = documentProcessed;
}
/// <summary>
/// Gets the block parsers.
/// </summary>
public BlockParserList BlockParsers { get; private set; }
internal bool PreciseSourceLocation { get; set; }
/// <summary>
/// Gets the inline parsers.
/// </summary>
public InlineParserList InlineParsers { get; private set; }
internal OrderedList<IMarkdownExtension> Extensions { get; }
/// <summary>
/// Gets or sets the renderer.
/// </summary>
public IMarkdownRenderer Renderer { get; set; }
internal BlockParserList BlockParsers { get; }
/// <summary>
/// Gets the register extensions.
/// </summary>
public OrderedList<IMarkdownExtension> Extensions { get; }
internal InlineParserList InlineParsers { get; }
/// <summary>
/// Gets or sets the string builder cache used by the parsers.
/// </summary>
public StringBuilderCache StringBuilderCache { get; set; }
internal StringBuilderCache StringBuilderCache { get; }
/// <summary>
/// Gets or sets the debug log.
/// </summary>
public TextWriter DebugLog { get; set; }
// TODO: Move the log to a better place
internal TextWriter DebugLog { get; }
/// <summary>
/// Occurs when a document has been processed after the <see cref="MarkdownParser.Parse"/> method.
/// </summary>
public event ProcessDocumentDelegate DocumentProcessed;
internal ProcessDocumentDelegate DocumentProcessed;
internal ProcessDocumentDelegate GetDocumentProcessed => DocumentProcessed;
/// <summary>
/// Initializes this instance.
/// </summary>
/// <exception cref="System.InvalidOperationException">An extension cannot be null</exception>
public void Initialize()
internal void Setup(IMarkdownRenderer renderer)
{
// Allow extensions to modify existing BlockParsers, InlineParsers and Renderer
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
foreach (var extension in Extensions)
{
if (extension == null)
{
throw new InvalidOperationException("An extension cannot be null");
}
extension.Setup(this);
extension.Setup(renderer);
}
}
}

View File

@@ -0,0 +1,125 @@
// 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 Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
namespace Markdig
{
/// <summary>
/// This class allows to modify the pipeline to parse and render a Markdown document.
/// </summary>
/// <remarks>NOTE: A pipeline is not thread-safe.</remarks>
public class MarkdownPipelineBuilder
{
private MarkdownPipeline pipeline;
/// <summary>
/// Initializes a new instance of the <see cref="MarkdownPipeline" /> class.
/// </summary>
public MarkdownPipelineBuilder()
{
// Add all default parsers
BlockParsers = new BlockParserList()
{
new ThematicBreakParser(),
new HeadingBlockParser(),
new QuoteBlockParser(),
new ListBlockParser(),
new HtmlBlockParser(),
new FencedCodeBlockParser(),
new IndentedCodeBlockParser(),
new ParagraphBlockParser(),
};
InlineParsers = new InlineParserList()
{
new HtmlEntityParser(),
new LinkInlineParser(),
new EscapeInlineParser(),
new EmphasisInlineParser(),
new CodeInlineParser(),
new AutolineInlineParser(),
new LineBreakInlineParser(),
};
Extensions = new OrderedList<IMarkdownExtension>();
StringBuilderCache = new StringBuilderCache();
}
/// <summary>
/// Gets the block parsers.
/// </summary>
public BlockParserList BlockParsers { get; private set; }
/// <summary>
/// Gets the inline parsers.
/// </summary>
public InlineParserList InlineParsers { get; private set; }
/// <summary>
/// Gets the register extensions.
/// </summary>
public OrderedList<IMarkdownExtension> Extensions { get; }
/// <summary>
/// Gets or sets the string builder cache used by the parsers.
/// </summary>
public StringBuilderCache StringBuilderCache { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable precise source location (slower parsing but accurate position for block and inline elements)
/// </summary>
public bool PreciseSourceLocation { get; set; }
/// <summary>
/// Gets or sets the debug log.
/// </summary>
public TextWriter DebugLog { get; set; }
/// <summary>
/// Occurs when a document has been processed after the <see cref="MarkdownParser.Parse"/> method.
/// </summary>
public event ProcessDocumentDelegate DocumentProcessed;
internal ProcessDocumentDelegate GetDocumentProcessed => DocumentProcessed;
/// <summary>
/// Builds a pipeline from this instance. Once the pipeline is build, it cannot be modified.
/// </summary>
/// <exception cref="System.InvalidOperationException">An extension cannot be null</exception>
public MarkdownPipeline Build()
{
if (pipeline != null)
{
return pipeline;
}
// TODO: Review the whole initialization process for extensions
// - It does not prevent a user to modify the pipeline after it has been used
// - a pipeline is not thread safe.
// We should find a proper way to make the pipeline safely modifiable/freezable (PipelineBuilder -> Pipeline)
// Allow extensions to modify existing BlockParsers, InlineParsers and Renderer
foreach (var extension in Extensions)
{
if (extension == null)
{
throw new InvalidOperationException("An extension cannot be null");
}
extension.Setup(this);
}
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions),
new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog,
GetDocumentProcessed) {PreciseSourceLocation = PreciseSourceLocation};
return pipeline;
}
}
}

View File

@@ -1,6 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
namespace Markdig.Parsers
{
/// <summary>
@@ -9,5 +12,19 @@ namespace Markdig.Parsers
/// <seealso cref="Markdig.Parsers.ParserList{Markdig.Parsers.BlockParser, Markdig.Parsers.BlockParserState}" />
public class BlockParserList : ParserList<BlockParser, BlockProcessor>
{
/// <summary>
/// Initializes a new instance of the <see cref="BlockParserList"/> class.
/// </summary>
public BlockParserList()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BlockParserList"/> class.
/// </summary>
/// <param name="parsers">The parsers.</param>
public BlockParserList(IEnumerable<BlockParser> parsers) : base(parsers)
{
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,14 @@ namespace Markdig.Parsers
/// <seealso cref="Markdig.Parsers.ParserList{Markdig.Parsers.InlineParser, Markdig.Parsers.InlineParserState}" />
public class InlineParserList : ParserList<InlineParser, InlineProcessor>
{
public InlineParserList()
{
}
public InlineParserList(IEnumerable<InlineParser> parsers) : base(parsers)
{
}
/// <summary>
/// Gets the registered delimiter processors.
/// </summary>

View File

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

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace Markdig.Parsers.Inlines
@@ -18,18 +19,33 @@ namespace Markdig.Parsers.Inlines
public AutolineInlineParser()
{
OpeningCharacters = new[] {'<'};
EnableHtmlParsing = true;
}
/// <summary>
/// Gets or sets a value indicating whether to enable HTML parsing. Default is <c>true</c>
/// </summary>
public bool EnableHtmlParsing { get; set; }
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
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
else if (EnableHtmlParsing)
{
slice = saved;
string htmlTag;
@@ -38,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;

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using 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;
}

View File

@@ -41,6 +41,23 @@ namespace Markdig.Parsers.Inlines
/// </summary>
public List<EmphasisDescriptor> EmphasisDescriptors { get; }
/// <summary>
/// Determines whether this parser is using the specified character as an emphasis delimiter.
/// </summary>
/// <param name="c">The character to look for.</param>
/// <returns><c>true</c> if this parser is using the specified character as an emphasis delimiter; otherwise <c>false</c></returns>
public bool HasEmphasisChar(char c)
{
foreach (var emphasis in EmphasisDescriptors)
{
if (emphasis.Character == c)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets or sets the create emphasis inline delegate (allowing to create a different emphasis inline class)
/// </summary>
@@ -127,6 +144,8 @@ namespace Markdig.Parsers.Inlines
return false;
}
var startPosition = slice.Start;
int delimiterCount = 0;
char c;
do
@@ -160,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;
@@ -224,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
@@ -250,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--)
{
@@ -260,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);
@@ -310,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);
@@ -334,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);

View File

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

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using 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;

View File

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

View File

@@ -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 ? "![" : "[")
};

View File

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

View File

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

View File

@@ -26,22 +26,26 @@ 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
pipeline.Initialize();
var stringBuilderCache = pipeline.StringBuilderCache ?? new StringBuilderCache();
document = new MarkdownDocument();
@@ -54,36 +58,31 @@ 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
};
documentProcessed = pipeline.GetDocumentProcessed;
documentProcessed = pipeline.DocumentProcessed;
}
/// <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));
pipeline = pipeline ?? new MarkdownPipeline();
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>
@@ -102,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);
}
@@ -123,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>

View File

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

Some files were not shown because too many files have changed in this diff Show More