Compare commits

...

45 Commits

Author SHA1 Message Date
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
76 changed files with 1989 additions and 461 deletions

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

View File

@@ -1,10 +1,12 @@
# 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 implems on [babelmark3](http://babelmark.github.io/)
You can **try Markdig online** and compare it to other implementations on [babelmark3](http://babelmark.github.io/)
## Features
@@ -86,13 +88,16 @@ This is an early preview of the benchmarking against various implementations:
- [MarkdownSharp](https://github.com/Kiri-rin/markdownsharp): Open source C# implementation of Markdown processor, as featured on Stack Overflow, regexp based.
- [Moonshine](https://github.com/brandonc/moonshine): popular C Markdown processor
Markdig is roughly x100 times faster than MarkdownSharp and extremelly competitive to other implems (that are not feature wise comparable)
### Analysis of the results:
Performance in x86:
- Markdig is roughly **x100 times faster than MarkdownSharp**
- **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
### Performance in x86:
```
// * Summary *
BenchmarkDotNet-Dev=v0.9.6.0+
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, ProcessorCount=8
@@ -105,24 +110,17 @@ WarmupCount=2 TargetCount=10
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
--------------------- |---------- |---------- |------- |------ |------- |------------------- |
TestMarkdig | 5.4870 ms | 0.0158 ms | 193.00 | 12.00 | 84.00 | 1,425,192.72 |
TestCommonMarkCpp | 4.0134 ms | 0.1008 ms | - | - | 180.00 | 454,859.74 |
TestCommonMarkNet | 4.6139 ms | 0.0581 ms | 193.00 | 12.00 | 84.00 | 1,406,367.27 |
TestCommonMarkNetNew | 5.5327 ms | 0.0461 ms | 193.00 | 96.00 | 84.00 | 1,738,465.42 |
TestMarkdownDeep | 7.5910 ms | 0.1006 ms | 205.00 | 96.00 | 84.00 | 1,758,383.79 |
TestMoonshine | 5.8843 ms | 0.1758 ms | - | - | 215.00 | 565,000.73 |
// * Diagnostic Output - MemoryDiagnoser *
// ***** BenchmarkRunner: End *****
TestMarkdig | 5.4332 ms | 0.0809 ms | 96.00 | 36.00 | 84.00 | 1,218,643.59 |
TestCommonMarkCpp | 4.0150 ms | 0.1208 ms | - | - | 180.00 | 454,859.74 |
TestCommonMarkNet | 4.5448 ms | 0.0323 ms | 193.00 | 12.00 | 84.00 | 1,406,367.27 |
TestCommonMarkNetNew | 5.4811 ms | 0.0327 ms | 193.00 | 96.00 | 84.00 | 1,738,465.42 |
TestMarkdownDeep | 7.5881 ms | 0.0554 ms | 205.00 | 96.00 | 84.00 | 1,758,383.79 |
TestMoonshine | 5.8586 ms | 0.0764 ms | - | - | 215.00 | 565,000.72 |
```
Performance for x64:
### Performance for x64:
```
// * Summary *
BenchmarkDotNet-Dev=v0.9.6.0+
OS=Microsoft Windows NT 6.2.9200.0
Processor=Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz, ProcessorCount=8
@@ -135,15 +133,10 @@ WarmupCount=2 TargetCount=10
Method | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
--------------------- |---------- |---------- |------- |------- |------ |------------------- |
TestMarkdig | 5.9539 ms | 0.0495 ms | 157.00 | 96.00 | 84.00 | 1,767,834.52 |
TestCommonMarkNet | 4.3158 ms | 0.0161 ms | 157.00 | 96.00 | 84.00 | 1,747,432.06 |
TestCommonMarkNetNew | 5.3421 ms | 0.0435 ms | 229.00 | 168.00 | 84.00 | 2,323,922.97 |
TestMarkdownDeep | 7.4750 ms | 0.0281 ms | 318.00 | 186.00 | 84.00 | 2,576,728.69 |
// * Diagnostic Output - MemoryDiagnoser *
// ***** BenchmarkRunner: End *****
TestMarkdig | 5.5276 ms | 0.0402 ms | 109.00 | 96.00 | 84.00 | 1,537,027.66 |
TestCommonMarkNet | 4.4661 ms | 0.1190 ms | 157.00 | 96.00 | 84.00 | 1,747,432.06 |
TestCommonMarkNetNew | 5.3151 ms | 0.0815 ms | 229.00 | 168.00 | 84.00 | 2,323,922.97 |
TestMarkdownDeep | 7.4076 ms | 0.0617 ms | 318.00 | 186.00 | 84.00 | 2,576,728.69 |
```
## Credits
@@ -152,7 +145,9 @@ Thanks to the fantastic work done by [John Mac Farlane](http://johnmacfarlane.ne
This project would not have been possible without this huge foundation.
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/BenchmarkDotNet) that makes benchmarking so easy to setup!
Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Helpers/EntityHelper.cs)) have been re-used from [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
## Author

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

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:

View File

@@ -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:
@@ -17088,7 +17097,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>", "emphasisextra");
TestParser.TestSpec("The following text ~~is deleted~~", "<p>The following text <del>is deleted</del></p>", "emphasisextras");
}
}
// ## Superscript and Subscript
@@ -17110,7 +17119,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>", "emphasisextra");
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 +17141,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>", "emphasisextra");
TestParser.TestSpec("++Inserted text++", "<p><ins>Inserted text</ins></p>", "emphasisextras");
}
}
// ## Marked
@@ -17154,7 +17163,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>", "emphasisextra");
TestParser.TestSpec("==Marked text==", "<p><mark>Marked text</mark></p>", "emphasisextras");
}
}
// # Extensions
@@ -17991,7 +18000,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>", "listextra");
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 +18026,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>", "listextra");
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 +18050,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>", "listextra");
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 +18078,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>", "listextra");
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 +18108,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>", "listextra");
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 +18136,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>", "listextra");
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 +18160,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>", "listextra");
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 +18191,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 +18215,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 +18237,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 +18263,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 +18283,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 +18303,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 +18323,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 +18343,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 +18363,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 +18383,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 +18403,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 +18423,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 +18455,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 +18592,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

View File

@@ -42,18 +42,18 @@ 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"), "emphasisextra"),
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"), "listextra"),
new KeyValuePair<string, string>(Host.ResolvePath("FigureFooterAndCiteSpecs.md"), "figures+footers+cites"),
new KeyValuePair<string, string>(Host.ResolvePath("MathSpecs.md"), "math"),
new KeyValuePair<string, string>(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"),
};

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

