Improve Attributes, CustomContainer and GridTable

This commit is contained in:
Alexandre Mutel
2016-03-09 21:21:09 +09:00
parent 9f067c0335
commit 2ac714e41d
12 changed files with 365 additions and 47 deletions

View File

@@ -4,12 +4,18 @@ This section describes the different extensions supported:
## Attributes
Attributes can be attached to a previous inline element or the current block if the previous inline element is a literal. Attributes can be:
Attributes can be attached to:
- The previous inline element if the previous element is not a literal
- The next block if the current block is a paragraph and the attributes is the only inline present in the paragraph
- Or the current block
Attributes can be of 3 kinds:
- An id element, starting by `#` that will be used to set the `id` property of the HTML element
- A class element, starting by `.` that will be appended to the CSS class property of the HTML element
- a `name=value` or `name="value"` that will be appended as an attribute of the HTML element
The following shows that attributes is attached to the current block or the previous inline:
```````````````````````````````` example
# This is a heading with an an attribute{#heading-link}
@@ -23,3 +29,15 @@ This is a heading{#heading-link2}
<p><a href="http://google.com" id="a-link" class="myclass" data-lang="fr" data-value="This is a value">This is a link</a></p>
<h2 id="heading-link2">This is a heading</h2>
````````````````````````````````
The following shows that the attributes is attached to the next fenced code block:
```````````````````````````````` example
{#fenced-id .fenced-class}
~~~
This is a fenced with attached attributes
~~~
.
<pre><code id="fenced-id" class="fenced-class">This is a fenced with attached attributes
</code></pre>
````````````````````````````````

View File

@@ -70,3 +70,14 @@ This is a spoiler
<div id="myspoiler" class="spoiler" myprop="yes">This is a spoiler
</div>
````````````````````````````````
The content of a custom container is not HTML escaped:
```````````````````````````````` example
:::mycontainer
<p>This is a raw spoiler</p>
:::
.
<div class="mycontainer"><p>This is a raw spoiler</p>
</div>
````````````````````````````````

View File

@@ -96,3 +96,91 @@ Col3a</td>
</tbody>
</table>
````````````````````````````````
A row header is separated using `+========+` instead of `+---------+`:
```````````````````````````````` example
+---------+---------+
| This is | a table |
+=========+=========+
.
<table>
<col style="width:50%">
<col style="width:50%">
<thead>
<tr>
<th>This is</th>
<th>a table</th>
</tr>
</thead>
</table>
````````````````````````````````
The last column separator `|` may be omitted:
```````````````````````````````` example
+---------+---------+
| This is | a table with a longer text in the second column
.
<table>
<col style="width:50%">
<col style="width:50%">
<tbody>
<tr>
<td>This is</td>
<td>a table with a longer text in the second column</td>
</tr>
</tbody>
</table>
````````````````````````````````
The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
Total size is : 16
- `----` -> 4
- `--------` -> 8
- `----` -> 4
So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
```````````````````````````````` example
+----+--------+----+
| A | B C D | E |
+----+--------+----+
.
<table>
<col style="width:25%">
<col style="width:50%">
<col style="width:25%">
<tbody>
<tr>
<td>A</td>
<td>B C D</td>
<td>E</td>
</tr>
</tbody>
</table>
````````````````````````````````
Alignment might be specified on the first row using the character `:`:
```````````````````````````````` example
+-----+:---:+-----+
| A | B | C |
+-----+-----+-----+
.
<table>
<col style="width:33.33%">
<col style="width:33.33%">
<col style="width:33.33%">
<tbody>
<tr>
<td>A</td>
<td style="text-align: center;">B</td>
<td>C</td>
</tr>
</tbody>
</table>
````````````````````````````````

View File

@@ -16895,11 +16895,18 @@ namespace Textamina.Markdig.Tests
//
// ## Attributes
//
// Attributes can be attached to a previous inline element or the current block if the previous inline element is a literal. Attributes can be:
// Attributes can be attached to:
// - The previous inline element if the previous element is not a literal
// - The next block if the current block is a paragraph and the attributes is the only inline present in the paragraph
// - Or the current block
//
// Attributes can be of 3 kinds:
//
// - An id element, starting by `#` that will be used to set the `id` property of the HTML element
// - A class element, starting by `.` that will be appended to the CSS class property of the HTML element
// - a `name=value` or `name="value"` that will be appended as an attribute of the HTML element
//
// The following shows that attributes is attached to the current block or the previous inline:
[TestFixture]
public partial class TestExtensionsAttributes
{
@@ -16925,6 +16932,30 @@ namespace Textamina.Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Attributes");
TestParser.TestSpec("# This is a heading with an an attribute{#heading-link}\n\n[This is a link](http://google.com){#a-link .myclass data-lang=fr data-value=\"This is a value\"}\n\nThis is a heading{#heading-link2}\n-----------------", "<h1 id=\"heading-link\">This is a heading with an an attribute</h1>\n<p><a href=\"http://google.com\" id=\"a-link\" class=\"myclass\" data-lang=\"fr\" data-value=\"This is a value\">This is a link</a></p>\n<h2 id=\"heading-link2\">This is a heading</h2>", "attributes");
}
}
// The following shows that the attributes is attached to the next fenced code block:
[TestFixture]
public partial class TestExtensionsAttributes
{
[Test]
public void Example002()
{
// Example 2
// Section: Extensions Attributes
//
// The following CommonMark:
// {#fenced-id .fenced-class}
// ~~~
// This is a fenced with attached attributes
// ~~~
//
// Should be rendered as:
// <pre><code id="fenced-id" class="fenced-class">This is a fenced with attached attributes
// </code></pre>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 2, "Extensions Attributes");
TestParser.TestSpec("{#fenced-id .fenced-class}\n~~~\nThis is a fenced with attached attributes\n~~~ ", "<pre><code id=\"fenced-id\" class=\"fenced-class\">This is a fenced with attached attributes\n</code></pre>", "attributes");
}
}
// # Extensions
//
@@ -17135,6 +17166,141 @@ namespace Textamina.Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 3, "Extensions Grid Table");
TestParser.TestSpec("+---------+---------+---------+\n| Col1 | Col2 | Col3 |\n| Col1a | Col2a | Col3a |\n| Col12 | Col3b |\n| Col123 |", "<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>Col1\nCol1a</td>\n<td>Col2\nCol2a</td>\n<td>Col3\nCol3a</td>\n</tr>\n<tr>\n<td colspan=\"2\">Col12</td>\n<td></td>\n</tr>\n<tr>\n<td colspan=\"3\">Col123</td>\n</tr>\n</tbody>\n</table>", "gridtables");
}
}
// A row header is separated using `+========+` instead of `+---------+`:
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example004()
{
// Example 4
// Section: Extensions Grid Table
//
// The following CommonMark:
// +---------+---------+
// | This is | a table |
// +=========+=========+
//
// Should be rendered as:
// <table>
// <col style="width:50%">
// <col style="width:50%">
// <thead>
// <tr>
// <th>This is</th>
// <th>a table</th>
// </tr>
// </thead>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 4, "Extensions Grid Table");
TestParser.TestSpec("+---------+---------+\n| This is | a table |\n+=========+=========+", "<table>\n<col style=\"width:50%\">\n<col style=\"width:50%\">\n<thead>\n<tr>\n<th>This is</th>\n<th>a table</th>\n</tr>\n</thead>\n</table>", "gridtables");
}
}
// The last column separator `|` may be omitted:
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example005()
{
// Example 5
// Section: Extensions Grid Table
//
// The following CommonMark:
// +---------+---------+
// | This is | a table with a longer text in the second column
//
// Should be rendered as:
// <table>
// <col style="width:50%">
// <col style="width:50%">
// <tbody>
// <tr>
// <td>This is</td>
// <td>a table with a longer text in the second column</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Grid Table");
TestParser.TestSpec("+---------+---------+\n| This is | a table with a longer text in the second column", "<table>\n<col style=\"width:50%\">\n<col style=\"width:50%\">\n<tbody>\n<tr>\n<td>This is</td>\n<td>a table with a longer text in the second column</td>\n</tr>\n</tbody>\n</table>", "gridtables");
}
}
// The respective width of the columns are calculated from the ratio between the total size of the first table row without counting the `+`: `+----+--------+----+` would be divided between:
//
// Total size is : 16
//
// - `----` -> 4
// - `--------` -> 8
// - `----` -> 4
//
// So the width would be 4/16 = 25%, 8/16 = 50%, 4/16 = 25%
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example006()
{
// Example 6
// Section: Extensions Grid Table
//
// The following CommonMark:
// +----+--------+----+
// | A | B C D | E |
// +----+--------+----+
//
// Should be rendered as:
// <table>
// <col style="width:25%">
// <col style="width:50%">
// <col style="width:25%">
// <tbody>
// <tr>
// <td>A</td>
// <td>B C D</td>
// <td>E</td>
// </tr>
// </tbody>
// </table>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Grid Table");
TestParser.TestSpec("+----+--------+----+\n| A | B C D | E |\n+----+--------+----+", "<table>\n<col style=\"width:25%\">\n<col style=\"width:50%\">\n<col style=\"width:25%\">\n<tbody>\n<tr>\n<td>A</td>\n<td>B C D</td>\n<td>E</td>\n</tr>\n</tbody>\n</table>", "gridtables");
}
}
// Alignment might be specified on the first row using the character `:`:
[TestFixture]
public partial class TestExtensionsGridTable
{
[Test]
public void Example007()
{
// Example 7
// Section: Extensions Grid Table
//
// The following CommonMark:
// +-----+:---:+-----+
// | A | B | C |
// +-----+-----+-----+
//
// Should be rendered as:
// <table>
// <col style="width:33.33%">
// <col style="width:33.33%">
// <col style="width:33.33%">
// <tbody>
// <tr>
// <td>A</td>
// <td style="text-align: center;">B</td>
// <td>C</td>
// </tr>
// </tbody>
// </table>
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");
}
}
// # Extensions
//
@@ -17267,6 +17433,29 @@ namespace Textamina.Markdig.Tests
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 5, "Extensions Custom Container");
TestParser.TestSpec(":::spoiler {#myspoiler myprop=yes}\nThis is a spoiler\n:::", "<div id=\"myspoiler\" class=\"spoiler\" myprop=\"yes\">This is a spoiler\n</div>", "customcontainers+attributes");
}
}
// The content of a custom container is not HTML escaped:
[TestFixture]
public partial class TestExtensionsCustomContainer
{
[Test]
public void Example006()
{
// Example 6
// Section: Extensions Custom Container
//
// The following CommonMark:
// :::mycontainer
// <p>This is a raw spoiler</p>
// :::
//
// Should be rendered as:
// <div class="mycontainer"><p>This is a raw spoiler</p>
// </div>
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 6, "Extensions Custom Container");
TestParser.TestSpec(":::mycontainer\n<p>This is a raw spoiler</p>\n:::", "<div class=\"mycontainer\"><p>This is a raw spoiler</p>\n</div>", "customcontainers+attributes");
}
}
// # Extensions
//

