Compare commits

...

31 Commits

Author SHA1 Message Date
Alexandre Mutel
e1032e5094 Bump version to 0.7.5 2016-09-18 11:21:17 +02:00
Alexandre Mutel
a32ac298c5 Merge branch 'pr/n58_christophano'
# Conflicts:
#	src/Markdig/Parsers/InlineProcessor.cs
2016-09-18 11:12:10 +02:00
Alexandre Mutel
2e6ab670cb Keep UrlSpan (and LabelSpan and TitleSpan) for a LinkReferenceDefinition (issue #51) 2016-09-18 11:07:56 +02:00
Alexandre Mutel
72b7fce48c Fix extension DisableHtml not working properly with inline HTML (issue #46) 2016-09-18 10:59:12 +02:00
Alexandre Mutel
fa281f1ca1 Fix pipetable parsing (issue #44) 2016-09-18 10:44:26 +02:00
Chris Rodgers
1b92311aeb Fixes issue when using colon (among others) on lines with abbreviations 2016-09-15 14:18:58 +01:00
Alexandre Mutel
a593212f03 Merge pull request #45 from christophano/feature/rowspan
Adds support for rowspans in grid tables
2016-09-15 09:03:44 +02:00
Alexandre Mutel
c4ec928953 Merge pull request #57 from christophano/bugfix/punctuation-exceptions
Adds exceptions to allow certain punctuation characters to behave as inline delimiters
2016-09-15 09:00:57 +02:00
Chris Rodgers
e7df7fabeb Adds exceptions to allow certain punctuation characters to behave as inline delimiters 2016-09-14 12:00:31 +01:00
Alexandre Mutel
1c187f2d81 Merge pull request #56 from christophano/bugfix/similar-abbreviations
Fixes problem with multiple, similar abbreviations not parsing correctly
2016-09-14 07:42:26 +02:00
Alexandre Mutel
da66cf90c3 Merge pull request #55 from christophano/bugfix/restart-list-after-paragraph
Checks if paragraph block is closed when deciding if continuation list is allowable with character other than '1.'
2016-09-14 07:40:33 +02:00
Alexandre Mutel
0e8bd7407f Merge pull request #49 from christophano/bugfix/nested-definition-lists
Fixes issue where multiple definition lists are created when they are nested in a list item.
2016-09-14 07:39:24 +02:00
Chris Rodgers
d70f14addb Fixes problem with multiple, similar abbreviations not parsing correctly. 2016-09-13 16:53:25 +01:00
Chris Rodgers
e7b9eea3a5 Checks if paragraph block is closed when deciding if continuation list is allowable with character other than '1.'
Fixes #54
2016-09-09 14:09:52 +01:00
Alexandre Mutel
97af9d822d Merge pull request #50 from christophano/bugfix/ordered-list-start-attribute
Changes List Extra to output valid integer for start attribute, instead of roman or latin character.
2016-08-27 09:07:46 +02:00
Alexandre Mutel
cf7a09ab76 Merge pull request #52 from christophano/bugfix/merge-auto-generated-id
fixes issue where using special attributes overwites element id, even when id is not specified
2016-08-27 09:05:22 +02:00
Chris Rodgers
e755627421 fixes issue where using special attributes overwites element id, even when id is not specified 2016-08-25 09:11:50 +01:00
Chris Rodgers
f9be64a988 Changes List Extra to output valid integer for start attribute, instead of roman or latin character. 2016-08-23 13:58:06 +01:00
Alexandre Mutel
d65431e6cc Merge pull request #47 from christophano/bugfix/inline-maths-with-trailing-text
Fixes issue where trailing text after an inline math block is both included in the math block and appended after the math block.
2016-08-23 08:19:01 +02:00
Chris Rodgers
86fb962fdb Removes BlankLineBlock if it is found after an active definition list. 2016-08-22 15:41:39 +01:00
Chris Rodgers
d003837b27 Fixes issue where multiple definition lists are created when they are nested in a list item. 2016-08-22 14:47:43 +01:00
Chris Rodgers
fd813e3c5a Fixes issue where trailing text after an inline math block is both included in the math block and appended after the math block. 2016-08-19 12:15:17 +01:00
Chris Rodgers
a3691c4423 Fixed issue when malformed tables can result in an unhandled exception being thrown. 2016-08-12 15:57:35 +01:00
Chris Rodgers
9506f22025 Adds support for rowspans in grid tables 2016-08-12 15:57:28 +01:00
Alexandre Mutel
06ae907949 Bump version to 0.7.4 2016-07-30 11:35:36 +02:00
Alexandre Mutel
555523b2af Add additional tests for emphasis (issue #43) 2016-07-30 11:33:45 +02:00
Alexandre Mutel
3b9772f772 Merge branch 'pr/n43_christophano' 2016-07-30 11:20:48 +02:00
Chris Rodgers
891c80f48c Fixes issue where sentences containing strong emphasis words did not correctly delimit the strong tag 2016-07-28 12:04:49 +01:00
Alexandre Mutel
105b09e1ec Bump version to 0.7.3 2016-07-23 12:47:04 +02:00
Alexandre Mutel
9fe7596a23 Update project.json for WebApp to .NET Core RTM 2016-07-23 12:46:48 +02:00
Alexandre Mutel
82af7cadc5 Fix issue with MarkdownPipeline that was not threadsafe and initialization was deferred at parsing time instead of pipeline build time (issue #40) 2016-07-23 12:46:26 +02:00
53 changed files with 1503 additions and 679 deletions

View File

@@ -60,6 +60,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>Specs.tt</DependentUpon>
</Compile>
<Compile Include="Specs\TestEmphasisPlus.cs" />
<Compile Include="TestHtmlAttributes.cs" />
<Compile Include="TestHtmlHelper.cs" />
<Compile Include="TestLineReader.cs" />
@@ -87,6 +88,7 @@
<None Include="Specs\HardlineBreakSpecs.md" />
<None Include="Specs\BootstrapSpecs.md" />
<None Include="Specs\DiagramsSpecs.md" />
<None Include="Specs\NoHtmlSpecs.md" />
<None Include="Specs\TaskListSpecs.md" />
<None Include="Specs\SmartyPantsSpecs.md" />
<None Include="Specs\MediaSpecs.md" />

View File

@@ -45,3 +45,15 @@ This is a 😃 HTML document
.
<p>This is a <abbr title="Hypertext Markup Language">😃 HTML</abbr> document</p>
````````````````````````````````
Abbreviations may be similar:
```````````````````````````````` example
*[1A]: First
*[1A1]: Second
*[1A2]: Third
We can abbreviate 1A, 1A1 and 1A2!
.
<p>We can abbreviate <abbr title="First">1A</abbr>, <abbr title="Second">1A1</abbr> and <abbr title="Third">1A2</abbr>!</p>
````````````````````````````````

View File

@@ -105,3 +105,28 @@ Term 1
<pre><code>: Not valid
</code></pre>
````````````````````````````````
Definition lists can be nested inside list items
```````````````````````````````` example
1. First
2. Second
Term 1
: Definition
Term 2
: Second Definition
.
<ol>
<li><p>First</p></li>
<li><p>Second</p>
<dl>
<dt>Term 1</dt>
<dd>Definition</dd>
<dt>Term 2</dt>
<dd>Second Definition</dd>
</dl></li>
</ol>
````````````````````````````````

View File

@@ -21,6 +21,17 @@ H~2~O is a liquid. 2^10^ is 1024
.
<p>H<sub>2</sub>O is a liquid. 2<sup>10</sup> is 1024</p>
````````````````````````````````
Certain punctuation characters are exempted from the rule forbidding them within inline delimiters
```````````````````````````````` example
One quintillionth can be expressed as 10^-18^
Daggers^†^ and double-daggers^‡^ can be used to denote notes.
.
<p>One quintillionth can be expressed as 10<sup>-18</sup></p>
<p>Daggers<sup>†</sup> and double-daggers<sup>‡</sup> can be used to denote notes.</p>
````````````````````````````````
## Inserted

View File

@@ -184,3 +184,92 @@ Alignment might be specified on the first row using the character `:`:
</tbody>
</table>
````````````````````````````````
A grid table may have cells spanning both columns and rows:
```````````````````````````````` example
+---+---+---+
| AAAAA | B |
+---+---+ B +
| D | E | B |
+ D +---+---+
| D | CCCCC |
+---+---+---+
.
<table>
<col style="width:33.33%">
<col style="width:33.33%">
<col style="width:33.33%">
<tbody>
<tr>
<td colspan="2">AAAAA</td>
<td rowspan="2">B
B
B</td>
</tr>
<tr>
<td rowspan="2">D
D
D</td>
<td>E</td>
</tr>
<tr>
<td colspan="2">CCCCC</td>
</tr>
</tbody>
</table>
````````````````````````````````
A grid table may have cells with both colspan and rowspan:
```````````````````````````````` example
+---+---+---+
| AAAAA | B |
+ AAAAA +---+
| AAAAA | C |
+---+---+---+
| D | E | F |
+---+---+---+
.
<table>
<col style="width:33.33%">
<col style="width:33.33%">
<col style="width:33.33%">
<tbody>
<tr>
<td colspan="2" rowspan="2">AAAAA
AAAAA
AAAAA</td>
<td>B</td>
</tr>
<tr>
<td>C</td>
</tr>
<tr>
<td>D</td>
<td>E</td>
<td>F</td>
</tr>
</tbody>
</table>
````````````````````````````````
A grid table may not have irregularly shaped cells:
```````````````````````````````` example
+---+---+---+
| AAAAA | B |
+ A +---+ B +
| A | C | B |
+---+---+---+
| DDDDD | E |
+---+---+---+
.
<p>+---+---+---+
| AAAAA | B |
+ A +---+ B +
| A | C | B |
+---+---+---+
| DDDDD | E |
+---+---+---+</p>
````````````````````````````````

View File

@@ -38,7 +38,7 @@ Like for numbered list, a list can start with a different letter
b. First item
c. Second item
.
<ol type="a" start="b">
<ol type="a" start="2">
<li>First item</li>
<li>Second item</li>
</ol>
@@ -100,8 +100,26 @@ Like for numbered list, a list can start with a different letter
ii. First item
iii. Second item
.
<ol type="i" start="ii">
<ol type="i" start="2">
<li>First item</li>
<li>Second item</li>
</ol>
````````````````````````````````
Lists can be restarted, specifying the start point.
```````````````````````````````` example
1. First item
Some text
2. Second item
.
<ol>
<li>First item</li>
</ol>
<p>Some text</p>
<ol start="2">
<li>Second item</li>
</ol>
````````````````````````````````

View File

@@ -68,6 +68,13 @@ This is a $$$math block$$$
<p>This is a <span class="math">$math block$</span></p>
````````````````````````````````
Regular text can come both before and after the math inline
```````````````````````````````` example
This is a $math block$ with text on both sides.
.
<p>This is a <span class="math">math block</span> with text on both sides.</p>
````````````````````````````````
A mathematic block takes precedence over standard emphasis `*` `_`:
```````````````````````````````` example

View File

@@ -0,0 +1,27 @@
# Extensions
## NoHTML
The extension DisableHtml allows to disable the parsing of HTML:
For inline HTML:
```````````````````````````````` example
this is some text</td></tr>
.
<p>this is some text&lt;/td&gt;&lt;/tr&gt;</p>
````````````````````````````````
For Block HTML:
```````````````````````````````` example
<div>
this is some text
</div>
.
<p>&lt;div&gt;
this is some text
&lt;/div&gt;</p>
````````````````````````````````

View File

@@ -122,7 +122,7 @@ c no d
c no d</p>
````````````````````````````````
The number of columns in the first row determine the number of columns for the whole table. Any extra columns delimiter `|` for sub-sequent lines are converted to literal strings instead:
If a row contains more column than the header row, it will still be added as a column:
```````````````````````````````` example
a | b
@@ -141,7 +141,8 @@ a | b
<tbody>
<tr>
<td>0</td>
<td>1 | 2</td>
<td>1</td>
<td>2</td>
</tr>
<tr>
<td>3</td>
@@ -507,3 +508,42 @@ a | b
</table>
````````````````````````````````
** Tests **
Tests trailing spaces after pipes
```````````````````````````````` example
| abc | def |
|---|:---|
| cde| ddd|
| eee| fff|
| fff | fffff |
|gggg | ffff |
.
<table>
<thead>
<tr>
<th>abc</th>
<th>def</th>
</tr>
</thead>
<tbody>
<tr>
<td>cde</td>
<td>ddd</td>
</tr>
<tr>
<td>eee</td>
<td>fff</td>
</tr>
<tr>
<td>fff</td>
<td>fffff</td>
</tr>
<tr>
<td>gggg</td>
<td>ffff</td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -16515,7 +16515,7 @@ namespace Markdig.Tests
TestParser.TestSpec("a | b\nc no d", "<p>a | b\nc no d</p>", "pipetables|advanced");
}
}
// The number of columns in the first row determine the number of columns for the whole table. Any extra columns delimiter `|` for sub-sequent lines are converted to literal strings instead:
// If a row contains more column than the header row, it will still be added as a column:
[TestFixture]
public partial class TestExtensionsPipeTable
{
@@ -16543,7 +16543,8 @@ namespace Markdig.Tests
// <tbody>
// <tr>
// <td>0</td>
// <td>1 | 2</td>
// <td>1</td>
// <td>2</td>
// </tr>
// <tr>
// <td>3</td>
@@ -16556,7 +16557,7 @@ namespace Markdig.Tests
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 7, "Extensions Pipe Table");
TestParser.TestSpec("a | b \n-- | --\n0 | 1 | 2\n3 | 4\n5 |", "<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 | 2</td>\n</tr>\n<tr>\n<td>3</td>\n<td>4</td>\n</tr>\n<tr>\n<td>5</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
TestParser.TestSpec("a | b \n-- | --\n0 | 1 | 2\n3 | 4\n5 |", "<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<td>2</td>\n</tr>\n<tr>\n<td>3</td>\n<td>4</td>\n</tr>\n<tr>\n<td>5</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
}
// **Rule #2**
@@ -17078,6 +17079,58 @@ namespace Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 21, "Extensions Pipe Table");
TestParser.TestSpec("a | b\n-- | --\n[This is a link with a | inside the label](http://google.com) | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><a href=\"http://google.com\">This is a link with a | inside the label</a></td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
}
// ** Tests **
//
// Tests trailing spaces after pipes
[TestFixture]
public partial class TestExtensionsPipeTable
{
[Test]
public void Example022()
{
// Example 22
// Section: Extensions Pipe Table
//
// The following CommonMark:
// | abc | def |
// |---|:---|
// | cde| ddd|
// | eee| fff|
// | fff | fffff |
// |gggg | ffff |
//
// Should be rendered as:
// <table>
// <thead>
// <tr>
// <th>abc</th>
// <th>def</th>
// </tr>
// </thead>
// <tbody>
// <tr>
// <td>cde</td>
// <td>ddd</td>
// </tr>
// <tr>
// <td>eee</td>
// <td>fff</td>
// </tr>
// <tr>
// <td>fff</td>
// <td>fffff</td>
// </tr>
// <tr>
// <td>gggg</td>
// <td>ffff</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 22, "Extensions Pipe Table");
TestParser.TestSpec("| abc | def | \n|---|:---|\n| cde| ddd| \n| eee| fff|\n| fff | fffff | \n|gggg | ffff | ", "<table>\n<thead>\n<tr>\n<th>abc</th>\n<th>def</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>cde</td>\n<td>ddd</td>\n</tr>\n<tr>\n<td>eee</td>\n<td>fff</td>\n</tr>\n<tr>\n<td>fff</td>\n<td>fffff</td>\n</tr>\n<tr>\n<td>gggg</td>\n<td>ffff</td>\n</tr>\n</tbody>\n</table>", "pipetables|advanced");
}
}
// # Extensions
//
@@ -17357,6 +17410,29 @@ namespace Markdig.Tests
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>", "emphasisextras|advanced");
}
}
// Certain punctuation characters are exempted from the rule forbidding them within inline delimiters
[TestFixture]
public partial class TestExtensionsSuperscriptandSubscript
{
[Test]
public void Example003()
{
// Example 3
// Section: Extensions Superscript and Subscript
//
// The following CommonMark:
// One quintillionth can be expressed as 10^-18^
//
// Daggers^†^ and double-daggers^‡^ can be used to denote notes.
//
// Should be rendered as:
// <p>One quintillionth can be expressed as 10<sup>-18</sup></p>
// <p>Daggers<sup>†</sup> and double-daggers<sup>‡</sup> can be used to denote notes.</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Superscript and Subscript");
TestParser.TestSpec("One quintillionth can be expressed as 10^-18^\n\nDaggers^†^ and double-daggers^‡^ can be used to denote notes.", "<p>One quintillionth can be expressed as 10<sup>-18</sup></p>\n<p>Daggers<sup>†</sup> and double-daggers<sup>‡</sup> can be used to denote notes.</p>", "emphasisextras|advanced");
}
}
// ## Inserted
//
@@ -17365,9 +17441,9 @@ namespace Markdig.Tests
public partial class TestExtensionsInserted
{
[Test]
public void Example003()
public void Example004()
{
// Example 3
// Example 4
// Section: Extensions Inserted
//
// The following CommonMark:
@@ -17376,7 +17452,7 @@ namespace Markdig.Tests
// Should be rendered as:
// <p><ins>Inserted text</ins></p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Inserted");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Inserted");
TestParser.TestSpec("++Inserted text++", "<p><ins>Inserted text</ins></p>", "emphasisextras|advanced");
}
}
@@ -17387,9 +17463,9 @@ namespace Markdig.Tests
public partial class TestExtensionsMarked
{
[Test]
public void Example004()
public void Example005()
{
// Example 4
// Example 5
// Section: Extensions Marked
//
// The following CommonMark:
@@ -17398,7 +17474,7 @@ namespace Markdig.Tests
// Should be rendered as:
// <p><mark>Marked text</mark></p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Marked");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Marked");
TestParser.TestSpec("==Marked text==", "<p><mark>Marked text</mark></p>", "emphasisextras|advanced");
}
}
@@ -17698,6 +17774,131 @@ namespace Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 7, "Extensions Grid Table");
TestParser.TestSpec("+-----+:---:+-----+\n| A | B | C |\n+-----+-----+-----+", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td>A</td>\n<td style=\"text-align: center;\">B</td>\n<td>C</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
}
// A grid table may have cells spanning both columns and rows:
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example008()
{
// Example 8
// Section: Extensions Grid Table
//
// The following CommonMark:
// +---+---+---+
// | AAAAA | B |
// +---+---+ B +
// | D | E | B |
// + D +---+---+
// | D | CCCCC |
// +---+---+---+
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <tbody>
// <tr>
// <td colspan="2">AAAAA</td>
// <td rowspan="2">B
// B
// B</td>
// </tr>
// <tr>
// <td rowspan="2">D
// D
// D</td>
// <td>E</td>
// </tr>
// <tr>
// <td colspan="2">CCCCC</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 8, "Extensions Grid Table");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+---+---+ B +\n| D | E | B |\n+ D +---+---+\n| D | CCCCC |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td colspan=\"2\">AAAAA</td>\n<td rowspan=\"2\">B\nB\nB</td>\n</tr>\n<tr>\n<td rowspan=\"2\">D\nD\nD</td>\n<td>E</td>\n</tr>\n<tr>\n<td colspan=\"2\">CCCCC</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
}
// A grid table may have cells with both colspan and rowspan:
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example009()
{
// Example 9
// Section: Extensions Grid Table
//
// The following CommonMark:
// +---+---+---+
// | AAAAA | B |
// + AAAAA +---+
// | AAAAA | C |
// +---+---+---+
// | D | E | F |
// +---+---+---+
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <tbody>
// <tr>
// <td colspan="2" rowspan="2">AAAAA
// AAAAA
// AAAAA</td>
// <td>B</td>
// </tr>
// <tr>
// <td>C</td>
// </tr>
// <tr>
// <td>D</td>
// <td>E</td>
// <td>F</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 9, "Extensions Grid Table");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+ AAAAA +---+\n| AAAAA | C |\n+---+---+---+\n| D | E | F |\n+---+---+---+", "<table>\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<col style=\"width:33.33%\">\n<tbody>\n<tr>\n<td colspan=\"2\" rowspan=\"2\">AAAAA\nAAAAA\nAAAAA</td>\n<td>B</td>\n</tr>\n<tr>\n<td>C</td>\n</tr>\n<tr>\n<td>D</td>\n<td>E</td>\n<td>F</td>\n</tr>\n</tbody>\n</table>", "gridtables|advanced");
}
}
// A grid table may not have irregularly shaped cells:
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example010()
{
// Example 10
// Section: Extensions Grid Table
//
// The following CommonMark:
// +---+---+---+
// | AAAAA | B |
// + A +---+ B +
// | A | C | B |
// +---+---+---+
// | DDDDD | E |
// +---+---+---+
//
// Should be rendered as:
// <p>+---+---+---+
// | AAAAA | B |
// + A +---+ B +
// | A | C | B |
// +---+---+---+
// | DDDDD | E |
// +---+---+---+</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Grid Table");
TestParser.TestSpec("+---+---+---+\n| AAAAA | B |\n+ A +---+ B +\n| A | C | B |\n+---+---+---+\n| DDDDD | E |\n+---+---+---+", "<p>+---+---+---+\n| AAAAA | B |\n+ A +---+ B +\n| A | C | B |\n+---+---+---+\n| DDDDD | E |\n+---+---+---+</p>", "gridtables|advanced");
}
}
// # Extensions
//
@@ -18084,6 +18285,43 @@ namespace Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Definition lists");
TestParser.TestSpec("Term 1\n\n : Not valid", "<p>Term 1</p>\n<pre><code>: Not valid\n</code></pre>", "definitionlists+attributes|advanced");
}
}
// Definition lists can be nested inside list items
[TestFixture]
public partial class TestExtensionsDefinitionlists
{
[Test]
public void Example006()
{
// Example 6
// Section: Extensions Definition lists
//
// The following CommonMark:
// 1. First
//
// 2. Second
//
// Term 1
// : Definition
//
// Term 2
// : Second Definition
//
// Should be rendered as:
// <ol>
// <li><p>First</p></li>
// <li><p>Second</p>
// <dl>
// <dt>Term 1</dt>
// <dd>Definition</dd>
// <dt>Term 2</dt>
// <dd>Second Definition</dd>
// </dl></li>
// </ol>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Definition lists");
TestParser.TestSpec("1. First\n \n2. Second\n \n Term 1\n : Definition\n \n Term 2\n : Second Definition", "<ol>\n<li><p>First</p></li>\n<li><p>Second</p>\n<dl>\n<dt>Term 1</dt>\n<dd>Definition</dd>\n<dt>Term 2</dt>\n<dd>Second Definition</dd>\n</dl></li>\n</ol>", "definitionlists+attributes|advanced");
}
}
// # Extensions
//
@@ -18226,6 +18464,30 @@ namespace Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Abbreviation");
TestParser.TestSpec("*[😃 HTML]: Hypertext Markup Language\n\nThis is a 😃 HTML document ", "<p>This is a <abbr title=\"Hypertext Markup Language\">😃 HTML</abbr> document</p>", "abbreviations|advanced");
}
}
// Abbreviations may be similar:
[TestFixture]
public partial class TestExtensionsAbbreviation
{
[Test]
public void Example005()
{
// Example 5
// Section: Extensions Abbreviation
//
// The following CommonMark:
// *[1A]: First
// *[1A1]: Second
// *[1A2]: Third
//
// We can abbreviate 1A, 1A1 and 1A2!
//
// Should be rendered as:
// <p>We can abbreviate <abbr title="First">1A</abbr>, <abbr title="Second">1A1</abbr> and <abbr title="Third">1A2</abbr>!</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Abbreviation");
TestParser.TestSpec("*[1A]: First\n*[1A1]: Second\n*[1A2]: Third\n\nWe can abbreviate 1A, 1A1 and 1A2!", "<p>We can abbreviate <abbr title=\"First\">1A</abbr>, <abbr title=\"Second\">1A1</abbr> and <abbr title=\"Third\">1A2</abbr>!</p>", "abbreviations|advanced");
}
}
// # Extensions
//
@@ -18300,13 +18562,13 @@ namespace Markdig.Tests
// c. Second item
//
// Should be rendered as:
// <ol type="a" start="b">
// <ol type="a" start="2">
// <li>First item</li>
// <li>Second item</li>
// </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>", "listextras|advanced");
TestParser.TestSpec("b. First item\nc. Second item", "<ol type=\"a\" start=\"2\">\n<li>First item</li>\n<li>Second item</li>\n</ol>", "listextras|advanced");
}
}
// A different type of list will break the existing list:
@@ -18410,13 +18672,43 @@ namespace Markdig.Tests
// iii. Second item
//
// Should be rendered as:
// <ol type="i" start="ii">
// <ol type="i" start="2">
// <li>First item</li>
// <li>Second item</li>
// </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>", "listextras|advanced");
TestParser.TestSpec("ii. First item\niii. Second item", "<ol type=\"i\" start=\"2\">\n<li>First item</li>\n<li>Second item</li>\n</ol>", "listextras|advanced");
}
}
// Lists can be restarted, specifying the start point.
[TestFixture]
public partial class TestExtensionsOrderedlistwithromanletter
{
[Test]
public void Example008()
{
// Example 8
// Section: Extensions Ordered list with roman letter
//
// The following CommonMark:
// 1. First item
//
// Some text
//
// 2. Second item
//
// Should be rendered as:
// <ol>
// <li>First item</li>
// </ol>
// <p>Some text</p>
// <ol start="2">
// <li>Second item</li>
// </ol>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 8, "Extensions Ordered list with roman letter");
TestParser.TestSpec("1. First item\n\nSome text\n\n2. Second item", "<ol>\n<li>First item</li>\n</ol>\n<p>Some text</p>\n<ol start=\"2\">\n<li>Second item</li>\n</ol>", "listextras|advanced");
}
}
// # Extensions
@@ -18662,7 +18954,7 @@ namespace Markdig.Tests
TestParser.TestSpec("This is a $$$math block$$$", "<p>This is a <span class=\"math\">$math block$</span></p>", "mathematics|advanced");
}
}
// A mathematic block takes precedence over standard emphasis `*` `_`:
// Regular text can come both before and after the math inline
[TestFixture]
public partial class TestExtensionsMathInline
{
@@ -18673,12 +18965,32 @@ namespace Markdig.Tests
// Section: Extensions Math Inline
//
// The following CommonMark:
// This is a $math block$ with text on both sides.
//
// Should be rendered as:
// <p>This is a <span class="math">math block</span> with text on both sides.</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 9, "Extensions Math Inline");
TestParser.TestSpec("This is a $math block$ with text on both sides.", "<p>This is a <span class=\"math\">math block</span> with text on both sides.</p>", "mathematics|advanced");
}
}
// A mathematic block takes precedence over standard emphasis `*` `_`:
[TestFixture]
public partial class TestExtensionsMathInline
{
[Test]
public void Example010()
{
// Example 10
// Section: Extensions Math Inline
//
// The following CommonMark:
// This is *a $math* block$
//
// Should be rendered as:
// <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");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Math Inline");
TestParser.TestSpec("This is *a $math* block$", "<p>This is *a <span class=\"math\">math* block</span></p>", "mathematics|advanced");
}
}
@@ -18690,9 +19002,9 @@ namespace Markdig.Tests
public partial class TestExtensionsMathBlock
{
[Test]
public void Example010()
public void Example011()
{
// Example 10
// Example 11
// Section: Extensions Math Block
//
// The following CommonMark:
@@ -18710,7 +19022,7 @@ namespace Markdig.Tests
// \end{equation}
// </div>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 10, "Extensions Math Block");
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 11, "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>", "mathematics|advanced");
}
}
@@ -19533,4 +19845,54 @@ namespace Markdig.Tests
}
}
// TODO: Add other text diagram languages
// # Extensions
//
// ## NoHTML
//
// The extension DisableHtml allows to disable the parsing of HTML:
//
// For inline HTML:
[TestFixture]
public partial class TestExtensionsNoHTML
{
[Test]
public void Example001()
{
// Example 1
// Section: Extensions NoHTML
//
// The following CommonMark:
// this is some text</td></tr>
//
// Should be rendered as:
// <p>this is some text&lt;/td&gt;&lt;/tr&gt;</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions NoHTML");
TestParser.TestSpec("this is some text</td></tr>", "<p>this is some text&lt;/td&gt;&lt;/tr&gt;</p>", "nohtml");
}
}
// For Block HTML:
[TestFixture]
public partial class TestExtensionsNoHTML
{
[Test]
public void Example002()
{
// Example 2
// Section: Extensions NoHTML
//
// The following CommonMark:
// <div>
// this is some text
// </div>
//
// Should be rendered as:
// <p>&lt;div&gt;
// this is some text
// &lt;/div&gt;</p>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions NoHTML");
TestParser.TestSpec("<div>\nthis is some text\n</div>", "<p>&lt;div&gt;\nthis is some text\n&lt;/div&gt;</p>", "nohtml");
}
}
}