@@ -42,20 +42,20 @@ namespace Markdig.Tests
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
.UseAbbreviation()
//.UseAutoIdentifier()
.UseCite()
.UseCustomContainer()
.UseDefinitionList()
.UseEmphasisExtra()
.UseFigure()
.UseFooter()
.UseAbbreviations()
//.UseAutoIdentifiers()
.UseCitations()
.UseCustomContainers()
.UseDefinitionLists()
.UseEmphasisExtras()
.UseFigures()
.UseFooters()
.UseFootnotes()
.UseGridTable()
.UseMath()
.UseMedia()
.UsePipeTable()
.UseListExtra()
.UseGridTables()
.UseMathematics()
.UseMediaLinks()
.UsePipeTables()
.UseListExtras()
.UseGenericAttributes().Build());
yield break;
@@ -69,7 +69,7 @@ namespace Markdig.Tests
}
}
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,8 +21,8 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
//> titi toto
//");
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtra());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviation().Build());
//var result = Markdown.ToHtml(text, new MarkdownPipeline().UseFootnotes().UseEmphasisExtras());
var result = Markdown.ToHtml(text, new MarkdownPipelineBuilder().UseAbbreviations().Build());
//File.WriteAllText("test.html", result, Encoding.UTF8);
Console.WriteLine(result);
}

View File

@@ -0,0 +1,643 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Text;
using Markdig.Helpers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
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 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-0
", "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 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.SourceStartPosition,2}-{val.SourceEndPosition}\n");
var attributes = val.TryGetAttributes();
if (attributes != null)
{
build.Append($"{"attributes",-12} ({attributes.Line,2},{attributes.Column,2}) {attributes.SourceStartPosition,2}-{attributes.SourceEndPosition}\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

@@ -31,6 +31,7 @@ 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 != '[')
{
@@ -55,7 +56,11 @@ namespace Markdig.Extensions.Abbreviations
var abbr = new Abbreviation(this)
{
Label = label,
Text = slice, Line = processor.LineIndex, Column = processor.Column
Text = slice,
SourceStartPosition = startPosition,
SourceEndPosition = slice.End,
Line = processor.LineIndex,
Column = processor.Column
};
if (!processor.Document.HasAbbreviations())
{
@@ -84,6 +89,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 +102,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 +124,31 @@ namespace Markdig.Extensions.Abbreviations
// If we don't have a container, create a new one
if (container == null)
{
container = new ContainerInline();
container = new ContainerInline()
{
SourceStartPosition = originalLiteral.SourceStartPosition,
SourceEndPosition = originalLiteral.SourceEndPosition,
Line = originalLiteral.Line,
Column = originalLiteral.Column,
};
}
var abbrInline = new AbbreviationInline(abbr);
int line;
int column;
var abbrInline = new AbbreviationInline(abbr)
{
SourceStartPosition = processor.GetSourcePosition(i, out line, out column),
Line = line,
Column = column
};
abbrInline.SourceEndPosition = abbrInline.SourceStartPosition + match.Length - 1;
// Append the previous literal
if (i > content.Start)
{
container.AppendChild(literal);
literal.SourceEndPosition = abbrInline.SourceStartPosition - 1;
// Truncate it before the abbreviation
literal.Content.End = i - 1;
}
@@ -143,7 +165,13 @@ namespace Markdig.Extensions.Abbreviations
}
// Process the remaining literal
literal = new LiteralInline();
literal = new LiteralInline()
{
SourceStartPosition = abbrInline.SourceEndPosition + 1,
SourceEndPosition = literal.SourceEndPosition,
Line = line,
Column = column + match.Length,
};
content.Start = indexAfterMatch;
literal.Content = content;
@@ -153,12 +181,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

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

View File

@@ -7,13 +7,13 @@ using Markdig.Renderers;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Cites
namespace Markdig.Extensions.Citations
{
/// <summary>
/// Extension for cite ""...""
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class CiteExtension : IMarkdownExtension
public class CitationExtension : IMarkdownExtension
{
public void Setup(MarkdownPipelineBuilder pipeline)
{

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,24 @@ namespace Markdig.Extensions.DefinitionLists
if (currentDefinitionList == null)
{
currentDefinitionList = new DefinitionList(this);
currentDefinitionList = new DefinitionList(this)
{
SourceStartPosition = paragraphBlock.SourceStartPosition,
SourceEndPosition = 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,
SourceStartPosition = startPosition,
SourceEndPosition = processor.Line.End,
OpeningCharacter = processor.CurrentChar,
};
currentDefinitionList.Add(definitionItem);
for (int i = 0; i < paragraphBlock.Lines.Count; i++)
{
@@ -80,14 +90,19 @@ namespace Markdig.Extensions.DefinitionLists
{
Column = paragraphBlock.Column,
Line = line.Line,
SourceStartPosition = paragraphBlock.SourceStartPosition,
SourceEndPosition = paragraphBlock.SourceEndPosition,
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.SourceEndPosition = processor.Line.End;
return BlockState.Continue;
}
@@ -100,11 +115,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 +135,8 @@ namespace Markdig.Extensions.DefinitionLists
{
definitionItem.RemoveAt(definitionItem.Count - 1);
}
list.SourceEndPosition = list.LastChild.SourceEndPosition;
return BlockState.None;
}
@@ -126,10 +145,12 @@ namespace Markdig.Extensions.DefinitionLists
processor.GoToColumn(column + 4);
}
var list = (DefinitionList) definitionItem.Parent;
processor.Close(definitionItem);
var nextDefinitionItem = new DefinitionItem(this)
{
SourceStartPosition = startPosition,
SourceEndPosition = processor.Line.End,
Line = processor.LineIndex,
Column = processor.Column,
OpeningCharacter = processor.CurrentChar,
};
@@ -161,6 +182,7 @@ namespace Markdig.Extensions.DefinitionLists
definitionItem.RemoveAt(definitionItem.Count - 1);
}
list.SourceEndPosition = list.LastChild.SourceEndPosition;
return BlockState.Break;
}
}

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,16 @@ 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)
{
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
Line = line,
Column = column,
Match = match
};
processor.Inline.SourceEndPosition = processor.Inline.SourceStartPosition + 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.

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,9 @@ namespace Markdig.Extensions.Figures
var figure = new Figure(this)
{
SourceStartPosition = startPosition,
SourceEndPosition = line.End,
Line = processor.LineIndex,
Column = processor.Column,
OpeningCharacter = matchChar,
OpeningCharacterCount = count
@@ -59,8 +64,15 @@ 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)
{
SourceStartPosition = line.Start,
SourceEndPosition = 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 +88,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 +105,20 @@ 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)
{
SourceStartPosition = line.Start,
SourceEndPosition = 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.SourceEndPosition = line.End;
// Don't keep the last line
return BlockState.BreakDiscard;
}
@@ -103,6 +126,8 @@ namespace Markdig.Extensions.Figures
// Reset the indentation to the column before the indent
processor.GoToColumn(processor.ColumnBeforeIndent);
figure.SourceEndPosition = 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;

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,14 @@ namespace Markdig.Extensions.Footers
{
processor.NextColumn();
}
processor.NewBlocks.Push(new FooterBlock(this) { OpeningCharacter = openingChar, Column = column});
processor.NewBlocks.Push(new FooterBlock(this)
{
SourceStartPosition = startPosition,
SourceEndPosition = processor.Line.End,
OpeningCharacter = openingChar,
Column = column,
Line = processor.LineIndex,
});
return BlockState.Continue;
}
@@ -60,19 +68,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.SourceEndPosition = processor.Line.End;
}
return BlockState.Continue;
return result;
}
}
}