View File

@@ -1,5 +1,10 @@
<#/*
The MIT License (MIT)
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.The MIT License (MIT)
This is a modified version of code released previously with the following license:
--------------------------
Copyright (c) 2014 Morten Houston Ludvigsen

View File

@@ -46,6 +46,21 @@ namespace Textamina.Markdig.Extensions.Attributes
}
var objectToAttach = inline == null || inline == processor.Root ? (MarkdownObject) processor.Block : inline;
// If the current block is a Paragraph, but only the HtmlAttributes is used,
// Try to attach the attributes to the following block
var paragraph = objectToAttach as ParagraphBlock;
if (paragraph != null && paragraph.Inline.FirstChild == null && processor.Inline == null && slice.IsEmptyOrWhitespace())
{
var parent = paragraph.Parent;
var indexOfParagraph = parent.IndexOf(paragraph);
if (indexOfParagraph + 1 < parent.Count)
{
objectToAttach = parent[indexOfParagraph + 1];
// We can remove the paragraph as it is empty
parent.RemoveAt(indexOfParagraph);
}
}
var currentHtmlAttributes = objectToAttach.GetAttributes();
attributes.CopyTo(currentHtmlAttributes);

View File

@@ -11,6 +11,8 @@ namespace Textamina.Markdig.Extensions.CustomContainers
public CustomContainerParser()
{
OpeningCharacters = new [] {':'};
// We don't prefix by LanguagePrefix
LanguagePrefix = null;
}
protected override FencedCodeBlock CreateFencedCodeBlock()

