Compare commits

...

111 Commits

Author SHA1 Message Date
Alexandre Mutel
16a9bbc84e Bump to 0.14.2 2017-11-01 07:35:07 +01:00
Alexandre Mutel
0e5338a709 Add option to disable smiley parsing in EmojiAndSmiley extension 2017-11-01 07:34:57 +01:00
Alexandre Mutel
9139e0142b Fix issue with emphasis preceded/followed by an HTML entity (#157) 2017-11-01 07:26:28 +01:00
Alexandre Mutel
9a38312df0 Add support for link reference definitions for Normalize renderer (#155) 2017-10-28 09:26:14 +02:00
Alexandre Mutel
d808dcf6f8 Bump to 0.14.1 2017-10-27 22:45:53 +02:00
Alexandre Mutel
d5985fc94c Add missing HtmlBlockRenderer 2017-10-27 22:44:58 +02:00
Alexandre Mutel
994687d5ae Use original bullet character if NormalizeOptions.ListItemCharacter is not set 2017-10-27 22:44:37 +02:00
Alexandre Mutel
2f4e958ab2 Bump to 0.14.0 2017-10-27 22:13:49 +02:00
Alexandre Mutel
26beaa81da Add comments to NormalizeOptions (#155) 2017-10-27 22:08:34 +02:00
Alexandre Mutel
de5ed11963 Add support for NormalizeOptions. Add a few more tests. Better handling of loose lists. 2017-10-27 22:01:43 +02:00
Alexandre Mutel
b557d51276 Don't output a space after a blockquote 2017-10-27 18:41:09 +02:00
Alexandre Mutel
27a8345943 Use Assert.AreEqual instead of string.Compare for better error reporting 2017-10-27 18:34:38 +02:00
Alexandre Mutel
131163ff9a Add bigger sample for normalize (#155) 2017-10-27 18:34:14 +02:00
Alexandre Mutel
241f674b99 Rename test names for normalize (#155) 2017-10-27 18:33:55 +02:00
Alexandre Mutel
194edee243 Fix normalize, don't introduce non necessary new lines (#155) 2017-10-27 18:32:43 +02:00
Alexandre Mutel
0995fa0cec Add tests for backslash and hard lines 2017-10-27 18:32:01 +02:00
Alexandre Mutel
6717be5210 Add support for escape characters for normalize (#155) 2017-10-27 18:30:32 +02:00
Alexandre Mutel
e15745f346 Fix escape inline parser to use the plain string instead of only a single string char (to allow literal continuation) 2017-10-27 18:28:57 +02:00
Alexandre Mutel
a513b0c587 Merge pull request #156 from leotsarev/add-plain-text-helper
Add simple helper that enables conversion to plain text
2017-10-27 10:54:03 +02:00
Leonid Tsarev
72adb963e8 Add simple helper that enables conversion to plain text 2017-10-27 11:35:23 +03:00
Alexandre Mutel
aac7df6b87 Merge remote-tracking branch 'origin/normalize' 2017-10-25 17:11:15 +02:00
Alexandre Mutel
4cf4bfb58f Merge pull request #154 from tthiery/normalize
Add support for normalize (core CommonMark for now)
2017-10-24 17:38:42 +02:00
T. Thiery
59630aec8e Review Fix: Make IntLog10Fast method private static. 2017-10-24 17:27:42 +02:00
Alexandre Mutel
70c4f6deda Merge pull request #140 from peinearydevelopment/master
Added EnableHtmlForBlock flag on HtmlRenderer for issue #104
2017-10-24 17:02:42 +02:00
T. Thiery
e2b3f812cb Merge remote-tracking branch 'upstream/normalize' into normalize 2017-10-24 16:53:42 +02:00
Alexandre Mutel
fc8adc70e0 Merge tag 'v0.13.4' into normalize
# Conflicts:
#	src/Markdig.Tests/Markdig.Tests.csproj
2017-10-24 16:28:58 +02:00
T. Thiery
72c2c06fcb Add normalization tests for space between leaves. 2017-10-24 15:58:44 +02:00
T. Thiery
7174e32b7a Remove incomplete UnorderedList syntax test case 2017-10-24 15:51:13 +02:00
T. Thiery
600219529c Add test approach for AST based arrange. 2017-10-20 16:10:02 +02:00
Alexandre Mutel
d7fd04d14c Bump to 0.13.4 2017-10-17 23:01:26 +02:00
Alexandre Mutel
2147e434f7 Add support for single table header row without a table body rows (#141) 2017-10-17 22:58:25 +02:00
Alexandre Mutel
7bd00d115f Merge pull request #149 from maulwuff/nomnomlSupport
Nomnoml support
2017-10-17 15:44:17 +02:00
maulwuff
80ef9d2799 doc: added nomnoml specs 2017-10-17 14:54:43 +02:00
maulwuff
d6a705d76c new: support nomnoml diagrams 2017-10-17 14:54:43 +02:00
Alexandre Mutel
42472085a6 Add mdtoc simple tool to generate a markdown TOC from a local markdown file or github link to a markdown file 2017-10-15 23:31:14 +02:00
Alexandre Mutel
3897e875ee Add KeepOpeningDigits and DiscardDots options to AutoIdentifiers to match GitHub behavior 2017-10-15 23:29:02 +02:00
T. Thiery
c63392657d Add finish block method and adjusted naming in test suite. 2017-09-18 23:28:34 +02:00
T. Thiery
34579b51a1 Relabeled unit tests. 2017-09-18 23:04:08 +02:00
T. Thiery
1bb35c5fc1 Fixed issues found in straight forward normalization test cases
- Code fences with attributes
- Escaping inline code
- Escaping link titles
- Encode hard line breaks
- HTML Entity emitting
- List intend.
2017-09-18 22:16:50 +02:00
T. Thiery
0c408951b8 Add straight forward unit tests. 2017-09-18 20:42:50 +02:00
Alexandre Mutel
218a094f0d Try to workaround tight/loose lists and paragraphs (wip #17) 2017-09-17 11:08:08 +02:00
Alexandre Mutel
3cc405b05b Add normalize renderers for core CommonMark components (wip #17) 2017-09-17 11:08:08 +02:00
Alexandre Mutel
d58db530bb wip normalize (issue #17) 2017-09-17 11:07:44 +02:00
Alexandre Mutel
3628dc3b17 Merge pull request #142 from mlaily/patch-1
Fix typos in EmojiSpecs.md
2017-09-13 22:30:24 +02:00
Melvyn Laïly
89ff42805d Fix typos in EmojiSpecs.md 2017-09-13 22:26:10 +02:00
Peineary Development
48866a2609 Added EnableHtmlForBlock flag on HtmlRenderer for issue #104 2017-09-11 22:43:23 -04:00
Alexandre Mutel
9cc5856c1c Bump to 0.13.3 2017-08-30 15:47:54 +02:00
Alexandre Mutel
a4bb174a77 Add .editorconfig 2017-08-30 15:44:08 +02:00
Alexandre Mutel
82987fa879 Merge pull request #139 from kenmuse/enhance/yaml-frontmatter
Implement Pandoc compatible front matter parsing
2017-08-30 15:36:38 +02:00
Ken Muse
2df2ff17c5 Replace tabs with spaces 2017-08-30 09:29:27 -04:00
Ken Muse
4e4825cb3f Implement Pandoc compatible front matter parsing 2017-08-29 18:49:14 -04:00
Alexandre Mutel
ed2371beae Update MSBuild.Sdk.Extras to version 1.0.9 to fix compatibility with pack for uap10.0 2017-08-29 08:06:29 +02:00
Alexandre Mutel
2c2898769e Bump to 0.13.2 2017-08-29 07:48:27 +02:00
Alexandre Mutel
001a99ab77 Add support for UAP10.0 (#137) 2017-08-29 07:48:14 +02:00
Alexandre Mutel
9233ec220c Bump to 0.13.1 2017-08-21 16:23:31 +02:00
Alexandre Mutel
bb30dc21c5 Fix indenting issue after a double digit list block using a tab (#134) 2017-08-21 16:04:47 +02:00
Alexandre Mutel
c761fa2243 Bump to 0.13.0 2017-08-03 19:26:48 +02:00
Alexandre Mutel
9d172ffcc1 Update URL links to accept balanced parenthesis for CommonMark specs 0.28 2017-08-03 19:25:17 +02:00
Alexandre Mutel
1863e22328 Update to emphasis to CommonMark specs 0.28 (when *** prefer em, strong vs strong, em) 2017-08-03 19:20:58 +02:00
Alexandre Mutel
c591d1950f Update CommonMark test specs to 0.28 2017-08-03 19:20:23 +02:00
Alexandre Mutel
339fcc9152 Add signed package link to readme.md (#11) 2017-08-03 14:57:28 +02:00
Alexandre Mutel
3a54f06540 Fix appveyor.yml for multiline cmd 2017-08-03 14:47:07 +02:00
Alexandre Mutel
3f305a25a8 Add sign assembly to build 2017-08-03 14:41:52 +02:00
Alexandre Mutel
5a210223b6 Bump to 0.12.4 2017-07-31 11:17:13 +02:00
Alexandre Mutel
a7786d934d Make Profile328 (for sl50 compat) not using unsafe code to pass verifier (issue #131) 2017-07-31 11:17:01 +02:00
Alexandre Mutel
cb3c1f1505 Merge pull request #124 from joannaz/master
Fix abbreviation matching
2017-07-03 14:38:25 +02:00
Joanna Zhang
50b33b8512 Add more test cases for abbreviation 2017-07-03 13:12:23 +01:00
Joanna Zhang
80790b5038 Fix abbreviation parsing 2017-07-03 11:55:31 +01:00
Joanna Zhang
311c28ae60 Fix abbreviation matching 2017-06-29 15:00:56 +01:00
Alexandre Mutel
9906a0554f Merge pull request #123 from joannaz/master
Fix abbreviation at start of paragraphs
2017-06-28 13:59:11 +02:00
Joanna Zhang
7e92f1881d Fix Abbrevation at start 2017-06-28 12:25:14 +01:00
Alexandre Mutel
41911806a0 Bump to 0.12.3 2017-06-26 11:53:59 +02:00
Alexandre Mutel
610f1519b0 Fix issue with HTML blocks for heading h2,h3,h4,h5,h6 that were not correctly identified as HTML blocks as per CommonMark spec 2017-06-26 11:53:47 +02:00
Alexandre Mutel
29aa56b4f1 Bump to 0.12.2 2017-06-18 10:58:40 +02:00
Alexandre Mutel
5004ecedb7 Use TestParser.TestSpec instead to mitigate problem with \r 2017-06-18 10:56:44 +02:00
Alexandre Mutel
ba06a796dc Merge branch 'pr/n121_pearsonn' 2017-06-18 10:50:39 +02:00
Nick Pearson
b1d6f34976 Fix issue in GenericAttributesParser causing inline elements to be skipped 2017-06-14 15:20:16 -07:00
Alexandre Mutel
28f4236a57 Bump to 0.12.1 2017-05-29 12:26:17 +02:00
Alexandre Mutel
0739a82735 Merge pull request #119 from meziantou/master
MediaLinkExtension: Remove unexpected closing tag
2017-05-28 19:00:54 +02:00
Meziantou
3ac9f2e788 Fix issue 2017-05-28 12:18:59 +02:00
Meziantou
d9607cc687 Update tests 2017-05-28 12:18:50 +02:00
Alexandre Mutel
f0573ef9e2 Bump to 0.12.0 2017-05-28 08:24:50 +02:00
Alexandre Mutel
017a3bd7e2 Merge branch 'pr/n118_meziantou' 2017-05-28 08:17:46 +02:00
Alexandre Mutel
d6f9a1055c Remove unnecessary test in BlockProcessor (#115) 2017-05-28 08:14:58 +02:00
Alexandre Mutel
2612e9d565 Fix issue in TryParseAutolink in LinkHelper (#115) 2017-05-28 08:02:55 +02:00
Alexandre Mutel
1076d7af10 Fix issue in ScanEntity (#115) 2017-05-28 07:56:58 +02:00
Meziantou
850cecfa6c Fix issue 2017-05-27 16:16:46 +02:00
Meziantou
bfdb03bd78 Update tests 2017-05-27 16:16:33 +02:00
Alexandre Mutel
56b6e329d5 Add missing update to Tests.csproj 2017-05-18 11:31:51 +02:00
Alexandre Mutel
006e384d1a Update links 2017-05-18 11:31:24 +02:00
Alexandre Mutel
9cd9b683a5 Remove specialized renderer for JiraLinks now going through the standard LinkInline 2017-05-18 11:04:42 +02:00
Alexandre Mutel
e01e2c3d2b Add StringBuilder extensions to accept a StringSlice 2017-05-18 11:04:06 +02:00
Alexandre Mutel
97ce0da564 Update JiraLinks, allow ( and ), use StringSlice instead of strings for key/issue, add support for attributes, add tests 2017-05-18 10:33:16 +02:00
Alexandre Mutel
26c5ce59b5 Update Benchmarks to latest DotNetBenchmark. Remove nuget.targets 2017-05-18 09:33:26 +02:00
Alexandre Mutel
d2d94ecf39 Merge branch 'pr/n113_clarkd' 2017-05-18 09:18:50 +02:00
Dave Clarke
0ec82bb81d Fix comment 2017-05-17 11:55:30 +01:00
Dave Clarke
ad9e941cb0 Fix missing space before target attribute
Rename Key -> ProjectKey
2017-05-16 16:48:25 +01:00
Dave Clarke
27dab6ca6d Automatically link JIRA issue references 2017-05-16 12:02:44 +01:00
Alexandre Mutel
762196f03b Bump to 0.11.0 2017-05-08 12:05:15 +02:00
Alexandre Mutel
26a565fa45 Fix/improve parsing of math inline, improving rules for matching, disallow newline between $$/$ for inline parsing (#87) 2017-05-08 11:50:40 +02:00
Alexandre Mutel
4369db1a43 Fix issue with $$ block parsing that should be interpreted as inline parsing (#105) 2017-05-08 11:48:44 +02:00
Alexandre Mutel
d83f1f87cc Add custom arrows to emoji 2017-05-08 09:25:44 +02:00
Alexandre Mutel
14027f4be3 Migrate to VS2017 - dotnet new csproj 2017-05-06 15:41:07 +02:00
Alexandre Mutel
8bcfb53607 Merge pull request #109 from aKzenT/issue-108
Fix for issue #108
2017-04-29 11:11:05 +02:00
Thomas Krause
700bb21e03 Fix for issue #108 2017-04-28 21:47:41 +02:00
Alexandre Mutel
1bc9d9083c Merge pull request #105 from MatthewRichards/DotTerminatedUrls
Treat trailing full stop after a URL as not being part of the URL
2017-04-04 10:02:38 +02:00
Matthew Richards
3305a74b06 Additional test case 2017-03-30 08:51:05 +01:00
Matthew Richards
6312bc0515 Treat trailing full stop after a URL as not being part of the URL 2017-03-29 16:47:41 +01:00
Alexandre Mutel
b20b111385 Try to workaround tight/loose lists and paragraphs (wip #17) 2016-06-28 23:55:01 +09:00
Alexandre Mutel
6ac2429e2a Add normalize renderers for core CommonMark components (wip #17) 2016-06-28 23:37:52 +09:00
Alexandre Mutel
9d52732f18 wip normalize (issue #17) 2016-06-28 10:53:08 +09:00
114 changed files with 7101 additions and 3764 deletions

38
.editorconfig Normal file
View File

@@ -0,0 +1,38 @@
# EditorConfig is awesome:http://EditorConfig.org
# top-most EditorConfig file
root = true
# All Files
[*]
charset = utf-8
end_of_line = crlf
indent_style = space
indent_size = 4
insert_final_newline = false
trim_trailing_whitespace = true
# Solution Files
[*.sln]
indent_style = tab
# XML Project Files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Configuration Files
[*.{json,xml,yml,config,props,targets,nuspec,resx,ruleset}]
indent_size = 2
# Markdown Files
[*.md]
trim_trailing_whitespace = false
# Web Files
[*.{htm,html,js,ts,css,scss,less}]
indent_size = 2
insert_final_newline = true
# Bash Files
[*.sh]
end_of_line = lf

46
appveyor.yml Normal file
View File

@@ -0,0 +1,46 @@
version: 10.0.{build}
image: Visual Studio 2017
configuration: Release
install:
- ps: >-
cd src
nuget restore Markdig.sln
$env:MARKDIG_BUILD_NUMBER = ([int]$env:APPVEYOR_BUILD_NUMBER).ToString("000")
$env:MARKDIG_VERSION_SUFFIX = ""
$env:appveyor_nuget_push = 'false'
if(-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) {
if($env:appveyor_repo_tag -eq 'True') {
if($env:appveyor_repo_tag_name -match '^v[0-9]') {
$env:appveyor_nuget_push = 'true'
$env:MARKDIG_VERSION_SUFFIX = ""
}
if($env:appveyor_repo_tag_name -eq 'latest') {
$env:appveyor_nuget_push = 'true'
$env:MARKDIG_VERSION_SUFFIX = "pre$env:MARKDIG_BUILD_NUMBER"
}
}
}
build:
project: src/Markdig.sln
verbosity: minimal
before_package:
- cmd: >-
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
msbuild /t:Clean Markdig/Markdig.csproj
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release;SignAssembly=true Markdig/Markdig.csproj
artifacts:
- path: src\Markdig\Bin\Release\*.nupkg
name: Markdig Nugets
deploy:
- provider: NuGet
api_key:
secure: 7cthHh+wYWZjhqxaxR6QObRaRnstvFkQOY7MkxIsC5kpQEBlKZXuinf0IybbYxJt
on:
appveyor_nuget_push: true

View File

@@ -14,54 +14,58 @@ You can **try Markdig online** and compare it to other implementations on [babel
- **Abstract Syntax Tree** with precise source code location for syntax tree, useful when building a Markdown editor.
- Checkout [MarkdownEditor for Visual Studio](https://visualstudiogallery.msdn.microsoft.com/eaab33c3-437b-4918-8354-872dfe5d1bfe) powered by Markdig!
- Converter to **HTML**
- Passing more than **600+ tests** from the latest [CommonMark specs (0.27)](http://spec.commonmark.org/)
- Passing more than **600+ tests** from the latest [CommonMark specs (0.28)](http://spec.commonmark.org/)
- Includes all the core elements of CommonMark:
- including **GFM fenced code blocks**.
- **Extensible** architecture
- Even the core Markdown/CommonMark parsing is pluggable, so it allows to disable builtin Markdown/Commonmark parsing (e.g [Disable HTML parsing](https://github.com/lunet-io/markdig/blob/7964bd0160d4c18e4155127a4c863d61ebd8944a/src/Markdig/MarkdownExtensions.cs#L306)) or change behaviour (e.g change matching `#` of a headers with `@`)
- Built-in with **20+ extensions**, including:
- 2 kind of tables:
- **Pipe tables** (inspired from Github tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- **Grid tables** (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
- **Extra emphasis** (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- [**Pipe tables**](src/Markdig.Tests/Specs/PipeTableSpecs.md) (inspired from Github tables and [PanDoc - Pipe Tables](http://pandoc.org/README.html#extension-pipe_tables))
- [**Grid tables**](src/Markdig.Tests/Specs/GridTableSpecs.md) (inspired from [Pandoc - Grid Tables](http://pandoc.org/README.html#extension-grid_tables))
- [**Extra emphasis**](src/Markdig.Tests/Specs/EmphasisExtraSpecs.md) (inspired from [Pandoc - Emphasis](http://pandoc.org/README.html#strikeout) and [Markdown-it](https://markdown-it.github.io/))
- strike through `~~`,
- Subscript `~`
- Superscript `^`
- Inserted `++`
- Marked `==`
- **Special attributes** or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- **Definition lists** (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- **Footnotes** (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- **Auto-identifiers** for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
- **Auto-links** generates links if a text starts with `http://` or `https://` or `ftp://` or `mailto:` or `www.xxx.yyy`
- **Task Lists** inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments).
- **Extra bullet lists**, supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
- **Media support** for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
- **Abbreviations** (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
- **Citation** text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
- **Custom containers** similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
- **Figures** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
- **Footers** (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
- **Mathematics**/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- **Soft lines as hard lines**
- **Emoji** support (inspired from [Markdown-it](https://markdown-it.github.io/))
- **SmartyPants** (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- **Bootstrap** class (to output bootstrap class)
- **Diagrams** extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports only for [`mermaid` diagrams](https://knsv.github.io/mermaid/))
- **YAML frontmatter** to parse without evaluating the frontmatter and to discard it from the HTML output (typically used for previewing without the frontmatter in MarkdownEditor)
- [**Special attributes**](src/Markdig.Tests/Specs/GenericAttributesSpecs.md) or attached HTML attributes (inspired from [PHP Markdown Extra - Special Attributes](https://michelf.ca/projects/php-markdown/extra/#spe-attr))
- [**Definition lists**](src/Markdig.Tests/Specs/DefinitionListSpecs.md) (inspired from [PHP Markdown Extra - Definitions Lists](https://michelf.ca/projects/php-markdown/extra/#def-list))
- [**Footnotes**](src/Markdig.Tests/Specs/FootnotesSpecs.md) (inspired from [PHP Markdown Extra - Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes))
- [**Auto-identifiers**](src/Markdig.Tests/Specs/AutoIdentifierSpecs.md) for headings (similar to [Pandoc - Auto Identifiers](http://pandoc.org/README.html#extension-auto_identifiers))
- [**Auto-links**](src/Markdig.Tests/Specs/AutoLinks.md) generates links if a text starts with `http://` or `https://` or `ftp://` or `mailto:` or `www.xxx.yyy`
- [**Task Lists**](src/Markdig.Tests/Specs/TaskListSpecs.md) inspired from [Github Task lists](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments).
- [**Extra bullet lists**](src/Markdig.Tests/Specs/ListExtraSpecs.md), supporting alpha bullet `a.` `b.` and roman bullet (`i`, `ii`...etc.)
- [**Media support**](src/Markdig.Tests/Specs/MediaSpecs.md) for media url (youtube, vimeo, mp4...etc.) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/embedded-audio-and-video/441))
- [**Abbreviations**](src/Markdig.Tests/Specs/AbbreviationSpecs.md) (inspired from [PHP Markdown Extra - Abbreviations](https://michelf.ca/projects/php-markdown/extra/#abbr))
- [**Citation**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) text by enclosing `""...""` (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/referencing-creative-works-with-cite/892))
- [**Custom containers**](src/Markdig.Tests/Specs/CustomContainerSpecs.md) similar to fenced code block `:::` for generating a proper `<div>...</div>` instead (inspired by this [CommonMark discussion ](https://talk.commonmark.org/t/custom-container-for-block-and-inline/2051))
- [**Figures**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/image-tag-should-expand-to-figure-when-used-with-title/265/5))
- [**Footers**](src/Markdig.Tests/Specs/FigureFooterAndCiteSpecs.md) (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/syntax-for-footer/2070))
- [**Mathematics**](src/Markdig.Tests/Specs/MathSpecs.md)/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- [**Soft lines as hard lines**](src/Markdig.Tests/Specs/HardlineBreakSpecs.md)
- [**Emoji**](src/Markdig.Tests/Specs/EmojiSpecs.md) support (inspired from [Markdown-it](https://markdown-it.github.io/))
- [**SmartyPants**](src/Markdig.Tests/Specs/SmartyPantsSpecs.md) (inspired from [Daring Fireball - SmartyPants](https://daringfireball.net/projects/smartypants/))
- [**Bootstrap**](src/Markdig.Tests/Specs/BootstrapSpecs.md) class (to output bootstrap class)
- [**Diagrams**](src/Markdig.Tests/Specs/DiagramsSpecs.md) extension whenever a fenced code block contains a special keyword, it will be converted to a div block with the content as-is (currently, supports [`mermaid`](https://knsv.github.io/mermaid/) and [`nomnoml`](https://github.com/skanaar/nomnoml) diagrams)
- [**YAML frontmatter**](src/Markdig.Tests/Specs/YamlSpecs.md) to parse without evaluating the frontmatter and to discard it from the HTML output (typically used for previewing without the frontmatter in MarkdownEditor)
- [**JIRA links**](src/Markdig.Tests/Specs/JiraLinks.md) to automatically generate links for JIRA project references (Thanks to @clarkd: https://github.com/clarkd/MarkdigJiraLinker)
- Compatible with .NET 3.5, 4.0+ and .NET Core (`netstandard1.1+`)
## Documentation
> The repository is under construction. There will be a dedicated website and proper documentation at some point!
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
While there is not yet a dedicated documentation, you can find from the [specs documentation](src/Markdig.Tests/Specs/readme.md) how to use these extensions.
In the meantime, you can have a "behind the scene" article about Markdig in my blog post ["Implementing a Markdown Engine for .NET"](http://xoofx.com/blog/2016/06/13/implementing-a-markdown-processor-for-dotnet/)
## Download
Markdig is available as a NuGet package: [![NuGet](https://img.shields.io/nuget/v/Markdig.svg)](https://www.nuget.org/packages/Markdig/)
Also [Markdig.Signed](https://www.nuget.org/packages/Markdig.Signed/) NuGet package provides signed assemblies.
## Usage
The main entry point for the API is the `Markdig.Markdown` class:
@@ -73,7 +77,7 @@ var result = Markdown.ToHtml("This is a text with some *emphasis*");
Console.WriteLine(result); // prints: <p>This is a text with some <em>emphasis</em></p>
```
In order to activate most of all advanced extensions (except Emoji, SoftLine as HarLine and SmartyPants)
In order to activate most of all advanced extensions (except Emoji, SoftLine as HardLine, JiraLinks and SmartyPants)
```csharp
// Configure the pipeline with all advanced extensions active
@@ -192,6 +196,7 @@ Thanks also to the project [BenchmarkDotNet](https://github.com/PerfDotNet/Bench
Some decoding part (e.g HTML [EntityHelper.cs](https://github.com/lunet-io/markdig/blob/master/src/Markdig/Helpers/EntityHelper.cs)) have been re-used from [CommonMark.NET](https://github.com/Knagis/CommonMark.NET)
Thanks to the work done by @clarkd on the JIRA Link extension (https://github.com/clarkd/MarkdigJiraLinker), now included with this project!
## Author
Alexandre MUTEL aka [xoofx](http://xoofx.com)

View File

@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Markdig.Benchmarks</RootNamespace>
<AssemblyName>Markdig.Benchmarks</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(NuGetPackageRoot)' == ''">
<NuGetPackageRoot>$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
</PropertyGroup>
<ImportGroup>
<Import Project="$(NuGetPackageRoot)microsoft.diagnostics.tracing.traceevent\1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.diagnostics.tracing.traceevent\1.0.41\build\Microsoft.Diagnostics.Tracing.TraceEvent.targets')" />
</ImportGroup>
</Project>

View File

@@ -131,7 +131,7 @@ namespace Testamina.Markdig.Benchmarks
var config = ManualConfig.Create(DefaultConfig.Instance);
//var gcDiagnoser = new MemoryDiagnoser();
//config.Add(new Job { Mode = Mode.SingleRun, LaunchCount = 2, WarmupCount = 2, IterationTime = 1024, TargetCount = 10 });
config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
//config.Add(new Job { Mode = Mode.Throughput, LaunchCount = 2, WarmupCount = 2, TargetCount = 10 });
//config.Add(gcDiagnoser);
//var config = DefaultConfig.Instance;

View File

@@ -1,3 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>

View File

@@ -4,7 +4,7 @@
"win-x64": {}
},
"frameworks": {
"net451": {
"net46": {
"compilationOptions": {
"define": [
"CLASSIC"
@@ -19,9 +19,9 @@
}
},
"dependencies": {
"BenchmarkDotNet": "0.9.6",
"BenchmarkDotNet.Diagnostics.Windows": "0.9.6",
"CommonMark.NET": "0.11.0",
"BenchmarkDotNet": "0.10.6",
"BenchmarkDotNet.Diagnostics.Windows": "0.10.6",
"CommonMark.NET": "0.15.1",
"MarkdownSharp": "1.13.0.0",
"Microsoft.Diagnostics.Runtime": "0.8.31-beta",
"Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41.0"

View File

@@ -39,10 +39,6 @@
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="Markdig">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Markdig\Bin\$(Configuration)\net40\Markdig.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@@ -65,7 +61,9 @@
<Compile Include="TestHtmlHelper.cs" />
<Compile Include="TestLineReader.cs" />
<Compile Include="TestLinkHelper.cs" />
<Compile Include="TestNormalize.cs" />
<Compile Include="TestOrderedList.cs" />
<Compile Include="TestPlainText.cs" />
<Compile Include="TestPragmaLines.cs" />
<Compile Include="TestSourcePosition.cs" />
<Compile Include="TestStringSliceList.cs" />
@@ -76,6 +74,7 @@
<ItemGroup>
<None Include="App.config" />
<None Include="project.json" />
<None Include="Specs\JiraLinks.md" />
<None Include="Specs\AutoLinks.md" />
<None Include="Specs\AutoIdentifierSpecs.md" />
<None Include="Specs\AbbreviationSpecs.md" />
@@ -91,6 +90,7 @@
<None Include="Specs\BootstrapSpecs.md" />
<None Include="Specs\DiagramsSpecs.md" />
<None Include="Specs\NoHtmlSpecs.md" />
<None Include="Specs\readme.md" />
<None Include="Specs\YamlSpecs.md" />
<None Include="Specs\TaskListSpecs.md" />
<None Include="Specs\SmartyPantsSpecs.md" />
@@ -109,6 +109,12 @@
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj">
<Project>{8a58a7e2-627c-4f41-933f-5ac92adfab48}</Project>
<Name>Markdig</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -77,3 +77,24 @@ Abbreviations should match whole word only, even if the word is the entire conte
.
<p>1.1A</p>
````````````````````````````````
Abbreviations should match whole word only, even if there is another glossary term:
```````````````````````````````` example
*[SCO]: First
*[SCOM]: Second
SCOM
.
<p><abbr title="Second">SCOM</abbr></p>
````````````````````````````````
Abbreviations should only match when surrounded by whitespace:
```````````````````````````````` example
*[PR]: Pull Request
PRAA
.
<p>PRAA</p>
````````````````````````````````

View File

@@ -23,4 +23,35 @@ graph TD;
</div>
````````````````````````````````
## nomnoml diagrams
Using a fenced code block with the `nomnoml` language info will output a `<div class='nomnoml'>` instead of a `pre/code` block:
```````````````````````````````` example
```nomnoml
[example|
propertyA: Int
propertyB: string
|
methodA()
methodB()
|
[subA]--[subB]
[subA]-:>[sub C]
]
```
.
<div class="nomnoml">[example|
propertyA: Int
propertyB: string
|
methodA()
methodB()
|
[subA]--[subB]
[subA]-:>[sub C]
]
</div>
````````````````````````````````
TODO: Add other text diagram languages

View File

@@ -31,9 +31,9 @@ We all need :), it makes us :muscle:. (and :ok_hand:).
Sentences can end with Emoji:
```````````````````````````````` example
This is a sentance :ok_hand:
This is a sentence :ok_hand:
and keeps going to the next line :)
.
<p>This is a sentance 👌
<p>This is a sentence 👌
and keeps going to the next line 😃</p>
````````````````````````````````
````````````````````````````````

View File

@@ -27,13 +27,13 @@ The following shows that attributes is attached to the current block or the prev
This is a heading{#heading-link2}
-----------------
This is a paragraph with an attached attributes {#myparagraph attached-bool-property}
This is a paragraph with an attached attributes {#myparagraph attached-bool-property attached-bool-property2}
.
<h1 id="heading-link">This is a heading with an an attribute</h1>
<h1 id="heading-link2">This is a heading</h1>
<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>
<p id="myparagraph" attached-bool-property>This is a paragraph with an attached attributes </p>
<p id="myparagraph" attached-bool-property attached-bool-property2>This is a paragraph with an attached attributes </p>
````````````````````````````````
The following shows that attributes can be attached to the next block if they are used inside a single line just preceding the block (and preceded by a blank line or beginning of a block container):

View File

@@ -0,0 +1,77 @@
## Jira Links
The JiraLinks extension will automatically add links to JIRA issue items within your markdown, e.g. XX-1234. For this to happen, you must configure the extension when adding to the pipeline, e.g.
```
var pipeline = new MarkdownPipelineBuilder()
.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"))
.Build();
```
The rules for detecting a link are:
- The project key must be composed of onre or more capitalised ASCII letter `[A-Z]+`
- A single hypen `-` must separate the project key and issue number.
- The issue number is composed of 1 or more digits `[0, 9]+`
- The reference must be preceeded by either `(` or whitespace or EOF.
- The reference must be followed by either `)` or whitespace or EOF.
The following are valid examples:
```````````````````````````````` example
This is a ABCD-123 issue
.
<p>This is a <a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a> issue</p>
````````````````````````````````
```````````````````````````````` example
This is a KIRA-1 issue
.
<p>This is a <a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a> issue</p>
````````````````````````````````
```````````````````````````````` example
This is a Z-1 issue
.
<p>This is a <a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a> issue</p>
````````````````````````````````
These are also valid links with `(` and `)`:
```````````````````````````````` example
This is a (ABCD-123) issue
.
<p>This is a (<a href="http://your.company.abc/browse/ABCD-123" target="blank">ABCD-123</a>) issue</p>
````````````````````````````````
```````````````````````````````` example
This is a (KIRA-1) issue
.
<p>This is a (<a href="http://your.company.abc/browse/KIRA-1" target="blank">KIRA-1</a>) issue</p>
````````````````````````````````
```````````````````````````````` example
This is a (Z-1) issue
.
<p>This is a (<a href="http://your.company.abc/browse/Z-1" target="blank">Z-1</a>) issue</p>
````````````````````````````````
These are not valid links:
```````````````````````````````` example
This is not aJIRA-123 issue
.
<p>This is not aJIRA-123 issue</p>
````````````````````````````````
```````````````````````````````` example
This is not JIRA-123a issue
.
<p>This is not JIRA-123a issue</p>
````````````````````````````````
```````````````````````````````` example
This is not JIRA- issue
.
<p>This is not JIRA- issue</p>
````````````````````````````````

View File

@@ -20,12 +20,47 @@ This is a $$math block$$
<p>This is a <span class="math">math block</span></p>
````````````````````````````````
The opening `$` and closing `$` is following the rules of the emphasis delimiter `_`:
Newlines inside an inline math are not allowed:
```````````````````````````````` example
This is not a $ math block $
This is not a $$math
block$$ and? this is a $$math block$$
.
<p>This is not a $ math block $</p>
<p>This is not a $$math
block$$ and? this is a <span class="math">math block</span></p>
````````````````````````````````
```````````````````````````````` example
This is not a $math
block$ and? this is a $math block$
.
<p>This is not a $math
block$ and? this is a <span class="math">math block</span></p>
````````````````````````````````
An opening `$` can be followed by a space if the closing is also preceded by a space `$`:
```````````````````````````````` example
This is a $ math block $
.
<p>This is a <span class="math">math block</span></p>
````````````````````````````````
```````````````````````````````` example
This is a $ math block $ after
.
<p>This is a <span class="math">math block</span> after</p>
````````````````````````````````
```````````````````````````````` example
This is a $$ math block $$ after
.
<p>This is a <span class="math">math block</span> after</p>
````````````````````````````````
```````````````````````````````` example
This is a not $ math block$ because there is not a whitespace before the closing
.
<p>This is a not $ math block$ because there is not a whitespace before the closing</p>
````````````````````````````````
For the opening `$` it requires a space or a punctuation before (but cannot be used within a word):
@@ -82,6 +117,13 @@ This is *a $math* block$
.
<p>This is *a <span class="math">math* block</span></p>
````````````````````````````````
An opening $$ at the beginning of a line should not be interpreted as a Math block:
```````````````````````````````` example
$$ math $$ starting at a line
.
<p><span class="math">math</span> starting at a line</p>
````````````````````````````````
## Math Block

View File

@@ -10,7 +10,10 @@ Allows to embed audio/video links to popular website:
![Video1](https://www.youtube.com/watch?v=mswPy5bt3TQ)
![Video2](https://vimeo.com/8607834)
![Video3](https://sample.com/video.mp4)
.
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
<p><video width="500" height="281" controls><source type="video/mp4" src="https://sample.com/video.mp4"></source></video></p>
````````````````````````````````

View File

@@ -513,6 +513,39 @@ a | b
</table>
````````````````````````````````
** Rule #9**
It is possible to have a single row header only:
```````````````````````````````` example
a | b
-- | --
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
</table>
````````````````````````````````
```````````````````````````````` example
|a|b|c
|---|---|---|
.
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
</table>
````````````````````````````````
** Tests **
Tests trailing spaces after pipes

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
<#/*
<#/*
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)
@@ -36,9 +36,10 @@ SOFTWARE.
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.CodeDom" #>
<#@ import namespace="System.CodeDom.Compiler" #>
<#@ output extension=".cs" #><#
<#@ output extension=".cs" encoding="utf-8"#>
<#
var specFiles = new KeyValuePair<string, string>[] {
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/791b1c121f16d3d7e80837c6f52917e57bbb2f61/spec.txt", string.Empty), // 0.27
new KeyValuePair<string, string>("https://raw.githubusercontent.com/jgm/CommonMark/4ec06917c3a3632be4a935ffa0973092bd2621be/spec.txt", string.Empty), // 0.28
new KeyValuePair<string, string>(Host.ResolvePath("PipeTableSpecs.md"), "pipetables|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("FootnotesSpecs.md"), "footnotes|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("GenericAttributesSpecs.md"), "attributes|advanced"),
@@ -61,6 +62,7 @@ SOFTWARE.
new KeyValuePair<string, string>(Host.ResolvePath("NoHtmlSpecs.md"), "nohtml"),
new KeyValuePair<string, string>(Host.ResolvePath("YamlSpecs.md"), "yaml"),
new KeyValuePair<string, string>(Host.ResolvePath("AutoLinks.md"), "autolinks|advanced"),
new KeyValuePair<string, string>(Host.ResolvePath("JiraLinks.md"), "jiralinks"),
};
var emptyLines = false;
var displayEmptyLines = false;

View File

@@ -13,13 +13,13 @@ namespace Markdig.Tests
[Test]
public void StrongNormal()
{
TestParser.TestSpec("***Strong emphasis*** normal", "<p><strong><em>Strong emphasis</em></strong> normal</p>", "");
TestParser.TestSpec("***Strong emphasis*** normal", "<p><em><strong>Strong emphasis</strong></em> normal</p>", "");
}
[Test]
public void NormalStrongNormal()
{
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <strong><em>Strong emphasis</em></strong> normal</p>", "");
TestParser.TestSpec("normal ***Strong emphasis*** normal", "<p>normal <em><strong>Strong emphasis</strong></em> normal</p>", "");
}
}
}

View File

@@ -42,3 +42,62 @@ This is a text
<p>This is a text</p>
````````````````````````````````
It can end with three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects exactly three dots `...`:
```````````````````````````````` example
---
this: is a frontmatter
....
This is a text
.
````````````````````````````````
Front matter ends with the first line containing three dots `...` or three dashes `...`:
```````````````````````````````` example
---
this: is a frontmatter
....
Hello
---
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects whitespace can exist after the leading characters
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````
It expects whitespace can exist after the trailing characters
```````````````````````````````` example
---
this: is a frontmatter
...
This is a text
.
<p>This is a text</p>
````````````````````````````````

View File

@@ -0,0 +1,36 @@
# Documentation Tests/Specs
You will find from the following links the supported extensions in markdig and their usage:
- 2 kind of tables:
- [**Pipe tables**](PipeTableSpecs.md)
- [**Grid tables**](GridTableSpecs.md)
- [**Extra emphasis**](EmphasisExtraSpecs.md)
- strike through `~~`,
- Subscript `~`
- Superscript `^`
- Inserted `++`
- Marked `==`
- [**Special attributes**](GenericAttributesSpecs.md)
- [**Definition lists**](DefinitionListSpecs.md)
- [**Footnotes**](FootnotesSpecs.md)
- [**Auto-identifiers**](AutoIdentifierSpecs.md)
- [**Auto-links**](AutoLinks.md)
- [**Task Lists**](TaskListSpecs.md)
- [**Extra bullet lists**](ListExtraSpecs.md)
- [**Media support**](MediaSpecs.md)
- [**Abbreviations**](AbbreviationSpecs.md)
- [**Citation**](FigureFooterAndCiteSpecs.md)
- [**Custom containers**](CustomContainerSpecs.md)
- [**Figures**](FigureFooterAndCiteSpecs.md)
- [**Footers**](FigureFooterAndCiteSpecs.md)
- [**Mathematics**](MathSpecs.md)/Latex extension by enclosing `$$` for block and `$` for inline math (inspired from this [CommonMark discussion](https://talk.commonmark.org/t/mathematics-extension/457/31))
- [**Soft lines as hard lines**](HardlineBreakSpecs.md)
- [**Emoji**](EmojiSpecs.md)
- [**SmartyPants**](SmartyPantsSpecs.md)
- [**Bootstrap**](BootstrapSpecs.md)
- [**Diagrams**](DiagramsSpecs.md)
- [**YAML frontmatter**](YamlSpecs.md)
- [**JIRA links**](JiraLinks.md)
> Notice that the links above are not yet the final documentation but are "specification" files used for testing the correctness of markdig for each extension

View File

@@ -1,6 +1,8 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Security;
using NUnit.Framework;
using Markdig.Helpers;
using Markdig.Syntax;
@@ -30,6 +32,18 @@ namespace Markdig.Tests
Assert.AreEqual(')', text.CurrentChar);
}
[Test]
[TestCase("http://google.com.")]
[TestCase("http://google.com. ")]
public void TestUrlTrailingFullStop(string uri)
{
var text = new StringSlice(uri);
string link;
Assert.True(LinkHelper.TryParseUrl(ref text, out link));
Assert.AreEqual("http://google.com", link);
Assert.AreEqual('.', text.CurrentChar);
}
[Test]
public void TestUrlNestedParenthesis()
{

View File

@@ -0,0 +1,452 @@
// 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;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.IO;
using Markdig.Renderers.Normalize;
using Markdig.Helpers;
namespace Markdig.Tests
{
[TestFixture]
public class TestNormalize
{
[Test]
public void SyntaxCodeBlock()
{
AssertSyntax("````csharp\npublic void HelloWorld()\n{\n}\n````", new FencedCodeBlock(null)
{
FencedChar = '`',
FencedCharCount = 4,
Info = "csharp",
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
AssertSyntax(" public void HelloWorld()\n {\n }", new CodeBlock(null)
{
Lines = new StringLineGroup(4)
{
new StringSlice("public void HelloWorld()"),
new StringSlice("{"),
new StringSlice("}"),
}
});
}
[Test]
public void SyntaxHeadline()
{
AssertSyntax("## Headline", new HeadingBlock(null)
{
HeaderChar = '#',
Level = 2,
Inline = new ContainerInline().AppendChild(new LiteralInline("Headline")),
});
}
[Test]
public void SyntaxParagraph()
{
AssertSyntax("This is a normal paragraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a normal paragraph")),
});
AssertSyntax("This is a\nnormal\nparagraph", new ParagraphBlock()
{
Inline = new ContainerInline()
.AppendChild(new LiteralInline("This is a"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("normal"))
.AppendChild(new LineBreakInline())
.AppendChild(new LiteralInline("paragraph")),
});
}
[Test]
public void CodeBlock()
{
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }");
AssertNormalizeNoTrim(" public void HelloWorld();\n {\n }\n\ntext after two newlines");
AssertNormalizeNoTrim("````\npublic void HelloWorld();\n{\n}\n````\n\ntext after two newlines");
AssertNormalizeNoTrim("````csharp\npublic void HelloWorld();\n{\n}\n````");
AssertNormalizeNoTrim("````csharp hideNewKeyword=true\npublic void HelloWorld();\n{\n}\n````");
}
[Test]
public void Heading()
{
AssertNormalizeNoTrim("# Heading");
AssertNormalizeNoTrim("## Heading");
AssertNormalizeNoTrim("### Heading");
AssertNormalizeNoTrim("#### Heading");
AssertNormalizeNoTrim("##### Heading");
AssertNormalizeNoTrim("###### Heading");
AssertNormalizeNoTrim("###### Heading\n\ntext after two newlines");
AssertNormalizeNoTrim("# Heading\nAnd Text1\n\nAndText2", options: new NormalizeOptions() { EmptyLineAfterHeading = false });
AssertNormalizeNoTrim("Heading\n=======\n\ntext after two newlines", "# Heading\n\ntext after two newlines");
}
[Test]
public void Backslash()
{
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void HtmlBlock()
{
/*AssertNormalizeNoTrim(@"<div id=""foo"" class=""bar
baz"">
</ div >");*/ // TODO: Bug: Throws Exception during emit
}
[Test]
public void Paragraph()
{
AssertNormalizeNoTrim("This is a plain paragraph");
AssertNormalizeNoTrim(@"This
is
a
plain
paragraph");
}
[Test]
public void ParagraphMulti()
{
AssertNormalizeNoTrim(@"line1
line2
line3");
}
[Test]
public void ListUnordered()
{
AssertNormalizeNoTrim(@"- a
- b
- c");
}
[Test]
public void ListUnorderedLoose()
{
AssertNormalizeNoTrim(@"- a
- b
- c");
}
[Test]
public void ListOrderedLooseAndCodeBlock()
{
AssertNormalizeNoTrim(@"1. ```
foo
```
bar");
}
[Test, Ignore("Not sure this is the correct normalize for this one. Need to check the specs")]
public void ListUnorderedLooseTop()
{
AssertNormalizeNoTrim(@"* foo
* bar
baz", options: new NormalizeOptions() { ListItemCharacter = '*' });
}
[Test]
public void ListUnorderedLooseMultiParagraph()
{
AssertNormalizeNoTrim(
@"- a
And another paragraph a
- b
And another paragraph b
- c");
}
[Test]
public void ListOrdered()
{
AssertNormalizeNoTrim(@"1. a
2. b
3. c");
}
[Test]
public void ListOrderedAndIntended()
{
AssertNormalizeNoTrim(@"1. a
2. b
- foo
- bar
a) 1234
b) 1324
3. c
4. c
5. c
6. c
7. c
8. c
9. c
10. c
- Foo
- Bar
11. c
12. c");
}
[Test]
public void HeaderAndParagraph()
{
AssertNormalizeNoTrim(@"# heading
paragraph
paragraph2 without newlines");
}
[Test]
public void QuoteBlock()
{
AssertNormalizeNoTrim(@"> test1
>
> test2");
AssertNormalizeNoTrim(@"> test1
This is a continuation
> test2",
@"> test1
> This is a continuation
> test2"
);
AssertNormalizeNoTrim(@"> test1
> -foobar
asdf
> test2
> -foobar sen.");
}
[Test]
public void ThematicBreak()
{
AssertNormalizeNoTrim("***\n");
AssertNormalizeNoTrim("* * *\n", "***\n");
}
[Test]
public void AutolinkInline()
{
AssertNormalizeNoTrim("This has a <auto.link.com>");
}
[Test]
public void CodeInline()
{
AssertNormalizeNoTrim("This has a `HelloWorld()` in it");
AssertNormalizeNoTrim(@"This has a ``Hello`World()`` in it");
}
[Test]
public void EmphasisInline()
{
AssertNormalizeNoTrim("This is a plain **paragraph**");
AssertNormalizeNoTrim("This is a plain *paragraph*");
AssertNormalizeNoTrim("This is a plain _paragraph_");
AssertNormalizeNoTrim("This is a plain __paragraph__");
AssertNormalizeNoTrim("This is a pl*ai*n **paragraph**");
}
[Test]
public void LineBreakInline()
{
AssertNormalizeNoTrim("normal\nline break");
AssertNormalizeNoTrim("hard \nline break");
AssertNormalizeNoTrim("This is a hardline \nAnd this is another hardline\\\nThis is standard newline");
AssertNormalizeNoTrim("This is a line\nWith another line\nAnd a last line");
}
[Test]
public void LinkInline()
{
AssertNormalizeNoTrim("This is a [link](http://company.com)");
AssertNormalizeNoTrim("This is an ![image](http://company.com)");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy Company"")");
AssertNormalizeNoTrim(@"This is a [link](http://company.com ""Crazy \"" Company"")");
}
[Test]
public void LinkReferenceDefinition()
{
// Full link
AssertNormalizeNoTrim("This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("[MyLink]: http://company.com\nThis is a [link][MyLink]",
"This is a [link][MyLink]\n\n[MyLink]: http://company.com");
AssertNormalizeNoTrim("This is a [link][MyLink] a normal link [link](http://google.com) and another def link [link2][MyLink2]\n\n[MyLink]: http://company.com\n[MyLink2]: http://company2.com");
// Collapsed link
AssertNormalizeNoTrim("This is a [link][]\n\n[link]: http://company.com");
// Shortcut link
AssertNormalizeNoTrim("This is a [link]\n\n[link]: http://company.com");
}
[Test]
public void EscapeInline()
{
AssertNormalizeNoTrim("This is an escape \\* with another \\[");
}
[Test]
public void HtmlEntityInline()
{
AssertNormalizeNoTrim("This is a &auml; blank");
}
[Test]
public void HtmlInline()
{
AssertNormalizeNoTrim("foo <hr/> bar");
AssertNormalizeNoTrim(@"foo <hr foo=""bar""/> bar");
}
[Test]
public void SpaceBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void SpaceBetweenNodesEvenForHeadlines()
{
AssertNormalizeNoTrim("# Hello World\n## Chapter 1\nFoobar is a better bar.",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceRemoveAtStartAndEnd()
{
AssertNormalizeNoTrim("\n\n# Hello World\n## Chapter 1\nFoobar is a better bar.\n\n",
"# Hello World\n\n## Chapter 1\n\nFoobar is a better bar.");
}
[Test]
public void SpaceShortenBetweenNodes()
{
AssertNormalizeNoTrim("# Hello World\n\n\n\nFoobar is a better bar.",
"# Hello World\n\nFoobar is a better bar.");
}
[Test]
public void BiggerSample()
{
var input = @"# Heading 1
This is a paragraph
This is another paragraph
- This is a list item 1
- This is a list item 2
- This is a list item 3
```C#
This is a code block
```
> This is a quote block
This is an indented code block
line 2 of indented
This is a last line";
AssertNormalizeNoTrim(input);
}
private static void AssertSyntax(string expected, MarkdownObject syntax)
{
var writer = new StringWriter();
var normalizer = new NormalizeRenderer(writer);
var document = new MarkdownDocument();
if (syntax is Block)
{
document.Add(syntax as Block);
}
else
{
throw new InvalidOperationException();
}
normalizer.Render(document);
var actual = writer.ToString();
Assert.AreEqual(expected, actual);
}
public void AssertNormalizeNoTrim(string input, string expected = null, NormalizeOptions options = null)
=> AssertNormalize(input, expected, false, options);
public void AssertNormalize(string input, string expected = null, bool trim = true, NormalizeOptions options = null)
{
expected = expected ?? input;
input = NormText(input, trim);
expected = NormText(expected, trim);
var result = Markdown.Normalize(input, options);
result = NormText(result, trim);
Console.WriteLine("```````````````````Source");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(input));
Console.WriteLine("```````````````````Result");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(result));
Console.WriteLine("```````````````````Expected");
Console.WriteLine(TestParser.DisplaySpaceAndTabs(expected));
Console.WriteLine("```````````````````");
Console.WriteLine();
TextAssert.AreEqual(expected, result);
}
private static string NormText(string text, bool trim)
{
if (trim)
{
text = text.Trim();
}
return text.Replace("\r\n", "\n").Replace('\r', '\n');
}
}
}

View File

@@ -1,15 +1,24 @@
// 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;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Markdig.Extensions.JiraLinks;
using NUnit.Framework;
namespace Markdig.Tests
{
public class TestParser
{
[Test]
public void TestEmphasisAndHtmlEntity()
{
var markdownText = "*Unlimited-Fun&#174;*&#174;";
TestSpec(markdownText, "<p><em>Unlimited-Fun®</em>®</p>");
}
public static void TestSpec(string inputText, string expectedOutputText, string extensions = null)
{
foreach (var pipeline in GetPipeline(extensions))
@@ -71,8 +80,15 @@ namespace Markdig.Tests
{
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());
if (extensionsText == "jiralinks")
{
builder.UseJiraLinks(new JiraLinkOptions("http://your.company.abc"));
}
else
{
builder = extensionsText == "self" ? builder.UseSelfPipeline() : builder.Configure(extensionsText);
}
yield return new KeyValuePair<string, MarkdownPipeline>(extensionsText, builder.Build());
}
}

View File

@@ -0,0 +1,17 @@
using NUnit.Framework;
namespace Markdig.Tests
{
[TestFixture]
public class TestPlainText
{
[Test]
public void TestPlain()
{
var markdownText = "*Hello*, [world](http://example.com)!";
var expected = "Hello, world!";
var actual = Markdown.ToPlainText(markdownText);
Assert.AreEqual(expected, actual);
}
}
}

View File

@@ -12,6 +12,18 @@ namespace Markdig.Tests
[TestFixture]
public class TestPlayParser
{
[Test]
public void TestListBug2()
{
TestParser.TestSpec("10.\t*test* test\n\n11.\t__test__ test\n\n", @"<ol start=""10"">
<li><p><em>test</em> test</p>
</li>
<li><p><strong>test</strong> test</p>
</li>
</ol>
");
}
[Test]
public void TestSimple()
{
@@ -109,6 +121,11 @@ blabla
<h1>header2</h1>");
}
[Test]
public void TestHtmlh4Bug()
{
TestParser.TestSpec(@"<h4>foobar</h4>", @"<h4>foobar</h4>");
}
[Test]
public void TestStandardUriEscape()
@@ -126,6 +143,14 @@ Paragraph
", "<p><code>https://{domain}/callbacks</code></p>\n<h4 id=\"heading\">HEADING</h4>\n<p>Paragraph</p>", "advanced");
}
[Test]
public void TestBugEmphAttribute()
{
// https://github.com/lunet-io/markdig/issues/108
TestParser.TestSpec(@"*test*{name=value}", "<p><em name=\"value\">test</em></p>", "advanced");
}
[Test]
public void TestBugPipeTables()
{
@@ -149,6 +174,37 @@ Paragraph
</table>", "advanced");
}
[Test]
public void TestGridTableWithCustomAttributes() {
var input = @"
{.table}
+---+---+
| a | b |
+===+===+
| 1 | 2 |
+---+---+
";
var expected = @"<table class=""table"">
<col style=""width:50%"">
<col style=""width:50%"">
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
</tr>
</tbody>
</table>
";
TestParser.TestSpec(input, expected, "advanced");
}
[Test]
public void TestSamePipelineAllExtensions()

View File

@@ -1,4 +1,4 @@
// 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;
@@ -86,7 +86,7 @@ namespace Markdig.Tests
//output.WriteLine();
}
Assert.True(string.CompareOrdinal(expectedValue, actualValue) == 0, "strings are differing");
Assert.AreEqual(expectedValue, actualValue);
}
private static string ToSafeString(this char c)

View File

@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<PreserveCompilationContext>true</PreserveCompilationContext>
<AssemblyName>Markdig.WebApp</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>Markdig.WebApp</PackageId>
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;dnxcore50;portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<None Update="Views">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Markdig\Markdig.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="1.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
</ItemGroup>
</Project>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>3cad9801-9976-46be-baca-f6d0d21fdc00</ProjectGuid>
<RootNamespace>Markdig.WebApp</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -1,58 +0,0 @@
{
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"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-preview2-final",
"imports": "portable-net45+win8+dnxcore50"
}
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"dnxcore50",
"portable-net45+win8"
]
}
},
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true
},
"runtimeOptions": {
"gcServer": true
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"appsettings.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}

View File

@@ -20,7 +20,7 @@ namespace Markdig.Extensions.Abbreviations
/// </summary>
public AbbreviationParser()
{
OpeningCharacters = new[] {'*'};
OpeningCharacters = new[] { '*' };
}
public override BlockState TryOpen(BlockProcessor processor)
@@ -90,7 +90,7 @@ namespace Markdig.Extensions.Abbreviations
inlineProcessor.LiteralInlineParser.PostMatch += (InlineProcessor processor, ref StringSlice slice) =>
{
var literal = (LiteralInline) processor.Inline;
var literal = (LiteralInline)processor.Inline;
var originalLiteral = literal;
ContainerInline container = null;
@@ -145,11 +145,13 @@ namespace Markdig.Extensions.Abbreviations
container.AppendChild(literal);
}
literal.Span.End = abbrInline.Span.Start - 1;
// Truncate it before the abbreviation
literal.Content.End = i - 1;
}
literal.Span.End = abbrInline.Span.Start - 1;
// Truncate it before the abbreviation
literal.Content.End = i - 1;
// Appned the abbreviation
container.AppendChild(abbrInline);
@@ -193,25 +195,41 @@ namespace Markdig.Extensions.Abbreviations
while (index >= content.Start)
{
var c = content.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
{
return false;
}
if (!c.IsAsciiPunctuation())
if (c.IsAlphaNumeric())
{
return false;
}
if (!c.IsAsciiPunctuation() || c.IsWhitespace())
{
break;
}
index--;
}
// This will check if the next char at the end of the StringSlice is whitespace, punctuation or \0.
var contentNew = content;
contentNew.End = content.End + 1;
index = matchIndex + match.Length;
while (index <= content.End)
while (index <= contentNew.End)
{
var c = content.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsAsciiPunctuation() || c.IsWhitespace()))
var c = contentNew.PeekCharAbsolute(index);
if (!(c == '\0' || c.IsWhitespace() || c.IsAsciiPunctuation()))
{
return false;
}
if (!c.IsAsciiPunctuation())
if (c.IsAlphaNumeric())
{
return false;
}
if (c.IsWhitespace())
{
break;
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -143,7 +143,12 @@ namespace Markdig.Extensions.AutoIdentifiers
stripRenderer.Render(headingBlock.Inline);
var headingText = headingWriter.ToString();
headingWriter.GetStringBuilder().Length = 0;
headingText = LinkHelper.Urilize(headingText, (options & AutoIdentifierOptions.AllowOnlyAscii) != 0);
// TODO: Should we have a struct with more configure optionss for LinkHelper.Urilize?
headingText = LinkHelper.Urilize(headingText,
(options & AutoIdentifierOptions.AllowOnlyAscii) != 0,
(options & AutoIdentifierOptions.KeepOpeningDigits) != 0,
(options & AutoIdentifierOptions.DiscardDots) != 0);
var baseHeadingId = string.IsNullOrEmpty(headingText) ? "section" : headingText;
int index = 0;

View File

@@ -1,4 +1,4 @@
// 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;
@@ -21,6 +21,11 @@ namespace Markdig.Extensions.AutoIdentifiers
/// </summary>
Default = AutoLink | AllowOnlyAscii,
/// <summary>
/// Renders auto identifiers like GitHub.
/// </summary>
GitHub = Default | KeepOpeningDigits | DiscardDots,
/// <summary>
/// Allows to link to a header by using the same text as the header for the link label. Default is <c>true</c>
/// </summary>
@@ -30,5 +35,15 @@ namespace Markdig.Extensions.AutoIdentifiers
/// Allows only ASCII characters in the url (HTML 5 allows to have UTF8 characters). Default is <c>true</c>
/// </summary>
AllowOnlyAscii = 2,
/// <summary>
/// Allows to keep digits starting a heading (by default, it keeps only characters starting from the first letter)
/// </summary>
KeepOpeningDigits = 4,
/// <summary>
/// Discard dots when computing an identifier.
/// </summary>
DiscardDots = 8
}
}

View File

@@ -25,6 +25,7 @@ namespace Markdig.Extensions.Diagrams
var codeRenderer = htmlRenderer.ObjectRenderers.FindExact<CodeBlockRenderer>();
// TODO: Add other well known diagram languages
codeRenderer.BlocksAsDiv.Add("mermaid");
codeRenderer.BlocksAsDiv.Add("nomnoml");
}
}
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -12,12 +12,19 @@ namespace Markdig.Extensions.Emoji
/// <seealso cref="Markdig.IMarkdownExtension" />
public class EmojiExtension : IMarkdownExtension
{
private readonly bool _enableSmiley;
public EmojiExtension(bool enableSmiley = true)
{
_enableSmiley = enableSmiley;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<EmojiParser>())
{
// Insert the parser before any other parsers
pipeline.InlineParsers.Insert(0, new EmojiParser());
pipeline.InlineParsers.Insert(0, new EmojiParser(_enableSmiley));
}
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -24,13 +24,19 @@ namespace Markdig.Extensions.Emoji
/// <summary>
/// Initializes a new instance of the <see cref="EmojiParser"/> class.
/// </summary>
public EmojiParser()
public EmojiParser(bool enableSmiley = true)
{
EnableSmiley = enableSmiley;
OpeningCharacters = null;
EmojiToUnicode = new Dictionary<string, string>(EmojiToUnicodeDefault);
SmileyToEmoji = new Dictionary<string, string>(SmileyToEmojiDefault);
}
/// <summary>
/// Gets or sets a boolean indicating whether to process smiley.
/// </summary>
public bool EnableSmiley { get; set; }
/// <summary>
/// Gets the emoji to unicode mapping. This can be modified before this parser is initialized.
/// </summary>
@@ -81,11 +87,14 @@ namespace Markdig.Extensions.Emoji
return false;
}
// If we have a smiley, we decode it to emoji
string emoji;
if (!SmileyToEmoji.TryGetValue(match, out emoji))
string emoji = match;
if (EnableSmiley)
{
emoji = match;
// If we have a smiley, we decode it to emoji
if (!SmileyToEmoji.TryGetValue(match, out emoji))
{
emoji = match;
}
}
// Decode the eomji to unicode
@@ -991,7 +1000,16 @@ namespace Markdig.Extensions.Emoji
{":large_orange_diamond:", "🔶"},
{":large_blue_diamond:", "🔷"},
{":small_orange_diamond:", "🔸"},
{":small_blue_diamond:", "🔹"}
{":small_blue_diamond:", "🔹"},
// Custom additions
{ ":custom_arrow_left:", "←"},
{ ":custom_arrow_right:", "→"},
{ ":custom_arrow_left_right:", "↔"},
{ ":custom_arrow_left_strong:", "⇐"},
{ ":custom_arrow_right_strong:", "⇒"},
{ ":custom_arrow_left_right_strong:", "⇔"},
};
SmileyToEmojiDefault = new Dictionary<string, string>()
@@ -1061,6 +1079,15 @@ namespace Markdig.Extensions.Emoji
{":-$", ":unamused:"},
{";)", ":wink:"},
{";-)", ":wink:"},
// Custom arrows
{"<-", ":custom_arrow_left:" },
{"->", ":custom_arrow_rigth:" },
{"<->", ":custom_arrow_left_rigth:" },
{"<=", ":custom_arrow_left_strong:" },
{"=>", ":custom_arrow_rigth_strong:" },
{"<=>", ":custom_arrow_left_rigth_strong:" },
};
}
#endregion

View File

@@ -59,7 +59,7 @@ namespace Markdig.Extensions.GenericAttributes
{
objectToAttach = parent[indexOfParagraph + 1];
// We can remove the paragraph as it is empty
parent.RemoveAt(indexOfParagraph);
paragraph.RemoveAfterProcessInlines = true;
}
}
@@ -171,9 +171,10 @@ namespace Markdig.Extensions.GenericAttributes
// Skip any whitespaces
line.TrimStart();
c = line.CurrentChar;
// Handle boolean properties that are not followed by =
if ((hasSpace && (line.CurrentChar == '.' || line.CurrentChar == '#' || IsStartAttributeName(line.CurrentChar))) || line.CurrentChar == '}')
if ((hasSpace && (c == '.' || c == '#' || IsStartAttributeName(c))) || c == '}')
{
if (properties == null)
{

View File

@@ -0,0 +1,31 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System.Diagnostics;
using Markdig.Helpers;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Model for a JIRA link item
/// </summary>
[DebuggerDisplay("{ProjectKey}-{Issue}")]
public class JiraLink : LinkInline
{
public JiraLink()
{
IsClosed = true;
}
/// <summary>
/// JIRA Project Key
/// </summary>
public StringSlice ProjectKey { get; set; }
/// <summary>
/// JIRA Issue Number
/// </summary>
public StringSlice Issue { get; set; }
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Parsers.Inlines;
using Markdig.Renderers;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Simple inline parser extension for Markdig to find, and
/// automatically add links to JIRA issue numbers.
/// </summary>
public class JiraLinkExtension : IMarkdownExtension
{
private readonly JiraLinkOptions _options;
public JiraLinkExtension(JiraLinkOptions options)
{
_options = options;
}
public void Setup(MarkdownPipelineBuilder pipeline)
{
if (!pipeline.InlineParsers.Contains<JiraLinkInlineParser>())
{
// Insert the parser before the link inline parser
pipeline.InlineParsers.InsertBefore<LinkInlineParser>(new JiraLinkInlineParser(_options));
}
}
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
// Nothing to setup, JiraLinks used a normal LinkInlineRenderer
}
}
}

View File

@@ -0,0 +1,115 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Text;
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax.Inlines;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Finds and replaces JIRA links inline
/// </summary>
public class JiraLinkInlineParser : InlineParser
{
private readonly JiraLinkOptions _options;
private readonly string _baseUrl;
public JiraLinkInlineParser(JiraLinkOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_baseUrl = _options.GetUrl();
//look for uppercase chars at the start (for the project key)
OpeningCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
// Allow preceding whitespace or `(`
var pc = slice.PeekCharExtra(-1);
if (!pc.IsWhiteSpaceOrZero() && pc != '(')
{
return false;
}
var current = slice.CurrentChar;
var startKey = slice.Start;
var endKey = slice.Start;
//read as many uppercase characters as required - project key
while (current.IsAlphaUpper())
{
endKey = slice.Start;
current = slice.NextChar();
}
//require a '-' between key and issue number
if (!current.Equals('-'))
{
return false;
}
current = slice.NextChar(); // skip -
//read as many numbers as required - issue number
if (!current.IsDigit())
{
return false;
}
var startIssue = slice.Start;
var endIssue = slice.Start;
while (current.IsDigit())
{
endIssue = slice.Start;
current = slice.NextChar();
}
if (!current.IsWhiteSpaceOrZero() && current != ')') //can be followed only by a whitespace or `)`
{
return false;
}
int line;
int column;
var jiraLink = new JiraLink() //create the link at the relevant position
{
Span =
{
Start = processor.GetSourcePosition(slice.Start, out line, out column)
},
Line = line,
Column = column,
Issue = new StringSlice(slice.Text, startIssue, endIssue),
ProjectKey = new StringSlice(slice.Text, startKey, endKey),
};
jiraLink.Span.End = jiraLink.Span.Start + (endIssue - startKey);
// Builds the Url
var builder = new StringBuilder();
builder.Append(_baseUrl).Append('/').Append(jiraLink.ProjectKey).Append('-').Append(jiraLink.Issue);
jiraLink.Url = builder.ToString();
// Builds the Label
builder.Length = 0;
builder.Append(jiraLink.ProjectKey).Append('-').Append(jiraLink.Issue);
jiraLink.AppendChild(new LiteralInline(builder.ToString()));
if (_options.OpenInNewWindow)
{
jiraLink.GetAttributes().AddProperty("target", "blank");
}
processor.Inline = jiraLink;
return true;
}
}
}

View File

@@ -0,0 +1,57 @@
// 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.Text;
namespace Markdig.Extensions.JiraLinks
{
/// <summary>
/// Available options for replacing JIRA links
/// </summary>
public class JiraLinkOptions
{
/// <summary>
/// The base Url (e.g. `https://mycompany.atlassian.net`)
/// </summary>
public string BaseUrl { get; set; }
/// <summary>
/// The base path after the base url (default is `/browse`)
/// </summary>
public string BasePath { get; set; }
/// <summary>
/// Should the link open in a new window when clicked
/// </summary>
public bool OpenInNewWindow { get; set; }
public JiraLinkOptions(string baseUrl)
{
OpenInNewWindow = true; //default
BaseUrl = baseUrl;
BasePath = "/browse";
}
/// <summary>
/// Gets the full url composed of the <see cref="BaseUrl"/> and <see cref="BasePath"/> with no trailing `/`
/// </summary>
public virtual string GetUrl()
{
var url = new StringBuilder();
var baseUrl = BaseUrl;
if (baseUrl != null)
{
url.Append(baseUrl.TrimEnd('/'));
}
url.Append("/");
if (BasePath != null)
{
url.Append(BasePath.Trim('/'));
}
return url.ToString();
}
}
}

View File

@@ -1,15 +1,18 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
namespace Markdig.Extensions.Mathematics
{
/// <summary>
/// The block parser for a <see cref="MathBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Parsers.FencedBlockParserBase{Markdig.Extensions.Mathematics.MathBlock}" />
/// <seealso cref="MathBlock" />
public class MathBlockParser : FencedBlockParserBase<MathBlock>
{
/// <summary>
@@ -22,6 +25,8 @@ namespace Markdig.Extensions.Mathematics
MinimumMatchCount = 2;
MaximumMatchCount = 2;
InfoParser = NoInfoParser;
DefaultClass = "math";
// We don't need a prefix
@@ -39,5 +44,19 @@ namespace Markdig.Extensions.Mathematics
}
return block;
}
private static bool NoInfoParser(BlockProcessor state, ref StringSlice line, IFencedBlock fenced)
{
var c = line.CurrentChar;
for (int i = line.Start; i <= line.End; i++)
{
c = line.Text[i];
if (!c.IsSpaceOrTab())
{
return false;
}
}
return true;
}
}
}

View File

@@ -50,11 +50,18 @@ namespace Markdig.Extensions.Mathematics
c = slice.NextChar();
}
bool canOpen;
bool canClose;
// Check that opening $/$$ is correct, using the same heuristics than for emphasis delimiters
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
if (!canOpen)
bool openPrevIsPunctuation;
bool openPrevIsWhiteSpace;
bool openNextIsPunctuation;
bool openNextIsWhiteSpace;
bool openNextIsDigit = c.IsDigit();
pc.CheckUnicodeCategory(out openPrevIsWhiteSpace, out openPrevIsPunctuation);
c.CheckUnicodeCategory(out openNextIsWhiteSpace, out openNextIsPunctuation);
// Check that opening $/$$ is correct, using the different heuristics than for emphasis delimiters
// If a $/$$ is not preceded by a whitespace or punctuation, or followed by a digit
// this is a not a math block
if ((!openPrevIsWhiteSpace && !openPrevIsPunctuation) || openNextIsDigit)
{
return false;
}
@@ -62,25 +69,60 @@ namespace Markdig.Extensions.Mathematics
bool isMatching = false;
int closeDollars = 0;
// Eat any leading spaces
while (c.IsSpaceOrTab())
{
c = slice.NextChar();
}
var start = slice.Start;
var end = 0;
pc = match;
var lastWhiteSpace = -1;
while (c != '\0')
{
// Don't allow newline in an inline math expression
if (c == '\r' || c == '\n')
{
return false;
}
// Don't process sticks if we have a '\' as a previous char
if (pc != '\\' )
{
while (c == match)
// Record continous whitespaces at the end
if (c.IsSpaceOrTab())
{
closeDollars++;
c = slice.NextChar();
if (lastWhiteSpace < 0)
{
lastWhiteSpace = slice.Start;
}
}
else
{
bool hasClosingDollars = c == match;
if (hasClosingDollars)
{
while (c == match)
{
closeDollars++;
c = slice.NextChar();
}
}
if (closeDollars >= openDollars)
{
break;
if (closeDollars >= openDollars)
{
break;
}
lastWhiteSpace = -1;
if (hasClosingDollars)
{
pc = match;
continue;
}
}
pc = match;
}
if (closeDollars > 0)
@@ -96,13 +138,30 @@ namespace Markdig.Extensions.Mathematics
if (closeDollars >= openDollars)
{
// Check that closing $/$$ is correct
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
if (!canClose || c.IsDigit())
bool closePrevIsPunctuation;
bool closePrevIsWhiteSpace;
bool closeNextIsPunctuation;
bool closeNextIsWhiteSpace;
pc.CheckUnicodeCategory(out closePrevIsWhiteSpace, out closePrevIsPunctuation);
c.CheckUnicodeCategory(out closeNextIsWhiteSpace, out closeNextIsPunctuation);
// A closing $/$$ should be followed by at least a punctuation or a whitespace
// and if the character after an openning $/$$ was a whitespace, it should be
// a whitespace as well for the character preceding the closing of $/$$
if ((!closeNextIsPunctuation && !closeNextIsWhiteSpace) || (openNextIsWhiteSpace != closePrevIsWhiteSpace))
{
return false;
}
end = slice.Start - 1;
if (closePrevIsWhiteSpace && lastWhiteSpace > 0)
{
end = lastWhiteSpace + openDollars - 1;
}
else
{
end = slice.Start - 1;
}
// Create a new MathInline
int line;
int column;

View File

@@ -115,7 +115,6 @@ namespace Markdig.Extensions.MediaLinks
renderer.WriteAttributes(htmlAttributes);
renderer.Write($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
renderer.Write("</iframe>");
return true;
}

View File

@@ -85,7 +85,7 @@ namespace Markdig.Extensions.Tables
{
renderer.Write($" rowspan=\"{cell.RowSpan}\"");
}
if (table.ColumnDefinitions != null)
if (table.ColumnDefinitions.Count > 0)
{
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i

View File

@@ -1,4 +1,4 @@
// 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.
namespace Markdig.Extensions.Tables

View File

@@ -440,12 +440,13 @@ namespace Markdig.Extensions.Tables
// If we have a header row, we can remove it
// TODO: we could optimize this by merging FindHeaderRow and the previous loop
var tableRow = (TableRow)table[0];
tableRow.IsHeader = Options.RequireHeaderSeparator;
if (aligns != null)
{
table.RemoveAt(1);
var tableRow = (TableRow) table[0];
table.ColumnDefinitions.AddRange(aligns);
tableRow.IsHeader = true;
table.RemoveAt(1);
table.ColumnDefinitions.AddRange(aligns);
}
// Perform delimiter processor that are coming after this processor
@@ -532,6 +533,13 @@ namespace Markdig.Extensions.Tables
? columnDelimiter.FirstChild
: delimiter.NextSibling;
// If there is no content after
if (IsNullOrSpace(nextSibling))
{
isValidRow = true;
break;
}
if (!ParseHeaderString(nextSibling, out align))
{
break;
@@ -606,6 +614,20 @@ namespace Markdig.Extensions.Tables
}
}
private static bool IsNullOrSpace(Inline inline)
{
if (inline == null)
{
return true;
}
var literal = inline as LiteralInline;
if (literal != null)
{
return literal.Content.IsEmptyOrWhitespace();
}
return false;
}
private class TableState
{
public TableState()

View File

@@ -33,7 +33,7 @@ namespace Markdig.Extensions.Tables
/// <summary>
/// Gets or sets the column alignments. May be null.
/// </summary>
public List<TableColumnDefinition> ColumnDefinitions { get; private set; }
public List<TableColumnDefinition> ColumnDefinitions { get; }
/// <summary>
/// Checks if the table structure is valid.

View File

@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Yaml
/// A YAML frontmatter block.
/// </summary>
/// <seealso cref="Markdig.Syntax.CodeBlock" />
public class YamlFrontMatterBlock : CodeBlock, IFencedBlock
public class YamlFrontMatterBlock : CodeBlock
{
/// <summary>
/// Initializes a new instance of the <see cref="YamlFrontMatterBlock"/> class.
@@ -19,13 +19,5 @@ namespace Markdig.Extensions.Yaml
public YamlFrontMatterBlock(BlockParser parser) : base(parser)
{
}
public string Info { get; set; }
public string Arguments { get; set; }
public int FencedCharCount { get; set; }
public char FencedChar { get; set; }
}
}

View File

@@ -1,44 +1,128 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using Markdig.Parsers;
using Markdig.Syntax;
namespace Markdig.Extensions.Yaml
{
/// <summary>
/// Block parser for a YAML frontmatter.
/// </summary>
/// <seealso cref="Markdig.Parsers.FencedBlockParserBase{YamlFrontMatterBlock}" />
public class YamlFrontMatterParser : FencedBlockParserBase<YamlFrontMatterBlock>
/// <seealso cref="YamlFrontMatterBlock" />
public class YamlFrontMatterParser : BlockParser
{
// We reuse a FencedCodeBlock parser to grab a frontmatter, only active if it happens on the first line of the document.
/// <summary>
/// Initializes a new instance of the <see cref="FencedCodeBlockParser"/> class.
/// Initializes a new instance of the <see cref="YamlFrontMatterParser"/> class.
/// </summary>
public YamlFrontMatterParser()
{
OpeningCharacters = new[] { '-' };
InfoPrefix = null;
// We expect only 3 --- at the beginning of the file no more, no less
MinimumMatchCount = 3;
MaximumMatchCount = 3;
this.OpeningCharacters = new[] { '-' };
}
protected override YamlFrontMatterBlock CreateFencedBlock(BlockProcessor processor)
/// <summary>
/// Creates the front matter block.
/// </summary>
/// <param name="processor">The block processor</param>
/// <returns>The front matter block</returns>
protected virtual YamlFrontMatterBlock CreateFrontMatterBlock(BlockProcessor processor)
{
return new YamlFrontMatterBlock(this);
}
/// <summary>
/// Tries to match a block opening.
/// </summary>
/// <param name="processor">The parser processor.</param>
/// <returns>The result of the match</returns>
public override BlockState TryOpen(BlockProcessor processor)
{
// Only accept a frontmatter at the beginning of the file
if (processor.LineIndex != 0)
// We expect no indentation for a fenced code block.
if (processor.IsCodeIndent)
{
return BlockState.None;
}
return base.TryOpen(processor);
// Only accept a frontmatter at the beginning of the file
if (processor.Start != 0)
{
return BlockState.None;
}
int count = 0;
var line = processor.Line;
char c = line.CurrentChar;
// Must consist of exactly three dashes
while (c == '-' && count < 4)
{
count++;
c = line.NextChar();
}
// If three dashes (optionally followed by whitespace)
// this is a YAML front matter blcok
if (count == 3 && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
{
// Create a front matter block
var block = this.CreateFrontMatterBlock(processor);
block.Column = processor.Column;
block.Span.Start = 0;
block.Span.End = line.Start;
// Store the number of matched string into the context
processor.NewBlocks.Push(block);
// Discard the current line as it is already parsed
return BlockState.ContinueDiscard;
}
return BlockState.None;
}
/// <summary>
/// Tries to continue matching a block already opened.
/// </summary>
/// <param name="processor">The parser processor.</param>
/// <param name="block">The block already opened.</param>
/// <returns>The result of the match. By default, don't expect any newline</returns>
public override BlockState TryContinue(BlockProcessor processor, Block block)
{
char matchChar;
int count = 0;
var c = processor.CurrentChar;
// Determine if we have a closing fence.
// It can start or end with either <c>---</c> or <c>...</c>
var line = processor.Line;
if (processor.Column == 0 && (c == '-' || c == '.'))
{
matchChar = c;
while (c == matchChar)
{
c = line.NextChar();
count++;
}
// If we have a closing fence, close it and discard the current line
// The line must contain only fence characters and optional following whitespace.
if (count == 3 && !processor.IsCodeIndent && (c == '\0' || c.IsWhitespace()) && line.TrimEnd())
{
block.UpdateSpanEnd(line.Start - 1);
// Don't keep the last line
return BlockState.BreakDiscard;
}
}
// Reset the indentation to the column before the indent
processor.GoToColumn(processor.ColumnBeforeIndent);
return BlockState.Continue;
}
}
}

View File

@@ -115,35 +115,44 @@ namespace Markdig.Helpers
/// <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>
public unsafe int IndexOfOpeningCharacter(string text, int start, int end)
public int IndexOfOpeningCharacter(string text, int start, int end)
{
var maxChar = isOpeningCharacter.Length;
#if SUPPORT_UNSAFE
unsafe
#endif
{
#if SUPPORT_FIXED_STRING
fixed (char* pText = text)
#else
var pText = text;
var pText = text;
#endif
#if SUPPORT_UNSAFE
fixed (bool* openingChars = isOpeningCharacter)
#else
var openingChars = isOpeningCharacter;
#endif
fixed (bool* openingChars = isOpeningCharacter)
{
if (nonAsciiMap == null)
{
for (int i = start; i <= end; i++)
if (nonAsciiMap == null)
{
var c = pText[i];
if (c < maxChar && openingChars[c])
for (int i = start; i <= end; i++)
{
return i;
var c = pText[i];
if (c < maxChar && openingChars[c])
{
return i;
}
}
}
}
else
{
for (int i = start; i <= end; i++)
else
{
var c = pText[i];
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
for (int i = start; i <= end; i++)
{
return i;
var c = pText[i];
if ((c < maxChar && openingChars[c]) || nonAsciiMap.ContainsKey(c))
{
return i;
}
}
}
}

View File

@@ -617,7 +617,7 @@ namespace Markdig.Helpers
{
// expect a letter and 1-31 letters or digits
c = s[pos + 1];
if ((c < 'A' || c > 'Z') && (c < 'a' && c > 'z'))
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')))
return 0;
for (i = pos + 2; i < lastPos; i++)

View File

@@ -31,6 +31,12 @@ namespace Markdig.Helpers
/// <returns>The next character. `\0` is end of the iteration.</returns>
char NextChar();
/// <summary>
/// Peeks at the next character, without incrementing the <see cref="Start"/> position.
/// </summary>
/// <returns>The next character. `\0` is end of the iteration.</returns>
char PeekChar();
/// <summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>

View File

@@ -1,4 +1,4 @@
// 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;
@@ -18,10 +18,10 @@ namespace Markdig.Helpers
return TryParseAutolink(ref text, out link, out isEmail);
}
public static string Urilize(string headingText, bool allowOnlyAscii)
public static string Urilize(string headingText, bool allowOnlyAscii, bool keepOpeningDigits = false, bool discardDots = false)
{
var headingBuffer = StringBuilderCache.Local();
bool hasLetter = false;
bool hasLetter = keepOpeningDigits && headingText.Length > 0 && char.IsLetterOrDigit(headingText[0]);
bool previousIsSpace = false;
for (int i = 0; i < headingText.Length; i++)
{
@@ -47,7 +47,7 @@ namespace Markdig.Helpers
}
else if (hasLetter)
{
if (IsReservedPunctuation(c))
if (IsReservedPunctuation(c, discardDots))
{
if (previousIsSpace)
{
@@ -67,7 +67,7 @@ namespace Markdig.Helpers
else if (!previousIsSpace && c.IsWhitespace())
{
var pc = headingBuffer[headingBuffer.Length - 1];
if (!IsReservedPunctuation(pc))
if (!IsReservedPunctuation(pc, discardDots))
{
headingBuffer.Append('-');
}
@@ -81,7 +81,7 @@ namespace Markdig.Helpers
while (headingBuffer.Length > 0)
{
var c = headingBuffer[headingBuffer.Length - 1];
if (IsReservedPunctuation(c))
if (IsReservedPunctuation(c, false))
{
headingBuffer.Length--;
}
@@ -97,9 +97,9 @@ namespace Markdig.Helpers
}
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
private static bool IsReservedPunctuation(char c)
private static bool IsReservedPunctuation(char c, bool discardDots)
{
return c == '_' || c == '-' || c == '.';
return c == '_' || c == '-' || (!discardDots && c == '.');
}
public static bool TryParseAutolink(ref StringSlice text, out string link, out bool isEmail)
@@ -116,6 +116,7 @@ namespace Markdig.Helpers
// An absolute URI, for these purposes, consists of a scheme followed by a colon (:)
// followed by zero or more characters other than ASCII whitespace and control characters, <, and >.
// If the URI includes these characters, they must be percent-encoded (e.g. %20 for a space).
// A URI that would end with a full stop (.) is treated instead as ending immediately before the full stop.
// a scheme is any sequence of 232 characters
// beginning with an ASCII letter
@@ -258,6 +259,7 @@ namespace Markdig.Helpers
break;
}
builder.Append(c);
pc = c;
}
}
else
@@ -559,10 +561,6 @@ namespace Markdig.Helpers
{
if (!hasEscape)
{
if (openedParent > 0)
{
break;
}
openedParent++;
}
}
@@ -595,7 +593,13 @@ namespace Markdig.Helpers
hasEscape = false;
if (c == '\0' || c.IsSpaceOrTab() || c.IsControl()) // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
if (IsEndOfUri(c))
{
isValid = true;
break;
}
if (c == '.' && IsEndOfUri(text.PeekChar()))
{
isValid = true;
break;
@@ -605,6 +609,11 @@ namespace Markdig.Helpers
c = text.NextChar();
}
if (openedParent > 0)
{
isValid = false;
}
}
link = isValid ? buffer.ToString() : null;
@@ -612,6 +621,11 @@ namespace Markdig.Helpers
return isValid;
}
private static bool IsEndOfUri(char c)
{
return c == '\0' || c.IsSpaceOrTab() || c.IsControl(); // TODO: specs unclear. space is strict or relaxed? (includes tabs?)
}
public static bool TryParseLinkReferenceDefinition<T>(T text, out string label, out string url,
out string title) where T : ICharIterator
{

View File

@@ -0,0 +1,23 @@
// 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.Text;
namespace Markdig.Helpers
{
/// <summary>
/// Extensions for StringBuilder with <see cref="StringSlice"/>
/// </summary>
public static class StringBuilderExtensions
{
/// <summary>
/// Appends the specified slice to this <see cref="StringBuilder"/> instance.
/// </summary>
/// <param name="builder">The builder.</param>
/// <param name="slice">The slice.</param>
public static StringBuilder Append(this StringBuilder builder, StringSlice slice)
{
return builder.Append(slice.Text, slice.Start, slice.Length);
}
}
}

View File

@@ -253,6 +253,22 @@ namespace Markdig.Helpers
return CurrentChar;
}
public char PeekChar()
{
if (Start + 1 > End)
{
return '\0';
}
var slice = (StringSlice)lines.Lines[SliceIndex];
if (offset + 1 >= slice.Length)
{
return '\n';
}
return slice[slice.Start + offset + 1];
}
public bool TrimStart()
{
var c = CurrentChar;

View File

@@ -112,6 +112,17 @@ namespace Markdig.Helpers
return index >= Start && index <= End ? Text[index] : (char) 0;
}
/// <summary>
/// Peeks the character immediately after the current <see cref="Start"/> position
/// or returns `\0` if after the <see cref="End"/> position.
/// </summary>
/// <returns>The next character, returns `\0` if none.</returns>
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
public char PeekChar()
{
return PeekChar(1);
}
/// <summary>
/// Peeks a character at the specified offset from the current beginning of the string, without taking into account <see cref="Start"/> and <see cref="End"/>
/// </summary>

101
src/Markdig/Markdig.csproj Normal file
View File

@@ -0,0 +1,101 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
<Copyright>Alexandre Mutel</Copyright>
<AssemblyTitle>Markdig</AssemblyTitle>
<NeutralLanguage>en-US</NeutralLanguage>
<VersionPrefix>0.14.2</VersionPrefix>
<Authors>Alexandre Mutel</Authors>
<TargetFrameworks>net35;net40;portable40-net40+sl5+win8+wp8+wpa81;netstandard1.1;uap10.0</TargetFrameworks>
<AssemblyName>Markdig</AssemblyName>
<PackageId>Markdig</PackageId>
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
<PackageReleaseNotes>
&gt; 0.14.2
- Fix issue with emphasis preceded/followed by an HTML entity (#157)
- Add support for link reference definitions for Normalize renderer (#155)
- Add option to disable smiley parsing in EmojiAndSmiley extension
&gt; 0.14.1
- Fix crash in Markdown.Normalize to handle HtmlBlock correctly
- Add better handling of bullet character for lists in Markdown.Normalize
&gt; 0.14.0
- Add Markdown.ToPlainText, Add option HtmlRenderer.EnableHtmlForBlock.
- Add Markdown.Normalize, to allow to normalize a markdown document. Add NormalizeRenderer, to render a MarkdownDocument back to markdown.
-
&gt; 0.13.4
- Add support for single table header row without a table body rows (#141)
- ADd support for `nomnoml` diagrams
</PackageReleaseNotes>
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/lunet-io/markdig/blob/master/license.txt</PackageLicenseUrl>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81' ">
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net35' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.1' ">
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.10240.0</TargetPlatformVersion>
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81'">
<TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile328</TargetFrameworkProfile>
<LanguageTargets>$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets</LanguageTargets>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<NoWarn>$(NoWarn);CS1591</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(SignAssembly)' == 'true' ">
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
<!-- Special packages and imports for UWP support -->
<ItemGroup>
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
</ItemGroup>
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
</Project>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>8A58A7E2-627C-4F41-933F-5AC92ADFAB48</ProjectGuid>
<RootNamespace>Markdig</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">.\obj</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">.\bin\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<ProduceOutputsOnBuild>True</ProduceOutputsOnBuild>
<TypeScriptCompileBlocked>True</TypeScriptCompileBlocked>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@@ -1,2 +0,0 @@
<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:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=syntax_005Cinlines/@EntryIndexedValue">False</s:Boolean></wpf:ResourceDictionary>

View File

@@ -1,11 +1,13 @@
// 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;
using System.IO;
using System.Reflection;
using Markdig.Extensions.SelfPipeline;
using Markdig.Parsers;
using Markdig.Renderers;
using Markdig.Renderers.Normalize;
using Markdig.Syntax;
namespace Markdig
@@ -15,6 +17,50 @@ namespace Markdig
/// </summary>
public static partial class Markdown
{
#if NETSTANDARD_11
public static readonly string Version = typeof(Markdown).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
#else
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
#endif
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.
/// </summary>
/// <param name="markdown">The markdown.</param>
/// <param name="options">The normalize options</param>
/// <param name="pipeline">The pipeline.</param>
/// <returns>A normalized markdown text.</returns>
public static string Normalize(string markdown, NormalizeOptions options = null, MarkdownPipeline pipeline = null)
{
var writer = new StringWriter();
Normalize(markdown, writer, options, pipeline);
return writer.ToString();
}
/// <summary>
/// Normalizes the specified markdown to a normalized markdown text.
/// </summary>
/// <param name="markdown">The markdown.</param>
/// <param name="writer">The destination <see cref="TextWriter"/> that will receive the result of the conversion.</param>
/// <param name="options">The normalize options</param>
/// <param name="pipeline">The pipeline.</param>
/// <returns>A normalized markdown text.</returns>
public static MarkdownDocument Normalize(string markdown, TextWriter writer, NormalizeOptions options = null, MarkdownPipeline pipeline = null)
{
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
var renderer = new NormalizeRenderer(writer, options);
pipeline.Setup(renderer);
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
return document;
}
/// <summary>
/// Converts a Markdown string to HTML.
/// </summary>
@@ -112,5 +158,50 @@ namespace Markdig
}
return pipeline;
}
/// <summary>
/// Converts a Markdown string to Plain text and output to the specified writer.
/// </summary>
/// <param name="markdown">A Markdown text.</param>
/// <param name="writer">The destination <see cref="TextWriter"/> that will receive the result of the conversion.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The Markdown document that has been parsed</returns>
/// <exception cref="System.ArgumentNullException">if reader or writer variable are null</exception>
public static MarkdownDocument ToPlainText(string markdown, TextWriter writer, MarkdownPipeline pipeline = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
if (writer == null) throw new ArgumentNullException(nameof(writer));
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
pipeline = CheckForSelfPipeline(pipeline, markdown);
// We override the renderer with our own writer
var renderer = new HtmlRenderer(writer)
{
EnableHtmlForBlock = false,
EnableHtmlForInline = false
};
pipeline.Setup(renderer);
var document = Parse(markdown, pipeline);
renderer.Render(document);
writer.Flush();
return document;
}
/// <summary>
/// Converts a Markdown string to HTML.
/// </summary>
/// <param name="markdown">A Markdown text.</param>
/// <param name="pipeline">The pipeline used for the conversion.</param>
/// <returns>The result of the conversion</returns>
/// <exception cref="System.ArgumentNullException">if markdown variable is null</exception>
public static string ToPlainText(string markdown, MarkdownPipeline pipeline = null)
{
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
var writer = new StringWriter();
ToPlainText(markdown, writer, pipeline);
return writer.ToString();
}
}
}

View File

@@ -1,4 +1,4 @@
// 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.
@@ -18,6 +18,7 @@ using Markdig.Extensions.Footers;
using Markdig.Extensions.Footnotes;
using Markdig.Extensions.GenericAttributes;
using Markdig.Extensions.Hardlines;
using Markdig.Extensions.JiraLinks;
using Markdig.Extensions.ListExtras;
using Markdig.Extensions.Mathematics;
using Markdig.Extensions.MediaLinks;
@@ -408,10 +409,14 @@ namespace Markdig
/// Uses the emoji and smiley extension.
/// </summary>
/// <param name="pipeline">The pipeline.</param>
/// <param name="enableSmiley">Enable smiley in addition to Emoji, <c>true</c> by default.</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline)
public static MarkdownPipelineBuilder UseEmojiAndSmiley(this MarkdownPipelineBuilder pipeline, bool enableSmiley = true)
{
pipeline.Extensions.AddIfNotAlready<EmojiExtension>();
if (!pipeline.Extensions.Contains<EmojiExtension>())
{
pipeline.Extensions.Add(new EmojiExtension(enableSmiley));
}
return pipeline;
}
@@ -426,6 +431,21 @@ namespace Markdig
return pipeline;
}
/// <summary>
/// Automatically link references to JIRA issues
/// </summary>
/// <param name="pipeline">The pipeline</param>
/// <param name="options">Set of required options</param>
/// <returns>The modified pipeline</returns>
public static MarkdownPipelineBuilder UseJiraLinks(this MarkdownPipelineBuilder pipeline, JiraLinkOptions options)
{
if (!pipeline.Extensions.Contains<JiraLinkExtension>())
{
pipeline.Extensions.Add(new JiraLinkExtension(options));
}
return pipeline;
}
/// <summary>
/// This will disable the HTML support in the markdown processor (for constraint/safe parsing).
/// </summary>

View File

@@ -617,12 +617,6 @@ namespace Markdig.Parsers
{
leaf.AppendLine(ref Line, Column, LineIndex, CurrentLineStartPosition);
}
if (NewBlocks.Count > 0)
{
throw new InvalidOperationException(
"The NewBlocks is not empty. This is happening if a LeafBlock is not the last to be pushed");
}
}
// A block is open only if it has a Continue state.

View File

@@ -159,7 +159,7 @@ namespace Markdig.Parsers
}
// Cannot start with </script </pre or </style
if ((tagIndex == 45 || tagIndex == 46 || tagIndex == 49))
if ((tagIndex == 50 || tagIndex == 51 || tagIndex == 54))
{
if (c == '/' || hasLeadingClose)
{
@@ -281,67 +281,72 @@ namespace Markdig.Parsers
private static readonly string[] HtmlTags =
{
"address", // 0
"article", // 1
"aside", // 2
"base", // 3
"basefont", // 4
"blockquote", // 5
"body", // 6
"caption", // 7
"center", // 8
"col", // 9
"colgroup", // 10
"dd", // 11
"details", // 12
"dialog", // 13
"dir", // 14
"div", // 15
"dl", // 16
"dt", // 17
"fieldset", // 18
"figcaption", // 19
"figure", // 20
"footer", // 21
"form", // 22
"frame", // 23
"frameset", // 24
"h1", // 25
"head", // 26
"header", // 27
"hr", // 28
"html", // 29
"iframe", // 30
"legend", // 31
"li", // 32
"link", // 33
"main", // 34
"menu", // 35
"menuitem", // 36
"meta", // 37
"nav", // 38
"noframes", // 39
"ol", // 40
"optgroup", // 41
"option", // 42
"p", // 43
"param", // 44
"pre", // 45 <- special group 1
"script", // 46 <- special group 1
"section", // 47
"source", // 48
"style", // 49 <- special group 1
"summary", // 50
"table", // 51
"tbody", // 52
"td", // 53
"tfoot", // 54
"th", // 55
"thead", // 56
"title", // 57
"tr", // 58
"track", // 59
"ul", // 60
"address", // 0
"article", // 1
"aside", // 2
"base", // 3
"basefont", // 4
"blockquote", // 5
"body", // 6
"caption", // 7
"center", // 8
"col", // 9
"colgroup", // 10
"dd", // 11
"details", // 12
"dialog", // 13
"dir", // 14
"div", // 15
"dl", // 16
"dt", // 17
"fieldset", // 18
"figcaption", // 19
"figure", // 20
"footer", // 21
"form", // 22
"frame", // 23
"frameset", // 24
"h1", // 25
"h2", // 26
"h3", // 27
"h4", // 28
"h5", // 29
"h6", // 30
"head", // 31
"header", // 32
"hr", // 33
"html", // 34
"iframe", // 35
"legend", // 36
"li", // 37
"link", // 38
"main", // 39
"menu", // 40
"menuitem", // 41
"meta", // 42
"nav", // 43
"noframes", // 44
"ol", // 45
"optgroup", // 46
"option", // 47
"p", // 48
"param", // 49
"pre", // 50 <=== special group 1
"script", // 51 <=== special group 1
"section", // 52
"source", // 53
"style", // 54 <=== special group 1
"summary", // 55
"table", // 56
"tbody", // 57
"td", // 58
"tfoot", // 59
"th", // 60
"thead", // 61
"title", // 62
"tr", // 63
"track", // 64
"ul", // 65
};
}
}

View File

@@ -101,7 +101,7 @@ namespace Markdig.Parsers.Inlines
}
// Move current_position forward in the delimiter stack (if needed) until
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
var child = container.LastChild;
while (child != null)
{
@@ -138,12 +138,24 @@ namespace Markdig.Parsers.Inlines
var delimiterChar = slice.CurrentChar;
var emphasisDesc = emphasisMap[delimiterChar];
var pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
{
return false;
}
var pc = (char)0;
if (processor.Inline is HtmlEntityInline)
{
var htmlEntityInline = (HtmlEntityInline) processor.Inline;
if (htmlEntityInline.Transcoded.Length > 0)
{
pc = htmlEntityInline.Transcoded[htmlEntityInline.Transcoded.End];
}
}
if (pc == 0)
{
pc = slice.PeekCharExtra(-1);
if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\')
{
return false;
}
}
var startPosition = slice.Start;
int delimiterCount = 0;
@@ -161,6 +173,14 @@ namespace Markdig.Parsers.Inlines
return false;
}
// The following character is actually an entity, we need to decode it
int htmlLength;
string htmlString;
if (HtmlEntityParser.TryParse(ref slice, out htmlString, out htmlLength))
{
c = htmlString[0];
}
// Calculate Open-Close for current character
bool canOpen;
bool canClose;
@@ -204,7 +224,7 @@ namespace Markdig.Parsers.Inlines
// at the end of the CommonMark specs.
// Move current_position forward in the delimiter stack (if needed) until
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
// we find the first potential closer with delimiter * or _. (This will be the potential closer closest to the beginning of the input the first one in parse order.)
for (int i = 0; i < delimiters.Count; i++)
{
var closeDelimiter = delimiters[i];
@@ -219,7 +239,7 @@ namespace Markdig.Parsers.Inlines
while (true)
{
// Now, look back in the stack (staying above stack_bottom and the openers_bottom for this delimiter type)
// for the first matching potential opener (“matching” means same delimiter).
// for the first matching potential opener (“matching” means same delimiter).
EmphasisDelimiterInline openDelimiter = null;
int openDelimiterIndex = -1;
for (int j = i - 1; j >= 0; j--)
@@ -274,20 +294,6 @@ namespace Markdig.Parsers.Inlines
var embracer = (ContainerInline)openDelimiter;
// Go down to the first emphasis with a lower level
while (true)
{
var previousEmphasis = embracer.FirstChild as EmphasisInline;
if (previousEmphasis != null && previousEmphasis.IsDouble && !isStrong && embracer.FirstChild == embracer.LastChild)
{
embracer = previousEmphasis;
}
else
{
break;
}
}
// Copy attributes attached to delimiter to the emphasis
var attributes = closeDelimiter.TryGetAttributes();
if (attributes != null)

View File

@@ -28,10 +28,11 @@ namespace Markdig.Parsers.Inlines
{
processor.Inline = new LiteralInline()
{
Content = new StringSlice(new string(c, 1)),
Content = new StringSlice(slice.Text, slice.Start, slice.Start),
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
Line = line,
Column = column
Column = column,
IsFirstCharacterEscaped = true,
};
processor.Inline.Span.End = processor.Inline.Span.Start + 1;
slice.NextChar();
@@ -44,6 +45,7 @@ namespace Markdig.Parsers.Inlines
processor.Inline = new LineBreakInline()
{
IsHard = true,
IsBackslash = true,
Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) },
Line = line,
Column = column

View File

@@ -21,18 +21,18 @@ namespace Markdig.Parsers.Inlines
OpeningCharacters = new[] {'&'};
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
public static bool TryParse(ref StringSlice slice, out string literal, out int match)
{
literal = null;
string entityName;
int entityValue;
var startPosition = slice.Start;
int match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
match = HtmlHelper.ScanEntity(slice.Text, slice.Start, slice.Length, out entityName, out entityValue);
if (match == 0)
{
return false;
}
string literal = null;
if (entityName != null)
{
literal = EntityHelper.DecodeEntity(entityName);
@@ -41,6 +41,19 @@ namespace Markdig.Parsers.Inlines
{
literal = (entityValue == 0 ? null : EntityHelper.DecodeEntity(entityValue)) ?? CharHelper.ZeroSafeString;
}
return true;
}
public override bool Match(InlineProcessor processor, ref StringSlice slice)
{
int match;
string literal;
if (!TryParse(ref slice, out literal, out match))
{
return false;
}
var startPosition = slice.Start;
if (literal != null)
{

View File

@@ -86,7 +86,7 @@ namespace Markdig.Parsers.Inlines
}
}
// If we dont find one, we return a literal slice node ].
// If we dont find one, we return a literal slice node ].
// (Done after by the LiteralInline parser)
return false;
}
@@ -95,7 +95,7 @@ namespace Markdig.Parsers.Inlines
return false;
}
private bool ProcessLinkReference(InlineProcessor state, string label, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
private bool ProcessLinkReference(InlineProcessor state, string label, bool isShortcut, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
{
bool isValidLink = false;
LinkReferenceDefinition linkRef;
@@ -120,6 +120,7 @@ namespace Markdig.Parsers.Inlines
LabelSpan = labelSpan,
UrlSpan = linkRef.UrlSpan,
IsImage = parent.IsImage,
IsShortcut = isShortcut,
Reference = linkRef,
Span = new SourceSpan(parent.Span.Start, endPosition),
Line = parent.Line,
@@ -189,7 +190,7 @@ namespace Markdig.Parsers.Inlines
if (openParent != null)
{
// If we do find one, but its not active,
// If we do find one, but its not active,
// we remove the inactive delimiter from the stack,
// and return a literal text node ].
if (!openParent.IsActive)
@@ -205,7 +206,7 @@ namespace Markdig.Parsers.Inlines
return false;
}
// If we find one and its active,
// If we find one and its active,
// then we parse ahead to see if we have
// an inline link/image, reference link/image,
// compact reference link/image,
@@ -261,6 +262,8 @@ namespace Markdig.Parsers.Inlines
var labelSpan = SourceSpan.Empty;
string label = null;
bool isLabelSpanLocal = true;
bool isShortcut = false;
// Handle Collapsed links
if (text.CurrentChar == '[')
{
@@ -276,6 +279,7 @@ namespace Markdig.Parsers.Inlines
else
{
label = openParent.Label;
isShortcut = true;
}
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
@@ -285,7 +289,7 @@ namespace Markdig.Parsers.Inlines
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
}
if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
if (ProcessLinkReference(inlineState, label, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
{
// Remove the open parent
openParent.Remove();

View File

@@ -230,17 +230,16 @@ namespace Markdig.Parsers
return BlockState.None;
}
// We require at least one char
state.NextColumn();
// Parse the following indent
state.RestartIndent();
var columnBeforeIndent = state.Column;
state.ParseIndent();
if (state.IsCodeIndent)
// We expect at most 4 columns after
// If we have more, we reset the position
if (state.Indent > 4)
{
state.GoToColumn(columnBeforeIndent);
state.GoToColumn(columnBeforeIndent + 1);
}
// Number of spaces required for the following content to be part of this list item

View File

@@ -88,7 +88,9 @@ namespace Markdig.Parsers
processor.NewBlocks.Push(new ThematicBreakBlock(this)
{
Column = processor.Column,
Span = new SourceSpan(startPosition, line.End)
Span = new SourceSpan(startPosition, line.End),
ThematicChar = breakChar,
ThematicCharCount = breakCharCount
});
return BlockState.BreakDiscard;
}

View File

@@ -1,30 +0,0 @@
using System.Resources;
using System.Reflection;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Markdig")]
[assembly: AssemblyDescription("A fast, powerfull, CommonMark compliant, extensible Markdown processor for .NET")]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Markdig")]
[assembly: AssemblyCopyright("Copyright © 2016 - Alexandre MUTEL")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: AssemblyVersion(Markdig.Markdown.Version)]
[assembly: AssemblyFileVersion(Markdig.Markdown.Version)]
namespace Markdig
{
public static partial class Markdown
{
public const string Version = "0.10.7";
}
}

View File

@@ -42,29 +42,50 @@ namespace Markdig.Renderers.Html
// We are replacing the HTML attribute `language-mylang` by `mylang` only for a div block
// NOTE that we are allocating a closure here
renderer.Write("<div")
.WriteAttributes(obj.TryGetAttributes(),
cls => cls.StartsWith(infoPrefix) ? cls.Substring(infoPrefix.Length) : cls)
.Write(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<div")
.WriteAttributes(obj.TryGetAttributes(),
cls => cls.StartsWith(infoPrefix) ? cls.Substring(infoPrefix.Length) : cls)
.Write(">");
}
renderer.WriteLeafRawLines(obj, true, true, true);
renderer.WriteLine("</div>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</div>");
}
}
else
{
renderer.Write("<pre");
if (OutputAttributesOnPre)
if (renderer.EnableHtmlForBlock)
{
renderer.WriteAttributes(obj);
renderer.Write("<pre");
if (OutputAttributesOnPre)
{
renderer.WriteAttributes(obj);
}
renderer.Write("><code");
if (!OutputAttributesOnPre)
{
renderer.WriteAttributes(obj);
}
renderer.Write(">");
}
renderer.Write("><code");
if (!OutputAttributesOnPre)
{
renderer.WriteAttributes(obj);
}
renderer.Write(">");
renderer.WriteLeafRawLines(obj, true, true);
renderer.WriteLine("</code></pre>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</code></pre>");
}
}
}
}

View File

@@ -27,9 +27,17 @@ namespace Markdig.Renderers.Html
? HeadingTexts[obj.Level - 1]
: "<h" + obj.Level.ToString(CultureInfo.InvariantCulture);
renderer.Write("<").Write(headingText).WriteAttributes(obj).Write(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<").Write(headingText).WriteAttributes(obj).Write(">");
}
renderer.WriteLeafInline(obj);
renderer.Write("</").Write(headingText).WriteLine(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("</").Write(headingText).WriteLine(">");
}
}
}
}

View File

@@ -37,7 +37,7 @@ namespace Markdig.Renderers.Html.Inlines
if (renderer.EnableHtmlForInline)
{
tag = GetTag(obj);
renderer.Write("<").Write(tag).Write(">");
renderer.Write("<").Write(tag).WriteAttributes(obj).Write(">");
}
renderer.WriteChildren(obj);
if (renderer.EnableHtmlForInline)

View File

@@ -15,27 +15,31 @@ namespace Markdig.Renderers.Html
protected override void Write(HtmlRenderer renderer, ListBlock listBlock)
{
renderer.EnsureLine();
if (listBlock.IsOrdered)
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<ol");
if (listBlock.BulletType != '1')
if (listBlock.IsOrdered)
{
renderer.Write(" type=\"").Write(listBlock.BulletType).Write("\"");
}
renderer.Write("<ol");
if (listBlock.BulletType != '1')
{
renderer.Write(" type=\"").Write(listBlock.BulletType).Write("\"");
}
if (listBlock.OrderedStart != null && (listBlock.OrderedStart != "1"))
{
renderer.Write(" start=\"").Write(listBlock.OrderedStart).Write("\"");
if (listBlock.OrderedStart != null && (listBlock.OrderedStart != "1"))
{
renderer.Write(" start=\"").Write(listBlock.OrderedStart).Write("\"");
}
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
else
{
renderer.Write("<ul");
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
else
{
renderer.Write("<ul");
renderer.WriteAttributes(listBlock);
renderer.WriteLine(">");
}
foreach (var item in listBlock)
{
var listItem = (ListItemBlock)item;
@@ -43,13 +47,25 @@ namespace Markdig.Renderers.Html
renderer.ImplicitParagraph = !listBlock.IsLoose;
renderer.EnsureLine();
renderer.Write("<li").WriteAttributes(listItem).Write(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<li").WriteAttributes(listItem).Write(">");
}
renderer.WriteChildren(listItem);
renderer.WriteLine("</li>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</li>");
}
renderer.ImplicitParagraph = previousImplicit;
}
renderer.WriteLine(listBlock.IsOrdered ? "</ol>" : "</ul>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine(listBlock.IsOrdered ? "</ol>" : "</ul>");
}
}
}
}

View File

@@ -13,16 +13,17 @@ namespace Markdig.Renderers.Html
{
protected override void Write(HtmlRenderer renderer, ParagraphBlock obj)
{
if (!renderer.ImplicitParagraph)
if (!renderer.ImplicitParagraph && renderer.EnableHtmlForBlock)
{
if (!renderer.IsFirstInContainer)
{
renderer.EnsureLine();
}
renderer.Write("<p").WriteAttributes(obj).Write(">");
}
renderer.WriteLeafInline(obj);
if (!renderer.ImplicitParagraph)
if (!renderer.ImplicitParagraph && renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</p>");
}

View File

@@ -14,12 +14,18 @@ namespace Markdig.Renderers.Html
protected override void Write(HtmlRenderer renderer, QuoteBlock obj)
{
renderer.EnsureLine();
renderer.Write("<blockquote").WriteAttributes(obj).WriteLine(">");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<blockquote").WriteAttributes(obj).WriteLine(">");
}
var savedImplicitParagraph = renderer.ImplicitParagraph;
renderer.ImplicitParagraph = false;
renderer.WriteChildren(obj);
renderer.ImplicitParagraph = savedImplicitParagraph;
renderer.WriteLine("</blockquote>");
if (renderer.EnableHtmlForBlock)
{
renderer.WriteLine("</blockquote>");
}
}
}
}

View File

@@ -13,7 +13,10 @@ namespace Markdig.Renderers.Html
{
protected override void Write(HtmlRenderer renderer, ThematicBreakBlock obj)
{
renderer.Write("<hr").WriteAttributes(obj).WriteLine(" />");
if (renderer.EnableHtmlForBlock)
{
renderer.Write("<hr").WriteAttributes(obj).WriteLine(" />");
}
}
}
}

View File

@@ -45,18 +45,27 @@ namespace Markdig.Renderers
ObjectRenderers.Add(new LinkInlineRenderer());
ObjectRenderers.Add(new LiteralInlineRenderer());
EnableHtmlForBlock = true;
EnableHtmlForInline = true;
EnableHtmlEscape = true;
}
/// <summary>
/// Gets or sets a value indicating whether to ouput HTML tags when rendering. See remarks.
/// Gets or sets a value indicating whether to output HTML tags when rendering. See remarks.
/// </summary>
/// <remarks>
/// This is used by some renderers to disable HTML tags when rendering some inlines (for image links).
/// This is used by some renderers to disable HTML tags when rendering some inline elements (for image links).
/// </remarks>
public bool EnableHtmlForInline { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to output HTML tags when rendering. See remarks.
/// </summary>
/// <remarks>
/// This is used by some renderers to disable HTML tags when rendering some block elements (for image links).
/// </remarks>
public bool EnableHtmlForBlock { get; set; }
public bool EnableHtmlEscape { get; set; }
/// <summary>

View File

@@ -0,0 +1,55 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Renderers.Html;
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// An Normalize renderer for a <see cref="CodeBlock"/> and <see cref="FencedCodeBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.CodeBlock}" />
public class CodeBlockRenderer : NormalizeObjectRenderer<CodeBlock>
{
public bool OutputAttributesOnPre { get; set; }
protected override void Write(NormalizeRenderer renderer, CodeBlock obj)
{
var fencedCodeBlock = obj as FencedCodeBlock;
if (fencedCodeBlock != null)
{
var opening = new string(fencedCodeBlock.FencedChar, fencedCodeBlock.FencedCharCount);
renderer.Write(opening);
if (fencedCodeBlock.Info != null)
{
renderer.Write(fencedCodeBlock.Info);
}
if (!string.IsNullOrEmpty(fencedCodeBlock.Arguments))
{
renderer.Write(" ").Write(fencedCodeBlock.Arguments);
}
/* TODO do we need this causes a empty space and would render html attributes to markdown.
var attributes = obj.TryGetAttributes();
if (attributes != null)
{
renderer.Write(" ");
renderer.Write(attributes);
}
*/
renderer.WriteLine();
renderer.WriteLeafRawLines(obj, true);
renderer.Write(opening);
}
else
{
renderer.WriteLeafRawLines(obj, false, true);
}
renderer.FinishBlock(renderer.Options.EmptyLineAfterCodeBlock);
}
}
}

View File

@@ -0,0 +1,36 @@
// 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.Globalization;
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// An Normalize renderer for a <see cref="HeadingBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.HeadingBlock}" />
public class HeadingRenderer : NormalizeObjectRenderer<HeadingBlock>
{
private static readonly string[] HeadingTexts = {
"#",
"##",
"###",
"####",
"#####",
"######",
};
protected override void Write(NormalizeRenderer renderer, HeadingBlock obj)
{
var headingText = obj.Level > 0 && obj.Level <= 6
? HeadingTexts[obj.Level - 1]
: new string('#', obj.Level);
renderer.Write(headingText).Write(' ');
renderer.WriteLeafInline(obj);
renderer.FinishBlock(renderer.Options.EmptyLineAfterHeading);
}
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
public class HtmlBlockRenderer : NormalizeObjectRenderer<HtmlBlock>
{
protected override void Write(NormalizeRenderer renderer, HtmlBlock obj)
{
renderer.WriteLeafRawLines(obj, true, false);
}
}
}

View File

@@ -0,0 +1,19 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for an <see cref="AutolinkInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.AutolinkInline}" />
public class AutolinkInlineRenderer : NormalizeObjectRenderer<AutolinkInline>
{
protected override void Write(NormalizeRenderer renderer, AutolinkInline obj)
{
renderer.Write('<').Write(obj.Url).Write('>');
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="CodeInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.CodeInline}" />
public class CodeInlineRenderer : NormalizeObjectRenderer<CodeInline>
{
protected override void Write(NormalizeRenderer renderer, CodeInline obj)
{
var delimiter = obj.Content.Contains(obj.Delimiter + "") ? new string(obj.Delimiter, 2) : obj.Delimiter + "";
renderer.Write(delimiter);
renderer.Write(obj.Content);
renderer.Write(delimiter);
}
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="DelimiterInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.DelimiterInline}" />
public class DelimiterInlineRenderer : NormalizeObjectRenderer<DelimiterInline>
{
protected override void Write(NormalizeRenderer renderer, DelimiterInline obj)
{
renderer.Write(obj.ToLiteral());
renderer.WriteChildren(obj);
}
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for an <see cref="EmphasisInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.EmphasisInline}" />
public class EmphasisInlineRenderer : NormalizeObjectRenderer<EmphasisInline>
{
protected override void Write(NormalizeRenderer renderer, EmphasisInline obj)
{
var emphasisText = new string(obj.DelimiterChar, obj.IsDouble ? 2 : 1);
renderer.Write(emphasisText);
renderer.WriteChildren(obj);
renderer.Write(emphasisText);
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="LineBreakInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.LineBreakInline}" />
public class LineBreakInlineRenderer : NormalizeObjectRenderer<LineBreakInline>
{
/// <summary>
/// Gets or sets a value indicating whether to render this softline break as a Normalize hardline break tag (&lt;br /&gt;)
/// </summary>
public bool RenderAsHardlineBreak { get; set; }
protected override void Write(NormalizeRenderer renderer, LineBreakInline obj)
{
if (obj.IsHard)
{
renderer.Write(obj.IsBackslash ? "\\" : " ");
}
renderer.WriteLine();
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="LinkInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.LinkInline}" />
public class LinkInlineRenderer : NormalizeObjectRenderer<LinkInline>
{
protected override void Write(NormalizeRenderer renderer, LinkInline link)
{
if (link.IsImage)
{
renderer.Write('!');
}
renderer.Write('[');
renderer.WriteChildren(link);
renderer.Write(']');
if (link.Label != null)
{
var literal = link.FirstChild as LiteralInline;
if (literal != null && literal.Content.Match(link.Label) && literal.Content.Length == link.Label.Length)
{
// collapsed reference and shortcut links
if (!link.IsShortcut)
{
renderer.Write("[]");
}
}
else
{
// full link
renderer.Write('[').Write(link.Label).Write(']');
}
}
else
{
if (!string.IsNullOrEmpty(link.Url))
{
renderer.Write('(').Write(link.Url);
if (!string.IsNullOrEmpty(link.Title))
{
renderer.Write(" \"");
renderer.Write(link.Title.Replace(@"""", @"\"""));
renderer.Write("\"");
}
renderer.Write(')');
}
}
}
}
}

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 Markdig.Helpers;
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="LiteralInline"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.Inlines.LiteralInline}" />
public class LiteralInlineRenderer : NormalizeObjectRenderer<LiteralInline>
{
protected override void Write(NormalizeRenderer renderer, LiteralInline obj)
{
if (obj.IsFirstCharacterEscaped && obj.Content.Length > 0 && obj.Content[obj.Content.Start].IsAsciiPunctuation())
{
renderer.Write('\\');
}
renderer.Write(ref obj.Content);
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="HtmlEntityInline"/>.
/// </summary>
public class NormalizeHtmlEntityInlineRenderer : NormalizeObjectRenderer<HtmlEntityInline>
{
protected override void Write(NormalizeRenderer renderer, HtmlEntityInline obj)
{
renderer.Write(obj.Original);
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax.Inlines;
namespace Markdig.Renderers.Normalize.Inlines
{
/// <summary>
/// A Normalize renderer for a <see cref="HtmlInline"/>.
/// </summary>
public class NormalizeHtmlInlineRenderer : NormalizeObjectRenderer<HtmlInline>
{
protected override void Write(NormalizeRenderer renderer, HtmlInline obj)
{
renderer.Write(obj.Tag);
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
public class LinkReferenceDefinitionGroupRenderer : NormalizeObjectRenderer<LinkReferenceDefinitionGroup>
{
protected override void Write(NormalizeRenderer renderer, LinkReferenceDefinitionGroup obj)
{
renderer.EnsureLine();
renderer.WriteChildren(obj);
renderer.FinishBlock(false);
}
}
}

View File

@@ -0,0 +1,28 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
public class LinkReferenceDefinitionRenderer : NormalizeObjectRenderer<LinkReferenceDefinition>
{
protected override void Write(NormalizeRenderer renderer, LinkReferenceDefinition linkDef)
{
renderer.EnsureLine();
renderer.Write('[');
renderer.Write(linkDef.Label);
renderer.Write("]: ");
renderer.Write(linkDef.Url);
if (linkDef.Title != null)
{
renderer.Write(" \"");
renderer.Write(linkDef.Title.Replace("\"", "\\\""));
renderer.Write('"');
}
renderer.FinishBlock(false);
}
}
}

View File

@@ -0,0 +1,93 @@
// 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.Globalization;
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="ListBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.ListBlock}" />
public class ListRenderer : NormalizeObjectRenderer<ListBlock>
{
protected override void Write(NormalizeRenderer renderer, ListBlock listBlock)
{
renderer.EnsureLine();
var compact = renderer.CompactParagraph;
renderer.CompactParagraph = !listBlock.IsLoose;
if (listBlock.IsOrdered)
{
int index = 0;
if (listBlock.OrderedStart != null)
{
switch (listBlock.BulletType)
{
case '1':
int.TryParse(listBlock.OrderedStart, out index);
break;
}
}
for (var i = 0; i < listBlock.Count; i++)
{
var item = listBlock[i];
var listItem = (ListItemBlock) item;
renderer.EnsureLine();
renderer.Write(index.ToString(CultureInfo.InvariantCulture));
renderer.Write(listBlock.OrderedDelimiter);
renderer.Write(' ');
renderer.PushIndent(new string(' ', IntLog10Fast(index) + 3));
renderer.WriteChildren(listItem);
renderer.PopIndent();
switch (listBlock.BulletType)
{
case '1':
index++;
break;
}
if (i + 1 < listBlock.Count && listBlock.IsLoose)
{
renderer.EnsureLine();
renderer.WriteLine();
}
}
}
else
{
for (var i = 0; i < listBlock.Count; i++)
{
var item = listBlock[i];
var listItem = (ListItemBlock) item;
renderer.EnsureLine();
renderer.Write(renderer.Options.ListItemCharacter ?? listBlock.BulletType);
renderer.Write(' ');
renderer.PushIndent(" ");
renderer.WriteChildren(listItem);
renderer.PopIndent();
if (i + 1 < listBlock.Count && listBlock.IsLoose)
{
renderer.EnsureLine();
renderer.WriteLine();
}
}
}
renderer.CompactParagraph = compact;
renderer.FinishBlock(true);
}
private static int IntLog10Fast(int input) =>
(input < 10) ? 0 :
(input < 100) ? 1 :
(input < 1000) ? 2 :
(input < 10000) ? 3 :
(input < 100000) ? 4 :
(input < 1000000) ? 5 :
(input < 10000000) ? 6 :
(input < 100000000) ? 7 :
(input < 1000000000) ? 8 : 9;
}
}

View File

@@ -0,0 +1,16 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A base class for Normalize rendering <see cref="Block"/> and <see cref="Markdig.Syntax.Inlines.Inline"/> Markdown objects.
/// </summary>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <seealso cref="Markdig.Renderers.IMarkdownObjectRenderer" />
public abstract class NormalizeObjectRenderer<TObject> : MarkdownObjectRenderer<NormalizeRenderer, TObject> where TObject : MarkdownObject
{
}
}

View File

@@ -0,0 +1,48 @@
// 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.
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// Defines the options used by <see cref="NormalizeRenderer"/>
/// </summary>
public class NormalizeOptions
{
/// <summary>
/// Initialize a new instance of <see cref="NormalizeOptions"/>
/// </summary>
public NormalizeOptions()
{
SpaceAfterQuoteBlock = true;
EmptyLineAfterCodeBlock = true;
EmptyLineAfterHeading = true;
EmptyLineAfterThematicBreak = true;
ListItemCharacter = null;
}
/// <summary>
/// Adds a space after a QuoteBlock &gt;. Default is <c>true</c>
/// </summary>
public bool SpaceAfterQuoteBlock { get; set; }
/// <summary>
/// Adds an empty line after a code block (fenced and tabbed). Default is <c>true</c>
/// </summary>
public bool EmptyLineAfterCodeBlock { get; set; }
/// <summary>
/// Adds an empty line after an heading. Default is <c>true</c>
/// </summary>
public bool EmptyLineAfterHeading { get; set; }
/// <summary>
/// Adds an empty line after an thematic break. Default is <c>true</c>
/// </summary>
public bool EmptyLineAfterThematicBreak { get; set; }
/// <summary>
/// The bullet character used for list items. Default is <c>null</c> leaving the original bullet character as-is.
/// </summary>
public char? ListItemCharacter { get; set; }
}
}

View File

@@ -0,0 +1,161 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.IO;
using Markdig.Syntax;
using Markdig.Renderers.Normalize.Inlines;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// Default HTML renderer for a Markdown <see cref="MarkdownDocument"/> object.
/// </summary>
/// <seealso cref="Markdig.Renderers.TextRendererBase{Markdig.Renderers.Normalize.NormalizeRenderer}" />
public class NormalizeRenderer : TextRendererBase<NormalizeRenderer>
{
/// <summary>
/// Initializes a new instance of the <see cref="NormalizeRenderer"/> class.
/// </summary>
/// <param name="writer">The writer.</param>
/// <param name="options">The normalize options</param>
public NormalizeRenderer(TextWriter writer, NormalizeOptions options = null) : base(writer)
{
Options = options ?? new NormalizeOptions();
// Default block renderers
ObjectRenderers.Add(new CodeBlockRenderer());
ObjectRenderers.Add(new ListRenderer());
ObjectRenderers.Add(new HeadingRenderer());
ObjectRenderers.Add(new HtmlBlockRenderer());
ObjectRenderers.Add(new ParagraphRenderer());
ObjectRenderers.Add(new QuoteBlockRenderer());
ObjectRenderers.Add(new ThematicBreakRenderer());
ObjectRenderers.Add(new LinkReferenceDefinitionGroupRenderer());
ObjectRenderers.Add(new LinkReferenceDefinitionRenderer());
// Default inline renderers
ObjectRenderers.Add(new AutolinkInlineRenderer());
ObjectRenderers.Add(new CodeInlineRenderer());
ObjectRenderers.Add(new DelimiterInlineRenderer());
ObjectRenderers.Add(new EmphasisInlineRenderer());
ObjectRenderers.Add(new LineBreakInlineRenderer());
ObjectRenderers.Add(new NormalizeHtmlInlineRenderer());
ObjectRenderers.Add(new NormalizeHtmlEntityInlineRenderer());
ObjectRenderers.Add(new LinkInlineRenderer());
ObjectRenderers.Add(new LiteralInlineRenderer());
}
public NormalizeOptions Options { get; }
public bool CompactParagraph { get; set; }
public void FinishBlock(bool emptyLine)
{
if (!IsLastInContainer)
{
WriteLine();
if (emptyLine)
{
WriteLine();
}
}
}
///// <summary>
///// Writes the attached <see cref="HtmlAttributes"/> on the specified <see cref="MarkdownObject"/>.
///// </summary>
///// <param name="obj">The object.</param>
///// <returns></returns>
//public NormalizeRenderer WriteAttributes(MarkdownObject obj)
//{
// if (obj == null) throw new ArgumentNullException(nameof(obj));
// return WriteAttributes(obj.TryGetAttributes());
//}
///// <summary>
///// Writes the specified <see cref="HtmlAttributes"/>.
///// </summary>
///// <param name="attributes">The attributes to render.</param>
///// <returns>This instance</returns>
//public NormalizeRenderer WriteAttributes(HtmlAttributes attributes)
//{
// if (attributes == null)
// {
// return this;
// }
// if (attributes.Id != null)
// {
// Write(" id=\"").WriteEscape(attributes.Id).Write("\"");
// }
// if (attributes.Classes != null && attributes.Classes.Count > 0)
// {
// Write(" class=\"");
// for (int i = 0; i < attributes.Classes.Count; i++)
// {
// var cssClass = attributes.Classes[i];
// if (i > 0)
// {
// Write(" ");
// }
// WriteEscape(cssClass);
// }
// Write("\"");
// }
// if (attributes.Properties != null && attributes.Properties.Count > 0)
// {
// foreach (var property in attributes.Properties)
// {
// Write(" ").Write(property.Key);
// if (property.Value != null)
// {
// Write("=").Write("\"");
// WriteEscape(property.Value);
// Write("\"");
// }
// }
// }
// return this;
//}
/// <summary>
/// Writes the lines of a <see cref="LeafBlock"/>
/// </summary>
/// <param name="leafBlock">The leaf block.</param>
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
/// <returns>This instance</returns>
public NormalizeRenderer WriteLeafRawLines(LeafBlock leafBlock, bool writeEndOfLines, bool indent = false)
{
if (leafBlock == null) throw new ArgumentNullException(nameof(leafBlock));
if (leafBlock.Lines.Lines != null)
{
var lines = leafBlock.Lines;
var slices = lines.Lines;
for (int i = 0; i < lines.Count; i++)
{
if (!writeEndOfLines && i > 0)
{
WriteLine();
}
if (indent)
{
Write(" ");
}
Write(ref slices[i].Slice);
if (writeEndOfLines)
{
WriteLine();
}
}
}
return this;
}
}
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="ParagraphBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.ParagraphBlock}" />
public class ParagraphRenderer : NormalizeObjectRenderer<ParagraphBlock>
{
protected override void Write(NormalizeRenderer renderer, ParagraphBlock obj)
{
renderer.WriteLeafInline(obj);
renderer.FinishBlock(!renderer.CompactParagraph);
}
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using Markdig.Syntax;
namespace Markdig.Renderers.Normalize
{
/// <summary>
/// A Normalize renderer for a <see cref="QuoteBlock"/>.
/// </summary>
/// <seealso cref="Markdig.Renderers.Normalize.NormalizeObjectRenderer{Markdig.Syntax.QuoteBlock}" />
public class QuoteBlockRenderer : NormalizeObjectRenderer<QuoteBlock>
{
protected override void Write(NormalizeRenderer renderer, QuoteBlock obj)
{
var quoteIndent = renderer.Options.SpaceAfterQuoteBlock ? obj.QuoteChar + " " : obj.QuoteChar.ToString();
renderer.PushIndent(quoteIndent);
renderer.WriteChildren(obj);
renderer.PopIndent();
renderer.FinishBlock(true);
}
}
}

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