View File

@@ -83,7 +83,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

@@ -52,11 +52,19 @@ namespace Markdig.Extensions.GenericAttributes
// Work on a copy
var copy = line;
copy.Start = indexOfAttributes;
var startOfAttributes = copy.Start;
HtmlAttributes attributes;
if (GenericAttributesParser.TryParse(ref copy, out attributes))
{
var htmlAttributes = block.GetAttributes();
attributes.CopyTo(htmlAttributes);
// Update position for HtmlAttributes
htmlAttributes.Line = processor.LineIndex;
htmlAttributes.Column = startOfAttributes - processor.CurrentLineStartPosition; // This is not accurate with tabs!
htmlAttributes.SourceStartPosition = startOfAttributes;
htmlAttributes.SourceEndPosition = 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.SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column);
currentHtmlAttributes.Line = line;
currentHtmlAttributes.Column = column;
currentHtmlAttributes.SourceEndPosition = currentHtmlAttributes.SourceStartPosition + 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

@@ -5,7 +5,7 @@
using Markdig.Parsers;
using Markdig.Renderers;
namespace Markdig.Extensions.ListExtra
namespace Markdig.Extensions.ListExtras
{
/// <summary>
/// Extension for adding new type of list items (a., A., i., I.)

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

@@ -38,6 +38,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 +65,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 +102,14 @@ namespace Markdig.Extensions.Mathematics
}
// Create a new MathInline
int line;
int column;
var inline = new MathInline()
{
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
SourceEndPosition = processor.GetSourcePosition(slice.End),
Line = line,
Column = column,
Delimiter = match,
DelimiterCount = openDollars,
Content = slice

View File

@@ -1,25 +1,26 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Renderers.Html.Inlines;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.Medias
namespace Markdig.Extensions.MediaLinks
{
/// <summary>
/// Extension for extending image Markdown links in case a video or an audio file is linked and output proper link.
/// </summary>
/// <seealso cref="Markdig.IMarkdownExtension" />
public class MediaExtension : IMarkdownExtension
public class MediaLinkExtension : IMarkdownExtension
{
public MediaExtension() : this(new MediaOptions())
public MediaLinkExtension() : this(new MediaOptions())
{
}
public MediaExtension(MediaOptions options)
public MediaLinkExtension(MediaOptions options)
{
Options = options ?? new MediaOptions();
}

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

@@ -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()
{
SourceStartPosition = processor.GetSourcePosition(startingPosition, out line, out column),
Line = line,
Column = column,
OpeningCharacter = openingChar,
Type = type
};
pant.SourceEndPosition = pant.SourceStartPosition + slice.Start - startingPosition - 1;
// We will check in a post-process step for balanaced open/close quotes
if (postProcess)
@@ -241,14 +249,20 @@ 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())
{
SourceStartPosition = toReplace.SourceStartPosition,
SourceEndPosition = toReplace.SourceEndPosition,
Line = toReplace.Line,
Column = toReplace.Column,
});
i--;
}
@@ -266,7 +280,13 @@ 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())
{
SourceStartPosition = quote.SourceStartPosition,
SourceEndPosition = quote.SourceEndPosition,
Line = quote.Line,
Column = quote.Column,
});
}
pants.Clear();

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

@@ -28,6 +28,10 @@ namespace Markdig.Extensions.Tables
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.BlockParsers.Contains<PipeTableBlockParser>())
{
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());
}
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
{
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(Options));

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,21 @@ namespace Markdig.Extensions.Tables
}
else
{
processor.Inline = new PiprTableDelimiterInline(this) { LocalLineIndex = processor.LocalLineIndex };
var deltaLine = processor.LocalLineIndex - tableState.LineIndex;
processor.Inline = new PiprTableDelimiterInline(this)
{
SourceStartPosition = position,
SourceEndPosition = 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 +200,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 +235,12 @@ namespace Markdig.Extensions.Tables
column = ((PiprTableDelimiterInline)column).FirstChild;
}
// TODO: This is not accurate for the table
table.SourceStartPosition = column.SourceStartPosition;
table.SourceEndPosition = column.SourceEndPosition;
table.Line = column.Line;
table.Column = column.Column;
int lastIndex = 0;
for (int i = 0; i < delimiters.Count; i++)
{
@@ -229,8 +250,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 +274,55 @@ 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.SourceStartPosition = item.SourceStartPosition;
isFirstItem = false;
}
cellContainer.SourceEndPosition = item.SourceEndPosition;
item = nextSibling;
}
var tableCell = new TableCell();
var tableParagraph = new ParagraphBlock() {Inline = columnContainer};
var tableParagraph = new ParagraphBlock()
{
SourceStartPosition = cellContainer.SourceStartPosition,
SourceEndPosition = cellContainer.SourceEndPosition,
Line = cellContainer.Line,
Column = cellContainer.Column,
Inline = cellContainer
};
var tableCell = new TableCell()
{
SourceStartPosition = cellContainer.SourceStartPosition,
SourceEndPosition = cellContainer.SourceEndPosition,
Line = cellContainer.Line,
Column = cellContainer.Column,
};
tableCell.Add(tableParagraph);
if (row == null)
{
row = new TableRow()
{
SourceStartPosition = cellContainer.SourceStartPosition,
SourceEndPosition = cellContainer.SourceEndPosition,
Line = cellContainer.Line,
Column = cellContainer.Column,
};
}
row.Add(tableCell);
cells.Add(tableCell);
@@ -291,6 +346,11 @@ namespace Markdig.Extensions.Tables
}
}
if (row != null)
{
table.Add(row);
}
TrimEnd(beforeDelimiter);
if (delimiter != null)

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

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

@@ -24,35 +24,21 @@ 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 MarkdownPipelineBuilder().Build();
@@ -60,7 +46,7 @@ namespace Markdig
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer);
var document = Parse(reader, pipeline);
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
}
@@ -68,17 +54,17 @@ namespace Markdig
/// <summary>
/// Converts a Markdown string using a custom <see cref="IMarkdownRenderer"/>.
/// </summary>
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
/// <param name="markdown">A Markdown text.</param>
/// <param name="renderer">The renderer to convert Markdown to.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
public static object Convert(TextReader reader, IMarkdownRenderer renderer, MarkdownPipeline pipeline = null)
/// <exception cref="System.ArgumentNullException">if markdown or writer variable are null</exception>
public static object Convert(string markdown, IMarkdownRenderer renderer, MarkdownPipeline pipeline = null)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
var document = Parse(reader, pipeline);
var document = Parse(markdown, pipeline);
pipeline.Setup(renderer);
return renderer.Render(document);
}
@@ -92,22 +78,22 @@ namespace Markdig
public static MarkdownDocument Parse(string markdown)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
return Parse(new StringReader(markdown));
return Parse(markdown, null);
}
/// <summary>
/// Parses the specified markdown into an AST <see cref="MarkdownDocument"/>
/// </summary>
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
/// <param name="markdown">The markdown text.</param>
/// <param name="pipeline">The pipeline used for the parsing.</param>
/// <returns>An AST Markdown document</returns>
/// <exception cref="System.ArgumentNullException">if reader variable is null</exception>
public static MarkdownDocument Parse(TextReader reader, MarkdownPipeline pipeline = null)
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static MarkdownDocument Parse(string markdown, MarkdownPipeline pipeline)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
return MarkdownParser.Parse(reader, pipeline);
return MarkdownParser.Parse(markdown, pipeline);
}
}
}