View File

@@ -15,29 +15,9 @@ namespace Textamina.Markdig.Extensions.CustomContainers
protected override void Write(HtmlRenderer renderer, CustomContainer obj)
{
renderer.EnsureLine();
renderer.Write("<div");
// If the custom container has already some attributes, try to use them before adding the class for the target
var attributes = obj.TryGetAttributes();
if (attributes == null)
{
if (!string.IsNullOrEmpty(obj.Language))
{
renderer.Write(" class=\"");
renderer.WriteEscape(obj.Language);
renderer.Write("\"");
}
}
else
{
if (!string.IsNullOrEmpty(obj.Language))
{
attributes.AddClass(obj.Language);
}
renderer.WriteAttributes(obj);
}
renderer.Write(">");
renderer.WriteLeafRawLines(obj, true, true);
renderer.Write("<div").WriteAttributes(obj).Write(">");
// We don't escape a CustomContainer
renderer.WriteLeafRawLines(obj, true, false);
renderer.WriteLine("</div>");
}
}

View File

@@ -82,7 +82,7 @@ namespace Textamina.Markdig.Extensions.Tables
int totalWidth = 0;
foreach (var columnSlice in tableState.ColumnSlices)
{
totalWidth += columnSlice.End - columnSlice.Start + 1;
totalWidth += columnSlice.End - columnSlice.Start;
}
// Store the column width and alignment
@@ -91,7 +91,7 @@ namespace Textamina.Markdig.Extensions.Tables
var columnDefinition = new TableColumnDefinition
{
// Column width proportional to the total width
Width = (float)(columnSlice.End - columnSlice.Start + 1) * 100.0f / totalWidth,
Width = (float)(columnSlice.End - columnSlice.Start) * 100.0f / totalWidth,
Alignment = columnSlice.Align,
};
table.ColumnDefinitions.Add(columnDefinition);