View File

@@ -59,6 +59,7 @@ SOFTWARE.
new KeyValuePair<string, string>(Host.ResolvePath("AutoIdentifierSpecs.md"), "autoidentifiers|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("TaskListSpecs.md"), "tasklists|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("DiagramsSpecs.md"), "diagrams|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("NoHtmlSpecs.md"), "nohtml"),
};
var emptyLines = false;
var displayEmptyLines = false;

View File

@@ -0,0 +1,25 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public partial class TestEmphasisPlus
{
[Test]
public void StrongNormal()
{
TestParser.TestSpec("***Strong emphasis*** normal", "<p><strong><em>Strong emphasis</em></strong> normal</p>", "");
}
[Test]
public void NormalStrongNormal()
{
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <strong><em>Strong emphasis</em></strong> normal</p>", "");
}
}
}

View File

@@ -65,6 +65,7 @@ namespace Markdig.Tests
foreach (var extensionsText in extensionGroups)
{
var builder = new MarkdownPipelineBuilder();
builder.DebugLog = Console.Out;
var pipeline = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, pipeline.Build());
}

View File

@@ -27,33 +27,6 @@ Later in a text we are using HTML and it becomes an abbr tag HTML
Console.WriteLine(result);
}
[Test]
public void TestPipeTables()
{
TestParser.TestSpec(@"
| abc | def | ghi |
|:---:|-----|----:|
| 1 | 2 | 3 |
", @"
<table>
<thead>
<tr>
<th style=""text-align: center;"">abc</th>
<th>def</th>
<th style=""text-align: right;"">ghi</th>
</tr>
</thead>
<tbody>
<tr>
<td style=""text-align: center;"">1</td>
<td>2</td>
<td style=""text-align: right;"">3</td>
</tr>
</tbody>
</table>
", "advanced");
}
[Test]
public void TestSelfPipeline1()
{

View File

@@ -1,25 +1,25 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0-rc2-3002702",
"version": "1.0.0",
"type": "platform"
},
"Microsoft.ApplicationInsights.AspNetCore": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0-rc2-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",
"Microsoft.Extensions.Logging": "1.0.0-rc2-final",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-final",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc2-final",
"Markdig": "0.2.1"
"Microsoft.ApplicationInsights.AspNetCore": "1.0.0",
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Markdig": "0.7.2"
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": {
"version": "1.0.0-preview1-final",
"version": "1.0.0-preview2-final",
"imports": "portable-net45+win8+dnxcore50"
}
},