View File

@@ -6,19 +6,19 @@ 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.Parsers;
@@ -39,29 +39,40 @@ namespace Markdig
public static MarkdownPipelineBuilder UseAdvancedExtensions(this MarkdownPipelineBuilder pipeline)
{
return pipeline
.UseAbbreviation()
.UseAutoIdentifier()
.UseCite()
.UseCustomContainer()
.UseDefinitionList()
.UseEmphasisExtra()
.UseFigure()
.UseFooter()
.UseAbbreviations()
.UseAutoIdentifiers()
.UseCitations()
.UseCustomContainers()
.UseDefinitionLists()
.UseEmphasisExtras()
.UseFigures()
.UseFooters()
.UseFootnotes()
.UseGridTable()
.UseMath()
.UseMedia()
.UsePipeTable()
.UseListExtra()
.UseGridTables()
.UseMathematics()
.UseMediaLinks()
.UsePipeTables()
.UseListExtras()
.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 custom container extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseCustomContainer(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseCustomContainers(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<CustomContainerExtension>();
return pipeline;
@@ -75,11 +86,11 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipelineBuilder UseMedia(this MarkdownPipelineBuilder pipeline, MediaOptions options = null)
public static MarkdownPipelineBuilder UseMediaLinks(this MarkdownPipelineBuilder pipeline, MediaOptions options = null)
{
if (!pipeline.Extensions.Contains<MediaExtension>())
if (!pipeline.Extensions.Contains<MediaLinkExtension>())
{
pipeline.Extensions.Add(new MediaExtension(options));
pipeline.Extensions.Add(new MediaLinkExtension(options));
}
return pipeline;
}
@@ -92,7 +103,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipelineBuilder UseAutoIdentifier(this MarkdownPipelineBuilder pipeline, AutoIdentifierOptions options = AutoIdentifierOptions.Default)
public static MarkdownPipelineBuilder UseAutoIdentifiers(this MarkdownPipelineBuilder pipeline, AutoIdentifierOptions options = AutoIdentifierOptions.Default)
{
if (!pipeline.Extensions.Contains<AutoIdentifierExtension>())
{
@@ -134,7 +145,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseMath(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseMathematics(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<MathExtension>();
return pipeline;
@@ -145,7 +156,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseFigure(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseFigures(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<FigureExtension>();
return pipeline;
@@ -156,7 +167,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseAbbreviation(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseAbbreviations(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<AbbreviationExtension>();
return pipeline;
@@ -167,7 +178,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseDefinitionList(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseDefinitionLists(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<DefinitionListExtension>();
return pipeline;
@@ -181,7 +192,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipelineBuilder UsePipeTable(this MarkdownPipelineBuilder pipeline, PipeTableOptions options = null)
public static MarkdownPipelineBuilder UsePipeTables(this MarkdownPipelineBuilder pipeline, PipeTableOptions options = null)
{
if (!pipeline.Extensions.Contains<PipeTableExtension>())
{
@@ -195,7 +206,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseGridTable(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseGridTables(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<GridTableExtension>();
return pipeline;
@@ -207,9 +218,9 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseCite(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseCitations(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<CiteExtension>();
pipeline.Extensions.AddIfNotAlready<CitationExtension>();
return pipeline;
}
@@ -218,7 +229,7 @@ namespace Markdig
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseFooter(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseFooters(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<FooterExtension>();
return pipeline;
@@ -254,7 +265,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipelineBuilder UseEmphasisExtra(this MarkdownPipelineBuilder pipeline, EmphasisExtraOptions options = EmphasisExtraOptions.Default)
public static MarkdownPipelineBuilder UseEmphasisExtras(this MarkdownPipelineBuilder pipeline, EmphasisExtraOptions options = EmphasisExtraOptions.Default)
{
if (!pipeline.Extensions.Contains<EmphasisExtraExtension>())
{
@@ -270,7 +281,7 @@ namespace Markdig
/// <returns>
/// The modified pipeline
/// </returns>
public static MarkdownPipelineBuilder UseListExtra(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseListExtras(this MarkdownPipelineBuilder pipeline)
{
pipeline.Extensions.AddIfNotAlready<ListExtraExtension>();
return pipeline;
@@ -322,7 +333,7 @@ namespace Markdig
/// <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="UsePipeTable"/> and <see cref="UseGridTable"/></param>
/// <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)
@@ -340,13 +351,13 @@ namespace Markdig
pipeline.UseAdvancedExtensions();
break;
case "pipetables":
pipeline.UsePipeTable();
pipeline.UsePipeTables();
break;
case "emphasisextra":
pipeline.UseEmphasisExtra();
case "emphasisextras":
pipeline.UseEmphasisExtras();
break;
case "listextra":
pipeline.UseListExtra();
case "listextras":
pipeline.UseListExtras();
break;
case "hardlinebreak":
pipeline.UseSoftlineBreakAsHardlineBreak();
@@ -355,46 +366,46 @@ namespace Markdig
pipeline.UseFootnotes();
break;
case "footers":
pipeline.UseFooter();
pipeline.UseFooters();
break;
case "cites":
pipeline.UseCite();
case "citations":
pipeline.UseCitations();
break;
case "attributes":
pipeline.UseGenericAttributes();
break;
case "gridtables":
pipeline.UseGridTable();
pipeline.UseGridTables();
break;
case "abbreviations":
pipeline.UseAbbreviation();
pipeline.UseAbbreviations();
break;
case "emojis":
pipeline.UseEmojiAndSmiley();
break;
case "definitionlists":
pipeline.UseDefinitionList();
pipeline.UseDefinitionLists();
break;
case "customcontainers":
pipeline.UseCustomContainer();
pipeline.UseCustomContainers();
break;
case "figures":
pipeline.UseFigure();
pipeline.UseFigures();
break;
case "math":
pipeline.UseMath();
case "mathematics":
pipeline.UseMathematics();
break;
case "bootstrap":
pipeline.UseBootstrap();
break;
case "medias":
pipeline.UseMedia();
case "medialinks":
pipeline.UseMediaLinks();
break;
case "smartypants":
pipeline.UseSmartyPants();
break;
case "autoidentifiers":
pipeline.UseAutoIdentifier();
pipeline.UseAutoIdentifiers();
break;
default:
throw new ArgumentException($"unknown extension {extension}");

View File

@@ -31,6 +31,9 @@ namespace Markdig
DebugLog = debugLog;
DocumentProcessed = documentProcessed;
}
internal bool PreciseSourceLocation { get; set; }
internal OrderedList<IMarkdownExtension> Extensions { get; }
internal BlockParserList BlockParsers { get; }

View File

@@ -73,6 +73,11 @@ namespace Markdig
/// </summary>
public StringBuilderCache StringBuilderCache { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable precise source location (slower parsing but accurate position for block and inline elements)
/// </summary>
public bool PreciseSourceLocation { get; set; }
/// <summary>
/// Gets or sets the debug log.
/// </summary>
@@ -111,7 +116,9 @@ namespace Markdig
extension.Setup(this);
}
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions), new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog, GetDocumentProcessed);
pipeline = new MarkdownPipeline(new OrderedList<IMarkdownExtension>(Extensions),
new BlockParserList(BlockParsers), new InlineParserList(InlineParsers), StringBuilderCache, DebugLog,
GetDocumentProcessed) {PreciseSourceLocation = PreciseSourceLocation};
return pipeline;
}
}

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>
@@ -365,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);
@@ -546,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)
@@ -669,7 +676,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
@@ -722,7 +729,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.SourceStartPosition = startPosition;
fenced.SourceEndPosition = 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.SourceEndPosition = 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,
SourceStartPosition = 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.SourceEndPosition = 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,78 @@ 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.SourceEndPosition = endof - 1;
result = BlockState.Break;
}
break;
case HtmlBlockType.CData:
if (line.Search("]]>"))
if (line.Search("]]>", out endof))
{
return BlockState.Break;
htmlBlock.SourceEndPosition = endof - 1;
result = BlockState.Break;
}
break;
case HtmlBlockType.ProcessingInstruction:
if (line.Search("?>"))
if (line.Search("?>", out endof))
{
return BlockState.Break;
htmlBlock.SourceEndPosition = endof - 1;
result = BlockState.Break;
}
break;
case HtmlBlockType.DocumentType:
if (line.Search(">"))
if (line.Search(">", out endof))
{
return BlockState.Break;
htmlBlock.SourceEndPosition = 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.SourceEndPosition = 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.SourceEndPosition = 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,
SourceStartPosition = startPosition,
// By default, setup to the end of line
SourceEndPosition = startPosition + state.Line.End
});
return BlockState.Continue;
}

View File

@@ -18,10 +18,16 @@ 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,
SourceStartPosition = startPosition,
SourceEndPosition = processor.Line.End
});
}
return result;
}
@@ -41,6 +47,10 @@ namespace Markdig.Parsers
{
processor.GoToCodeIndent();
}
if (block != null)
{
block.SourceEndPosition = processor.Line.End;
}
return BlockState.Continue;
}

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,6 +111,48 @@ namespace Markdig.Parsers
/// </summary>
public LiteralInlineParser LiteralInlineParser { get; }
public int GetSourcePosition(int sliceOffset)
{
int column;
int lineIndex;
return GetSourcePosition(sliceOffset, 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>
@@ -124,8 +169,9 @@ 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();
@@ -133,13 +179,6 @@ namespace Markdig.Parsers
{
var c = text.CurrentChar;
// Update line index
if (text.Start >= lineOffsets[LocalLineIndex])
{
LineIndex++;
LocalLineIndex++;
}
var textSaved = text;
var parsers = Parsers.GetParsersForOpeningCharacter(c);
if (parsers != null)

View File

@@ -31,9 +31,19 @@ namespace Markdig.Parsers.Inlines
string link;
bool isEmail;
var saved = slice;
int line;
int column;
if (LinkHelper.TryParseAutolink(ref slice, out link, out isEmail))
{
processor.Inline = new AutolinkInline() {IsEmail = isEmail, Url = link};
processor.Inline = new AutolinkInline()
{
IsEmail = isEmail,
Url = link,
SourceStartPosition = processor.GetSourcePosition(saved.Start, out line, out column),
SourceEndPosition = processor.GetSourcePosition(slice.Start - 1),
Line = line,
Column = column
};
}
else if (EnableHtmlParsing)
{
@@ -44,7 +54,14 @@ namespace Markdig.Parsers.Inlines
return false;
}
processor.Inline = new HtmlInline() { Tag = htmlTag };
processor.Inline = new HtmlInline()
{
Tag = htmlTag,
SourceStartPosition = processor.GetSourcePosition(saved.Start, out line, out column),
SourceEndPosition = processor.GetSourcePosition(slice.Start - 1),
Line = line,
Column = column
};
}
return true;

View File

@@ -29,6 +29,8 @@ namespace Markdig.Parsers.Inlines
return false;
}
var startPosition = slice.Start;
// Match the opened sticks
var c = slice.CurrentChar;
while (c == match)
@@ -98,15 +100,18 @@ namespace Markdig.Parsers.Inlines
builder.Length--;
}
}
int line;
int column;
processor.Inline = new CodeInline()
{
Delimiter = match,
Content = builder.ToString()
Content = builder.ToString(),
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
SourceEndPosition = processor.GetSourcePosition(slice.Start - 1),
Line = line,
Column = column
};
isMatching = true;
processor.LocalLineIndex += newLinesFound;
processor.LineIndex += newLinesFound;
}
// Release the builder if not used

View File

@@ -144,6 +144,8 @@ namespace Markdig.Parsers.Inlines
return false;
}
var startPosition = slice.Start;
int delimiterCount = 0;
char c;
do
@@ -177,10 +179,16 @@ namespace Markdig.Parsers.Inlines
delimiterType |= DelimiterType.Close;
}
int line;
int column;
var delimiter = new EmphasisDelimiterInline(this, emphasisDesc)
{
DelimiterCount = delimiterCount,
Type = delimiterType,
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
SourceEndPosition = processor.GetSourcePosition(slice.Start - 1),
Column = column,
Line = line,
};
processor.Inline = delimiter;
@@ -241,6 +249,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.SourceStartPosition = openDelimiter.SourceStartPosition;
emphasis.Line = openDelimiter.Line;
emphasis.Column = openDelimiter.Column;
emphasis.SourceEndPosition = closeDelimiter.SourceEndPosition - closeDelimitercount + delimiterDelta;
openDelimiter.SourceStartPosition += delimiterDelta;
openDelimiter.Column += delimiterDelta;
closeDelimiter.SourceStartPosition += delimiterDelta;
closeDelimiter.Column += delimiterDelta;
openDelimiter.DelimiterCount -= delimiterDelta;
closeDelimiter.DelimiterCount -= delimiterDelta;
var embracer = (ContainerInline)openDelimiter;
// Go down to the first emphasis with a lower level
@@ -267,9 +293,6 @@ namespace Markdig.Parsers.Inlines
// Embrace all delimiters
embracer.EmbraceChildrenBy(emphasis);
openDelimiter.DelimiterCount -= isStrong ? 2 : 1;
closeDelimiter.DelimiterCount -= isStrong ? 2 : 1;
// Remove any intermediate emphasis
for (int k = i - 1; k >= openDelimiterIndex + 1; k--)
{
@@ -277,7 +300,11 @@ namespace Markdig.Parsers.Inlines
var literal = new LiteralInline()
{
Content = new StringSlice(literalDelimiter.ToLiteral()),
IsClosed = true
IsClosed = true,
SourceStartPosition = literalDelimiter.SourceStartPosition,
SourceEndPosition = literalDelimiter.SourceEndPosition,
Line = literalDelimiter.Line,
Column = literalDelimiter.Column
};
literalDelimiter.ReplaceBy(literal);
@@ -327,7 +354,11 @@ namespace Markdig.Parsers.Inlines
var literal = new LiteralInline()
{
Content = new StringSlice(closeDelimiter.ToLiteral()),
IsClosed = true
IsClosed = true,
SourceStartPosition = closeDelimiter.SourceStartPosition,
SourceEndPosition = closeDelimiter.SourceEndPosition,
Line = closeDelimiter.Line,
Column = closeDelimiter.Column
};
closeDelimiter.ReplaceBy(literal);
@@ -351,7 +382,11 @@ namespace Markdig.Parsers.Inlines
var literal = new LiteralInline()
{
Content = new StringSlice(delimiter.ToLiteral()),
IsClosed = true
IsClosed = true,
SourceStartPosition = delimiter.SourceStartPosition,
SourceEndPosition = delimiter.SourceEndPosition,
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)),
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
Line = line,
Column = column
};
processor.Inline.SourceEndPosition = processor.Inline.SourceStartPosition + 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,
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
Line = line,
Column = column
};
processor.Inline.SourceEndPosition = processor.Inline.SourceStartPosition + 1;
slice.NextChar();
return true;
}

View File

@@ -24,6 +24,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 +45,16 @@ 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),
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
SourceEndPosition = 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
{
SourceStartPosition = processor.GetSourcePosition(startPosition, out line, out column),
IsHard = EnableSoftAsHard || (slice.Start != 0 && hasDoubleSpacesBefore),
Line = line,
Column = column
};
processor.Inline.SourceEndPosition = processor.Inline.SourceStartPosition;
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 == '!')
{
@@ -63,7 +67,11 @@ namespace Markdig.Parsers.Inlines
{
Type = DelimiterType.Open,
Label = label,
IsImage = isImage
IsImage = isImage,
SourceStartPosition = startPosition,
SourceEndPosition = processor.GetSourcePosition(slice.Start - 1),
Line = line,
Column = column
};
return true;
@@ -86,7 +94,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, LinkDelimiterInline parent, int endPosition)
{
bool isValidLink = false;
LinkReferenceDefinition linkRef;
@@ -96,7 +104,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 +115,29 @@ namespace Markdig.Parsers.Inlines
{
Url = HtmlHelper.Unescape(linkRef.Url),
Title = HtmlHelper.Unescape(linkRef.Title),
IsImage = isImage,
IsImage = parent.IsImage,
SourceStartPosition = parent.SourceStartPosition,
SourceEndPosition = 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
SourceStartPosition = parent.SourceStartPosition,
SourceEndPosition = parent.SourceEndPosition,
Line = parent.Line,
Column = parent.Column,
};
containerLink.AppendChild(child);
}
@@ -175,7 +193,11 @@ namespace Markdig.Parsers.Inlines
{
inlineState.Inline = new LiteralInline()
{
Content = new StringSlice("[")
Content = new StringSlice("["),
SourceStartPosition = openParent.SourceStartPosition,
SourceEndPosition = openParent.SourceStartPosition,
Line = openParent.Line,
Column = openParent.Column,
};
openParent.ReplaceBy(inlineState.Inline);
return false;
@@ -200,6 +222,10 @@ namespace Markdig.Parsers.Inlines
Url = HtmlHelper.Unescape(url),
Title = HtmlHelper.Unescape(title),
IsImage = openParent.IsImage,
SourceStartPosition = openParent.SourceStartPosition,
SourceEndPosition = inlineState.GetSourcePosition(text.Start -1),
Line = openParent.Line,
Column = openParent.Column,
};
openParent.ReplaceBy(link);
@@ -242,8 +268,7 @@ namespace Markdig.Parsers.Inlines
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label))
{
if (ProcessLinkReference(inlineState, label, openParent.IsImage,
openParent.FirstChild))
if (ProcessLinkReference(inlineState, label, openParent, inlineState.GetSourcePosition(text.Start - 1)))
{
// Remove the open parent
openParent.Remove();

View File

@@ -33,6 +33,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 +63,16 @@ 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,
SourceStartPosition = startPosition,
SourceEndPosition = 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.SourceEndPosition = state.Line.End;
return BlockState.Continue;
}
@@ -170,6 +173,9 @@ namespace Markdig.Parsers
state.GoToColumn(columWidth);
}
// Update list-item source end position
listItem.SourceEndPosition = 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,9 @@ namespace Markdig.Parsers
var newListItem = new ListItemBlock(this)
{
Column = initColumn,
ColumnWidth = columnWidth
ColumnWidth = columnWidth,
SourceStartPosition = sourcePosition,
SourceEndPosition = sourceEndPosition
};
state.NewBlocks.Push(newListItem);
@@ -276,6 +286,8 @@ namespace Markdig.Parsers
var newList = new ListBlock(this)
{
Column = initColumn,
SourceStartPosition = sourcePosition,
SourceEndPosition = sourceEndPosition,
IsOrdered = isOrdered,
BulletType = listInfo.BulletType,
OrderedDelimiter = listInfo.OrderedDelimiter,
@@ -342,6 +354,12 @@ namespace Markdig.Parsers
isLastListItem = false;
}
// Update end-position for the list
if (listBlock.Count > 0)
{
listBlock.SourceEndPosition = listBlock[listBlock.Count - 1].SourceEndPosition;
}
return true;
}
}