View File

@@ -47,6 +47,9 @@ namespace Textamina.Markdig.Extensions.Tables
CurrentColumnSpan = -1;
}
/// <summary>
/// Gets or sets the index position of this column (after the |)
/// </summary>
public int Start { get; set; }
public int End { get; set; }

View File

@@ -2,6 +2,7 @@
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Textamina.Markdig.Helpers;
using Textamina.Markdig.Renderers.Html;
using Textamina.Markdig.Syntax;
namespace Textamina.Markdig.Parsers
@@ -28,6 +29,7 @@ namespace Textamina.Markdig.Parsers
{
OpeningCharacters = new[] {'`', '~'};
InfoParser = DefaultInfoParser;
LanguagePrefix = "language-";
}
/// <summary>
@@ -35,6 +37,12 @@ namespace Textamina.Markdig.Parsers
/// </summary>
public InfoParserDelegate InfoParser { get; set; }
/// <summary>
/// Gets or sets the language prefix (default is "language-")
/// </summary>
public string LanguagePrefix { get; set; }
/// <summary>
/// The default parser for the information after the fenced code block special characters (usually ` or ~)
/// </summary>
@@ -67,7 +75,7 @@ namespace Textamina.Markdig.Parsers
if (firstSpace > 0)
{
infoString = line.Text.Substring(line.Start, firstSpace - line.Start);
infoString = line.Text.Substring(line.Start, firstSpace - line.Start).Trim();
// Skip any spaces after info string
firstSpace++;
@@ -84,11 +92,11 @@ namespace Textamina.Markdig.Parsers
}
}
argString = line.Text.Substring(firstSpace, line.End - firstSpace + 1);
argString = line.Text.Substring(firstSpace, line.End - firstSpace + 1).Trim();
}
else
{
infoString = line.ToString();
infoString = line.ToString().Trim();
}
fenced.Language = HtmlHelper.Unescape(infoString);
@@ -143,6 +151,19 @@ namespace Textamina.Markdig.Parsers
return BlockState.None;
}
// Add the language as an attribute by default
if (!string.IsNullOrEmpty(fenced.Language))
{
if (string.IsNullOrEmpty(LanguagePrefix))
{
fenced.GetAttributes().AddClass(fenced.Language);
}
else
{
fenced.GetAttributes().AddClass(LanguagePrefix + fenced.Language);
}
}
// Store the number of matched string into the context
processor.NewBlocks.Push(fenced);

View File

@@ -22,23 +22,9 @@ namespace Textamina.Markdig.Renderers.Html
renderer.WriteAttributes(obj);
}
renderer.Write("><code");
var fencedCode = obj as FencedCodeBlock;
if (fencedCode != null && !string.IsNullOrEmpty(fencedCode.Language))
if (!OutputAttributesOnPre)
{
// TODO: this could be output on the pre attribute instead
// If the fenced code has already some attributes, try to use them before adding the class language
var attributes = obj.TryGetAttributes();
if (OutputAttributesOnPre || attributes == null)
{
renderer.Write(" class=\"language-");
renderer.WriteEscape(fencedCode.Language);
renderer.Write("\"");
}
else
{
attributes.AddClass("language-" + fencedCode.Language);
renderer.WriteAttributes(obj);
}
renderer.WriteAttributes(obj);
}
renderer.Write(">");
renderer.WriteLeafRawLines(obj, true, true);