View File

@@ -126,12 +126,13 @@ namespace Markdig.Extensions.Abbreviations
// If we don't have a container, create a new one
if (container == null)
{
container = new ContainerInline()
{
Span = originalLiteral.Span,
Line = originalLiteral.Line,
Column = originalLiteral.Column,
};
container = literal.Parent ??
new ContainerInline
{
Span = originalLiteral.Span,
Line = originalLiteral.Line,
Column = originalLiteral.Column,
};
}
int line;
@@ -150,7 +151,10 @@ namespace Markdig.Extensions.Abbreviations
// Append the previous literal
if (i > content.Start)
{
container.AppendChild(literal);
if (literal.Parent == null)
{
container.AppendChild(literal);
}
literal.Span.End = abbrInline.Span.Start - 1;
// Truncate it before the abbreviation

View File

@@ -51,8 +51,7 @@ namespace Markdig.Extensions.DefinitionLists
}
var previousParent = paragraphBlock.Parent;
var indexOfParagraph = previousParent.IndexOf(paragraphBlock);
var currentDefinitionList = indexOfParagraph - 1 >= 0 ? previousParent[indexOfParagraph - 1] as DefinitionList : null;
var currentDefinitionList = GetCurrentDefinitionList(paragraphBlock, previousParent);
processor.Discard(paragraphBlock);
@@ -103,6 +102,19 @@ namespace Markdig.Extensions.DefinitionLists
return BlockState.Continue;
}
private static DefinitionList GetCurrentDefinitionList(ParagraphBlock paragraphBlock, ContainerBlock previousParent)
{
var index = previousParent.IndexOf(paragraphBlock) - 1;
if (index < 0) return null;
var lastBlock = previousParent[index];
if (lastBlock is BlankLineBlock)
{
lastBlock = previousParent[index - 1];
previousParent.RemoveAt(index);
}
return lastBlock as DefinitionList;
}
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var definitionItem = (DefinitionItem)block;

View File

@@ -41,7 +41,7 @@ namespace Markdig.Extensions.Emoji
/// </summary>
public Dictionary<string, string> SmileyToEmoji { get; }
public override void Initialize(InlineProcessor processor)
public override void Initialize()
{
var firstChars = new HashSet<char>();
var textToMatch = new HashSet<string>();

View File

@@ -64,7 +64,7 @@ namespace Markdig.Extensions.GenericAttributes
}
var currentHtmlAttributes = objectToAttach.GetAttributes();
attributes.CopyTo(currentHtmlAttributes, false, false);
attributes.CopyTo(currentHtmlAttributes, true, false);
// Update the position of the attributes
int line;

View File