View File

@@ -26,19 +26,24 @@ namespace Markdig.Parsers
private readonly InlineProcessor inlineProcessor;
private readonly MarkdownDocument document;
private readonly ProcessDocumentDelegate documentProcessed;
private readonly bool preciseSourceLocation;
private LineReader lineReader;
/// <summary>
/// Initializes a new instance of the <see cref="MarkdownParser" /> class.
/// </summary>
/// <param name="reader">The reader.</param>
/// <param name="text">The reader.</param>
/// <param name="pipeline">The pipeline.</param>
/// <exception cref="System.ArgumentNullException">
/// </exception>
private MarkdownParser(TextReader reader, MarkdownPipeline pipeline)
private MarkdownParser(string text, MarkdownPipeline pipeline)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (text == null) throw new ArgumentNullException(nameof(text));
if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
Reader = reader;
text = FixupZero(text);
lineReader = new LineReader(text);
preciseSourceLocation = pipeline.PreciseSourceLocation;
// Initialize the pipeline
var stringBuilderCache = pipeline.StringBuilderCache ?? new StringBuilderCache();
@@ -53,7 +58,7 @@ namespace Markdig.Parsers
// Initialize the inline parsers
var inlineParserList = new InlineParserList();
inlineParserList.AddRange(pipeline.InlineParsers);
inlineProcessor = new InlineProcessor(stringBuilderCache, document, inlineParserList)
inlineProcessor = new InlineProcessor(stringBuilderCache, document, inlineParserList, pipeline.PreciseSourceLocation)
{
DebugLog = pipeline.DebugLog
};
@@ -64,25 +69,20 @@ namespace Markdig.Parsers
/// <summary>
/// Parses the specified markdown into an AST <see cref="MarkdownDocument"/>
/// </summary>
/// <param name="reader">A Markdown text from a <see cref="TextReader"/>.</param>
/// <param name="text">A Markdown text</param>
/// <param name="pipeline">The pipeline used for the parsing.</param>
/// <returns>An AST Markdown document</returns>
/// <exception cref="System.ArgumentNullException">if reader variable is null</exception>
public static MarkdownDocument Parse(TextReader reader, MarkdownPipeline pipeline = null)
public static MarkdownDocument Parse(string text, MarkdownPipeline pipeline = null)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (text == null) throw new ArgumentNullException(nameof(text));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
// Perform the parsing
var markdownParser = new MarkdownParser(reader, pipeline);
var markdownParser = new MarkdownParser(text, pipeline);
return markdownParser.Parse();
}
/// <summary>
/// Gets the text reader used.
/// </summary>
private TextReader Reader { get; }
/// <summary>
/// Parses the current <see cref="Reader"/> into a Markdown <see cref="MarkdownDocument"/>.
/// </summary>
@@ -101,17 +101,15 @@ namespace Markdig.Parsers
{
while (true)
{
// TODO: A TextReader doesn't allow to precisely track position in file due to line endings
var lineText = Reader.ReadLine();
// Get the precise position of the begining of the line
var lineText = lineReader.ReadLine();
// If this is the end of file and the last line is empty
if (lineText == null)
{
break;
}
lineText = FixupZero(lineText);
blockProcessor.ProcessLine(new StringSlice(lineText));
blockProcessor.ProcessLine(lineText.Value);
}
blockProcessor.CloseAll(true);
}
@@ -122,29 +120,7 @@ namespace Markdig.Parsers
/// <param name="text">The text to secure.</param>
private string FixupZero(string text)
{
int startPos = 0;
int nextZero;
StringBuilder newLine = null;
while ((nextZero = text.IndexOf('\0', startPos)) >= 0)
{
if (newLine == null)
{
newLine = StringBuilderCache.Local();
}
newLine.Append(text, startPos, nextZero - startPos);
newLine.Append(CharHelper.ZeroSafeChar);
startPos = nextZero + 1;
}
if (newLine == null)
{
return text;
}
newLine.Append(text, startPos, text.Length - startPos);
var result = newLine.ToString();
newLine.Length = 0;
return result;
return text.Replace('\0', CharHelper.ZeroSafeChar);
}
private class ContainerItemCache : DefaultObjectCache<ContainerItem>