@@ -7,6 +7,8 @@ using Markdig.Parsers;
namespace Markdig.Extensions.ListExtras
{
using System;
/// <summary>
/// Parser that adds supports for parsing alpha/roman list items (e.g: `a)` or `a.` or `ii.` or `II.`)
/// </summary>
@@ -52,7 +54,7 @@ namespace Markdig.Extensions.ListExtras
c = state.NextChar();
}
result.OrderedStart = state.Line.Text.Substring(startChar, endChar - startChar + 1);
result.OrderedStart = CharHelper.RomanToArabic(state.Line.Text.Substring(startChar, endChar - startChar + 1)).ToString();
result.BulletType = isRomanLow ? 'i' : 'I';
result.DefaultOrderedStart = isRomanLow ? "i" : "I";
}
@@ -60,7 +62,7 @@ namespace Markdig.Extensions.ListExtras
{
// otherwise we expect a regular alpha lettered list with a single character.
var isUpper = c.IsAlphaUpper();
result.OrderedStart = c.ToString();
result.OrderedStart = (Char.ToUpper(c) - 64).ToString();
result.BulletType = isUpper ? 'A' : 'a';
result.DefaultOrderedStart = isUpper ? "A" : "a";
state.NextChar();

View File

@@ -63,6 +63,7 @@ namespace Markdig.Extensions.Mathematics
int closeDollars = 0;
var start = slice.Start;
var end = 0;
pc = match;
while (c != '\0')
{
@@ -101,7 +102,7 @@ namespace Markdig.Extensions.Mathematics
{
return false;
}
end = slice.Start - 1;
// Create a new MathInline
int line;
int column;
@@ -116,7 +117,7 @@ namespace Markdig.Extensions.Mathematics
};
inline.Content.Start = start;
// We substract the end to the number of opening $ to keep inside the block the additionals $
inline.Content.End = inline.Content.End - openDollars;
inline.Content.End = end - openDollars;
// Add the default class if necessary
if (DefaultClass != null)

View File

@@ -1,6 +1,9 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Collections.Generic;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
@@ -10,7 +13,7 @@ namespace Markdig.Extensions.Tables
{
public GridTableParser()
{
OpeningCharacters = new[] {'+'};
OpeningCharacters = new[] { '+' };
}
public override BlockState TryOpen(BlockProcessor processor)
@@ -22,65 +25,47 @@ namespace Markdig.Extensions.Tables
}
var line = processor.Line;
// A grid table must start with a line like this:
// + ------------- + ------------ + ---------------------------------------- +
// Spaces are optional
GridTableState tableState = null;
var c = line.CurrentChar;
var startPosition = processor.Start;
while (true)
var lineStart = line.Start;
int startPosition = -1;
bool hasLeft = false,
hasRight = false;
while (line.Length > 0)
{
if (c == '+')
{
var startCharacter = line.Start;
line.NextChar();
if (line.IsEmptyOrWhitespace())
tableState = tableState ?? new GridTableState { Start = processor.Start, ExpectRow = true };
if (startPosition != -1)
{
if (tableState == null)
{
return BlockState.None;
}
break;
}
TableColumnAlign align;
if (TableHelper.ParseColumnHeader(ref line, '-', out align))
{
if (tableState == null)
{
tableState = new GridTableState()
{
Start = processor.Start,
ExpectRow = true,
};
}
tableState.AddColumn(startCharacter - startPosition, line.Start - 1 - startPosition, align);
c = line.CurrentChar;
continue;
hasRight = line.PeekCharAbsolute(line.Start - 1) == ':';
tableState.AddColumn(startPosition - lineStart, line.Start - lineStart, GetAlignment(hasLeft, hasRight));
}
hasLeft = line.PeekChar(1) == ':';
startPosition = line.Start;
}
// If we have any other characters, this is an invalid line
else if (c != ':' && c != '-')
{
return BlockState.None;
}
c = line.NextChar();
}
if (tableState == null || tableState.ColumnSlices.Count == (processor.Line.Length - 1))
{
return BlockState.None;
}
// Store the line (if we need later to build a ParagraphBlock because the GridTable was in fact invalid)
tableState.AddLine(ref processor.Line);
// Create the grid table
var table = new Table(this);
table.SetData(typeof(GridTableState), tableState);
// Calculate the total width of all columns
int totalWidth = 0;
foreach (var columnSlice in tableState.ColumnSlices)
{
totalWidth += columnSlice.End - columnSlice.Start;
totalWidth += columnSlice.End - columnSlice.Start - 1;
}
// Store the column width and alignment
@@ -89,8 +74,8 @@ namespace Markdig.Extensions.Tables
var columnDefinition = new TableColumnDefinition
{
// Column width proportional to the total width
Width = (float)(columnSlice.End - columnSlice.Start) * 100.0f / totalWidth,
Alignment = columnSlice.Align,
Width = (float)(columnSlice.End - columnSlice.Start - 1) * 100.0f / totalWidth,
Alignment = columnSlice.Align
};
table.ColumnDefinitions.Add(columnDefinition);
}
@@ -100,205 +85,99 @@ namespace Markdig.Extensions.Tables
return BlockState.ContinueDiscard;
}
private static TableColumnAlign GetAlignment(bool hasLeft, bool hasRight)
{
return hasLeft && hasRight
? TableColumnAlign.Center
: hasRight ? TableColumnAlign.Right : TableColumnAlign.Left;
}
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
var gridTable = (Table) block;
var gridTable = (Table)block;
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
// We expect to start at the same
//if (processor.Start == tableState.Start)
tableState.AddLine(ref processor.Line);
if (processor.CurrentChar == '+')
{
var columns = tableState.ColumnSlices;
foreach (var columnSlice in columns)
{
columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
columnSlice.CurrentColumnSpan = 0;
}
if (processor.CurrentChar == '+')
{
var result = ParseRowSeparator(processor, tableState, gridTable);
if (result != BlockState.None)
{
return result;
}
}
else if (processor.CurrentChar == '|')
{
var line = processor.Line;
// | ------------- | ------------ | ---------------------------------------- |
// Calculate the colspan for the new row
int columnIndex = -1;
for (int i = 0; i < columns.Count; i++)
{
var columnSlice = columns[i];
if (line.PeekCharExtra(columnSlice.Start) == '|')
{
columnIndex = i;
}
if (columnIndex >= 0)
{
columns[columnIndex].CurrentColumnSpan++;
}
}
// Check if the colspan of the current row is the same than the previous row
bool continueRow = true;
foreach (var columnSlice in columns)
{
if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
{
continueRow = false;
break;
}
}
// If the current row doesn't continue the previous row (col span are different)
// Close the previous row
if (!continueRow)
{
TerminateLastRow(processor, tableState, gridTable, false);
}
for (int i = 0; i < columns.Count;)
{
var column = columns[i];
var nextColumnIndex = i + column.CurrentColumnSpan;
// If the span is 0, we exit
if (nextColumnIndex == i)
{
break;
}
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
var sliceForCell = line;
sliceForCell.Start = line.Start + column.Start + 1;
if (nextColumn != null)
{
sliceForCell.End = line.Start + nextColumn.Start - 1;
}
else
{
var columnEnd = columns[columns.Count - 1].End;
// If there is a `|` exactly at the expected end of the table row, we cut the line
// otherwise we allow to have the last cell of a row to be open for longer cell content
if (line.PeekCharExtra(columnEnd + 1) == '|')
{
sliceForCell.End = line.Start + columnEnd;
}
}
sliceForCell.TrimEnd();
// Process the content of the cell
column.BlockProcessor.LineIndex = processor.LineIndex;
column.BlockProcessor.ProcessLine(sliceForCell);
// Go to next column
i = nextColumnIndex;
}
return BlockState.ContinueDiscard;
}
return HandleNewRow(processor, tableState, gridTable);
}
TerminateLastRow(processor, tableState, gridTable, true);
// If we don't have a row, it means that only the header was valid
// So we need to remove the grid table, and create a ParagraphBlock
// with the 2 slices
if (gridTable.Count == 0)
if (processor.CurrentChar == '|')
{
var parser = processor.Parsers.FindExact<ParagraphBlockParser>();
// Discard the grid table
var parent = gridTable.Parent;
processor.Discard(gridTable);
var paragraphBlock = new ParagraphBlock(parser)
{
Lines = tableState.Lines,
};
parent.Add(paragraphBlock);
processor.Open(paragraphBlock);
return HandleContents(processor, tableState, gridTable);
}
TerminateCurrentRow(processor, tableState, gridTable, true);
// If the table is not valid we need to remove the grid table,
// and create a ParagraphBlock with the slices
if (!gridTable.IsValid())
{
Undo(processor, tableState, gridTable);
}
return BlockState.Break;
}
public override bool Close(BlockProcessor processor, Block block)
private BlockState HandleNewRow(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
// Work only on Table, not on TableCell
var gridTable = block as Table;
if (gridTable != null)
bool isHeaderRow, hasRowSpan;
var columns = tableState.ColumnSlices;
SetRowSpanState(columns, processor.Line, out isHeaderRow, out hasRowSpan);
SetColumnSpanState(columns, processor.Line);
TerminateCurrentRow(processor, tableState, gridTable, false);
if (isHeaderRow)
{
var tableState = (GridTableState) block.GetData(typeof (GridTableState));
TerminateLastRow(processor, tableState, gridTable, true);
for (int i = 0; i < gridTable.Count; i++)
{
var row = (TableRow)gridTable[i];
row.IsHeader = true;
}
}
tableState.StartRowGroup = gridTable.Count;
if (hasRowSpan)
{
HandleContents(processor, tableState, gridTable);
}
return BlockState.ContinueDiscard;
}
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
{
var lineStart = line.Start;
isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
hasRowSpan = false;
foreach (var columnSlice in columns)
{
if (columnSlice.CurrentCell != null)
{
line.Start = lineStart + columnSlice.Start + 1;
line.End = lineStart + columnSlice.End - 1;
line.Trim();
if (line.IsEmptyOrWhitespace() || !IsRowSeperator(line))
{
hasRowSpan = true;
columnSlice.CurrentCell.RowSpan++;
columnSlice.CurrentCell.AllowClose = false;
}
else
{
columnSlice.CurrentCell.AllowClose = true;
}
}
}
}
private static bool IsRowSeperator(StringSlice slice)
{
while (slice.Length > 0)
{
if (slice.CurrentChar != '-' && slice.CurrentChar != '=' && slice.CurrentChar != ':')
{
return false;
}
slice.NextChar();
}
return true;
}
private BlockState ParseRowSeparator(BlockProcessor state, GridTableState tableState, Table gridTable)
{
// A grid table must start with a line like this:
// + ------------- + ------------ + ---------------------------------------- +
// Spaces are optional
var line = state.Line;
var c = line.CurrentChar;
bool isFirst = true;
var delimiterChar = '\0';
while (true)
{
if (c == '+')
{
line.NextChar();
if (line.IsEmptyOrWhitespace())
{
if (isFirst)
{
return BlockState.None;
}
break;
}
TableColumnAlign align;
if (TableHelper.ParseColumnHeaderDetect(ref line, ref delimiterChar, out align))
{
isFirst = false;
c = line.CurrentChar;
continue;
}
}
// If we have any other characters, this is an invalid line
return BlockState.None;
}
// If we have an header row
var isHeader = delimiterChar == '=';
// Terminate the current row
TerminateLastRow(state, tableState, gridTable, false);
// If we had a header row separator, we can mark all rows since last row separator
// to be header rows
if (isHeader)
{
for (int i = tableState.StartRowGroup; i < gridTable.Count; i++)
{
var row = (TableRow) gridTable[i];
row.IsHeader = true;
}
}
// Makr the next start row group continue on the next row
tableState.StartRowGroup = gridTable.Count;
// We don't keep the line
return BlockState.ContinueDiscard;
}
private void TerminateLastRow(BlockProcessor state, GridTableState tableState, Table gridTable, bool isLastRow)
private static void TerminateCurrentRow(BlockProcessor processor, GridTableState tableState, Table gridTable, bool isLastRow)
{
var columns = tableState.ColumnSlices;
TableRow currentRow = null;
@@ -311,47 +190,180 @@ namespace Markdig.Extensions.Tables
{
currentRow = new TableRow();
}
currentRow.Add(columnSlice.CurrentCell);
columnSlice.BlockProcessor.Close(columnSlice.CurrentCell);
// If this cell does not already belong to a row
if (columnSlice.CurrentCell.Parent == null)
{
currentRow.Add(columnSlice.CurrentCell);
}
// If the cell is not going to span through to the next row
if (columnSlice.CurrentCell.AllowClose)
{
columnSlice.BlockProcessor.Close(columnSlice.CurrentCell);
}
}
// Renew the block parser processor (or reset it for the last row)
if (columnSlice.BlockProcessor != null)
if (columnSlice.BlockProcessor != null && (columnSlice.CurrentCell == null || columnSlice.CurrentCell.AllowClose))
{
columnSlice.BlockProcessor.ReleaseChild();
columnSlice.BlockProcessor = isLastRow ? null : state.CreateChild();
columnSlice.BlockProcessor = isLastRow ? null : processor.CreateChild();
}
// Create or erase the cell
if (isLastRow || columnSlice.CurrentColumnSpan == 0)
if (isLastRow || columnSlice.CurrentColumnSpan == 0 || (columnSlice.CurrentCell != null && columnSlice.CurrentCell.AllowClose))
{
// We don't need the cell anymore if we have a last row
// Or the cell has a columnspan == 0
// And the cell does not have to be kept open to span rows
columnSlice.CurrentCell = null;
}
else
{
// Else we can create a new cell
columnSlice.CurrentCell = new TableCell(this)
{
ColumnSpan = columnSlice.CurrentColumnSpan,
ColumnIndex = i
};
if (columnSlice.BlockProcessor == null)
{
columnSlice.BlockProcessor = state.CreateChild();
}
// Ensure that the BlockParser is aware that the TableCell is the top-level container
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
}
}
if (currentRow != null)
if (currentRow != null && currentRow.Count > 0)
{
gridTable.Add(currentRow);
}
}
private BlockState HandleContents(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
var isRowLine = processor.CurrentChar == '+';
var columns = tableState.ColumnSlices;
var line = processor.Line;
SetColumnSpanState(columns, line);
if (!isRowLine && !CanContinueRow(columns))
{
TerminateCurrentRow(processor, tableState, gridTable, false);
}
for (int i = 0; i < columns.Count;)
{
var columnSlice = columns[i];
var nextColumnIndex = i + columnSlice.CurrentColumnSpan;
// If the span is 0, we exit
if (nextColumnIndex == i)
{
break;
}
var nextColumn = nextColumnIndex < columns.Count ? columns[nextColumnIndex] : null;
var sliceForCell = line;
sliceForCell.Start = line.Start + columnSlice.Start + 1;
if (nextColumn != null)
{
sliceForCell.End = line.Start + nextColumn.Start - 1;
}
else
{
var columnEnd = columns[columns.Count - 1].End;
var columnEndChar = line.PeekCharExtra(columnEnd);
// If there is a `|` (or a `+` in the case that we are dealing with a row line
// with spanned contents) exactly at the expected end of the table row, we cut the line
// otherwise we allow to have the last cell of a row to be open for longer cell content
if (columnEndChar == '|' || (isRowLine && columnEndChar == '+'))
{
sliceForCell.End = line.Start + columnEnd - 1;
}
else if (line.PeekCharExtra(line.End) == '|')
{
sliceForCell.End = line.End - 1;
}
}
sliceForCell.TrimEnd();
if (!isRowLine || !IsRowSeperator(sliceForCell))
{
if (columnSlice.CurrentCell == null)
{
columnSlice.CurrentCell = new TableCell(this)
{
ColumnSpan = columnSlice.CurrentColumnSpan,
ColumnIndex = i
};
if (columnSlice.BlockProcessor == null)
{
columnSlice.BlockProcessor = processor.CreateChild();
}
// Ensure that the BlockParser is aware that the TableCell is the top-level container
columnSlice.BlockProcessor.Open(columnSlice.CurrentCell);
}
// Process the content of the cell
columnSlice.BlockProcessor.LineIndex = processor.LineIndex;
columnSlice.BlockProcessor.ProcessLine(sliceForCell);
}
// Go to next column
i = nextColumnIndex;
}
return BlockState.ContinueDiscard;
}
private static void SetColumnSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line)
{
foreach (var columnSlice in columns)
{
columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan;
columnSlice.CurrentColumnSpan = 0;
}
// | ------------- | ------------ | ---------------------------------------- |
// Calculate the colspan for the new row
int columnIndex = -1;
for (int i = 0; i < columns.Count; i++)
{
var columnSlice = columns[i];
var peek = line.PeekChar(columnSlice.Start);
if (peek == '|' || peek == '+')
{
columnIndex = i;
}
if (columnIndex >= 0)
{
columns[columnIndex].CurrentColumnSpan++;
}
}
}
private static bool CanContinueRow(List<GridTableState.ColumnSlice> columns)
{
foreach (var columnSlice in columns)
{
if (columnSlice.PreviousColumnSpan != columnSlice.CurrentColumnSpan)
{
return false;
}
}
return true;
}
private static void Undo(BlockProcessor processor, GridTableState tableState, Table gridTable)
{
var parser = processor.Parsers.FindExact<ParagraphBlockParser>();
// Discard the grid table
var parent = gridTable.Parent;
processor.Discard(gridTable);
var paragraphBlock = new ParagraphBlock(parser)
{
Lines = tableState.Lines,
};
parent.Add(paragraphBlock);
processor.Open(paragraphBlock);
}
public override bool Close(BlockProcessor processor, Block block)
{
// Work only on Table, not on TableCell
var gridTable = block as Table;
if (gridTable != null)
{
var tableState = (GridTableState)block.GetData(typeof(GridTableState));
TerminateCurrentRow(processor, tableState, gridTable, true);
if (!gridTable.IsValid())
{
Undo(processor, tableState, gridTable);
}
}
return true;
}
}
}
}