View File

@@ -20,7 +20,12 @@ 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,
SourceStartPosition = processor.Line.Start,
SourceEndPosition = processor.Line.End
});
return BlockState.Continue;
}
@@ -35,6 +40,8 @@ namespace Markdig.Parsers
{
return TryParseSetexHeading(processor, block);
}
block.SourceEndPosition = processor.Line.End;
return BlockState.Continue;
}
@@ -121,6 +128,8 @@ namespace Markdig.Parsers
var heading = new HeadingBlock(this)
{
Column = paragraph.Column,
SourceStartPosition = paragraph.SourceStartPosition,
SourceEndPosition = line.Start,
Level = level,
Lines = paragraph.Lines,
};
@@ -132,6 +141,8 @@ namespace Markdig.Parsers
return BlockState.BreakDiscard;
}
block.SourceEndPosition = state.Line.End;
return BlockState.Continue;
}

View File

@@ -28,6 +28,7 @@ namespace Markdig.Parsers
}
var column = processor.Column;
var sourcePosition = processor.Start;
// 5.1 Block quotes
// A block quote marker consists of 0-3 spaces of initial indent, plus (a) the character > together with a following space, or (b) a single character > not followed by a space.
@@ -37,7 +38,13 @@ namespace Markdig.Parsers
{
processor.NextColumn();
}
processor.NewBlocks.Push(new QuoteBlock(this) {QuoteChar = quoteChar, Column = column});
processor.NewBlocks.Push(new QuoteBlock(this)
{
QuoteChar = quoteChar,
Column = column,
SourceStartPosition = sourcePosition,
SourceEndPosition = processor.Line.End,
});
return BlockState.Continue;
}
@@ -55,6 +62,7 @@ namespace Markdig.Parsers
var c = processor.CurrentChar;
if (c != quote.QuoteChar)
{
block.SourceEndPosition = processor.Start - 1;
return processor.IsBlankLine ? BlockState.BreakDiscard : BlockState.None;
}
@@ -64,7 +72,18 @@ namespace Markdig.Parsers
processor.NextChar(); // Skip following space
}
block.SourceEndPosition = processor.Line.End;
return BlockState.Continue;
}
public override bool Close(BlockProcessor processor, Block block)
{
var quoteBlock = block as QuoteBlock;
if (quoteBlock?.LastChild != null)
{
quoteBlock.SourceEndPosition = quoteBlock.LastChild.SourceEndPosition;
}
return true;
}
}
}

View File

@@ -32,6 +32,8 @@ namespace Markdig.Parsers
return BlockState.None;
}
var startPosition = processor.Start;
var line = processor.Line;
// 4.1 Thematic breaks
@@ -83,7 +85,12 @@ namespace Markdig.Parsers
}
// Push a new block
processor.NewBlocks.Push(new ThematicBreakBlock(this) { Column = processor.Column });
processor.NewBlocks.Push(new ThematicBreakBlock(this)
{
Column = processor.Column,
SourceStartPosition = startPosition,
SourceEndPosition = line.End
});
return BlockState.BreakDiscard;
}
}

View File

@@ -25,6 +25,6 @@ namespace Markdig
{
public static partial class Markdown
{
public const string Version = "0.3.2";
public const string Version = "0.5.0";
}
}

View File

@@ -11,7 +11,7 @@ namespace Markdig.Renderers.Html
/// <summary>
/// Attached HTML attributes to a <see cref="MarkdownObject"/>.
/// </summary>
public class HtmlAttributes
public class HtmlAttributes : MarkdownObject
{
/// <summary>
/// Initializes a new instance of the <see cref="HtmlAttributes"/> class.

View File

@@ -1,26 +0,0 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Html.Inlines
{
/// <summary>
/// A HTML renderer for a <see cref="HardlineBreakInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Syntax.Inlines.HardlineBreakInline}" />
public class HardlineBreakInlineRenderer : HtmlObjectRenderer<HardlineBreakInline>
{
protected override void Write(HtmlRenderer renderer, HardlineBreakInline obj)
{
if (renderer.EnableHtmlForInline)
{
renderer.WriteLine("<br />");
}
else
{
renderer.Write(" ");
}
}
}
}

View File

@@ -6,21 +6,21 @@ using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Html.Inlines
{
/// <summary>
/// A HTML renderer for a <see cref="SoftlineBreakInline"/>.
/// A HTML renderer for a <see cref="LineBreakInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Syntax.Inlines.SoftlineBreakInline}" />
public class SoftlineBreakInlineRenderer : HtmlObjectRenderer<SoftlineBreakInline>
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{Markdig.Syntax.Inlines.LineBreakInline}" />
public class LineBreakInlineRenderer : HtmlObjectRenderer<LineBreakInline>
{
/// <summary>
/// Gets or sets a value indicating whether to render this softline break as a HTML hardline break tag (&lt;br /&gt;)
/// </summary>
public bool RenderAsHardlineBreak { get; set; }
protected override void Write(HtmlRenderer renderer, SoftlineBreakInline obj)
protected override void Write(HtmlRenderer renderer, LineBreakInline obj)
{
if (renderer.EnableHtmlForInline)
{
if (RenderAsHardlineBreak)
if (obj.IsHard || RenderAsHardlineBreak)
{
renderer.WriteLine("<br />");
}

View File

@@ -39,8 +39,7 @@ namespace Markdig.Renderers
ObjectRenderers.Add(new CodeInlineRenderer());
ObjectRenderers.Add(new DelimiterInlineRenderer());
ObjectRenderers.Add(new EmphasisInlineRenderer());
ObjectRenderers.Add(new HardlineBreakInlineRenderer());
ObjectRenderers.Add(new SoftlineBreakInlineRenderer());
ObjectRenderers.Add(new LineBreakInlineRenderer());
ObjectRenderers.Add(new HtmlInlineRenderer());
ObjectRenderers.Add(new HtmlEntityInlineRenderer());
ObjectRenderers.Add(new LinkInlineRenderer());

View File

@@ -23,16 +23,6 @@ namespace Markdig.Syntax
IsBreakable = true;
}
/// <summary>
/// Gets or sets the text column this instance was declared (zero-based).
/// </summary>
public int Column { get; set; }
/// <summary>
/// Gets or sets the text line this instance was declared (zero-based).
/// </summary>
public int Line { get; set; }
/// <summary>
/// Gets the parent of this container. May be null.
/// </summary>

View File

@@ -68,6 +68,11 @@ namespace Markdig.Syntax
}
children[Count++] = item;
item.Parent = this;
if (item.SourceEndPosition > SourceEndPosition)
{
SourceEndPosition = item.SourceEndPosition;
}
}
private void EnsureCapacity(int min)

View File

@@ -1,17 +0,0 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Diagnostics;
namespace Markdig.Syntax.Inlines
{
/// <summary>
/// A hard line break (Section 6.9 CommonMark specs).
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.LeafInline" />
[DebuggerDisplay("<br >/")]
public class HardlineBreakInline : LineBreakInline
{
}
}

View File

@@ -7,7 +7,8 @@ namespace Markdig.Syntax.Inlines
/// A base class for a line break.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.LeafInline" />
public abstract class LineBreakInline : LeafInline
public class LineBreakInline : LeafInline
{
public bool IsHard { get; set; }
}
}

View File

@@ -1,17 +0,0 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Diagnostics;
namespace Markdig.Syntax.Inlines
{
/// <summary>
/// A soft line break (Section 6.10 CommonMark specs)
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.LineBreakInline" />
[DebuggerDisplay("\\n")]
public class SoftlineBreakInline : LineBreakInline
{
}
}

View File

@@ -46,14 +46,15 @@ namespace Markdig.Syntax
/// <param name="slice">The slice.</param>
/// <param name="column">The column.</param>
/// <param name="line">The line.</param>
public void AppendLine(ref StringSlice slice, int column, int line)
/// <param name="sourceLinePosition"></param>
public void AppendLine(ref StringSlice slice, int column, int line, int sourceLinePosition)
{
if (Lines.Lines == null)
{
Lines = new StringLineGroup(4);
}
var stringLine = new StringLine(ref slice, line, column);
var stringLine = new StringLine(ref slice, line, column, sourceLinePosition);
// Regular case, we are not in the middle of a tab
if (slice.CurrentChar != '\t' || !CharHelper.IsAcrossTab(column))
{

View File

@@ -18,6 +18,33 @@ namespace Markdig.Syntax
private DataEntry[] attachedDatas;
private int count;
/// <summary>
/// Gets or sets the text column this instance was declared (zero-based).
/// </summary>
public int Column { get; set; }
/// <summary>
/// Gets or sets the text line this instance was declared (zero-based).
/// </summary>
public int Line { get; set; }
/// <summary>
/// Gets or sets the starting character position from the original text source.
/// Note that for inline elements, this is only valid if <see cref="MarkdownExtensions.UsePreciseSourceLocation"/> is setup on the pipeline.
/// </summary>
public int SourceStartPosition { get; set; }
/// <summary>
/// Gets or sets the ending character position from the original text source.
/// Note that for inline elements, this is only valid if <see cref="MarkdownExtensions.UsePreciseSourceLocation"/> is setup on the pipeline.
/// </summary>
public int SourceEndPosition { get; set; }
/// <summary>
/// Gets the character length of this element within the original source code.
/// </summary>
public int SourceLength => SourceEndPosition - SourceStartPosition + 1;
/// <summary>
/// Stores a key/value pair for this instance.
/// </summary>

View File

@@ -1,6 +1,6 @@
{
"title": "Markdig",
"version": "0.3.2",
"version": "0.5.0",
"authors": [ "Alexandre Mutel" ],
"description": "A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET",
"copyright": "Alexandre Mutel",
@@ -9,8 +9,9 @@
"owners": [ "Alexandre Mutel" ],
"licenseUrl": "https://github.com/lunet-io/markdig/blob/master/license.txt",
"projectUrl": "https://github.com/lunet-io/markdig",
"iconUrl": "https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png",
"requireLicenseAcceptance": false,
"releaseNotes": "Fix exception when Media extension was used with non absolute URI. Make the UseAdvancedExtensions() CommonMark compliant on core specs",
"releaseNotes": "Add support for precise source location pipelineBuilder.UsePreciseSourceLocation. Breaking change: Markdown.ToHtml() accept now only a string instead of a TextReader as this improve the overall performance.",
"tags": [ "Markdown CommonMark md html md2html" ]
},
"configurations": {