View File

@@ -81,9 +81,16 @@ namespace Markdig.Extensions.Tables
{
renderer.Write($" colspan=\"{cell.ColumnSpan}\"");
}
var columnIndex = cell.ColumnIndex == -1 ? i : cell.ColumnIndex;
if (table.ColumnDefinitions != null && columnIndex < table.ColumnDefinitions.Count)
if (cell.RowSpan != 1)
{
renderer.Write($" rowspan=\"{cell.RowSpan}\"");
}
if (table.ColumnDefinitions != null)
{
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i
: cell.ColumnIndex;
columnIndex = columnIndex >= table.ColumnDefinitions.Count ? table.ColumnDefinitions.Count - 1 : columnIndex;
switch (table.ColumnDefinitions[columnIndex].Alignment)
{
case TableColumnAlign.Center:
@@ -104,7 +111,7 @@ namespace Markdig.Extensions.Tables
}
renderer.Write(cell);
renderer.ImplicitParagraph = previousImplicitParagraph;
renderer.WriteLine(row.IsHeader ? "</th>" : "</td>");
}
renderer.WriteLine("</tr>");

View File

@@ -10,9 +10,9 @@ namespace Markdig.Extensions.Tables
/// The delimiter used to separate the columns of a pipe table.
/// </summary>
/// <seealso cref="Markdig.Syntax.Inlines.DelimiterInline" />
public class PiprTableDelimiterInline : DelimiterInline
public class PipeTableDelimiterInline : DelimiterInline
{
public PiprTableDelimiterInline(InlineParser parser) : base(parser)
public PipeTableDelimiterInline(InlineParser parser) : base(parser)
{
}

View File

@@ -34,9 +34,10 @@ namespace Markdig.Extensions.Tables
{
pipeline.BlockParsers.Insert(0, new PipeTableBlockParser());
}
var lineBreakParser = pipeline.InlineParsers.FindExact<LineBreakInlineParser>();
if (!pipeline.InlineParsers.Contains<PipeTableParser>())
{
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(Options));
pipeline.InlineParsers.InsertBefore<EmphasisInlineParser>(new PipeTableParser(lineBreakParser, Options));
}
}

View File

@@ -1,7 +1,10 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Parsers.Inlines;
@@ -18,14 +21,17 @@ namespace Markdig.Extensions.Tables
/// <seealso cref="IPostInlineProcessor" />
public class PipeTableParser : InlineParser, IPostInlineProcessor
{
private LineBreakInlineParser lineBreakParser;
private readonly LineBreakInlineParser lineBreakParser;
/// <summary>
/// Initializes a new instance of the <see cref="PipeTableParser" /> class.
/// </summary>
/// <param name="lineBreakParser">The linebreak parser to use</param>
/// <param name="options">The options.</param>
public PipeTableParser(PipeTableOptions options = null)
public PipeTableParser(LineBreakInlineParser lineBreakParser, PipeTableOptions options = null)
{
if (lineBreakParser == null) throw new ArgumentNullException(nameof(lineBreakParser));
this.lineBreakParser = lineBreakParser;
OpeningCharacters = new[] { '|', '\n' };
Options = options ?? new PipeTableOptions();
}
@@ -35,12 +41,6 @@ namespace Markdig.Extensions.Tables
/// </summary>
public PipeTableOptions Options { get; }
public override void Initialize(InlineProcessor processor)
{
// We are using the linebreak parser
lineBreakParser = processor.Parsers.Find<LineBreakInlineParser>() ?? new LineBreakInlineParser();
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Only working on Paragraph block
@@ -96,11 +96,12 @@ namespace Markdig.Extensions.Tables
if (!isFirstLineEmpty)
{
tableState.ColumnAndLineDelimiters.Add(processor.Inline);
tableState.EndOfLines.Add(processor.Inline);
}
}
else
{
processor.Inline = new PiprTableDelimiterInline(this)
processor.Inline = new PipeTableDelimiterInline(this)
{
Span = new SourceSpan(position, position),
Line = globalLineIndex,
@@ -137,16 +138,16 @@ namespace Markdig.Extensions.Tables
}
var child = container.LastChild;
List<PiprTableDelimiterInline> delimitersToRemove = null;
List<PipeTableDelimiterInline> delimitersToRemove = null;
while (child != null)
{
var pipeDelimiter = child as PiprTableDelimiterInline;
var pipeDelimiter = child as PipeTableDelimiterInline;
if (pipeDelimiter != null)
{
if (delimitersToRemove == null)
{
delimitersToRemove = new List<PiprTableDelimiterInline>();
delimitersToRemove = new List<PipeTableDelimiterInline>();
}
delimitersToRemove.Add(pipeDelimiter);
}
@@ -168,8 +169,7 @@ namespace Markdig.Extensions.Tables
for (int i = 0; i < delimitersToRemove.Count; i++)
{
var pipeDelimiter = delimitersToRemove[i];
var literalInline = new LiteralInline() {Content = new StringSlice("|"), IsClosed = true};
pipeDelimiter.ReplaceBy(literalInline);
pipeDelimiter.ReplaceByLiteral();
// Check that the pipe that is being removed is not going to make a line without pipe delimiters
var tableDelimiters = tableState.ColumnAndLineDelimiters;
@@ -177,12 +177,12 @@ namespace Markdig.Extensions.Tables
if (i == 0)
{
leftIsDelimiter = delimiterIndex > 0 && tableDelimiters[delimiterIndex - 1] is PiprTableDelimiterInline;
leftIsDelimiter = delimiterIndex > 0 && tableDelimiters[delimiterIndex - 1] is PipeTableDelimiterInline;
}
else if (i + 1 == delimitersToRemove.Count)
{
rightIsDelimiter = delimiterIndex + 1 < tableDelimiters.Count &&
tableDelimiters[delimiterIndex + 1] is PiprTableDelimiterInline;
tableDelimiters[delimiterIndex + 1] is PipeTableDelimiterInline;
}
// Remove this delimiter from the table processor
tableState.ColumnAndLineDelimiters.Remove(pipeDelimiter);
@@ -198,14 +198,18 @@ namespace Markdig.Extensions.Tables
return true;
}
// Remove previous state
state.ParserStates[Index] = null;
// Continue
if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe ) //|| tableState.LineIndex != state.LocalLineIndex)
{
return true;
}
// Detect the header row
var delimiters = tableState.ColumnAndLineDelimiters;
delimiters.Add(null);
// TODO: we could optimize this by merging FindHeaderRow and the cell loop
var aligns = FindHeaderRow(delimiters);
if (Options.RequireHeaderSeparator && aligns == null)
@@ -223,153 +227,210 @@ namespace Markdig.Extensions.Tables
}
state.BlockNew = table;
TableRow firstRow = null;
int maxColumn = 0;
var cells = tableState.Cells;
cells.Clear();
Inline column = container.FirstChild;
if (column is PiprTableDelimiterInline)
//delimiters[0].DumpTo(state.DebugLog);
// delimiters contain a list of `|` and `\n` delimiters
// The `|` delimiters are created as child containers.
// So the following:
// | a | b \n
// | d | e \n
//
// Will generate a tree of the following node:
// |
// a
// |
// b
// \n
// |
// d
// |
// e
// \n
// When parsing delimiters, we need to recover whether a row is of the following form:
// 0) | a | b | \n
// 1) | a | b \n
// 2) a | b \n
// 3) a | b | \n
// If the last element is not a line break, add a line break to homogenize parsing in the next loop
var lastElement = delimiters[delimiters.Count - 1];
if (!(lastElement is LineBreakInline))
{
column = ((PiprTableDelimiterInline)column).FirstChild;
while (true)
{
if (lastElement is ContainerInline)
{
var nextElement = ((ContainerInline) lastElement).LastChild;
if (nextElement != null)
{
lastElement = nextElement;
continue;
}
}
break;
}
var endOfTable = new LineBreakInline();
lastElement.InsertAfter(endOfTable);
delimiters.Add(endOfTable);
tableState.EndOfLines.Add(endOfTable);
}
// TODO: This is not accurate for the table
table.Span.Start = column.Span.Start;
table.Span.End = column.Span.End;
table.Line = column.Line;
table.Column = column.Column;
int lastIndex = 0;
// Cell loop
// Reconstruct the table from the delimiters
TableRow row = null;
TableRow firstRow = null;
for (int i = 0; i < delimiters.Count; i++)
{
var delimiter = delimiters[i];
if (delimiter == null || IsLine(delimiter))
var pipeSeparator = delimiter as PipeTableDelimiterInline;
var isLine = delimiter is LineBreakInline;
if (row == null)
{
var beforeDelimiter = delimiter?.PreviousSibling;
var nextLineColumn = delimiter?.NextSibling;
TableRow row = null;
for (int j = lastIndex; j <= i; j++)
{
var columnSeparator = delimiters[j];
var pipeSeparator = columnSeparator as PiprTableDelimiterInline;
var endOfColumn = columnSeparator?.PreviousSibling;
// This is the first column empty
if (j == lastIndex && pipeSeparator != null && endOfColumn == null)
{
columnSeparator.Remove();
column = pipeSeparator.FirstChild;
continue;
}
if (pipeSeparator != null && IsTrailingColumnDelimiter(pipeSeparator))
{
TrimEnd(endOfColumn);
columnSeparator.Remove();
continue;
}
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();
cellContainer.AppendChild(item);
if (isFirstItem)
{
cellContainer.Line = item.Line;
cellContainer.Column = item.Column;
cellContainer.Span.Start = item.Span.Start;
isFirstItem = false;
}
cellContainer.Span.End = item.Span.End;
item = nextSibling;
}
var tableParagraph = new ParagraphBlock()
{
Span = cellContainer.Span,
Line = cellContainer.Line,
Column = cellContainer.Column,
Inline = cellContainer
};
var tableCell = new TableCell()
{
Span = cellContainer.Span,
Line = cellContainer.Line,
Column = cellContainer.Column,
};
tableCell.Add(tableParagraph);
if (row == null)
{
row = new TableRow()
{
Span = cellContainer.Span,
Line = cellContainer.Line,
Column = cellContainer.Column,
};
}
row.Add(tableCell);
cells.Add(tableCell);
// If we have reached the end, we can add remaining delimiters as pure child of the current cell
if (row.Count == maxColumn && columnSeparator is PiprTableDelimiterInline)
{
columnSeparator.Remove();
tableParagraph.Inline.AppendChild(columnSeparator);
break;
}
TrimEnd(endOfColumn);
//TrimEnd(previousSibling);
if (columnSeparator != null)
{
if (pipeSeparator != null)
{
column = pipeSeparator.FirstChild;
}
columnSeparator.Remove();
}
}
if (row != null)
{
table.Add(row);
}
TrimEnd(beforeDelimiter);
if (delimiter != null)
{
delimiter.Remove();
}
if (nextLineColumn != null)
{
column = nextLineColumn;
}
row = new TableRow();
if (firstRow == null)
{
firstRow = row;
maxColumn = firstRow.Count;
}
lastIndex = i + 1;
// If the first delimiter is a pipe and doesn't have any parent or previous sibling, for cases like:
// 0) | a | b | \n
// 1) | a | b \n
if (pipeSeparator != null && (delimiter.PreviousSibling == null || delimiter.PreviousSibling is LineBreakInline))
{
delimiter.Remove();
continue;
}
}
// We need to find the beginning/ending of a cell from a right delimiter. From the delimiter 'x', we need to find a (without the delimiter start `|`)
// So we iterate back to the first pipe or line break
// x
// 1) | a | b \n
// 2) a | b \n
Inline endOfCell = null;
Inline beginOfCell = null;
var cellContentIt = delimiter;
while (true)
{
cellContentIt = cellContentIt.PreviousSibling ?? cellContentIt.Parent;
if (cellContentIt == null || cellContentIt is LineBreakInline)
{
break;
}
// The cell begins at the first effective child after a | or the top ContainerInline (which is not necessary to bring into the tree + it contains an invalid span calculation)
if (cellContentIt is PipeTableDelimiterInline || (cellContentIt.GetType() == typeof(ContainerInline) && cellContentIt.Parent == null ))
{
beginOfCell = ((ContainerInline)cellContentIt).FirstChild;
if (endOfCell == null)
{
endOfCell = beginOfCell;
}
break;
}
beginOfCell = cellContentIt;
if (endOfCell == null)
{
endOfCell = beginOfCell;
}
}
// If the current deilimiter is a pipe `|` OR
// the beginOfCell/endOfCell are not null and
// either they are :
// - different
// - they contain a single element, but it is not a line break (\n) or an empty/whitespace Literal.
// Then we can add a cell to the current row
if (!isLine || (beginOfCell != null && endOfCell != null && ( beginOfCell != endOfCell || !(beginOfCell is LineBreakInline || (beginOfCell is LiteralInline && ((LiteralInline)beginOfCell).Content.IsEmptyOrWhitespace())))))
{
if (!isLine)
{
// If the delimiter is a pipe, we need to remove it from the tree
// so that previous loop looking for a parent will not go further on subsequent cells
delimiter.Remove();
}
// We trim whitespace at the beginning and ending of the cell
TrimStart(beginOfCell);
TrimEnd(endOfCell);
var cellContainer = new ContainerInline();
// Copy elements from beginOfCell on the first level
var cellIt = beginOfCell;
while (cellIt != null && !IsLine(cellIt) && !(cellIt is PipeTableDelimiterInline))
{
var nextSibling = cellIt.NextSibling;
cellIt.Remove();
if (cellContainer.Span.IsEmpty)
{
cellContainer.Line = cellIt.Line;
cellContainer.Column = cellIt.Column;
cellContainer.Span = cellIt.Span;
}
cellContainer.AppendChild(cellIt);
cellContainer.Span.End = cellIt.Span.End;
cellIt = nextSibling;
}
// Create the cell and add it to the pending row
var tableParagraph = new ParagraphBlock()
{
Span = cellContainer.Span,
Line = cellContainer.Line,
Column = cellContainer.Column,
Inline = cellContainer
};
var tableCell = new TableCell()
{
Span = cellContainer.Span,
Line = cellContainer.Line,
Column = cellContainer.Column,
};
tableCell.Add(tableParagraph);
if (row.Span.IsEmpty)
{
row.Span = cellContainer.Span;
row.Line = cellContainer.Line;
row.Column = cellContainer.Column;
}
row.Add(tableCell);
cells.Add(tableCell);
}
// If we have a new line, we can add the row
if (isLine)
{
Debug.Assert(row != null);
if (table.Span.IsEmpty)
{
table.Span = row.Span;
table.Line = row.Line;
table.Column = row.Column;
}
table.Add(row);
row = null;
}
}
// Once we are done with the cells, we can remove all end of lines in the table tree
foreach (var endOfLine in tableState.EndOfLines)
{
endOfLine.Remove();
}
// If we have a header row, we can remove it
// TODO: we could optimize this by merging FindHeaderRow and the previous loop
if (aligns != null)
{
table.RemoveAt(1);
@@ -421,60 +482,62 @@ namespace Markdig.Extensions.Tables
List<TableColumnDefinition> aligns = null;
for (int i = 0; i < delimiters.Count; i++)
{
if (delimiters[i] != null && IsLine(delimiters[i]))
if (!IsLine(delimiters[i]))
{
// The last delimiter is always null,
for (int j = i + 1; j < delimiters.Count - 1; j++)
{
var delimiter = delimiters[j];
var nextDelimiter = delimiters[j + 1];
var columnDelimiter = delimiter as PiprTableDelimiterInline;
if (j == i + 1 && IsStartOfLineColumnDelimiter(columnDelimiter))
{
continue;
}
// Check the left side of a `|` delimiter
TableColumnAlign align = TableColumnAlign.Left;
if (delimiter.PreviousSibling != null && !ParseHeaderString(delimiter.PreviousSibling, out align))
{
break;
}
// Create aligns until we may have a header row
if (aligns == null)
{
aligns = new List<TableColumnDefinition>();
}
aligns.Add(new TableColumnDefinition() { Alignment = align });
// If this is the last delimiter, we need to check the right side of the `|` delimiter
if (nextDelimiter == null)
{
var nextSibling = columnDelimiter != null
? columnDelimiter.FirstChild
: delimiter.NextSibling;
if (!ParseHeaderString(nextSibling, out align))
{
break;
}
isValidRow = true;
aligns.Add(new TableColumnDefinition() { Alignment = align });
break;
}
// If we are on a Line delimiter, exit
if (IsLine(delimiter))
{
isValidRow = true;
break;
}
}
break;
continue;
}
// The last delimiter is always null,
for (int j = i + 1; j < delimiters.Count; j++)
{
var delimiter = delimiters[j];
var nextDelimiter = j + 1 < delimiters.Count ? delimiters[j + 1] : null;
var columnDelimiter = delimiter as PipeTableDelimiterInline;
if (j == i + 1 && IsStartOfLineColumnDelimiter(columnDelimiter))
{
continue;
}
// Check the left side of a `|` delimiter
TableColumnAlign align = TableColumnAlign.Left;
if (delimiter.PreviousSibling != null && !ParseHeaderString(delimiter.PreviousSibling, out align))
{
break;
}
// Create aligns until we may have a header row
if (aligns == null)
{
aligns = new List<TableColumnDefinition>();
}
aligns.Add(new TableColumnDefinition() { Alignment = align });
// If this is the last delimiter, we need to check the right side of the `|` delimiter
if (nextDelimiter == null)
{
var nextSibling = columnDelimiter != null
? columnDelimiter.FirstChild
: delimiter.NextSibling;
if (!ParseHeaderString(nextSibling, out align))
{
break;
}
isValidRow = true;
aligns.Add(new TableColumnDefinition() { Alignment = align });
break;
}
// If we are on a Line delimiter, exit
if (IsLine(delimiter))
{
isValidRow = true;
break;
}
}
break;
}
return isValidRow ? aligns : null;
@@ -509,21 +572,6 @@ namespace Markdig.Extensions.Tables
return previous == null || IsLine(previous);
}
private static bool IsTrailingColumnDelimiter(PiprTableDelimiterInline inline)
{
var child = inline.FirstChild;
var literal = child as LiteralInline;
if (literal != null)
{
if (!literal.Content.IsEmptyOrWhitespace())
{
return false;
}
child = child.NextSibling;
}
return child == null || IsLine(child);
}
private static void TrimStart(Inline inline)
{
while (inline is ContainerInline && !(inline is DelimiterInline))
@@ -552,6 +600,7 @@ namespace Markdig.Extensions.Tables
{
ColumnAndLineDelimiters = new List<Inline>();
Cells = new List<TableCell>();
EndOfLines = new List<Inline>();
}
public bool IsInvalidTable { get; set; }
@@ -563,6 +612,8 @@ namespace Markdig.Extensions.Tables
public List<Inline> ColumnAndLineDelimiters { get; }
public List<TableCell> Cells { get; }
public List<Inline> EndOfLines { get; }
}
}
}

View File

@@ -34,5 +34,40 @@ namespace Markdig.Extensions.Tables
/// Gets or sets the column alignments. May be null.
/// </summary>
public List<TableColumnDefinition> ColumnDefinitions { get; private set; }
/// <summary>
/// Checks if the table structure is valid.
/// </summary>
/// <returns><c>True</c> if the table has rows and the number of cells per row is correct, other wise <c>false</c>.</returns>
public bool IsValid()
{
// A table with no rows is not valid.
if (Count == 0)
{
return false;
}
var columnCount = ColumnDefinitions.Count;
var rows = new int[Count];
for (int i = 0; i < Count; i++)
{
var row = (TableRow)this[i];
for (int j = 0; j < row.Count; j++)
{
var cell = (TableCell)row[j];
rows[i] += cell.ColumnSpan;
var rowSpan = cell.RowSpan - 1;
while (rowSpan > 0)
{
rows[i + rowSpan] += cell.ColumnSpan;
rowSpan--;
}
}
if (rows[i] > columnCount)
{
return false;
}
}
return true;
}
}
}

View File

@@ -26,8 +26,10 @@ namespace Markdig.Extensions.Tables
/// <param name="parser">The parser used to create this block.</param>
public TableCell(BlockParser parser) : base(parser)
{
AllowClose = true;
ColumnSpan = 1;
ColumnIndex = -1;
RowSpan = 1;
}
/// <summary>
@@ -39,5 +41,15 @@ namespace Markdig.Extensions.Tables
/// Gets or sets the column span this cell is covering. Default is 1.
/// </summary>
public int ColumnSpan { get; set; }
/// <summary>
/// Gets or sets the row span this cell is covering. Default is 1.
/// </summary>
public int RowSpan { get; set; }
/// <summary>
/// Gets or sets whether this cell can be closed.
/// </summary>
public bool AllowClose { get; set; }
}
}

View File

@@ -7,6 +7,8 @@ using System.Runtime.CompilerServices;
namespace Markdig.Helpers
{
using System.Collections.Generic;
/// <summary>
/// Helper class for handling characters.
/// </summary>
@@ -18,6 +20,11 @@ namespace Markdig.Helpers
public const string ZeroSafeString = "\uFFFD";
// We don't support LCDM
private static IDictionary<char, int> romanMap = new Dictionary<char, int> { { 'I', 1 }, { 'V', 5 }, { 'X', 10 } };
private static readonly char[] punctuationExceptions = { '', '-', '†', '‡' };
public static void CheckOpenCloseDelimiter(char pc, char c, bool enableWithinWord, out bool canOpen, out bool canClose)
{
// A left-flanking delimiter run is a delimiter run that is
@@ -32,8 +39,11 @@ namespace Markdig.Helpers
pc.CheckUnicodeCategory(out prevIsWhiteSpace, out prevIsPunctuation);
c.CheckUnicodeCategory(out nextIsWhiteSpace, out nextIsPunctuation);
var prevIsExcepted = prevIsPunctuation && punctuationExceptions.Contains(pc);
var nextIsExcepted = nextIsPunctuation && punctuationExceptions.Contains(c);
canOpen = !nextIsWhiteSpace &&
(!nextIsPunctuation || prevIsWhiteSpace || prevIsPunctuation);
((!nextIsPunctuation || nextIsExcepted) || prevIsWhiteSpace || prevIsPunctuation);
// A right-flanking delimiter run is a delimiter run that is
@@ -42,7 +52,7 @@ namespace Markdig.Helpers
// or a punctuation character.
// For purposes of this definition, the beginning and the end of the line count as Unicode whitespace.
canClose = !prevIsWhiteSpace &&
(!prevIsPunctuation || nextIsWhiteSpace || nextIsPunctuation);
((!prevIsPunctuation || prevIsExcepted) || nextIsWhiteSpace || nextIsPunctuation);
if (!enableWithinWord)
{
@@ -80,6 +90,26 @@ namespace Markdig.Helpers
return c == 'I' || c == 'V' || c == 'X';
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static int RomanToArabic(string text)
{
int result = 0;
for (int i = 0; i < text.Length; i++)
{
var character = Char.ToUpper(text[i]);
var candidate = romanMap[character];
if (i + 1 < text.Length && candidate < romanMap[Char.ToUpper(text[i + 1])])
{
result -= candidate;
}
else
{
result += candidate;
}
}
return result;
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public static int AddTab(int column)
{

View File

@@ -649,7 +649,7 @@ namespace Markdig.Helpers
}
text.NextChar(); // Skip ':'
// Skip any whitespaces before the url
// Skip any whitespace before the url
text.TrimStart();
urlSpan.Start = text.Start;

View File

@@ -53,17 +53,17 @@ namespace Markdig.Helpers
CharNode nextNode;
if (!node.TryGetValue(c, out nextNode))
{
return false;
break;
}
node = nextNode;
if (node.Content != null)
{
match = node.Content;
return true;
}
offset++;
length--;
}
if (node.Content != null)
{
match = node.Content;
return true;
}
return false;
}

View File

@@ -487,6 +487,9 @@ namespace Markdig
case "nofollowlinks":
pipeline.UseNoFollowLinks();
break;
case "nohtml":
pipeline.DisableHtml();
break;
default:
throw new ArgumentException($"Invalid extension `{extension}` from `{extensions}`", nameof(extensions));
}

View File

@@ -24,7 +24,7 @@ namespace Markdig
public MarkdownPipelineBuilder()
{
// Add all default parsers
BlockParsers = new BlockParserList()
BlockParsers = new OrderedList<BlockParser>()
{
new ThematicBreakParser(),
new HeadingBlockParser(),
@@ -37,7 +37,7 @@ namespace Markdig
new ParagraphBlockParser(),
};
InlineParsers = new InlineParserList()
InlineParsers = new OrderedList<InlineParser>()
{
new HtmlEntityParser(),
new LinkInlineParser(),
@@ -56,12 +56,12 @@ namespace Markdig
/// <summary>
/// Gets the block parsers.
/// </summary>
public BlockParserList BlockParsers { get; private set; }
public OrderedList<BlockParser> BlockParsers { get; private set; }
/// <summary>
/// Gets the inline parsers.
/// </summary>
public InlineParserList InlineParsers { get; private set; }
public OrderedList<InlineParser> InlineParsers { get; private set; }
/// <summary>
/// Gets the register extensions.

View File

@@ -12,13 +12,6 @@ namespace Markdig.Parsers
/// <seealso cref="Markdig.Parsers.ParserList{Markdig.Parsers.BlockParser, Markdig.Parsers.BlockParserState}" />
public class BlockParserList : ParserList<BlockParser, BlockProcessor>
{
/// <summary>
/// Initializes a new instance of the <see cref="BlockParserList"/> class.
/// </summary>
public BlockParserList()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BlockParserList"/> class.
/// </summary>

View File

@@ -51,7 +51,6 @@ namespace Markdig.Parsers
Document = document;
document.IsOpen = true;
Parsers = parsers;
parsers.Initialize(this);
OpenedBlocks = new List<Block>();
NewBlocks = new Stack<Block>();
root = this;
@@ -402,6 +401,11 @@ namespace Markdig.Parsers
parserStateCache.Release(this);
}
internal bool IsOpen(Block block)
{
return OpenedBlocks.Contains(block);
}
/// <summary>
/// Closes a block at the specified index.
/// </summary>

View File

@@ -17,8 +17,7 @@ namespace Markdig.Parsers
/// <summary>
/// Initializes this parser with the specified parser processor.
/// </summary>
/// <param name="processor">The parser processor.</param>
void Initialize(TProcessor processor);
void Initialize();
/// <summary>
/// Gets the index of this parser in <see cref="BlockParserList"/> or <see cref="InlineParserList"/>.

View File

@@ -11,20 +11,7 @@ namespace Markdig.Parsers
/// <seealso cref="Markdig.Parsers.ParserList{Markdig.Parsers.InlineParser, Markdig.Parsers.InlineParserState}" />
public class InlineParserList : ParserList<InlineParser, InlineProcessor>
{
public InlineParserList()
{
}
public InlineParserList(IEnumerable<InlineParser> parsers) : base(parsers)
{
}
/// <summary>
/// Gets the registered post inline processors.
/// </summary>
public IPostInlineProcessor[] PostInlineProcessors { get; private set; }
public override void Initialize(InlineProcessor initState)
{
// Prepare the list of post inline processors
var postInlineProcessors = new List<IPostInlineProcessor>();
@@ -37,8 +24,11 @@ namespace Markdig.Parsers
}
}
PostInlineProcessors = postInlineProcessors.ToArray();
base.Initialize(initState);
}
/// <summary>
/// Gets the registered post inline processors.
/// </summary>
public IPostInlineProcessor[] PostInlineProcessors { get; private set; }
}
}

View File

@@ -46,7 +46,6 @@ namespace Markdig.Parsers
Parsers = parsers;
PreciseSourceLocation = preciseSourcelocation;
lineOffsets = new List<StringLineGroup.LineOffset>();
Parsers.Initialize(this);
ParserStates = new object[Parsers.Count];
LiteralInlineParser = new LiteralInlineParser();
}
@@ -239,7 +238,21 @@ namespace Markdig.Parsers
if (nextInline.Parent == null)
{
// Get deepest container
FindLastContainer().AppendChild(nextInline);
var container = FindLastContainer();
if (!ReferenceEquals(container, nextInline))
{
container.AppendChild(nextInline);
}
if (container == Root)
{
if (container.Span.IsEmpty)
{
container.Span = nextInline.Span;
}
container.Span.End = nextInline.Span.End;
}
}
}
else
@@ -254,31 +267,31 @@ namespace Markdig.Parsers
}
}
if (DebugLog != null)
{
DebugLog.WriteLine($"** Dump: char '{c}");
leafBlock.Inline.DumpTo(DebugLog);
}
//if (DebugLog != null)
//{
// DebugLog.WriteLine($"** Dump: char '{c}");
// leafBlock.Inline.DumpTo(DebugLog);
//}
}
Inline = null;
if (DebugLog != null)
{
DebugLog.WriteLine("** Dump before Emphasis:");
leafBlock.Inline.DumpTo(DebugLog);
}
//if (DebugLog != null)
//{
// DebugLog.WriteLine("** Dump before Emphasis:");
// leafBlock.Inline.DumpTo(DebugLog);
//}
// PostProcess all inlines
PostProcessInlines(0, Root, null, true);
//TransformDelimitersToLiterals();
if (DebugLog != null)
{
DebugLog.WriteLine();
DebugLog.WriteLine("** Dump after Emphasis:");
leafBlock.Inline.DumpTo(DebugLog);
}
//if (DebugLog != null)
//{
// DebugLog.WriteLine();
// DebugLog.WriteLine("** Dump after Emphasis:");
// leafBlock.Inline.DumpTo(DebugLog);
//}
}
public void PostProcessInlines(int startingIndex, Inline root, Inline lastChild, bool isFinalProcessing)

View File

@@ -62,6 +62,10 @@ namespace Markdig.Parsers.Inlines
Column = column
};
}
else
{
return false;
}
return true;
}

View File

@@ -63,7 +63,7 @@ namespace Markdig.Parsers.Inlines
/// </summary>
public CreateEmphasisInlineDelegate CreateEmphasisInline { get; set; }
public override void Initialize(InlineProcessor processor)
public override void Initialize()
{
OpeningCharacters = new char[EmphasisDescriptors.Count];
@@ -319,7 +319,8 @@ namespace Markdig.Parsers.Inlines
if (closeDelimiter.DelimiterCount == 0)
{
closeDelimiter.MoveChildrenAfter(emphasis);
var newParent = openDelimiter.DelimiterCount > 0 ? emphasis : emphasis.Parent;
closeDelimiter.MoveChildrenAfter(newParent);
closeDelimiter.Remove();
delimiters.RemoveAt(i);
i--;

View File

@@ -118,6 +118,7 @@ namespace Markdig.Parsers.Inlines
Title = HtmlHelper.Unescape(linkRef.Title),
Label = label,
LabelSpan = labelSpan,
UrlSpan = linkRef.UrlSpan,
IsImage = parent.IsImage,
Reference = linkRef,
Span = new SourceSpan(parent.Span.Start, endPosition),

View File

@@ -33,7 +33,7 @@ namespace Markdig.Parsers
/// </summary>
public OrderedList<ListItemParser> ItemParsers { get; }
public override void Initialize(BlockProcessor processor)
public override void Initialize()
{
var tempMap = new Dictionary<char, ListItemParser>();
@@ -253,10 +253,14 @@ namespace Markdig.Parsers
// - an empty list item follows a paragraph
// - an ordered list is not starting by '1'
var isPreviousParagraph = (block ?? state.LastBlock) is ParagraphBlock;
if (isPreviousParagraph && (state.IsBlankLine || (listInfo.BulletType == '1' && listInfo.OrderedStart != "1")))
if (isPreviousParagraph)
{
state.GoToColumn(initColumn);
return BlockState.None;
var isOpen = state.IsOpen(block ?? state.LastBlock);
if (state.IsBlankLine || (isOpen && listInfo.BulletType == '1' && listInfo.OrderedStart != "1"))
{
state.GoToColumn(initColumn);
return BlockState.None;
}
}
var newListItem = new ListItemBlock(this)

View File

@@ -51,14 +51,10 @@ namespace Markdig.Parsers
document = new MarkdownDocument();
// Initialize the block parsers
var blockParserList = new BlockParserList();
blockParserList.AddRange(pipeline.BlockParsers);
blockProcessor = new BlockProcessor(stringBuilderCache, document, blockParserList);
blockProcessor = new BlockProcessor(stringBuilderCache, document, pipeline.BlockParsers);
// Initialize the inline parsers
var inlineParserList = new InlineParserList();
inlineParserList.AddRange(pipeline.InlineParsers);
inlineProcessor = new InlineProcessor(stringBuilderCache, document, inlineParserList, pipeline.PreciseSourceLocation)
inlineProcessor = new InlineProcessor(stringBuilderCache, document, pipeline.InlineParsers, pipeline.PreciseSourceLocation)
{
DebugLog = pipeline.DebugLog
};

View File

@@ -19,8 +19,7 @@ namespace Markdig.Parsers
/// <summary>
/// Initializes this parser with the specified parser processor.
/// </summary>
/// <param name="processor">The parser processor.</param>
public virtual void Initialize(TProcessor processor)
public virtual void Initialize()
{
}

View File

@@ -16,60 +16,10 @@ namespace Markdig.Parsers
/// <seealso cref="Markdig.Helpers.OrderedList{T}" />
public abstract class ParserList<T, TState> : OrderedList<T> where T : ParserBase<TState>
{
private CharacterMap<T[]> charMap;
private T[] globalParsers;
private readonly CharacterMap<T[]> charMap;
private readonly T[] globalParsers;
protected ParserList()
{
}
protected ParserList(IEnumerable<T> parsers) : base(parsers)
{
}
/// <summary>
/// Gets the list of global parsers (that don't have any opening characters defined)
/// </summary>
public T[] GlobalParsers => globalParsers;
/// <summary>
/// Gets all the opening characters defined.
/// </summary>
public char[] OpeningCharacters => charMap.OpeningCharacters;
/// <summary>
/// Gets the list of parsers valid for the specified opening character.
/// </summary>
/// <param name="openingChar">The opening character.</param>
/// <returns>A list of parsers valid for the specified opening character or null if no parsers registered.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T[] GetParsersForOpeningCharacter(char openingChar)
{
return charMap[openingChar];
}
/// <summary>
/// Searches for an opening character from a registered parser in the specified string.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns>Index position within the string of the first opening character found in the specified text; if not found, returns -1</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public int IndexOfOpeningCharacter(string text, int start, int end)
{
return charMap.IndexOfOpeningCharacter(text, start, end);
}
/// <summary>
/// Initializes this instance with specified parser state.
/// </summary>
/// <param name="initState">State of the initialize.</param>
/// <exception cref="System.InvalidOperationException">
/// Unexpected null parser found
/// or
/// </exception>
public virtual void Initialize(TState initState)
protected ParserList(IEnumerable<T> parsersArg) : base(parsersArg)
{
var charCounter = new Dictionary<char, int>();
int globalCounter = 0;
@@ -82,7 +32,7 @@ namespace Markdig.Parsers
throw new InvalidOperationException("Unexpected null parser found");
}
parser.Initialize(initState);
parser.Initialize();
parser.Index = i;
if (parser.OpeningCharacters != null && parser.OpeningCharacters.Length != 0)
{
@@ -134,5 +84,50 @@ namespace Markdig.Parsers
charMap = new CharacterMap<T[]>(tempCharMap);
}
/// <summary>
/// Gets the list of global parsers (that don't have any opening characters defined)
/// </summary>
public T[] GlobalParsers => globalParsers;
/// <summary>
/// Gets all the opening characters defined.
/// </summary>
public char[] OpeningCharacters => charMap.OpeningCharacters;
/// <summary>
/// Gets the list of parsers valid for the specified opening character.
/// </summary>
/// <param name="openingChar">The opening character.</param>
/// <returns>A list of parsers valid for the specified opening character or null if no parsers registered.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public T[] GetParsersForOpeningCharacter(char openingChar)
{
return charMap[openingChar];
}
/// <summary>
/// Searches for an opening character from a registered parser in the specified string.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns>Index position within the string of the first opening character found in the specified text; if not found, returns -1</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public int IndexOfOpeningCharacter(string text, int start, int end)
{
return charMap.IndexOfOpeningCharacter(text, start, end);
}
/// <summary>
/// Initializes this instance with specified parser state.
/// </summary>
/// <exception cref="System.InvalidOperationException">
/// Unexpected null parser found
/// or
/// </exception>
private void Initialize()
{
}
}
}

View File

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

View File

@@ -23,7 +23,7 @@ namespace Markdig.Renderers.Html
renderer.Write(" type=\"").Write(listBlock.BulletType).Write("\"");
}
if (listBlock.OrderedStart != null && (listBlock.DefaultOrderedStart != listBlock.OrderedStart))
if (listBlock.OrderedStart != null && (listBlock.OrderedStart != "1"))
{
renderer.Write(" start=\"").Write(listBlock.OrderedStart).Write("\"");
}

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using System;
using System.Diagnostics;
using Markdig.Helpers;
using Markdig.Parsers;
namespace Markdig.Syntax.Inlines
@@ -41,5 +42,18 @@ namespace Markdig.Syntax.Inlines
/// </summary>
/// <returns>The string representation of this delimiter</returns>
public abstract string ToLiteral();
public void ReplaceByLiteral()
{
var literalInline = new LiteralInline()
{
Content = new StringSlice(ToLiteral()),
Span = Span,
Line = Line,
Column = Column,
IsClosed = true
};
ReplaceBy(literalInline);
}
}
}

View File

@@ -229,6 +229,24 @@ namespace Markdig.Syntax.Inlines
}
}
public Inline FindBestParent()
{
var current = this;
while (current.Parent != null || current.PreviousSibling != null)
{
if (current.Parent != null)
{
current = current.Parent;
continue;
}
current = current.PreviousSibling;
}
return current;
}
protected virtual void OnChildRemove(Inline child)
{

View File

@@ -47,5 +47,10 @@ namespace Markdig.Syntax.Inlines
/// The content as a <see cref="StringSlice"/>.
/// </summary>
public StringSlice Content;
public override string ToString()
{
return Content.ToString();
}
}
}

View File

@@ -59,7 +59,23 @@ namespace Markdig.Syntax
public string Title { get; set; }
/// <summary>
/// Gets or sets the create link inline calback for this instance.
/// The label span
/// </summary>
public SourceSpan LabelSpan;
/// <summary>
/// The URL span
/// </summary>
public SourceSpan UrlSpan;
/// <summary>
/// The title span
/// </summary>
public SourceSpan TitleSpan;
/// <summary>
/// Gets or sets the create link inline callback for this instance.
/// </summary>
/// <remarks>
/// This callback is called when an inline link is matching this reference definition.
@@ -72,20 +88,28 @@ namespace Markdig.Syntax
/// <typeparam name="T">Type of the text</typeparam>
/// <param name="text">The text.</param>
/// <param name="block">The block.</param>
/// <returns><c>true</c> if parsing is successfull; <c>false</c> otherwise</returns>
/// <returns><c>true</c> if parsing is successful; <c>false</c> otherwise</returns>
public static bool TryParse<T>(ref T text, out LinkReferenceDefinition block) where T : ICharIterator
{
block = null;
string label;
string url;
string title;
SourceSpan labelSpan;
SourceSpan urlSpan;
SourceSpan titleSpan;
if (!LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title))
if (!LinkHelper.TryParseLinkReferenceDefinition(ref text, out label, out url, out title, out labelSpan, out urlSpan, out titleSpan))
{
return false;
}
block = new LinkReferenceDefinition(label, url, title);
block = new LinkReferenceDefinition(label, url, title)
{
LabelSpan = labelSpan,
UrlSpan = urlSpan,
TitleSpan = titleSpan
};
return true;
}
}

View File

@@ -1,6 +1,6 @@
{
"title": "Markdig",
"version": "0.7.2",
"version": "0.7.5",
"authors": [ "Alexandre Mutel" ],
"description": "A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)",
"copyright": "Alexandre Mutel",
@@ -11,7 +11,7 @@
"projectUrl": "https://github.com/lunet-io/markdig",
"iconUrl": "https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png",
"requireLicenseAcceptance": false,
"releaseNotes": "> 0.7.2\n- Fix rendering of table colspan with non english locale\n- Fix grid table colspan parsing\n- Add nofollow extension for links\n> 0.7.1\n- Fix issue in smarty pants which could lead to an InvalidCastException\n- Update parsers to latest CommonMark specs\n>0.7.0\n- Update to latest NETStandard.Library 1.6.0\n- Fix issue with digits in auto-identifier extension\n- Fix incorrect start of span calculated for code indented blocks",
"releaseNotes": "> 0.7.5\n- several bug fixes (pipe tables, disable HTML, special attributes, inline maths, abbreviations...)\n- add support for rowspan in grid tables\n> 0.7.4\n- Fix bug with strong emphasis starting at the beginning of a line\n> 0.7.3\n- Fix threading issue with pipeline\n> 0.7.2\n- Fix rendering of table colspan with non english locale\n- Fix grid table colspan parsing\n- Add nofollow extension for links\n> 0.7.1\n- Fix issue in smarty pants which could lead to an InvalidCastException\n- Update parsers to latest CommonMark specs\n>0.7.0\n- Update to latest NETStandard.Library 1.6.0\n- Fix issue with digits in auto-identifier extension\n- Fix incorrect start of span calculated for code indented blocks",
"tags": [ "Markdown CommonMark md html md2html" ]
},
"configurations": {

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) Alexandre Mutel. All rights reserved.&#xD;
This file is licensed under the BSD-Clause 2 license. &#xD;
See the license.txt file in the project root for more information.</s:String></wpf:ResourceDictionary>
See the license.txt file in the project root for more information.</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String></wpf:ResourceDictionary>