├── CommonMark.Console ├── Sample.txt ├── App.config ├── App.nuget.config ├── Properties │ └── AssemblyInfo.cs ├── CommonMark.Console.csproj └── Program.cs ├── CommonMark ├── Properties │ ├── public.snk │ └── AssemblyInfo.cs ├── Syntax │ ├── ListDelimiter.cs │ ├── ListType.cs │ ├── DocumentData.cs │ ├── HeadingData.cs │ ├── EmphasisData.cs │ ├── InlineContentLinkable.cs │ ├── FencedCodeData.cs │ ├── StringContentPart.cs │ ├── HtmlBlockType.cs │ ├── ListData.cs │ ├── Reference.cs │ ├── BlockTag.cs │ ├── InlineTag.cs │ ├── EnumeratorEntry.cs │ ├── Enumerable.cs │ ├── Inline.cs │ └── Block.cs ├── CommonMark.NET20.csproj ├── CommonMark.NET45.csproj ├── CommonMark.NET35.csproj ├── CommonMark.NET40.csproj ├── CommonMark.Profile259.csproj ├── Func.cs ├── OutputFormat.cs ├── Parser │ ├── PositionOffset.cs │ ├── LineInfo.cs │ ├── Subject.cs │ ├── TabTextReader.cs │ ├── PositionTracker.cs │ ├── ScannerCharacterMatcher.cs │ └── InlineStack.cs ├── CommonMarkAdditionalFeatures.cs ├── CommonMark.Targets.csproj ├── Lazy.cs ├── CommonMark.Base.csproj ├── Utilities.cs ├── CommonMarkException.cs ├── Formatters │ └── HtmlTextWriter.cs └── CommonMarkSettings.cs ├── global.json ├── appveyor.yml ├── CommonMark.Tests ├── HeadingTests.cs ├── SettingsTests.cs ├── Properties │ └── AssemblyInfo.cs ├── GeneralTests.cs ├── HtmlTests.cs ├── UrlTests.cs ├── ListTests.cs ├── EmphasisTests.cs ├── EnumeratorTests.cs ├── DelimiterCharTests.cs ├── Helpers.cs ├── StrikethroughTests.cs ├── CommonMark.Tests.csproj └── Specification │ └── Specs.tt ├── CommonMark.NETCore ├── project.json └── CommonMark.NETCore.xproj ├── LICENSE.md ├── CommonMark.NET.nuspec ├── .gitattributes ├── .gitignore ├── README.md ├── runtests.pl └── CommonMark.sln /CommonMark.Console/Sample.txt: -------------------------------------------------------------------------------- 1 | [CommonMark] [1] 2 | 3 | [1]: http://commonmark.org/ -------------------------------------------------------------------------------- /CommonMark/Properties/public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/josephwoodward/CommonMark.NET/master/CommonMark/Properties/public.snk -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "1.0.0-beta8", 4 | "runtime": "coreclr", 5 | "architecture": "x86" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CommonMark.Console/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | os: Visual Studio 2015 3 | configuration: 4 | - Release 5 | - Debug 6 | environment: 7 | DNX_FEED: https://www.nuget.org/api/v2 8 | install: 9 | - cmd: >- 10 | set PATH=C:\Program Files (x86)\MSBuild\14.0\Bin;%PATH% 11 | 12 | dnvm install latest -r coreclr -a x86 -NoNative 13 | 14 | dnvm install latest -r clr -a x86 -NoNative 15 | 16 | dnu restore 17 | build: 18 | parallel: true 19 | verbosity: minimal -------------------------------------------------------------------------------- /CommonMark/Syntax/ListDelimiter.cs: -------------------------------------------------------------------------------- 1 | namespace CommonMark.Syntax 2 | { 3 | /// 4 | /// Defines the delimiter used in the source for ordered lists. 5 | /// 6 | public enum ListDelimiter : byte 7 | { 8 | /// 9 | /// The item numbering is followed with a period (1. foo). 10 | /// 11 | Period = 0, 12 | 13 | /// 14 | /// The item numbering is followed with a closing parenthesis (1) foo). 15 | /// 16 | Parenthesis 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CommonMark.Console/App.nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /CommonMark.Tests/HeadingTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CommonMark.Tests 4 | { 5 | [TestClass] 6 | public class HeadingTests 7 | { 8 | [TestMethod] 9 | [TestCategory("Leaf blocks - ATX headings")] 10 | public void HeadingsAndThematicBreaks() 11 | { 12 | // see https://github.com/Knagis/CommonMark.NET/issues/60 13 | Helpers.ExecuteTest("##### A\n---\n\n##### B\n---\n\n##### C\n---", "
A
\n
\n
B
\n
\n
C
\n
\n"); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CommonMark/CommonMark.NET20.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | v2.0 5 | {075E7F00-F1C0-49AA-808D-8B15F1A82DDE} 6 | v2.0 7 | OptimizeFor20 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CommonMark/CommonMark.NET45.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | v4.5 5 | v4.5 6 | {40AB0D9C-9464-45B2-AC79-72A566FA1371} 7 | OptimizeFor45 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CommonMark/CommonMark.NET35.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | v3.5 5 | {61829669-5091-4F2A-A0B1-7348D7CED840} 6 | v3.5 7 | Client 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CommonMark/CommonMark.NET40.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | v4.0 5 | v4.0 6 | Client 7 | {0176CB03-177C-464A-A155-089595E77520} 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CommonMark/CommonMark.Profile259.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | portable_259 5 | v4.5 6 | Profile259 7 | {E42ABBF6-884C-4D2C-B21D-B96D8B42F8FE} 8 | OptimizeFor45 9 | 10 | 11 | -------------------------------------------------------------------------------- /CommonMark/Syntax/ListType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Defines the type of a list block element. 9 | /// 10 | public enum ListType 11 | { 12 | /// 13 | /// The list is unordered and its items are represented with bullets. 14 | /// 15 | Bullet = 0, 16 | 17 | /// 18 | /// The list is ordered and its items are numbered. 19 | /// 20 | Ordered 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CommonMark/Func.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommonMark 4 | { 5 | #if v2_0 6 | /// An alternative to System.Func which is not present in .NET 2.0. 7 | public delegate TResult Func(); 8 | 9 | /// An alternative to System.Func which is not present in .NET 2.0. 10 | public delegate TResult Func(T arg); 11 | 12 | /// An alternative to System.Action which is not present in .NET 2.0. 13 | public delegate void Action(T1 arg1, T2 arg2, T3 arg3); 14 | #endif 15 | } 16 | -------------------------------------------------------------------------------- /CommonMark.NETCore/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.12.0", 3 | "frameworks": { 4 | "dnxcore50": { 5 | "compilationOptions": { 6 | "define": [ 7 | "NETCore", 8 | "OptimizeFor45" 9 | ] 10 | }, 11 | "dependencies": { 12 | "System.IO": "4.0.10", 13 | "System.Runtime": "4.0.20", 14 | "System.Reflection": "4.0.10", 15 | "System.Collections": "4.0.10", 16 | "System.Diagnostics.Debug": "4.0.10", 17 | "System.Globalization.Extensions": "4.0.0" 18 | } 19 | } 20 | }, 21 | "compile": "../CommonMark/**/*.cs" 22 | } -------------------------------------------------------------------------------- /CommonMark/Syntax/DocumentData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CommonMark.Syntax 4 | { 5 | /// 6 | /// Contains additional data for document elements. Used in the property. 7 | /// 8 | public class DocumentData 9 | { 10 | /// 11 | /// Gets or sets the dictionary containing resolved link references. Only set on the document node, null 12 | /// and not used for all other elements. 13 | /// 14 | public Dictionary ReferenceMap { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CommonMark.Tests/SettingsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class SettingsTests 12 | { 13 | [TestMethod] 14 | [TestCategory("Inlines - Soft line break")] 15 | public void RenderSoftLineBreakAsLineBreak() 16 | { 17 | var settings = CommonMarkSettings.Default.Clone(); 18 | settings.RenderSoftLineBreaksAsLineBreaks = true; 19 | Helpers.ExecuteTest("A\nB\nC", "

A
\nB
\nC

", settings); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CommonMark/Syntax/HeadingData.cs: -------------------------------------------------------------------------------- 1 | namespace CommonMark.Syntax 2 | { 3 | /// 4 | /// Contains additional data for heading elements. Used in the property. 5 | /// 6 | public struct HeadingData 7 | { 8 | /// 9 | /// Initializes a new instance of the structure. 10 | /// 11 | /// Heading level. 12 | public HeadingData(int level) 13 | { 14 | Level = level <= byte.MaxValue ? (byte)level : byte.MaxValue; 15 | } 16 | 17 | /// 18 | /// Gets or sets the heading level. 19 | /// 20 | public byte Level { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CommonMark/Syntax/EmphasisData.cs: -------------------------------------------------------------------------------- 1 | namespace CommonMark.Syntax 2 | { 3 | /// 4 | /// Contains additional data for emphasis elements. Used in the property. 5 | /// 6 | public struct EmphasisData 7 | { 8 | /// 9 | /// Initializes a new instance of the struccture. 10 | /// 11 | /// Delimiter character. 12 | public EmphasisData(char delimiterCharacter) 13 | { 14 | DelimiterCharacter = delimiterCharacter; 15 | } 16 | 17 | /// 18 | /// Gets or sets the delimiter character. 19 | /// 20 | public char DelimiterCharacter { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CommonMark/OutputFormat.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark 6 | { 7 | /// 8 | /// Specifies different formatters supported by the converter. 9 | /// 10 | public enum OutputFormat 11 | { 12 | /// 13 | /// The output is standard HTML format according to the CommonMark specification. 14 | /// 15 | Html, 16 | 17 | /// 18 | /// The output is a debug view of the syntax tree. Usable for debugging. 19 | /// 20 | SyntaxTree, 21 | 22 | /// 23 | /// The output is written using a delegate function specified in . 24 | /// 25 | CustomDelegate 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CommonMark/Parser/PositionOffset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Parser 6 | { 7 | internal struct PositionOffset 8 | { 9 | public PositionOffset(int position, int offset) 10 | { 11 | this.Position = position; 12 | this.Offset = offset; 13 | } 14 | 15 | public int Position; 16 | public int Offset; 17 | 18 | public override string ToString() 19 | { 20 | if (this.Offset == 0) 21 | return "empty"; 22 | 23 | return (this.Offset < 0 ? string.Empty : "+") 24 | + this.Offset.ToString(System.Globalization.CultureInfo.InvariantCulture) 25 | + " chars @ " + this.Position.ToString(System.Globalization.CultureInfo.InvariantCulture); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CommonMark/Syntax/InlineContentLinkable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// An obsolete class. Used to contain properties specific to link inline elements. 9 | /// 10 | [Obsolete("These properties have been moved directly into the Inline element.")] 11 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 12 | public sealed class InlineContentLinkable 13 | { 14 | /// 15 | /// Gets or sets the URL of a link. Moved to . 16 | /// 17 | public string Url { get; set; } 18 | 19 | /// 20 | /// Gets or sets the title of a link. Moved to . 21 | /// 22 | public string Title { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CommonMark/CommonMarkAdditionalFeatures.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommonMark 4 | { 5 | /// 6 | /// Lists additional features that can be enabled in . 7 | /// These features are not part of the standard and should not be used if interoperability with other 8 | /// CommonMark implementations is required. 9 | /// 10 | [Flags] 11 | public enum CommonMarkAdditionalFeatures 12 | { 13 | /// 14 | /// No additional features are enabled. This is the default. 15 | /// 16 | None = 0, 17 | 18 | /// 19 | /// The parser will recognize syntax ~~foo~~ that will be rendered as <del>foo</del>. 20 | /// 21 | StrikethroughTilde = 1, 22 | 23 | /// 24 | /// All additional features are enabled. 25 | /// 26 | All = 0x7FFFFFFF 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CommonMark/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | 5 | [assembly: AssemblyTitle("CommonMark.NET")] 6 | [assembly: AssemblyDescription("https://github.com/Knagis/CommonMark.NET")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("CommonMark.NET")] 10 | [assembly: AssemblyCopyright("Copyright © Kārlis Gaņģis 2014-2015")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | // NETCore project gets the assembly version from project.json file. 15 | #if !NETCore 16 | [assembly: AssemblyVersion("0.1.0.0")] 17 | [assembly: AssemblyFileVersion("0.1.0.0")] 18 | #endif 19 | 20 | [assembly: CLSCompliant(true)] 21 | 22 | [assembly: InternalsVisibleTo("CommonMark.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d3a4c92a527690d5bb3b30c00821eb1346022f309ea9b069e4aae5187bb1336b2c16f4333ec41a71824aecbbb0205a76b4abe426be615d2bc254549a99f5caeda5a682773da4961419520dcafbd61e81b4e85b6a5d2a18b4ff52661cf88548a7559338daff82758d83bd00423fd5a8e4122ba0899e678045e4ff5c1d165475c4")] -------------------------------------------------------------------------------- /CommonMark/Syntax/FencedCodeData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Contains additional data for fenced code blocks. Used in / 9 | /// 10 | public sealed class FencedCodeData 11 | { 12 | /// 13 | /// Gets or sets the number of characters that were used in the opening code fence. 14 | /// 15 | public int FenceLength { get; set; } 16 | 17 | /// 18 | /// Gets or sets the number of spaces the opening fence was indented. 19 | /// 20 | public int FenceOffset { get; set; } 21 | 22 | /// 23 | /// Gets or sets the character that is used in the fences. 24 | /// 25 | public char FenceChar { get; set; } 26 | 27 | /// 28 | /// Gets or sets the additional information that was present in the same line as the opening fence. 29 | /// 30 | public string Info { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CommonMark/Syntax/StringContentPart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Represents a part of . 9 | /// 10 | internal struct StringPart 11 | { 12 | public StringPart(string source, int startIndex, int length) 13 | { 14 | this.Source = source; 15 | this.StartIndex = startIndex; 16 | this.Length = length; 17 | } 18 | 19 | /// 20 | /// Gets or sets the string object this part is created from. 21 | /// 22 | public string Source; 23 | 24 | /// 25 | /// Gets or sets the index at which this part starts. 26 | /// 27 | public int StartIndex; 28 | 29 | /// 30 | /// Gets or sets the length of the part. 31 | /// 32 | public int Length; 33 | 34 | public override string ToString() 35 | { 36 | if (this.Source == null) 37 | return null; 38 | 39 | return this.Source.Substring(this.StartIndex, this.Length); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CommonMark/CommonMark.Targets.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bin\$(BuildMode)\$(Configuration)\ 5 | obj\$(BuildMode)\ 6 | $(TargetFrameworkVersionEx) 7 | $(TargetFrameworkProfileEx) 8 | 9 | $(OutputPath)\CommonMark.xml 10 | $(DefineConstants);$(BuildMode.Replace('.', '_'));$(DefineConstantsEx) 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CommonMark.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CommonMark.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CommonMark.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © Kārlis Gaņģis 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("63cd64df-e46c-439d-942a-e0c461ff06c6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /CommonMark.Console/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CommonMark.NET Console")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CommonMark.NET")] 13 | [assembly: AssemblyCopyright("Copyright © Kārlis Gaņģis 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("c23df822-e0ff-4545-af75-6db40fa843ce")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Kārlis Gaņģis 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Kārlis Gaņģis nor the names of other contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /CommonMark/Lazy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommonMark 4 | { 5 | #if v2_0 || v3_5 6 | enum LazyThreadSafetyMode 7 | { 8 | None, 9 | PublicationOnly, 10 | ExecutionAndPublication 11 | } 12 | 13 | class Lazy 14 | { 15 | private readonly Func valueFactory; 16 | private readonly bool isThreadSafe; 17 | private readonly object _lock = new object(); 18 | private T value; 19 | 20 | public Lazy(Func valueFactory) 21 | : this(valueFactory, true) 22 | { 23 | } 24 | 25 | public Lazy(Func valueFactory, LazyThreadSafetyMode mode) 26 | : this(valueFactory, mode != LazyThreadSafetyMode.None) 27 | { 28 | } 29 | 30 | public Lazy(Func valueFactory, bool isThreadSafe) 31 | { 32 | this.valueFactory = valueFactory; 33 | this.isThreadSafe = isThreadSafe; 34 | } 35 | 36 | public T Value 37 | { 38 | get 39 | { 40 | if (value == null) 41 | { 42 | if (!isThreadSafe) 43 | { 44 | return value = valueFactory(); 45 | } 46 | lock (_lock) 47 | { 48 | if (value == null) 49 | { 50 | value = valueFactory(); 51 | } 52 | } 53 | } 54 | return value; 55 | } 56 | } 57 | } 58 | #endif 59 | } 60 | -------------------------------------------------------------------------------- /CommonMark.NETCore/CommonMark.NETCore.xproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 47c48819-62b7-4bd3-89ff-ba9a0b16c8fb 10 | CommonMark.NETCore 11 | obj\core\ 12 | bin\core\ 13 | 14 | 15 | CommonMark 16 | 17 | 18 | 2.0 19 | 20 | 21 | True 22 | 23 | 24 | True 25 | 26 | 27 | -------------------------------------------------------------------------------- /CommonMark/Syntax/HtmlBlockType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Specifies the type of the HTML block a instance represents. 9 | /// See http://spec.commonmark.org/0.22/#html-block 10 | /// 11 | public enum HtmlBlockType 12 | { 13 | /// 14 | /// This is not a HTML block. 15 | /// 16 | None = 0, 17 | 18 | /// 19 | /// The HTML block represents script, pre or style element. Unline other HTML tags 20 | /// these are allowed to contain blank lines. 21 | /// 22 | InterruptingBlockWithEmptyLines = 1, 23 | 24 | /// 25 | /// The block represents an HTML comment. 26 | /// 27 | Comment = 2, 28 | 29 | /// 30 | /// The block represents a processing instruction <??> 31 | /// 32 | ProcessingInstruction = 3, 33 | 34 | /// 35 | /// The block represents a doctype element <!...> 36 | /// 37 | DocumentType = 4, 38 | 39 | /// 40 | /// The block represents <![CDATA[...]] element. 41 | /// 42 | CData = 5, 43 | 44 | /// 45 | /// This HTML block can interrupt paragraphs. 46 | /// 47 | InterruptingBlock = 6, 48 | 49 | /// 50 | /// This HTML block cannot interrupt paragraphs. 51 | /// 52 | NonInterruptingBlock = 7, 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CommonMark/Parser/LineInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Parser 6 | { 7 | internal sealed class LineInfo 8 | { 9 | public LineInfo(bool trackPositions) 10 | { 11 | this.IsTrackingPositions = trackPositions; 12 | } 13 | 14 | public readonly bool IsTrackingPositions; 15 | 16 | public string Line; 17 | 18 | /// 19 | /// Gets or sets the offset in the source data at which the current line starts. 20 | /// 21 | public int LineOffset; 22 | 23 | public int LineNumber; 24 | 25 | public PositionOffset[] Offsets = new PositionOffset[20]; 26 | 27 | public int OffsetCount; 28 | 29 | public void AddOffset(int position, int offset) 30 | { 31 | if (offset == 0) 32 | return; 33 | 34 | if (this.OffsetCount == this.Offsets.Length) 35 | Array.Resize(ref this.Offsets, this.OffsetCount + 20); 36 | 37 | this.Offsets[this.OffsetCount++] = new PositionOffset(position, offset); 38 | } 39 | 40 | public override string ToString() 41 | { 42 | string ln; 43 | if (this.Line == null) 44 | ln = string.Empty; 45 | else if (this.Line.Length < 50) 46 | ln = this.Line; 47 | else 48 | ln = this.Line.Substring(0, 49) + "…"; 49 | 50 | return this.LineNumber.ToString(System.Globalization.CultureInfo.InvariantCulture) 51 | + ": " + ln; 52 | } 53 | 54 | public int CalculateOrigin(int position, bool isStartPosition) 55 | { 56 | return PositionTracker.CalculateOrigin(this.Offsets, this.OffsetCount, this.LineOffset + position, true, isStartPosition); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CommonMark.Tests/GeneralTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class GeneralTests 12 | { 13 | /// 14 | /// Verifies that the U+0000 characters are removed from the source data. 15 | /// 16 | [TestMethod] 17 | [TestCategory("Security")] 18 | public void TestZeroCharRemoval() 19 | { 20 | Helpers.ExecuteTest("\u0000*foo*\0", "

\uFFFDfoo\uFFFD

"); 21 | } 22 | 23 | [TestMethod] 24 | [TestCategory("Other")] 25 | public void EmptyString() 26 | { 27 | Helpers.ExecuteTest("", ""); 28 | } 29 | 30 | /// 31 | /// Verifies that the U+0000 characters are removed from the source data. 32 | /// 33 | [TestMethod] 34 | [TestCategory("Other")] 35 | public void Version() 36 | { 37 | var version = CommonMarkConverter.Version; 38 | 39 | Assert.AreEqual(0, version.Major, "The version number is incorrect: {0}", version); 40 | Assert.IsTrue(version.Minor > 5, "The version number is incorrect: {0}", version); 41 | } 42 | 43 | [TestMethod] 44 | [TestCategory("CommonMarkConverter")] 45 | public void ConvertShortcutMethod() 46 | { 47 | var expected = "

foo

"; 48 | var result = CommonMarkConverter.Convert("**foo**"); 49 | 50 | // Assert 51 | Helpers.LogValue("Expected", expected); 52 | Helpers.LogValue("Actual", result); 53 | Assert.AreEqual(Helpers.Tidy(expected), Helpers.Tidy(result)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CommonMark/Syntax/ListData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Contains additional data for list block elements. Used in property. 9 | /// 10 | public sealed class ListData 11 | { 12 | /// 13 | /// Gets or sets the number of spaces the list markers are indented. 14 | /// 15 | public int MarkerOffset { get; set; } 16 | 17 | /// 18 | /// Gets or sets the position of the list item contents in the source text line. 19 | /// 20 | public int Padding { get; set; } 21 | 22 | /// 23 | /// Gets or sets the number for the first list item if is set to 24 | /// . 25 | /// 26 | public int Start { get; set; } 27 | 28 | /// 29 | /// Gets or sets the character used for unordered lists. Used if is set to 30 | /// . 31 | /// 32 | public char BulletChar { get; set; } 33 | 34 | /// 35 | /// Gets or sets the type (ordered or unordered) of this list. 36 | /// 37 | public ListType ListType { get; set; } 38 | 39 | /// 40 | /// Gets or sets the character that follows the number if is set to 41 | /// . 42 | /// 43 | public ListDelimiter Delimiter { get; set; } 44 | 45 | /// 46 | /// Gets or sets a value indicating whether the list is tight (such list will not render additional explicit 47 | /// paragraph elements). 48 | /// 49 | public bool IsTight { get; set; } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CommonMark.Tests/HtmlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class HtmlTests 12 | { 13 | [TestMethod] 14 | [TestCategory("Inlines - Raw HTML")] 15 | public void HtmlTagsWithDigits() 16 | { 17 | Helpers.ExecuteTest("foo

asd

bar", "

foo

asd

bar

"); 18 | } 19 | 20 | [TestMethod] 21 | [TestCategory("Inlines - Raw HTML")] 22 | public void HtmlTagAttributes() 23 | { 24 | Helpers.ExecuteTest( 25 | "foo <čā\uD83D\uDF13\">asd bar", 26 | "

foo <čā\uD83D\uDF13\">asd bar

"); 27 | } 28 | 29 | [TestMethod] 30 | [TestCategory("Inlines - Raw HTML")] 31 | public void HtmlTagAttributesLink() 32 | { 33 | Helpers.ExecuteTest( 34 | "foo asd bar", 35 | "

foo asd bar

"); 36 | } 37 | 38 | /// 39 | /// Tests HTML block tag names of various lengths (see https://github.com/Knagis/CommonMark.NET/issues/16) 40 | /// 41 | [TestMethod] 42 | [TestCategory("Leaf blocks - HTML blocks")] 43 | public void HtmlTagNameLengths() 44 | { 45 | var source = ""; 46 | var result = ""; 47 | foreach (var tag in new[] { "p", "h1", "map", "form", "style", "object", "section", "progress", "progress2", "blockquote", "blockquoteX" }) 48 | { 49 | source += "<" + tag + ">\n\t*" + tag + "*\n\n\n"; 50 | result += "<" + tag + ">\n\t*" + tag + "*\n\n"; 51 | } 52 | 53 | Helpers.ExecuteTest(source, result); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CommonMark/Syntax/Reference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Represents a parsed reference link definition. 9 | /// 10 | public sealed class Reference 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | public Reference() 16 | { 17 | } 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | public Reference(string label, string url, string title) 23 | { 24 | this.Label = label; 25 | this.Url = url; 26 | this.Title = title; 27 | } 28 | 29 | /// 30 | /// Represents the maximum allowed length of a reference definition (foo in [foo]: /url). 31 | /// 32 | public const int MaximumReferenceLabelLength = 1000; 33 | 34 | /// 35 | /// A special constant reference that represents an collapsed reference link: [foo][] 36 | /// 37 | internal static readonly Reference SelfReference = new Reference(); 38 | 39 | /// 40 | /// A special constant reference that signifies that the reference label was not found: [foo][bar] 41 | /// 42 | internal static readonly Reference InvalidReference = new Reference(); 43 | 44 | /// 45 | /// Gets or sets the label (the key by which it is referenced in the mapping) of the reference. 46 | /// 47 | public string Label { get; set; } 48 | 49 | /// 50 | /// Gets or sets the URL of the reference. 51 | /// 52 | public string Url { get; set; } 53 | 54 | /// 55 | /// Gets or sets the title of the reference (used in <a title="...">). 56 | /// 57 | public string Title { get; set; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /CommonMark.Tests/UrlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class UrlTests 12 | { 13 | [TestMethod] 14 | [TestCategory("Inlines - Links")] 15 | public void NewlineInUrl() 16 | { 17 | Helpers.ExecuteTest("[foo](\r\n/url)\r\n\r\n[foo](", "

foo

\r\n

[foo](

"); 18 | } 19 | 20 | [TestMethod] 21 | [TestCategory("Inlines - Links")] 22 | public void UTF32CharacterEscape() 23 | { 24 | Helpers.ExecuteTest("[foo](/\uD835\uDD6B\uD835\uDCB5)", "

foo

"); 25 | } 26 | 27 | [TestMethod] 28 | [TestCategory("Inlines - Links")] 29 | public void CustomUriResolver() 30 | { 31 | var settings = CommonMarkSettings.Default.Clone(); 32 | settings.UriResolver = url => url.Replace("~/", "/app/dir/"); 33 | Helpers.ExecuteTest("foo␣**[a](~/temp)**␣[b][c]␣\r\n\r\n[c]:␣~/bar", "

foo a b

", settings); 34 | } 35 | 36 | [TestMethod] 37 | [TestCategory("Inlines - Links")] 38 | public void CustomUriResolverException() 39 | { 40 | var settings = CommonMarkSettings.Default.Clone(); 41 | settings.UriResolver = url => { throw new ArgumentNullException(); }; 42 | 43 | var gotException = false; 44 | try 45 | { 46 | Helpers.ExecuteTest("foo␣**[a](~/temp)**␣[b][c]␣\r\n\r\n[c]:␣~/bar", "

foo a b

", settings); 47 | } 48 | catch(CommonMarkException) 49 | { 50 | gotException = true; 51 | } 52 | 53 | Assert.IsTrue(gotException, "A required exception was not thrown."); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CommonMark.NET.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CommonMark.NET 5 | 0.12.0 6 | CommonMark.NET 7 | Kārlis Gaņģis 8 | https://raw.githubusercontent.com/Knagis/CommonMark.NET/master/LICENSE.md 9 | https://github.com/Knagis/CommonMark.NET/ 10 | https://raw.githubusercontent.com/dcurtis/markdown-mark/master/png/32x20.png 11 | false 12 | Fastest .NET library for converting Markdown documents to HTML. 13 | .NET library for converting Markdown documents to HTML according to the CommonMark specification. Optimized for maximum performance and targets every .NET version since 2.0, including PCL, Mono and .NET Core. 14 | Updated to specification 0.25 15 | Copyright © Kārlis Gaņģis 2014-2015 16 | CommonMark Markdown 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /CommonMark.Tests/ListTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class ListTests 12 | { 13 | [TestMethod] 14 | [TestCategory("Container blocks - Lists")] 15 | public void Example210WithPositionTracking() 16 | { 17 | // Example 210 handles case that relies on Block.SourcePosition property (everything else just sets it) 18 | 19 | var s = CommonMarkSettings.Default.Clone(); 20 | s.TrackSourcePosition = true; 21 | 22 | Helpers.Log("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 210, "Container blocks - Lists"); 23 | Helpers.ExecuteTest("* a\n*\n\n* c", @" 24 | 25 | a

26 | 27 | 28 | 29 | c

30 | 31 | 32 | ", s); 33 | } 34 | 35 | [TestMethod] 36 | [TestCategory("Container blocks - List items")] 37 | public void ListWithTabs() 38 | { 39 | Helpers.ExecuteTest("*\tbar", "
    \n
  • bar
  • \n
"); 40 | } 41 | 42 | [TestMethod] 43 | [TestCategory("Container blocks - List items")] 44 | public void UnicodeBulletEscape() 45 | { 46 | Helpers.ExecuteTest("\\• foo\n\n\\* bar", "

• foo

\n

* bar

"); 47 | } 48 | 49 | [TestMethod] 50 | [TestCategory("Container blocks - List items")] 51 | public void UnicodeBulletList() 52 | { 53 | Helpers.ExecuteTest("• foo\n• bar", "
    \n
  • foo
  • \n
  • bar
  • \n
"); 54 | } 55 | 56 | [TestMethod] 57 | [TestCategory("Container blocks - List items")] 58 | public void EmptyList1() 59 | { 60 | Helpers.ExecuteTest("1.\n2.", "
    \n
  1. \n
  2. \n
"); 61 | } 62 | 63 | [TestMethod] 64 | [TestCategory("Container blocks - List items")] 65 | public void EmptyList2() 66 | { 67 | Helpers.ExecuteTest("+\n+", "
    \n
  • \n
  • \n
"); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /CommonMark/Syntax/BlockTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommonMark.Syntax 4 | { 5 | /// 6 | /// Specifies the element type of a instance. 7 | /// 8 | public enum BlockTag : byte 9 | { 10 | /// 11 | /// The root element that represents the document itself. There should only be one in the tree. 12 | /// 13 | Document, 14 | 15 | /// 16 | /// A block-quote element. 17 | /// 18 | BlockQuote, 19 | 20 | /// 21 | /// A list element. Will contain nested blocks with type of . 22 | /// 23 | List, 24 | 25 | /// 26 | /// An item in a block element of type . 27 | /// 28 | ListItem, 29 | 30 | /// 31 | /// A code block element that was formatted with fences (for example, ~~~\nfoo\n~~~). 32 | /// 33 | FencedCode, 34 | 35 | /// 36 | /// A code block element that was formatted by indenting the lines with at least 4 spaces. 37 | /// 38 | IndentedCode, 39 | 40 | /// 41 | /// A raw HTML code block element. 42 | /// 43 | HtmlBlock, 44 | 45 | /// 46 | /// A paragraph block element. 47 | /// 48 | Paragraph, 49 | 50 | /// 51 | /// A heading element that was parsed from an ATX style markup (## heading 2). 52 | /// 53 | AtxHeading, 54 | 55 | /// 56 | /// Obsolete. Use instead. 57 | /// 58 | [Obsolete("Use " + nameof(AtxHeading) + " instead.")] 59 | AtxHeader = AtxHeading, 60 | 61 | /// 62 | /// A heading element that was parsed from a Setext style markup (heading\n========). 63 | /// 64 | SetextHeading, 65 | 66 | /// 67 | /// Obsolete. Use instead. 68 | /// 69 | [Obsolete("Use " + nameof(SetextHeading) + " instead.")] 70 | SETextHeader = SetextHeading, 71 | 72 | /// 73 | /// A thematic break element. 74 | /// 75 | ThematicBreak, 76 | 77 | /// 78 | /// Obsolete. Use instead. 79 | /// 80 | [Obsolete("Use " + nameof(ThematicBreak) + " instead.")] 81 | HorizontalRuler = ThematicBreak, 82 | 83 | /// 84 | /// A text block that contains only link reference definitions. 85 | /// 86 | ReferenceDefinition 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CommonMark.Tests/EmphasisTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class EmphasisTests 12 | { 13 | [TestMethod] 14 | [TestCategory("Inlines - Emphasis and strong emphasis")] 15 | public void UnderscoreWithinEmphasis() 16 | { 17 | // See https://github.com/jgm/stmd/issues/51 for additional info 18 | // The rule is that inlines are processed left-to-right 19 | 20 | Helpers.ExecuteTest("*_*_", "

__

"); 21 | } 22 | 23 | [TestMethod] 24 | [TestCategory("Inlines - Emphasis and strong emphasis")] 25 | public void UnderscoreWithinEmphasis2() 26 | { 27 | // See https://github.com/jgm/stmd/issues/51 for additional info 28 | 29 | Helpers.ExecuteTest("*a _b _c d_ e*", "

a _b c d e

"); 30 | } 31 | 32 | [TestMethod] 33 | [TestCategory("Inlines - Emphasis and strong emphasis")] 34 | public void EmphasisWithCommas() 35 | { 36 | Helpers.ExecuteTest("**foo, *bar*, abc**", "

foo, bar, abc

"); 37 | } 38 | 39 | [TestMethod] 40 | [TestCategory("Inlines - Emphasis and strong emphasis")] 41 | public void DelayedEmphasisMatch1() 42 | { 43 | // '[' char in the middle will delay the ** match to the post-process phase. 44 | Helpers.ExecuteTest("foo ****ba[r****", "

foo ba[r

"); 45 | } 46 | 47 | [TestMethod] 48 | [TestCategory("Inlines - Emphasis and strong emphasis")] 49 | public void DelayedEmphasisMatch2() 50 | { 51 | // '[' char in the middle will delay the ** match to the post-process phase. 52 | Helpers.ExecuteTest("foo ****ba[r**", "

foo **ba[r

"); 53 | } 54 | 55 | [TestMethod] 56 | [TestCategory("Inlines - Emphasis and strong emphasis")] 57 | public void DelayedEmphasisMatch3() 58 | { 59 | // '[' char in the middle will delay the ** match to the post-process phase. 60 | Helpers.ExecuteTest("foo **ba[r****", "

foo ba[r**

"); 61 | } 62 | 63 | [TestMethod] 64 | [TestCategory("Inlines - Emphasis and strong emphasis")] 65 | public void DelayedEmphasisMatch4() 66 | { 67 | Helpers.ExecuteTest("**[foo* bar", "

*[foo bar

"); 68 | } 69 | 70 | [TestMethod] 71 | [TestCategory("Inlines - Emphasis and strong emphasis")] 72 | public void DelayedEmphasisMatch5() 73 | { 74 | Helpers.ExecuteTest("[a*b**c*]", "

[abc]

"); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /CommonMark.Console/CommonMark.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {83F44787-27A2-4C18-97E2-497437C8EF75} 8 | Exe 9 | Properties 10 | CommonMark.Console 11 | CommonMark.Console 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | false 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {40ab0d9c-9464-45b2-ac79-72a566fa1371} 56 | CommonMark.NET45 57 | 58 | 59 | 60 | 67 | -------------------------------------------------------------------------------- /CommonMark.Tests/EnumeratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class EnumeratorTests 12 | { 13 | [TestMethod] 14 | [TestCategory("Enumerable iterator")] 15 | public void EnumeratorSimple() 16 | { 17 | var doc = Helpers.ParseDocument("> **foo**"); 18 | 19 | var inline = doc.AsEnumerable().FirstOrDefault(o => o.Inline != null && o.Inline.Tag == Syntax.InlineTag.Strong); 20 | Assert.IsNotNull(inline); 21 | Assert.IsTrue(inline.IsOpening); 22 | Assert.IsFalse(inline.IsClosing); 23 | } 24 | 25 | [TestMethod] 26 | [TestCategory("Enumerable iterator")] 27 | public void EnumeratorSimple2() 28 | { 29 | var doc = Helpers.ParseDocument("* > **foo** *bar*"); 30 | 31 | var inlines = doc.AsEnumerable().Count(o => o.Inline != null && o.IsOpening); 32 | Assert.AreEqual(5, inlines); 33 | } 34 | 35 | [TestMethod] 36 | [TestCategory("Enumerable iterator")] 37 | public void EnumeratorNoEmptyNodes() 38 | { 39 | var doc = Helpers.ParseDocument("* > *[*foo** *bar*"); 40 | 41 | var empty = doc.AsEnumerable().FirstOrDefault(o => 42 | o.Inline != null 43 | && o.Inline.Tag == Syntax.InlineTag.String 44 | && string.IsNullOrEmpty(o.Inline.LiteralContent)); 45 | 46 | Assert.IsNull(empty); 47 | } 48 | 49 | [TestMethod] 50 | [TestCategory("Enumerable iterator")] 51 | public void EnumeratorForChildElement() 52 | { 53 | // tests that the sibling elements for the enumarable root are not returned. 54 | var doc = Helpers.ParseDocument("foo\n\nbar"); 55 | 56 | var fooNodes = doc.FirstChild.AsEnumerable(); 57 | var foostring = fooNodes.FirstOrDefault(o => o.Inline != null && o.Inline.Tag == Syntax.InlineTag.String && o.Inline.LiteralContent == "foo"); 58 | 59 | // 3: open para, string foo, close para 60 | Assert.AreEqual(3, fooNodes.Count()); 61 | Assert.IsNotNull(foostring); 62 | } 63 | 64 | [TestMethod] 65 | [TestCategory("Enumerable iterator")] 66 | public void EnumeratorSyncOpenClose() 67 | { 68 | var doc = Helpers.ParseDocument("* > **foo** *bar*\n\n\tqwe\n\n* * [asd](/url)"); 69 | 70 | var openInl = doc.AsEnumerable().Count(o => o.Inline != null && o.IsOpening); 71 | var openBlo = doc.AsEnumerable().Count(o => o.Block != null && o.IsOpening); 72 | var closeInl = doc.AsEnumerable().Count(o => o.Inline != null && o.IsClosing); 73 | var closeBlo = doc.AsEnumerable().Count(o => o.Block != null && o.IsClosing); 74 | 75 | Assert.AreEqual(openInl, closeInl, "Inlines - opener and closer cound do not match."); 76 | Assert.AreEqual(openBlo, closeBlo, "Blocks - opener and closer cound do not match."); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | .vs/ 9 | 10 | # Local-history plugin 11 | .localhistory/ 12 | 13 | # stmd-master* files - these are downloads from the reference implementation 14 | stmd-master* 15 | 16 | # Build results 17 | 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | build/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | *.nupkg 25 | 26 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 27 | !packages/*/build/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | *_i.c 34 | *_p.c 35 | *.ilk 36 | *.meta 37 | *.obj 38 | *.pch 39 | *.pdb 40 | *.pgc 41 | *.pgd 42 | *.rsp 43 | *.sbr 44 | *.tlb 45 | *.tli 46 | *.tlh 47 | *.tmp 48 | *.tmp_proj 49 | *.log 50 | *.vspscc 51 | *.vssscc 52 | .builds 53 | *.pidb 54 | *.log 55 | *.scc 56 | 57 | # Visual C++ cache files 58 | ipch/ 59 | *.aps 60 | *.ncb 61 | *.opensdf 62 | *.sdf 63 | *.cachefile 64 | 65 | # Visual Studio profiler 66 | *.psess 67 | *.vsp 68 | *.vspx 69 | 70 | # Guidance Automation Toolkit 71 | *.gpState 72 | 73 | # ReSharper is a .NET coding add-in 74 | _ReSharper*/ 75 | *.[Rr]e[Ss]harper 76 | 77 | # TeamCity is a build add-in 78 | _TeamCity* 79 | 80 | # DotCover is a Code Coverage Tool 81 | *.dotCover 82 | 83 | # NCrunch 84 | *.ncrunch* 85 | .*crunch*.local.xml 86 | 87 | # Installshield output folder 88 | [Ee]xpress/ 89 | 90 | # DocProject is a documentation generator add-in 91 | DocProject/buildhelp/ 92 | DocProject/Help/*.HxT 93 | DocProject/Help/*.HxC 94 | DocProject/Help/*.hhc 95 | DocProject/Help/*.hhk 96 | DocProject/Help/*.hhp 97 | DocProject/Help/Html2 98 | DocProject/Help/html 99 | 100 | # Click-Once directory 101 | publish/ 102 | 103 | # Publish Web Output 104 | *.Publish.xml 105 | 106 | # NuGet Packages Directory 107 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 108 | #packages/ 109 | 110 | # Windows Azure Build Output 111 | csx 112 | *.build.csdef 113 | 114 | # Windows Store app package directory 115 | AppPackages/ 116 | 117 | # Others 118 | sql/ 119 | *.Cache 120 | ClientBin/ 121 | [Ss]tyle[Cc]op.* 122 | ~$* 123 | *~ 124 | *.dbmdl 125 | *.[Pp]ublish.xml 126 | *.pfx 127 | *.publishsettings 128 | 129 | # RIA/Silverlight projects 130 | Generated_Code/ 131 | 132 | # Backup & report files from converting an old project file to a newer 133 | # Visual Studio version. Backup files are not needed, because we have git ;-) 134 | _UpgradeReport_Files/ 135 | Backup*/ 136 | UpgradeLog*.XML 137 | UpgradeLog*.htm 138 | 139 | # SQL Server files 140 | App_Data/*.mdf 141 | App_Data/*.ldf 142 | 143 | 144 | #LightSwitch generated files 145 | GeneratedArtifacts/ 146 | _Pvt_Extensions/ 147 | ModelManifest.xml 148 | 149 | # ========================= 150 | # Windows detritus 151 | # ========================= 152 | 153 | # Windows image file caches 154 | Thumbs.db 155 | ehthumbs.db 156 | 157 | # Folder config file 158 | Desktop.ini 159 | 160 | # Recycle Bin used on file shares 161 | $RECYCLE.BIN/ 162 | 163 | # Mac desktop service store files 164 | .DS_Store 165 | -------------------------------------------------------------------------------- /CommonMark/Syntax/InlineTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommonMark.Syntax 4 | { 5 | /// 6 | /// Specifies the element type of an instance. 7 | /// 8 | public enum InlineTag : byte 9 | { 10 | /// 11 | /// Represents a simple literal string content. Uses to specify the data. 12 | /// Cannot contain nested elements. 13 | /// 14 | String = 0, 15 | 16 | /// 17 | /// Represents a soft-break which by default is rendered as a simple newline and thus does not impact 18 | /// the display of the resulting HTML code. The 19 | /// property can be used to override this behavior and render soft-breaks as <br;> HTML 20 | /// elements. 21 | /// Cannot contain literal content or nested elements. 22 | /// 23 | SoftBreak, 24 | 25 | /// 26 | /// Represents a line-break which by default is rendered as a <br;> HTML element. 27 | /// Cannot contain literal content or nested elements. 28 | /// 29 | LineBreak, 30 | 31 | /// 32 | /// Represents an inline code element. Uses to specify the data. 33 | /// Cannot contain nested elements. 34 | /// 35 | Code, 36 | 37 | /// 38 | /// Represents raw HTML code. Uses to specify the data. 39 | /// Cannot contain nested elements. 40 | /// 41 | RawHtml, 42 | 43 | /// 44 | /// Represents an emphasis element. Uses to specify the contents. 45 | /// Cannot contain literal content. 46 | /// 47 | Emphasis, 48 | 49 | /// 50 | /// Represents a strong emphasis element. Uses to specify the contents. 51 | /// Cannot contain literal content. 52 | /// 53 | Strong, 54 | 55 | /// 56 | /// Represents a link element. Uses to specify the content (or label). 57 | /// Uses to specify the target of the link and 58 | /// to store the title of the link. 59 | /// 60 | Link, 61 | 62 | /// 63 | /// Represents an image element. Uses to specify the label (description). 64 | /// Uses to specify the source of the image and 65 | /// to store the title of the image. 66 | /// 67 | Image, 68 | 69 | /// 70 | /// Represents an inline element that has been "removed" (visually represented as strikethrough). 71 | /// Only present if the is enabled. 72 | /// 73 | Strikethrough 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CommonMark.Tests/DelimiterCharTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace CommonMark.Tests 4 | { 5 | [TestClass] 6 | public class DelimiterCharTests 7 | { 8 | public DelimiterCharTests() 9 | { 10 | } 11 | 12 | [TestMethod] 13 | public void EmphasisDelimiterChar() 14 | { 15 | var md = "*foo*"; 16 | var doc = Helpers.ParseDocument(md); 17 | Assert.AreEqual('*', doc.FirstChild.InlineContent.Emphasis.DelimiterCharacter); 18 | } 19 | 20 | [TestMethod] 21 | public void EmphasisDelimiterChar2() 22 | { 23 | var md = "_foo_"; 24 | var doc = Helpers.ParseDocument(md); 25 | Assert.AreEqual('_', doc.FirstChild.InlineContent.Emphasis.DelimiterCharacter); 26 | } 27 | 28 | [TestMethod] 29 | public void EmphasisDelimiterChar3() 30 | { 31 | var md = "_foo[bar_"; 32 | var doc = Helpers.ParseDocument(md); 33 | Assert.AreEqual('_', doc.FirstChild.InlineContent.Emphasis.DelimiterCharacter); 34 | } 35 | 36 | [TestMethod] 37 | public void EmphasisDelimiterChar4() 38 | { 39 | var md = "*foo[bar*"; 40 | var doc = Helpers.ParseDocument(md); 41 | Assert.AreEqual('*', doc.FirstChild.InlineContent.Emphasis.DelimiterCharacter); 42 | } 43 | 44 | [TestMethod] 45 | public void DoubleEmphasisDelimiterChar() 46 | { 47 | var md = "_*foo*_"; 48 | var doc = Helpers.ParseDocument(md); 49 | Assert.AreEqual('*', doc.FirstChild.InlineContent.FirstChild.Emphasis.DelimiterCharacter); 50 | } 51 | 52 | [TestMethod] 53 | public void DoubleEmphasisDelimiterChar2() 54 | { 55 | var md = "*_foo_*"; 56 | var doc = Helpers.ParseDocument(md); 57 | Assert.AreEqual('_', doc.FirstChild.InlineContent.FirstChild.Emphasis.DelimiterCharacter); 58 | } 59 | 60 | [TestMethod] 61 | public void DoubleEmphasisDelimiterChar3() 62 | { 63 | var md = "_*foo[bar*_"; 64 | var doc = Helpers.ParseDocument(md); 65 | Assert.AreEqual('*', doc.FirstChild.InlineContent.FirstChild.Emphasis.DelimiterCharacter); 66 | } 67 | 68 | [TestMethod] 69 | public void DoubleEmphasisDelimiterChar4() 70 | { 71 | var md = "*_foo[bar_*"; 72 | var doc = Helpers.ParseDocument(md); 73 | Assert.AreEqual('_', doc.FirstChild.InlineContent.FirstChild.Emphasis.DelimiterCharacter); 74 | } 75 | 76 | [TestMethod] 77 | public void DoubleEmphasisDelimiterChar5() 78 | { 79 | var md = "_*foo[bar*baz_"; 80 | var doc = Helpers.ParseDocument(md); 81 | Assert.AreEqual('*', doc.FirstChild.InlineContent.FirstChild.Emphasis.DelimiterCharacter); 82 | } 83 | 84 | [TestMethod] 85 | public void DoubleEmphasisDelimiterChar6() 86 | { 87 | var md = "*_foo[bar_baz*"; 88 | var doc = Helpers.ParseDocument(md); 89 | Assert.AreEqual('_', doc.FirstChild.InlineContent.FirstChild.Emphasis.DelimiterCharacter); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /CommonMark/Parser/Subject.cs: -------------------------------------------------------------------------------- 1 | using CommonMark.Syntax; 2 | using System.Text; 3 | 4 | namespace CommonMark.Parser 5 | { 6 | [System.Diagnostics.DebuggerDisplay("{DebugToString()}")] 7 | internal sealed class Subject 8 | { 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | /// Document data. 13 | public Subject(DocumentData documentData) 14 | { 15 | this.DocumentData = documentData; 16 | } 17 | 18 | /// 19 | /// Initializes a new instance of the class. 20 | /// 21 | /// String buffer. 22 | /// Document data. 23 | public Subject(string buffer, DocumentData documentData) 24 | { 25 | this.Buffer = buffer; 26 | this.Length = buffer.Length; 27 | this.DocumentData = documentData; 28 | } 29 | 30 | #if DEBUG 31 | public int DebugStartIndex; 32 | #endif 33 | 34 | /// 35 | /// Gets or sets the whole buffer this instance is created over. 36 | /// 37 | public string Buffer; 38 | 39 | /// 40 | /// Gets or sets the current position in the buffer. 41 | /// 42 | public int Position; 43 | 44 | /// 45 | /// Gets or sets the length of the usable buffer. This can be less than the actual length of the 46 | /// buffer if some characters at the end of the buffer have to be ignored. 47 | /// 48 | public int Length; 49 | 50 | /// 51 | /// The last top-level inline parsed from this subject. 52 | /// 53 | public Inline LastInline; 54 | 55 | /// 56 | /// The last entry of the current stack of possible emphasis openers. Can be null. 57 | /// 58 | public InlineStack LastPendingInline; 59 | 60 | /// 61 | /// The first entry of the current stack of possible emphasis openers. Can be null. 62 | /// 63 | public InlineStack FirstPendingInline; 64 | 65 | /// 66 | /// A reusable StringBuilder that should be used instead of creating new instances to conserve memory. 67 | /// 68 | public StringBuilder ReusableStringBuilder = new StringBuilder(); 69 | 70 | /// 71 | /// Additional properties that apply to document nodes. 72 | /// 73 | public readonly DocumentData DocumentData; 74 | 75 | #if !NETCore 76 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by [DebuggerDisplay]")] 77 | #endif 78 | // ReSharper disable once UnusedMethodReturnValue.Local 79 | private string DebugToString() 80 | { 81 | var res = this.Buffer.Insert(this.Length, "|"); 82 | res = res.Insert(this.Position, "⁞"); 83 | 84 | #if DEBUG 85 | res = res.Insert(this.DebugStartIndex, "|"); 86 | #endif 87 | 88 | return res; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CommonMark/Parser/TabTextReader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace CommonMark.Parser 5 | { 6 | internal sealed class TabTextReader 7 | { 8 | private const int _bufferSize = 4000; 9 | private readonly TextReader _inner; 10 | private readonly char[] _buffer; 11 | private int _bufferLength; 12 | private int _bufferPosition; 13 | private int _previousBufferLength; 14 | private readonly StringBuilder _builder; 15 | private bool _endOfStream; 16 | 17 | public TabTextReader(TextReader inner) 18 | { 19 | this._inner = inner; 20 | this._buffer = new char[_bufferSize]; 21 | this._builder = new StringBuilder(256); 22 | } 23 | 24 | private bool ReadBuffer() 25 | { 26 | if (this._endOfStream) 27 | return false; 28 | 29 | this._previousBufferLength += this._bufferLength; 30 | this._bufferLength = this._inner.Read(this._buffer, 0, _bufferSize); 31 | this._endOfStream = this._bufferLength == 0; 32 | this._bufferPosition = 0; 33 | return !this._endOfStream; 34 | } 35 | 36 | public void ReadLine(LineInfo line) 37 | { 38 | line.LineOffset = this._previousBufferLength + this._bufferPosition; 39 | line.LineNumber++; 40 | line.OffsetCount = 0; 41 | line.Line = null; 42 | var tabIncreaseCount = 0; 43 | 44 | if (this._bufferPosition == this._bufferLength && !this.ReadBuffer()) 45 | return; 46 | 47 | bool useBuilder = false; 48 | int num; 49 | char c; 50 | 51 | while (true) 52 | { 53 | num = this._bufferPosition; 54 | do 55 | { 56 | c = this._buffer[num]; 57 | if (c == '\r' || c == '\n') 58 | goto IL_4A; 59 | 60 | if (c == '\0') 61 | this._buffer[num] = '\uFFFD'; 62 | 63 | num++; 64 | } 65 | while (num < this._bufferLength); 66 | 67 | num = this._bufferLength - this._bufferPosition; 68 | if (!useBuilder) 69 | { 70 | useBuilder = true; 71 | this._builder.Length = 0; 72 | } 73 | 74 | this._builder.Append(this._buffer, this._bufferPosition, num); 75 | if (!this.ReadBuffer()) 76 | { 77 | this._builder.Append('\n'); 78 | line.Line = this._builder.ToString(); 79 | return; 80 | } 81 | } 82 | 83 | IL_4A: 84 | string result; 85 | this._buffer[num] = '\n'; 86 | if (useBuilder) 87 | { 88 | this._builder.Append(this._buffer, this._bufferPosition, num - this._bufferPosition + 1); 89 | result = this._builder.ToString(); 90 | } 91 | else 92 | { 93 | result = new string(this._buffer, this._bufferPosition, num - this._bufferPosition + 1); 94 | } 95 | 96 | this._bufferPosition = num + 1; 97 | 98 | if (c == '\r' && (this._bufferPosition < this._bufferLength || this.ReadBuffer()) && this._buffer[this._bufferPosition] == '\n') 99 | { 100 | if (line.IsTrackingPositions) 101 | line.AddOffset(this._previousBufferLength + this._bufferPosition - 1 + tabIncreaseCount, 1); 102 | 103 | this._bufferPosition++; 104 | } 105 | 106 | line.Line = result; 107 | } 108 | 109 | public bool EndOfStream() 110 | { 111 | return this._endOfStream || (this._bufferPosition == this._bufferLength && !this.ReadBuffer()); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/9jo20450utqy1p0o/branch/master?svg=true)](https://ci.appveyor.com/project/Knagis/commonmark-net/branch/master) 2 | 3 | # CommonMark.NET 4 | 5 | Implementation of [CommonMark] [1] specification (passes tests from version 0.25) in C# for converting Markdown documents to HTML. 6 | 7 | The current version of the library is also [available on NuGet] [nuget]. 8 | 9 | ## Usage 10 | 11 | To convert Markdown data in a stream or file: 12 | ```C# 13 | using (var reader = new System.IO.StreamReader("source.md")) 14 | using (var writer = new System.IO.StreamWriter("result.html")) 15 | { 16 | CommonMark.CommonMarkConverter.Convert(reader, writer); 17 | } 18 | ``` 19 | 20 | To convert data stored in a string: 21 | ```C# 22 | var result = CommonMark.CommonMarkConverter.Convert("**Hello world!**"); 23 | ``` 24 | 25 | See [wiki] [extensibility] for an example how the parser can be extended with additional logic. 26 | 27 | **Important:** The converter [does not include any HTML sanitizing][XSS]. 28 | 29 | ## Compatibility 30 | 31 | The library uses no references except for `System` - it has no external dependencies. It is cross compiled to: 32 | 33 | * .NET Framework 2.0 34 | * .NET Framework 3.5 Client Profile 35 | * .NET Framework 4.0 Client Profile 36 | * .NET 4.0 Portable Class Library 37 | * .NET Framework 4.0 Client Profile 38 | * Silverlight 5 39 | * Windows 8 40 | * Windows Phone 8.1 41 | * Windows Phone Silverlight 8 42 | * Xamarin.Android 43 | * Xamarin.iOS 44 | * .NET 4.5 Portable Class Library (Optimized) 45 | * .NET Framework 4.5 46 | * Windows 8 47 | * Windows Phone 8.1 48 | * Windows Phone Silverlight 8 49 | * Xamarin.Android 50 | * Xamarin.iOS 51 | * .NET Framework 4.5 (Optimized) 52 | * .NET Framework 5.0 Core (also known as ASP.NET vNext Core CLR) 53 | 54 | For working with the source code you will need Visual Studio 2015 or newer (Community edition is supported). 55 | 56 | ## Performance 57 | 58 | Using a [simple tool][3] to compare the performance of various Markdown implementations for .NET yields the 59 | following results: 60 | 61 | CommonMark.NET 0.4.1 4 ms 7% 62 | 63 | CommonMarkSharp 0.3.2 30 ms 46% 64 | MarkdownSharp 1.13 55 ms 84% (might not conform to CommonMark specification) 65 | MarkdownDeep 1.5 7 ms 11% (might not conform to CommonMark specification) 66 | MoonShine (sundown) 3 ms 6% (wrapper for a native x86 .dll) 67 | Baseline 65 ms 100% (used to compare results on different machines) 68 | 69 | This benchmark is very simple and tests the processing of the CommonMark specification document itself (a 70 | 115 KB file). The results are provided just for a high-level comparison. 71 | 72 | ## Reliability 73 | 74 | The parser uses algorithms that completely avoid recursion so even specifically crafted input documents 75 | will not cause exceptions and will be rendered correctly. This is important because a `StackOverflowException` 76 | cannot be caught and will bring down the entire process. 77 | 78 | ## References 79 | 80 | This library is based on a port of the reference implementation in C, available on [jgm/cmark repo] [2]. 81 | It follows the same approach - the source is parsed into a syntax tree that can be used to add custom 82 | processing if the application needs it and then formatted into HTML. 83 | 84 | ## Running tests 85 | 86 | All tests from `spec.txt` are merged into the unit testing project and can be executed from within Visual Studio. 87 | 88 | The project also includes a slightly modified version of `runtests.pl` for compatibility with the original 89 | implementation. Use `CommonMark.Console.exe --perltest` as the argument to `runtests.pl` so that the 90 | application can correctly handle input from the Perl script. 91 | 92 | [1]: http://spec.commonmark.org/ 93 | [2]: https://github.com/jgm/cmark 94 | [3]: https://github.com/Knagis/CommonMarkBenchmark 95 | [extensibility]: https://github.com/Knagis/CommonMark.NET/wiki 96 | [XSS]: http://talk.commonmark.org/t/cross-site-scripting-issue-in-standard-markdown-example-at-try-standardmarkdown-com/55 97 | [nuget]: https://www.nuget.org/packages/CommonMark.NET/ 98 | -------------------------------------------------------------------------------- /CommonMark/Parser/PositionTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Parser 6 | { 7 | internal sealed class PositionTracker 8 | { 9 | public PositionTracker(int blockOffset) 10 | { 11 | this._blockOffset = blockOffset; 12 | } 13 | 14 | private static readonly PositionOffset EmptyPositionOffset = default(PositionOffset); 15 | 16 | private int _blockOffset; 17 | 18 | public void AddBlockOffset(int offset) 19 | { 20 | this._blockOffset += offset; 21 | } 22 | 23 | public void AddOffset(LineInfo line, int startIndex, int length) 24 | { 25 | if (this.OffsetCount + line.OffsetCount + 2 >= this.Offsets.Length) 26 | Array.Resize(ref this.Offsets, this.Offsets.Length + line.OffsetCount + 20); 27 | 28 | PositionOffset po1, po2; 29 | 30 | if (startIndex > 0) 31 | po1 = new PositionOffset( 32 | CalculateOrigin(line.Offsets, line.OffsetCount, line.LineOffset, false, true), 33 | startIndex); 34 | else 35 | po1 = EmptyPositionOffset; 36 | 37 | if (line.Line.Length - startIndex - length > 0) 38 | po2 = new PositionOffset( 39 | CalculateOrigin(line.Offsets, line.OffsetCount, line.LineOffset + startIndex + length, false, true), 40 | line.Line.Length - startIndex - length); 41 | else 42 | po2 = EmptyPositionOffset; 43 | 44 | var indexAfterLastCopied = 0; 45 | 46 | if (po1.Offset == 0) 47 | { 48 | if (po2.Offset == 0) 49 | goto FINTOTAL; 50 | 51 | po1 = po2; 52 | po2 = EmptyPositionOffset; 53 | } 54 | 55 | for (var i = 0; i < line.OffsetCount; i++) 56 | { 57 | var pc = line.Offsets[i]; 58 | if (pc.Position > po1.Position) 59 | { 60 | if (i > indexAfterLastCopied) 61 | { 62 | Array.Copy(line.Offsets, indexAfterLastCopied, this.Offsets, this.OffsetCount, i - indexAfterLastCopied); 63 | this.OffsetCount += i - indexAfterLastCopied; 64 | indexAfterLastCopied = i; 65 | } 66 | 67 | this.Offsets[this.OffsetCount++] = po1; 68 | 69 | po1 = po2; 70 | 71 | if (po1.Offset == 0) 72 | goto FIN; 73 | 74 | po2 = EmptyPositionOffset; 75 | } 76 | } 77 | 78 | FIN: 79 | if (po1.Offset != 0) 80 | this.Offsets[this.OffsetCount++] = po1; 81 | 82 | if (po2.Offset != 0) 83 | this.Offsets[this.OffsetCount++] = po2; 84 | 85 | FINTOTAL: 86 | Array.Copy(line.Offsets, indexAfterLastCopied, this.Offsets, this.OffsetCount, line.OffsetCount - indexAfterLastCopied); 87 | this.OffsetCount += line.OffsetCount - indexAfterLastCopied; 88 | } 89 | 90 | public int CalculateInlineOrigin(int position, bool isStartPosition) 91 | { 92 | return CalculateOrigin(this.Offsets, this.OffsetCount, this._blockOffset + position, true, isStartPosition); 93 | } 94 | 95 | internal static int CalculateOrigin(PositionOffset[] offsets, int offsetCount, int position, bool includeReduce, bool isStart) 96 | { 97 | if (isStart) 98 | position++; 99 | 100 | var minus = 0; 101 | for (var i = 0; i < offsetCount; i++) 102 | { 103 | var po = offsets[i]; 104 | if (po.Position < position) 105 | { 106 | if (po.Offset > 0) 107 | position += po.Offset; 108 | else 109 | minus += po.Offset; 110 | } 111 | else 112 | break; 113 | } 114 | 115 | if (includeReduce) 116 | position += minus; 117 | 118 | if (isStart) 119 | position--; 120 | 121 | return position; 122 | } 123 | 124 | private PositionOffset[] Offsets = new PositionOffset[10]; 125 | private int OffsetCount; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /CommonMark.Tests/Helpers.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace CommonMark.Tests 8 | { 9 | internal static class Helpers 10 | { 11 | public static Syntax.Block ParseDocument(string commonMark, CommonMarkSettings settings = null) 12 | { 13 | using (var reader = new System.IO.StringReader(Helpers.Normalize(commonMark))) 14 | { 15 | return CommonMarkConverter.Parse(reader, settings); 16 | } 17 | } 18 | 19 | public static void ExecuteTest(string commonMark, string html, CommonMarkSettings settings = null) 20 | { 21 | if (settings == null) 22 | settings = CommonMarkSettings.Default.Clone(); 23 | 24 | Helpers.LogValue("CommonMark", Denormalize(commonMark)); 25 | Helpers.LogValue("Expected", Denormalize(html)); 26 | 27 | // Arrange 28 | commonMark = Helpers.Normalize(commonMark); 29 | html = Helpers.Normalize(html); 30 | 31 | string actual; 32 | Syntax.Block document; 33 | 34 | // Act 35 | using (var reader = new System.IO.StringReader(commonMark)) 36 | using (var writer = new System.IO.StringWriter()) 37 | { 38 | document = CommonMarkConverter.ProcessStage1(reader, settings); 39 | CommonMarkConverter.ProcessStage2(document, settings); 40 | CommonMarkConverter.ProcessStage3(document, writer, settings); 41 | actual = writer.ToString(); 42 | } 43 | 44 | // Assert 45 | Helpers.LogValue("Actual", Denormalize(actual)); 46 | actual = Helpers.Tidy(actual); 47 | Assert.AreEqual(Helpers.Tidy(html), actual); 48 | 49 | // Verify that the extendable HTML formatter returns the same result 50 | var settingsHtmlFormatter = settings.Clone(); 51 | settingsHtmlFormatter.OutputDelegate = (doc, target, stngs) => new Formatters.HtmlFormatter(target, stngs).WriteDocument(doc); 52 | var actual2 = CommonMarkConverter.Convert(commonMark, settingsHtmlFormatter); 53 | Assert.AreEqual(actual, Helpers.Tidy(actual2), "HtmlFormatter returned a different result than HtmlFormatterSlim."); 54 | 55 | // Additionally verify that the parser included source position information. 56 | // This is done here to catch cases during specification tests that might not be 57 | // covered in SourcePositionTests.cs. 58 | var firstFail = document.AsEnumerable().FirstOrDefault(o => o.Inline != null && o.IsOpening && o.Inline.SourceLength <= 0); 59 | if (firstFail != null) 60 | { 61 | Assert.Fail("Incorrect source position: " + firstFail); 62 | } 63 | } 64 | 65 | private static string Denormalize(string value) 66 | { 67 | value = value.Replace('\t', '→'); 68 | value = value.Replace(' ', '␣'); 69 | return value; 70 | } 71 | 72 | private static string Normalize(string value) 73 | { 74 | value = value.Replace('→', '\t'); 75 | value = value.Replace('␣', ' '); 76 | return value; 77 | } 78 | 79 | public static string Tidy(string html) 80 | { 81 | html = html.Replace("\r", "").Trim(); 82 | 83 | // collapse spaces and newlines before and after
  • 84 | html = Regex.Replace(html, @"\s+
  • ", ""); 85 | html = Regex.Replace(html, @"
  • \s+", "
  • "); 86 | 87 | // needed to compare UTF-32 characters 88 | html = html.Normalize(NormalizationForm.FormKD); 89 | return html; 90 | } 91 | 92 | public static void Log(string format, params object[] args) 93 | { 94 | if (args != null && args.Length > 0) 95 | Console.WriteLine(format, args); 96 | else 97 | Console.WriteLine(format); 98 | } 99 | 100 | public static void LogValue(string caption, string text) 101 | { 102 | Console.Write(caption); 103 | Console.WriteLine(":"); 104 | Console.WriteLine(); 105 | 106 | Console.Write(" "); 107 | Console.WriteLine(text.Replace("\n", "\n ")); 108 | 109 | Console.WriteLine(); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /runtests.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use warnings; 3 | use strict; 4 | use Term::ANSIColor; 5 | use IO::Handle; 6 | use IPC::Open2; 7 | 8 | my $usage="runtests.pl SPEC PROGRAM\nSet ANSI_COLORS_DISABLED=1 if you redirect to a file.\nSet PATT='...' to restrict tests to sections matching a regex.\n"; 9 | 10 | my $SPEC = shift @ARGV; 11 | my @PROG = @ARGV; 12 | my $PATT=$ENV{'PATT'}; 13 | 14 | if (!(@PROG && defined $SPEC)) { 15 | print STDERR $usage; 16 | exit 1; 17 | } 18 | 19 | my $passed = 0; 20 | my $failed = 0; 21 | my $skipped = 0; 22 | 23 | # Markdown implementations vary on insignificant whitespace. 24 | # Some leave blanks between block elements, others don't. 25 | # This function tries to normalize the output so it can be 26 | # compared with our test. tidy takes two arguments: the 27 | # string containing the actual output, and a pathname of the 28 | # file to which the tidied output is to be saved. 29 | sub tidy 30 | { 31 | my $inpre = 0; 32 | my $out = ""; 33 | my $outfh; 34 | open($outfh, '>', \$out); 35 | for (split /^/, $_[0]) { 36 | if (/
     in tag
     53 |       s/  *\/>/\/>/;
     54 | 	  # add a newline after 
  • tag 55 | s/
  • (.+)/
  • \n$1/; 56 | # skip blank line 57 | if (/^$/) { 58 | next; 59 | } 60 | print $outfh $_; 61 | } 62 | } 63 | close $outfh; 64 | return $out; 65 | } 66 | 67 | sub dotest 68 | { 69 | my $markdown = $_[0]; 70 | my $html = $_[1]; 71 | my $testname = $_[2]; 72 | my $actual = ""; 73 | # We use → to indicate tab and ␣ space in the spec 74 | $markdown =~ s/→/\t/g;s/␣/ /g; 75 | $html =~ s/→/\t/g;s/␣/ /g; 76 | my $pid = open2(my $out, my $in, @PROG); 77 | print $in $markdown; 78 | close $in; 79 | flush $out; 80 | $actual = do { local $/; <$out>; }; 81 | close $out; 82 | waitpid($pid, 0); 83 | $html = &tidy($html); 84 | $actual = &tidy($actual); 85 | $actual =~ s/\'/'/g; 86 | 87 | my $expectedLength = length($html); 88 | my $actualLength = length($actual); 89 | if ($actual eq $html) { 90 | print " PASS"; 91 | return 1; 92 | } else { 93 | print "\nFAIL $testname"; 94 | print "\n"; 95 | #print color "cyan"; 96 | print "=== markdown ===============\n"; 97 | print $markdown; 98 | print "=== expected $expectedLength ===============\n"; 99 | print $html; 100 | print "=== got $actualLength ====================\n"; 101 | print $actual; 102 | print "=== fin ====================\n"; 103 | #print color "black"; 104 | return 0; 105 | } 106 | } 107 | 108 | my $stage = 0; 109 | my $markdown = ""; 110 | my $html = ""; 111 | my $example = 0; 112 | my $linenum = 0; 113 | my $exampleline = 0; 114 | my @secnums = (); 115 | my $secheading; 116 | 117 | open(SPEC, "<", "$SPEC"); 118 | while () { 119 | $linenum++; 120 | if (/^\.$/) { 121 | $stage = ($stage + 1) % 3; 122 | if ($stage == 1) { 123 | $exampleline = $linenum; 124 | } 125 | if ($stage == 0) { 126 | $example++; 127 | if (!$PATT || $secheading =~ /$PATT/) { 128 | if (&dotest($markdown, $html, 129 | "Example $example (line $exampleline)")) { 130 | $passed++; 131 | } else { 132 | $failed++; 133 | } 134 | } else { 135 | $skipped++; 136 | } 137 | $markdown = ""; 138 | $html = ""; 139 | } 140 | } elsif ($stage == 0 && $_ =~ /^/) { 141 | last; 142 | } elsif ($stage == 0 && $_ =~ /^(#+) +(.*)/) { 143 | my $seclevel = length($1); 144 | $secheading = $2; 145 | if ($#secnums == $seclevel - 1) { 146 | $secnums[$#secnums]++; 147 | } elsif ($#secnums > $seclevel - 1) { 148 | @secnums = @secnums[0..($seclevel - 1)]; 149 | $secnums[$#secnums]++; 150 | } else { 151 | while ($#secnums < $seclevel - 1) { 152 | push(@secnums, 1); 153 | } 154 | } 155 | if (!$PATT || $secheading =~ /$PATT/) { 156 | print ("\n", join(".", @secnums) . " " . $secheading, " "); 157 | } 158 | } elsif ($stage == 1) { 159 | $markdown .= $_; 160 | } elsif ($stage == 2) { 161 | $html .= $_; 162 | } 163 | } 164 | 165 | print "\n"; 166 | print STDERR "$passed tests passed, $failed failed, $skipped skipped."; 167 | print STDERR "\n"; 168 | exit $failed; 169 | -------------------------------------------------------------------------------- /CommonMark/Syntax/EnumeratorEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark.Syntax 6 | { 7 | /// 8 | /// Represents a single element in the document tree when traversing through it with the enumerator. 9 | /// 10 | /// 11 | public sealed class EnumeratorEntry 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// Specifies if this instance represents the opening of the block element. 17 | /// Specifies if this instance represents the closing of the block element (returned by the 18 | /// enumerator after the children have been enumerated). Both and 19 | /// can be specified at the same time if there are no children for the element. 20 | /// The block element being returned from the enumerator. 21 | public EnumeratorEntry(bool opening, bool closing, Block block) 22 | { 23 | this.IsOpening = opening; 24 | this.IsClosing = closing; 25 | this.Block = block; 26 | } 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// Specifies if this instance represents the opening of the inline element. 32 | /// Specifies if this instance represents the closing of the inline element (returned by the 33 | /// enumerator after the children have been enumerated). Both and 34 | /// can be specified at the same time if there are no children for the element. 35 | /// The inlien element being returned from the enumerator. 36 | public EnumeratorEntry(bool opening, bool closing, Inline inline) 37 | { 38 | this.IsOpening = opening; 39 | this.IsClosing = closing; 40 | this.Inline = inline; 41 | } 42 | 43 | /// 44 | /// Gets the value indicating whether this instance represents the opening of the element (returned before enumerating 45 | /// over the children of the element). 46 | /// 47 | public bool IsOpening { get; private set; } 48 | 49 | /// 50 | /// Gets the value indicating whether this instance represents the closing of the element (returned by the 51 | /// enumerator after the children have been enumerated). Both and 52 | /// can be true at the same time if there are no children for the given element. 53 | /// 54 | public bool IsClosing { get; private set; } 55 | 56 | /// 57 | /// Gets the inline element. Can be null if is set. 58 | /// 59 | public Inline Inline { get; private set; } 60 | 61 | /// 62 | /// Gets the inline element. Can be null if is set. 63 | /// 64 | public Block Block { get; private set; } 65 | 66 | /// 67 | /// Returns a string that represents the current object. 68 | /// 69 | public override string ToString() 70 | { 71 | string r; 72 | 73 | if (this.IsOpening && this.IsClosing) 74 | r = "Complete "; 75 | else if (this.IsOpening) 76 | r = "Opening "; 77 | else if (this.IsClosing) 78 | r = "Closing "; 79 | else 80 | r = "Invalid "; 81 | 82 | if (this.Block != null) 83 | r += "block " + this.Block.Tag.ToString(); 84 | else if (this.Inline != null) 85 | { 86 | r += "inline " + this.Inline.Tag.ToString(); 87 | 88 | if (this.Inline.Tag == InlineTag.String) 89 | { 90 | if (this.Inline.LiteralContent == null) 91 | r += ": "; 92 | else if (this.Inline.LiteralContent.Length < 20) 93 | r += ": " + this.Inline.LiteralContent; 94 | else 95 | r += ": " + this.Inline.LiteralContent.Substring(0, 19) + "…"; 96 | } 97 | } 98 | else 99 | r += "empty"; 100 | 101 | return r; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /CommonMark/CommonMark.Base.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | portable 6 | Debug 7 | AnyCPU 8 | {0FD4B1DD-45A8-4F02-BEB0-5881CD512573} 9 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10 | Library 11 | Properties 12 | CommonMark 13 | CommonMark 14 | 512 15 | v4.0 16 | Profile328 17 | 10.0 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | TRACE;DEBUG 25 | prompt 26 | 4 27 | 28 | 29 | false 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | bin\Release\CommonMark.XML 39 | false 40 | AllRules.ruleset 41 | 42 | 43 | true 44 | Properties\public.snk 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Properties\CommonMark.NET.nuspec 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /CommonMark.Tests/StrikethroughTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace CommonMark.Tests 9 | { 10 | [TestClass] 11 | public class StrikethroughTests 12 | { 13 | private static CommonMarkSettings _settings; 14 | private static CommonMarkSettings Settings 15 | { 16 | get 17 | { 18 | var s = _settings; 19 | if (s == null) 20 | { 21 | s = CommonMarkSettings.Default.Clone(); 22 | s.AdditionalFeatures |= CommonMarkAdditionalFeatures.StrikethroughTilde; 23 | _settings = s; 24 | } 25 | return s; 26 | } 27 | } 28 | 29 | [TestMethod] 30 | [TestCategory("Inlines - Strikethrough")] 31 | public void StrikethroughDisabledByDefault() 32 | { 33 | Helpers.ExecuteTest("foo ~~bar~~", "

    foo ~~bar~~

    "); 34 | } 35 | 36 | [TestMethod] 37 | [TestCategory("Inlines - Strikethrough")] 38 | public void StrikethroughExample1() 39 | { 40 | Helpers.ExecuteTest("foo ~~bar~~", "

    foo bar

    ", Settings); 41 | } 42 | 43 | [TestMethod] 44 | [TestCategory("Inlines - Strikethrough")] 45 | public void StrikethroughExample2() 46 | { 47 | Helpers.ExecuteTest("foo ~~~bar~~", "

    foo ~bar

    ", Settings); 48 | } 49 | 50 | [TestMethod] 51 | [TestCategory("Inlines - Strikethrough")] 52 | public void StrikethroughExample3() 53 | { 54 | Helpers.ExecuteTest("foo ~~~~bar~~ asd~~", "

    foo bar asd

    ", Settings); 55 | } 56 | 57 | [TestMethod] 58 | [TestCategory("Inlines - Strikethrough")] 59 | public void StrikethroughExample4() 60 | { 61 | Helpers.ExecuteTest("foo ~~*bar~~*", "

    foo *bar*

    ", Settings); 62 | } 63 | 64 | [TestMethod] 65 | [TestCategory("Inlines - Strikethrough")] 66 | public void StrikethroughExample5() 67 | { 68 | Helpers.ExecuteTest("foo *~~bar~~*", "

    foo bar

    ", Settings); 69 | } 70 | 71 | [TestMethod] 72 | [TestCategory("Inlines - Strikethrough")] 73 | public void StrikethroughExample6() 74 | { 75 | Helpers.ExecuteTest("foo **~~bar**~~", "

    foo ~~bar~~

    ", Settings); 76 | } 77 | 78 | [TestMethod] 79 | [TestCategory("Inlines - Strikethrough")] 80 | public void StrikethroughExample7() 81 | { 82 | Helpers.ExecuteTest("~~bar~~~", "

    bar~

    ", Settings); 83 | } 84 | 85 | [TestMethod] 86 | [TestCategory("Inlines - Strikethrough")] 87 | public void StrikethroughExample8() 88 | { 89 | // make sure that the fenced code blocks are not broken because of this feature 90 | Helpers.ExecuteTest("~~~foo\n~~", "
    ~~\n
    ", Settings); 91 | } 92 | 93 | [TestMethod] 94 | [TestCategory("Inlines - Strikethrough")] 95 | public void StrikethroughExample9() 96 | { 97 | Helpers.ExecuteTest("foo ~~~~bar~~~~", "

    foo bar

    ", Settings); 98 | } 99 | 100 | [TestMethod] 101 | [TestCategory("Inlines - Strikethrough")] 102 | public void StrikethroughExample10() 103 | { 104 | // '[' char in the middle will delay the ~~ match to the post-process phase. 105 | Helpers.ExecuteTest("foo ~~~~ba[r~~~~", "

    foo ba[r

    ", Settings); 106 | } 107 | 108 | [TestMethod] 109 | [TestCategory("Inlines - Strikethrough")] 110 | public void StrikethroughExample10a() 111 | { 112 | // '[' char in the middle will delay the ~~ match to the post-process phase. 113 | Helpers.ExecuteTest("foo ~~~~ba[r~~", "

    foo ~~ba[r

    ", Settings); 114 | } 115 | 116 | [TestMethod] 117 | [TestCategory("Inlines - Strikethrough")] 118 | public void StrikethroughExample10b() 119 | { 120 | // '[' char in the middle will delay the ~~ match to the post-process phase. 121 | Helpers.ExecuteTest("foo ~~ba[r~~~~", "

    foo ba[r~~

    ", Settings); 122 | } 123 | 124 | [TestMethod] 125 | [TestCategory("Inlines - Strikethrough")] 126 | public void StrikethroughExample10c() 127 | { 128 | // '[' char in the middle will delay the ~~ match to the post-process phase. 129 | Helpers.ExecuteTest("foo ~~ba[r~~~", "

    foo ba[r~

    ", Settings); 130 | } 131 | 132 | [TestMethod] 133 | [TestCategory("Inlines - Strikethrough")] 134 | public void StrikethroughExample10d() 135 | { 136 | // '[' char in the middle will delay the ~~ match to the post-process phase. 137 | Helpers.ExecuteTest("~~~~[foo~~ bar", "

    ~~[foo bar

    ", Settings); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /CommonMark.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.Base", "CommonMark\CommonMark.Base.csproj", "{0FD4B1DD-45A8-4F02-BEB0-5881CD512573}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.Console", "CommonMark.Console\CommonMark.Console.csproj", "{83F44787-27A2-4C18-97E2-497437C8EF75}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Other", "Other", "{EC5FFA77-658F-4D2F-A5CE-5F1BD44F3FC0}" 11 | ProjectSection(SolutionItems) = preProject 12 | CommonMark.NET.nuspec = CommonMark.NET.nuspec 13 | global.json = global.json 14 | LICENSE.md = LICENSE.md 15 | README.md = README.md 16 | runtests.pl = runtests.pl 17 | EndProjectSection 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.NET20", "CommonMark\CommonMark.NET20.csproj", "{075E7F00-F1C0-49AA-808D-8B15F1A82DDE}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.NET35", "CommonMark\CommonMark.NET35.csproj", "{61829669-5091-4F2A-A0B1-7348D7CED840}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.NET40", "CommonMark\CommonMark.NET40.csproj", "{0176CB03-177C-464A-A155-089595E77520}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.NET45", "CommonMark\CommonMark.NET45.csproj", "{40AB0D9C-9464-45B2-AC79-72A566FA1371}" 26 | EndProject 27 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.Tests", "CommonMark.Tests\CommonMark.Tests.csproj", "{B3158916-F54D-4F00-B553-E86D4370FF96}" 28 | EndProject 29 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CommonMark.NETCore", "CommonMark.NETCore\CommonMark.NETCore.xproj", "{47C48819-62B7-4BD3-89FF-BA9A0B16C8FB}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonMark.Profile259", "CommonMark\CommonMark.Profile259.csproj", "{E42ABBF6-884C-4D2C-B21D-B96D8B42F8FE}" 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | Release|Any CPU = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {0FD4B1DD-45A8-4F02-BEB0-5881CD512573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {0FD4B1DD-45A8-4F02-BEB0-5881CD512573}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {0FD4B1DD-45A8-4F02-BEB0-5881CD512573}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {0FD4B1DD-45A8-4F02-BEB0-5881CD512573}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {83F44787-27A2-4C18-97E2-497437C8EF75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {83F44787-27A2-4C18-97E2-497437C8EF75}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {83F44787-27A2-4C18-97E2-497437C8EF75}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {83F44787-27A2-4C18-97E2-497437C8EF75}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {075E7F00-F1C0-49AA-808D-8B15F1A82DDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {075E7F00-F1C0-49AA-808D-8B15F1A82DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {075E7F00-F1C0-49AA-808D-8B15F1A82DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {075E7F00-F1C0-49AA-808D-8B15F1A82DDE}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {61829669-5091-4F2A-A0B1-7348D7CED840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {61829669-5091-4F2A-A0B1-7348D7CED840}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {61829669-5091-4F2A-A0B1-7348D7CED840}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {61829669-5091-4F2A-A0B1-7348D7CED840}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {0176CB03-177C-464A-A155-089595E77520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {0176CB03-177C-464A-A155-089595E77520}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {0176CB03-177C-464A-A155-089595E77520}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {0176CB03-177C-464A-A155-089595E77520}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {40AB0D9C-9464-45B2-AC79-72A566FA1371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {40AB0D9C-9464-45B2-AC79-72A566FA1371}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {40AB0D9C-9464-45B2-AC79-72A566FA1371}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {40AB0D9C-9464-45B2-AC79-72A566FA1371}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {B3158916-F54D-4F00-B553-E86D4370FF96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {B3158916-F54D-4F00-B553-E86D4370FF96}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {B3158916-F54D-4F00-B553-E86D4370FF96}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {B3158916-F54D-4F00-B553-E86D4370FF96}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {47C48819-62B7-4BD3-89FF-BA9A0B16C8FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {47C48819-62B7-4BD3-89FF-BA9A0B16C8FB}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {47C48819-62B7-4BD3-89FF-BA9A0B16C8FB}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {47C48819-62B7-4BD3-89FF-BA9A0B16C8FB}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {E42ABBF6-884C-4D2C-B21D-B96D8B42F8FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {E42ABBF6-884C-4D2C-B21D-B96D8B42F8FE}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {E42ABBF6-884C-4D2C-B21D-B96D8B42F8FE}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {E42ABBF6-884C-4D2C-B21D-B96D8B42F8FE}.Release|Any CPU.Build.0 = Release|Any CPU 75 | EndGlobalSection 76 | GlobalSection(SolutionProperties) = preSolution 77 | HideSolutionNode = FALSE 78 | EndGlobalSection 79 | EndGlobal 80 | -------------------------------------------------------------------------------- /CommonMark/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark 6 | { 7 | /// 8 | /// Reusable utility functions, not directly related to parsing or formatting data. 9 | /// 10 | internal static class Utilities 11 | { 12 | /// 13 | /// Writes a warning to the Debug window. 14 | /// 15 | /// The message with optional formatting placeholders. 16 | /// The arguments for the formatting placeholders. 17 | [System.Diagnostics.Conditional("DEBUG")] 18 | public static void Warning(string message, params object[] args) 19 | { 20 | if (args != null && args.Length > 0) 21 | message = string.Format(System.Globalization.CultureInfo.InvariantCulture, message, args); 22 | 23 | System.Diagnostics.Debug.WriteLine(message, "Warning"); 24 | } 25 | 26 | #if OptimizeFor45 27 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 28 | #endif 29 | public static bool IsEscapableSymbol(char c) 30 | { 31 | // char.IsSymbol also works with Unicode symbols that cannot be escaped based on the specification. 32 | return (c > ' ' && c < '0') || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z' && c < 127) || c == '•'; 33 | } 34 | 35 | #if OptimizeFor45 36 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 37 | #endif 38 | public static bool IsAsciiLetter(char c) 39 | { 40 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 41 | } 42 | 43 | #if OptimizeFor45 44 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 45 | #endif 46 | public static bool IsAsciiLetterOrDigit(char c) 47 | { 48 | return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'); 49 | } 50 | 51 | #if OptimizeFor45 52 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 53 | #endif 54 | public static bool IsWhitespace(char c) 55 | { 56 | return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f'; 57 | } 58 | 59 | /// 60 | /// Checks if the given character is an Unicode space or punctuation character. 61 | /// 62 | #if OptimizeFor45 63 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 64 | #endif 65 | public static void CheckUnicodeCategory(char c, out bool space, out bool punctuation) 66 | { 67 | // This method does the same as would calling the two built-in methods: 68 | // // space = char.IsWhiteSpace(c); 69 | // // punctuation = char.IsPunctuation(c); 70 | // 71 | // The performance benefit for using this method is ~50% when calling only on ASCII characters 72 | // and ~12% when calling only on Unicode characters. 73 | 74 | if (c <= 'ÿ') 75 | { 76 | space = c == ' ' || (c >= '\t' && c <= '\r') || c == '\u00a0' || c == '\u0085'; 77 | punctuation = (c >= 33 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 96) || (c >= 123 && c <= 126); 78 | } 79 | else 80 | { 81 | var category = System.Globalization.CharUnicodeInfo.GetUnicodeCategory(c); 82 | space = category == System.Globalization.UnicodeCategory.SpaceSeparator 83 | || category == System.Globalization.UnicodeCategory.LineSeparator 84 | || category == System.Globalization.UnicodeCategory.ParagraphSeparator; 85 | punctuation = !space && 86 | (category == System.Globalization.UnicodeCategory.ConnectorPunctuation 87 | || category == System.Globalization.UnicodeCategory.DashPunctuation 88 | || category == System.Globalization.UnicodeCategory.OpenPunctuation 89 | || category == System.Globalization.UnicodeCategory.ClosePunctuation 90 | || category == System.Globalization.UnicodeCategory.InitialQuotePunctuation 91 | || category == System.Globalization.UnicodeCategory.FinalQuotePunctuation 92 | || category == System.Globalization.UnicodeCategory.OtherPunctuation); 93 | } 94 | } 95 | 96 | /// 97 | /// Determines if the first line (ignoring the first ) of a string contains only spaces. 98 | /// 99 | public static bool IsFirstLineBlank(string source, int startIndex) 100 | { 101 | char c; 102 | var lastIndex = source.Length; 103 | 104 | while (startIndex < lastIndex) 105 | { 106 | c = source[startIndex]; 107 | if (c == '\n') 108 | return true; 109 | 110 | if (c != ' ') 111 | return false; 112 | 113 | startIndex++; 114 | } 115 | 116 | return true; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /CommonMark.Tests/CommonMark.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {B3158916-F54D-4F00-B553-E86D4370FF96} 7 | Library 8 | Properties 9 | CommonMark.Tests 10 | CommonMark.Tests 11 | v4.5.1 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | true 39 | 40 | 41 | ..\CommonMark\Properties\public.snk 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | True 74 | True 75 | Specs.tt 76 | 77 | 78 | 79 | 80 | TextTemplatingFileGenerator 81 | Specs.cs 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {0fd4b1dd-45a8-4f02-beb0-5881cd512573} 90 | CommonMark.Base 91 | 92 | 93 | 94 | 95 | 96 | 97 | False 98 | 99 | 100 | False 101 | 102 | 103 | False 104 | 105 | 106 | False 107 | 108 | 109 | 110 | 111 | 112 | 113 | 120 | -------------------------------------------------------------------------------- /CommonMark/CommonMarkException.cs: -------------------------------------------------------------------------------- 1 | using CommonMark.Syntax; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace CommonMark 7 | { 8 | /// 9 | /// An exception that is caught during CommonMark parsing or formatting. 10 | /// 11 | #if v2_0 || v3_5 || v4_0 || v4_5 12 | [Serializable] 13 | #endif 14 | public class CommonMarkException : Exception 15 | { 16 | /// 17 | /// Gets the block that caused the exception. Can be null. 18 | /// 19 | public Block BlockElement { get; private set; } 20 | 21 | /// 22 | /// Gets the inline element that caused the exception. Can be null. 23 | /// 24 | public Inline InlineElement { get; private set; } 25 | 26 | /// Initializes a new instance of the class. 27 | public CommonMarkException() { } 28 | 29 | /// Initializes a new instance of the class with a specified error message. 30 | /// The message that describes the error. 31 | public CommonMarkException(string message) : base(message) { } 32 | 33 | /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. 34 | /// The error message that explains the reason for the exception. 35 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 36 | public CommonMarkException(string message, Exception innerException) : base(message, innerException) { } 37 | 38 | /// Initializes a new instance of the class with a specified error message, a reference to the element that caused it and a reference to the inner exception that is the cause of this exception. 39 | /// The error message that explains the reason for the exception. 40 | /// The inline element that is related to the exception cause. 41 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 42 | public CommonMarkException(string message, Inline inline, Exception innerException = null) 43 | : base(message, innerException) 44 | { 45 | this.InlineElement = inline; 46 | } 47 | 48 | /// Initializes a new instance of the class with a specified error message, a reference to the element that caused it and a reference to the inner exception that is the cause of this exception. 49 | /// The error message that explains the reason for the exception. 50 | /// The block element that is related to the exception cause. 51 | /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. 52 | public CommonMarkException(string message, Block block, Exception innerException = null) 53 | : base(message, innerException) 54 | { 55 | this.BlockElement = block; 56 | } 57 | 58 | #if v2_0 || v3_5 || v4_0 || v4_5 59 | /// Initializes a new instance of the class from the specified instances of the and classes. 60 | /// A instance that contains the information required to deserialize the new instance. 61 | /// A instance. 62 | [System.Security.SecuritySafeCritical] 63 | protected CommonMarkException( 64 | System.Runtime.Serialization.SerializationInfo serializationInfo, 65 | System.Runtime.Serialization.StreamingContext streamingContext) 66 | : base(serializationInfo, streamingContext) 67 | { 68 | // Block and Inline classes are not marked [Serializable] and thus cannot be used here. 69 | // Currently there also aren't any good use cases where this would provide added value. 70 | ////this.BlockElement = (Block)info.GetValue("BlockElement", typeof(Block)); 71 | ////this.InlineElement = (Inline)info.GetValue("InlineElement", typeof(Inline)); 72 | } 73 | 74 | /// Sets the with information about the exception. 75 | /// The that holds the serialized object data about the exception being thrown. 76 | /// The that contains contextual information about the source or destination. 77 | /// The parameter is a null reference (Nothing in Visual Basic). 78 | [System.Security.SecurityCritical] 79 | public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 80 | { 81 | base.GetObjectData(info, context); 82 | ////info.AddValue("BlockElement", this.BlockElement); 83 | ////info.AddValue("InlineElement", this.InlineElement); 84 | } 85 | #endif 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /CommonMark/Formatters/HtmlTextWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using CommonMark.Syntax; 3 | 4 | namespace CommonMark.Formatters 5 | { 6 | /// 7 | /// A wrapper for that keeps track if the last symbol has been a newline. 8 | /// 9 | internal sealed class HtmlTextWriter 10 | { 11 | private readonly TextWriter _inner; 12 | private char _last = '\n'; 13 | private readonly bool _windowsNewLine; 14 | private readonly char[] _newline; 15 | 16 | /// 17 | /// A reusable char buffer. This is used internally in (and thus will modify the buffer) 18 | /// but can also be used from class. 19 | /// 20 | internal char[] Buffer = new char[256]; 21 | 22 | public HtmlTextWriter(TextWriter inner) 23 | { 24 | this._inner = inner; 25 | 26 | var nl = inner.NewLine; 27 | this._newline = nl.ToCharArray(); 28 | this._windowsNewLine = nl == "\r\n"; 29 | } 30 | 31 | public void WriteLine() 32 | { 33 | this._inner.Write(this._newline); 34 | this._last = '\n'; 35 | } 36 | 37 | public void WriteLine(char data) 38 | { 39 | if (data == '\n' && this._windowsNewLine && this._last != '\r') 40 | this._inner.Write('\r'); 41 | 42 | this._inner.Write(data); 43 | this._inner.Write(this._newline); 44 | this._last = '\n'; 45 | } 46 | 47 | public void Write(StringPart value) 48 | { 49 | if (value.Length == 0) 50 | return; 51 | 52 | if (this.Buffer.Length < value.Length) 53 | this.Buffer = new char[value.Length]; 54 | 55 | value.Source.CopyTo(value.StartIndex, this.Buffer, 0, value.Length); 56 | 57 | if (this._windowsNewLine) 58 | { 59 | var lastPos = value.StartIndex; 60 | var pos = lastPos; 61 | 62 | while (-1 != (pos = value.Source.IndexOf('\n', pos, value.Length - pos + value.StartIndex))) 63 | { 64 | var lastC = pos == 0 ? this._last : value.Source[pos - 1]; 65 | 66 | if (lastC != '\r') 67 | { 68 | this._inner.Write(this.Buffer, lastPos - value.StartIndex, pos - lastPos); 69 | this._inner.Write('\r'); 70 | lastPos = pos; 71 | } 72 | 73 | pos++; 74 | } 75 | 76 | this._inner.Write(this.Buffer, lastPos - value.StartIndex, value.Length - lastPos + value.StartIndex); 77 | } 78 | else 79 | { 80 | this._inner.Write(this.Buffer, 0, value.Length); 81 | } 82 | 83 | this._last = this.Buffer[value.Length - 1]; 84 | } 85 | 86 | /// 87 | /// Writes a value that is known not to contain any newlines. 88 | /// 89 | public void WriteConstant(char[] value) 90 | { 91 | this._last = 'c'; 92 | this._inner.Write(value, 0, value.Length); 93 | } 94 | 95 | /// 96 | /// Writes a value that is known not to contain any newlines. 97 | /// 98 | public void WriteConstant(char[] value, int startIndex, int length) 99 | { 100 | this._last = 'c'; 101 | this._inner.Write(value, startIndex, length); 102 | } 103 | 104 | /// 105 | /// Writes a value that is known not to contain any newlines. 106 | /// 107 | public void WriteConstant(string value) 108 | { 109 | this._last = 'c'; 110 | this._inner.Write(value); 111 | } 112 | 113 | /// 114 | /// Writes a value that is known not to contain any newlines. 115 | /// 116 | public void WriteLineConstant(string value) 117 | { 118 | this._last = '\n'; 119 | this._inner.Write(value); 120 | this._inner.Write(this._newline); 121 | } 122 | 123 | public void Write(char[] value, int index, int count) 124 | { 125 | if (value == null || count == 0) 126 | return; 127 | 128 | if (this._windowsNewLine) 129 | { 130 | var lastPos = index; 131 | var pos = index; 132 | 133 | while (pos < index + count) 134 | { 135 | if (value[pos] != '\n') 136 | { 137 | pos++; 138 | continue; 139 | } 140 | 141 | var lastC = pos == index ? this._last : value[pos - 1]; 142 | 143 | if (lastC != '\r') 144 | { 145 | this._inner.Write(value, lastPos, pos - lastPos); 146 | this._inner.Write('\r'); 147 | lastPos = pos; 148 | } 149 | 150 | pos++; 151 | } 152 | 153 | this._inner.Write(value, lastPos, index + count - lastPos); 154 | } 155 | else 156 | { 157 | this._inner.Write(value, index, count); 158 | } 159 | 160 | this._last = value[index + count - 1]; 161 | } 162 | 163 | public void Write(char value) 164 | { 165 | if (value == '\n' && this._windowsNewLine && this._last != '\r') 166 | this._inner.Write('\r'); 167 | 168 | this._last = value; 169 | this._inner.Write(value); 170 | } 171 | 172 | /// 173 | /// Adds a newline if the writer does not currently end with a newline. 174 | /// 175 | public void EnsureLine() 176 | { 177 | if (this._last != '\n') 178 | this.WriteLine(); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /CommonMark/Syntax/Enumerable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CommonMark.Syntax 5 | { 6 | internal class Enumerable : IEnumerable 7 | { 8 | private Block _root; 9 | 10 | public Enumerable(Block root) 11 | { 12 | if (root == null) 13 | throw new ArgumentNullException(nameof(root)); 14 | 15 | this._root = root; 16 | } 17 | 18 | public IEnumerator GetEnumerator() 19 | { 20 | return new Enumerator(this._root); 21 | } 22 | 23 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 24 | { 25 | return this.GetEnumerator(); 26 | } 27 | 28 | private sealed class Enumerator : IEnumerator 29 | { 30 | private Block _root; 31 | private EnumeratorEntry _current; 32 | private Stack _blockStack = new Stack(); 33 | private Stack _inlineStack = new Stack(); 34 | 35 | public Enumerator(Block root) 36 | { 37 | this._root = root; 38 | this._blockStack.Push(new BlockStackEntry(root, null)); 39 | } 40 | 41 | public EnumeratorEntry Current 42 | { 43 | get { return this._current; } 44 | } 45 | 46 | object System.Collections.IEnumerator.Current 47 | { 48 | get { return this._current; } 49 | } 50 | 51 | #if OptimizeFor45 52 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 53 | #endif 54 | private bool ShouldSkip(Inline inline) 55 | { 56 | if (inline.Tag == InlineTag.String 57 | && inline.FirstChild == null 58 | && inline.LiteralContentValue.Length == 0) 59 | return true; 60 | 61 | return false; 62 | } 63 | 64 | public bool MoveNext() 65 | { 66 | repeatMoveNext: 67 | 68 | Inline inline; 69 | if (this._inlineStack.Count > 0) 70 | { 71 | var entry = this._inlineStack.Pop(); 72 | if (entry.NeedsClose != null) 73 | { 74 | inline = entry.NeedsClose; 75 | this._current = new EnumeratorEntry(false, true, inline); 76 | 77 | if (entry.Target != null) 78 | { 79 | entry.NeedsClose = null; 80 | this._inlineStack.Push(entry); 81 | } 82 | 83 | return true; 84 | } 85 | 86 | if (entry.Target != null) 87 | { 88 | inline = entry.Target; 89 | this._current = new EnumeratorEntry(true, inline.FirstChild == null, inline); 90 | 91 | if (inline.FirstChild != null) 92 | { 93 | this._inlineStack.Push(new InlineStackEntry(inline.NextSibling, inline)); 94 | this._inlineStack.Push(new InlineStackEntry(inline.FirstChild, null)); 95 | } 96 | else if (inline.NextSibling != null) 97 | { 98 | this._inlineStack.Push(new InlineStackEntry(inline.NextSibling, null)); 99 | } 100 | 101 | if (this.ShouldSkip(this._current.Inline)) 102 | { 103 | goto repeatMoveNext; 104 | } 105 | } 106 | 107 | return true; 108 | } 109 | 110 | Block block; 111 | if (this._blockStack.Count > 0) 112 | { 113 | var entry = this._blockStack.Pop(); 114 | if (entry.NeedsClose != null) 115 | { 116 | block = entry.NeedsClose; 117 | this._current = new EnumeratorEntry(false, true, block); 118 | 119 | if (entry.Target != null) 120 | { 121 | entry.NeedsClose = null; 122 | this._blockStack.Push(entry); 123 | } 124 | 125 | return true; 126 | } 127 | 128 | if (entry.Target != null) 129 | { 130 | block = entry.Target; 131 | this._current = new EnumeratorEntry(true, block.FirstChild == null && block.InlineContent == null, block); 132 | 133 | if (block.FirstChild != null) 134 | { 135 | this._blockStack.Push(new BlockStackEntry(block.NextSibling, block)); 136 | this._blockStack.Push(new BlockStackEntry(block.FirstChild, null)); 137 | } 138 | else if (block.NextSibling != null && block != this._root) 139 | { 140 | this._blockStack.Push(new BlockStackEntry(block.NextSibling, block.InlineContent == null ? null : block)); 141 | } 142 | else if (block.InlineContent != null) 143 | { 144 | this._blockStack.Push(new BlockStackEntry(null, block)); 145 | } 146 | 147 | if (block.InlineContent != null) 148 | { 149 | this._inlineStack.Push(new InlineStackEntry(block.InlineContent, null)); 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | return false; 157 | } 158 | 159 | public void Reset() 160 | { 161 | this._current = null; 162 | this._blockStack.Clear(); 163 | this._inlineStack.Clear(); 164 | } 165 | 166 | void IDisposable.Dispose() 167 | { 168 | } 169 | 170 | private struct BlockStackEntry 171 | { 172 | public readonly Block Target; 173 | public Block NeedsClose; 174 | public BlockStackEntry(Block target, Block needsClose) 175 | { 176 | this.Target = target; 177 | this.NeedsClose = needsClose; 178 | } 179 | } 180 | private struct InlineStackEntry 181 | { 182 | public readonly Inline Target; 183 | public Inline NeedsClose; 184 | public InlineStackEntry(Inline target, Inline needsClose) 185 | { 186 | this.Target = target; 187 | this.NeedsClose = needsClose; 188 | } 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /CommonMark/CommonMarkSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CommonMark 6 | { 7 | /// 8 | /// Class used to configure the behavior of . 9 | /// 10 | /// This class is not thread-safe so any changes to a instance that is reused (for example, the 11 | /// ) has to be updated while it is not in use otherwise the 12 | /// behaviour is undefined. 13 | public sealed class CommonMarkSettings 14 | { 15 | /// Initializes a new instance of the class. 16 | [Obsolete("Use CommonMarkSettings.Default.Clone() instead", false)] 17 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 18 | public CommonMarkSettings() 19 | { } 20 | 21 | /// 22 | /// Gets or sets the output format used by the last stage of conversion. 23 | /// 24 | public OutputFormat OutputFormat { get; set; } 25 | 26 | private Action _outputDelegate; 27 | /// 28 | /// Gets or sets the custom output delegate function used for formatting CommonMark output. 29 | /// Setting this to a non-null value will also set to . 30 | /// 31 | public Action OutputDelegate 32 | { 33 | get { return this._outputDelegate; } 34 | set 35 | { 36 | if (this._outputDelegate != value) 37 | { 38 | this._outputDelegate = value; 39 | this.OutputFormat = value == null ? default(OutputFormat) : OutputFormat.CustomDelegate; 40 | } 41 | } 42 | } 43 | 44 | /// 45 | /// Gets or sets a value indicating whether soft line breaks should be rendered as hard line breaks. 46 | /// 47 | public bool RenderSoftLineBreaksAsLineBreaks { get; set; } 48 | 49 | /// 50 | /// Gets or sets a value indicating whether the parser tracks precise positions in the source data for 51 | /// block and inline elements. This is disabled by default because it incurs an additional performance cost to 52 | /// keep track of the original position. 53 | /// Setting this to true will populate , 54 | /// , and 55 | /// properties with correct information, otherwise the values 56 | /// of these properties are undefined. 57 | /// This also controls if these values will be written to the output. 58 | /// 59 | public bool TrackSourcePosition { get; set; } 60 | 61 | private CommonMarkAdditionalFeatures _additionalFeatures; 62 | 63 | /// 64 | /// Gets or sets any additional features (that are not present in the current CommonMark specification) that 65 | /// the parser and/or formatter will recognize. 66 | /// 67 | public CommonMarkAdditionalFeatures AdditionalFeatures 68 | { 69 | get { return this._additionalFeatures; } 70 | set { this._additionalFeatures = value; this._inlineParsers = null; this._inlineParserSpecialCharacters = null; } 71 | } 72 | 73 | private Func _uriResolver; 74 | /// 75 | /// Gets or sets the delegate that is used to resolve addresses during rendering process. Can be used to process application relative URLs (~/foo/bar). 76 | /// 77 | /// CommonMarkSettings.Default.UriResolver = VirtualPathUtility.ToAbsolute; 78 | public Func UriResolver 79 | { 80 | get { return this._uriResolver; } 81 | set 82 | { 83 | if (value != null) 84 | { 85 | var orig = value; 86 | value = x => 87 | { 88 | try 89 | { 90 | return orig(x); 91 | } 92 | catch (Exception ex) 93 | { 94 | throw new CommonMarkException("An error occurred while executing the CommonMarkSettings.UriResolver delegate. View inner exception for details.", ex); 95 | } 96 | }; 97 | } 98 | 99 | this._uriResolver = value; 100 | } 101 | } 102 | 103 | #pragma warning disable 0618 104 | private static readonly CommonMarkSettings _default = new CommonMarkSettings(); 105 | #pragma warning restore 0618 106 | 107 | /// 108 | /// The default settings for the converter. If the properties of this instance are modified, the changes will be applied to all 109 | /// future conversions that do not specify their own settings. 110 | /// 111 | public static CommonMarkSettings Default { get { return _default; } } 112 | 113 | /// 114 | /// Creates a copy of this configuration object. 115 | /// 116 | public CommonMarkSettings Clone() 117 | { 118 | return (CommonMarkSettings)this.MemberwiseClone(); 119 | } 120 | 121 | #region [ Properties that cache structures used in the parsers ] 122 | 123 | private Func[] _inlineParsers; 124 | 125 | /// 126 | /// Gets the delegates that parse inline elements according to these settings. 127 | /// 128 | internal Func[] InlineParsers 129 | { 130 | get 131 | { 132 | var p = this._inlineParsers; 133 | if (p == null) 134 | { 135 | p = Parser.InlineMethods.InitializeParsers(this); 136 | this._inlineParsers = p; 137 | } 138 | 139 | return p; 140 | } 141 | } 142 | 143 | private char[] _inlineParserSpecialCharacters; 144 | 145 | /// 146 | /// Gets the characters that have special meaning for inline element parsers according to these settings. 147 | /// 148 | internal char[] InlineParserSpecialCharacters 149 | { 150 | get 151 | { 152 | var v = this._inlineParserSpecialCharacters; 153 | if (v == null) 154 | { 155 | var p = this.InlineParsers; 156 | var vs = new List(20); 157 | for (var i = 0; i < p.Length; i++) 158 | if (p[i] != null) 159 | vs.Add((char)i); 160 | 161 | v = this._inlineParserSpecialCharacters = vs.ToArray(); 162 | } 163 | 164 | return v; 165 | } 166 | } 167 | 168 | #endregion 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /CommonMark/Parser/ScannerCharacterMatcher.cs: -------------------------------------------------------------------------------- 1 | namespace CommonMark.Parser 2 | { 3 | /// 4 | /// Class containing methods for fast forward matching of string contents 5 | /// 6 | internal static class ScannerCharacterMatcher 7 | { 8 | /// 9 | /// Moves along the given string as long as the current character is a whitespace. 10 | /// 11 | #if OptimizeFor45 12 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 13 | #endif 14 | internal static bool MatchWhitespaces(string data, ref char currentCharacter, ref int currentPosition, int lastPosition) 15 | { 16 | var matched = false; 17 | while (Utilities.IsWhitespace(currentCharacter) && currentPosition < lastPosition) 18 | { 19 | currentCharacter = data[++currentPosition]; 20 | matched = true; 21 | } 22 | return matched; 23 | } 24 | 25 | /// 26 | /// Moves along the given string as long as the current character is a ASCII letter. 27 | /// 28 | #if OptimizeFor45 29 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 30 | #endif 31 | internal static bool MatchAsciiLetter(string data, ref char currentCharacter, ref int currentPosition, int lastPosition) 32 | { 33 | var matched = false; 34 | while (((currentCharacter >= 'a' && currentCharacter <= 'z') 35 | || (currentCharacter >= 'A' && currentCharacter <= 'Z')) 36 | && currentPosition < lastPosition) 37 | { 38 | currentCharacter = data[++currentPosition]; 39 | matched = true; 40 | } 41 | return matched; 42 | } 43 | 44 | /// 45 | /// Moves along the given string as long as the current character is a valid HTML tag character 46 | /// (ASCII letter or digit or dash). 47 | /// 48 | #if OptimizeFor45 49 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 50 | #endif 51 | internal static bool MatchHtmlTagNameCharacter(string data, ref char currentCharacter, ref int currentPosition, int lastPosition) 52 | { 53 | var matched = false; 54 | while (( (currentCharacter >= 'a' && currentCharacter <= 'z') 55 | || (currentCharacter >= 'A' && currentCharacter <= 'Z') 56 | || (currentCharacter >= '0' && currentCharacter <= '9') 57 | || (currentCharacter == '-')) 58 | && currentPosition < lastPosition) 59 | { 60 | currentCharacter = data[++currentPosition]; 61 | matched = true; 62 | } 63 | return matched; 64 | } 65 | 66 | /// 67 | /// Moves along the given string as long as the current character is a ASCII letter or digit or one of the given additional characters. 68 | /// 69 | #if OptimizeFor45 70 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 71 | #endif 72 | internal static bool MatchAsciiLetterOrDigit(string data, ref char currentCharacter, ref int currentPosition, int lastPosition, char valid1, char valid2, char valid3, char valid4) 73 | { 74 | var matched = false; 75 | while (( currentCharacter == valid1 76 | || currentCharacter == valid2 77 | || currentCharacter == valid3 78 | || currentCharacter == valid4 79 | || (currentCharacter >= 'a' && currentCharacter <= 'z') 80 | || (currentCharacter >= 'A' && currentCharacter <= 'Z') 81 | || (currentCharacter >= '0' && currentCharacter <= '9')) 82 | && currentPosition < lastPosition) 83 | { 84 | currentCharacter = data[++currentPosition]; 85 | matched = true; 86 | } 87 | return matched; 88 | } 89 | 90 | /// 91 | /// Moves along the given string as long as the current character is a ASCII letter or digit or one of the given additional characters. 92 | /// 93 | #if OptimizeFor45 94 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 95 | #endif 96 | internal static bool MatchAsciiLetterOrDigit(string data, ref char currentCharacter, ref int currentPosition, int lastPosition, char valid1) 97 | { 98 | var matched = false; 99 | while ((currentCharacter == valid1 100 | || (currentCharacter >= 'a' && currentCharacter <= 'z') 101 | || (currentCharacter >= 'A' && currentCharacter <= 'Z') 102 | || (currentCharacter >= '0' && currentCharacter <= '9')) 103 | && currentPosition < lastPosition) 104 | { 105 | currentCharacter = data[++currentPosition]; 106 | matched = true; 107 | } 108 | return matched; 109 | } 110 | 111 | /// 112 | /// Moves along the given string as long as the current character is a ASCII letter or one of the given additional characters. 113 | /// 114 | #if OptimizeFor45 115 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 116 | #endif 117 | internal static bool MatchAsciiLetter(string data, ref char currentCharacter, ref int currentPosition, int lastPosition, char valid1, char valid2) 118 | { 119 | var matched = false; 120 | while (( currentCharacter == valid1 121 | || currentCharacter == valid2 122 | || (currentCharacter >= 'a' && currentCharacter <= 'z') 123 | || (currentCharacter >= 'A' && currentCharacter <= 'Z') 124 | || (currentCharacter >= '0' && currentCharacter <= '9')) 125 | && currentPosition < lastPosition) 126 | { 127 | currentCharacter = data[++currentPosition]; 128 | matched = true; 129 | } 130 | return matched; 131 | } 132 | 133 | #if OptimizeFor45 134 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 135 | #endif 136 | internal static bool MatchAnythingExcept(string data, ref char currentCharacter, ref int currentPosition, int lastPosition, char invalid1) 137 | { 138 | var matched = false; 139 | while (currentCharacter != invalid1 && currentPosition < lastPosition) 140 | { 141 | currentCharacter = data[++currentPosition]; 142 | matched = true; 143 | } 144 | return matched; 145 | } 146 | 147 | #if OptimizeFor45 148 | [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] 149 | #endif 150 | internal static bool MatchAnythingExceptWhitespaces(string data, ref char currentCharacter, ref int currentPosition, int lastPosition, 151 | char invalid1, char invalid2, char invalid3, char invalid4, char invalid5, char invalid6) 152 | { 153 | var matched = false; 154 | while (currentCharacter != invalid1 155 | && currentCharacter != invalid2 156 | && currentCharacter != invalid3 157 | && currentCharacter != invalid4 158 | && currentCharacter != invalid5 159 | && currentCharacter != invalid6 160 | && (currentCharacter != ' ' && currentCharacter != '\n') 161 | && currentPosition < lastPosition) 162 | { 163 | currentCharacter = data[++currentPosition]; 164 | matched = true; 165 | } 166 | return matched; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /CommonMark/Syntax/Inline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommonMark.Syntax 4 | { 5 | /// 6 | /// Represents a parsed inline element in the document. 7 | /// 8 | public sealed class Inline 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public Inline() 14 | { 15 | } 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | /// The type of inline element. 21 | public Inline(InlineTag tag) 22 | { 23 | this.Tag = tag; 24 | } 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The type of inline element. Should be one of the types that require literal content, for example, . 30 | /// The literal contents of the inline element. 31 | public Inline(InlineTag tag, string content) 32 | { 33 | this.Tag = tag; 34 | this.LiteralContent = content; 35 | } 36 | 37 | /// 38 | /// Initializes a new instance of the class. 39 | /// 40 | internal Inline(InlineTag tag, string content, int startIndex, int length) 41 | { 42 | this.Tag = tag; 43 | this.LiteralContentValue.Source = content; 44 | this.LiteralContentValue.StartIndex = startIndex; 45 | this.LiteralContentValue.Length = length; 46 | } 47 | 48 | /// 49 | /// Initializes a new instance of the class. The element type is set to 50 | /// 51 | /// The literal string contents of the inline element. 52 | public Inline(string content) 53 | { 54 | // this is not assigned because it is the default value. 55 | ////this.Tag = InlineTag.String; 56 | 57 | this.LiteralContent = content; 58 | } 59 | 60 | /// 61 | /// Initializes a new instance of the class. The element type is set to 62 | /// 63 | internal Inline(string content, int sourcePosition, int sourceLastPosition) 64 | { 65 | this.LiteralContent = content; 66 | this.SourcePosition = sourcePosition; 67 | this.SourceLastPosition = sourceLastPosition; 68 | } 69 | 70 | /// 71 | /// Initializes a new instance of the class. The element type is set to 72 | /// 73 | internal Inline(string content, int startIndex, int length, int sourcePosition, int sourceLastPosition, char delimiterCharacter) 74 | { 75 | this.LiteralContentValue.Source = content; 76 | this.LiteralContentValue.StartIndex = startIndex; 77 | this.LiteralContentValue.Length = length; 78 | this.SourcePosition = sourcePosition; 79 | this.SourceLastPosition = sourceLastPosition; 80 | this.Emphasis = new EmphasisData(delimiterCharacter); 81 | } 82 | 83 | /// 84 | /// Initializes a new instance of the class. 85 | /// 86 | /// The type of inline element. Should be one of the types that contain child elements, for example, . 87 | /// The first descendant element of the inline that is being created. 88 | public Inline(InlineTag tag, Inline content) 89 | { 90 | this.Tag = tag; 91 | this.FirstChild = content; 92 | } 93 | 94 | internal static Inline CreateLink(Inline label, string url, string title) 95 | { 96 | return new Inline() 97 | { 98 | Tag = InlineTag.Link, 99 | FirstChild = label, 100 | TargetUrl = url, 101 | LiteralContent = title 102 | }; 103 | } 104 | 105 | /// 106 | /// Gets of sets the type of the inline element this instance represents. 107 | /// 108 | public InlineTag Tag { get; set; } 109 | 110 | /// 111 | /// Gets or sets the literal content of this element. This is only used if the property specifies 112 | /// a type that can have literal content. 113 | /// 114 | /// Note that for this property contains the title of the link. 115 | /// 116 | public string LiteralContent 117 | { 118 | get 119 | { 120 | // since the .ToString() has been called once, cache the value. 121 | return this.LiteralContent = this.LiteralContentValue.ToString(); 122 | } 123 | 124 | set 125 | { 126 | this.LiteralContentValue.Source = value; 127 | this.LiteralContentValue.StartIndex = 0; 128 | this.LiteralContentValue.Length = value == null ? 0 : value.Length; 129 | } 130 | } 131 | 132 | internal StringPart LiteralContentValue; 133 | 134 | /// 135 | /// Gets or sets the target URL for this element. Only used for and 136 | /// . 137 | /// 138 | public string TargetUrl { get; set; } 139 | 140 | /// 141 | /// Gets or sets the first descendant of this element. This is only used if the property specifies 142 | /// a type that can have nested elements. 143 | /// 144 | public Inline FirstChild { get; set; } 145 | 146 | /// 147 | /// Gets or sets the position of the element within the source data. 148 | /// Note that if is not enabled, this property will contain 149 | /// the position relative to the containing block and not the whole document (not accounting for processing done 150 | /// in earlier parser stage, such as converting tabs to spaces). 151 | /// 152 | /// 153 | public int SourcePosition { get; set; } 154 | 155 | internal int SourceLastPosition { get; set; } 156 | 157 | /// 158 | /// Gets or sets the length of the element within the source data. 159 | /// Note that if is not enabled, this property will contain 160 | /// the length within the containing block (not accounting for processing done in earlier parser stage, such as 161 | /// converting tabs to spaces). 162 | /// 163 | /// 164 | public int SourceLength 165 | { 166 | get { return this.SourceLastPosition - this.SourcePosition; } 167 | set { this.SourceLastPosition = this.SourcePosition + value; } 168 | } 169 | 170 | /// 171 | /// Gets the link details. This is now obsolete in favor of and 172 | /// properties and this property will be removed in future. 173 | /// 174 | [Obsolete("The link properties have been moved to TargetUrl and LiteralContent (previously Title) properties to reduce number of objects created. This property will be removed in future versions.", false)] 175 | [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] 176 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 177 | public InlineContentLinkable Linkable { get { return new InlineContentLinkable() { Url = this.TargetUrl, Title = this.LiteralContent }; } } 178 | 179 | private Inline _next; 180 | 181 | /// 182 | /// Gets the next sibling inline element. Returns null if this is the last element. 183 | /// 184 | public Inline NextSibling 185 | { 186 | get { return this._next; } 187 | set { this._next = value; } 188 | } 189 | 190 | /// 191 | /// Gets the last sibling of this inline. If no siblings are defined, returns self. 192 | /// 193 | public Inline LastSibling 194 | { 195 | get 196 | { 197 | var x = this._next; 198 | if (x == null) 199 | return this; 200 | 201 | while (x._next != null) 202 | x = x._next; 203 | 204 | return x; 205 | } 206 | } 207 | 208 | /// 209 | /// Gets the additional properties that apply to emphasis elements. 210 | /// 211 | public EmphasisData Emphasis { get; } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /CommonMark/Parser/InlineStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommonMark.Syntax; 3 | 4 | namespace CommonMark.Parser 5 | { 6 | /// 7 | /// Describes an element in a stack of possible inline openers. 8 | /// 9 | internal sealed class InlineStack 10 | { 11 | /// 12 | /// The parser priority if this stack entry. 13 | /// 14 | public InlineStackPriority Priority; 15 | 16 | /// 17 | /// Previous entry in the stack. null if this is the last one. 18 | /// 19 | public InlineStack Previous; 20 | 21 | /// 22 | /// Next entry in the stack. null if this is the last one. 23 | /// 24 | public InlineStack Next; 25 | 26 | /// 27 | /// The at-the-moment text inline that could be transformed into the opener. 28 | /// 29 | public Inline StartingInline; 30 | 31 | /// 32 | /// The number of delimiter characters found for this opener. 33 | /// 34 | public int DelimiterCount; 35 | 36 | /// 37 | /// The character that was used in the opener. 38 | /// 39 | public char Delimiter; 40 | 41 | /// 42 | /// The position in the where this inline element was found. 43 | /// Used only if the specific parser requires this information. 44 | /// 45 | public int StartPosition; 46 | 47 | /// 48 | /// The flags set for this stack entry. 49 | /// 50 | public InlineStackFlags Flags; 51 | 52 | [Flags] 53 | public enum InlineStackFlags : byte 54 | { 55 | None = 0, 56 | Opener = 1, 57 | Closer = 2, 58 | ImageLink = 4 59 | } 60 | 61 | public enum InlineStackPriority : byte 62 | { 63 | Emphasis = 0, 64 | Links = 1, 65 | Maximum = Links 66 | } 67 | 68 | public static InlineStack FindMatchingOpener(InlineStack seachBackwardsFrom, InlineStackPriority priority, char delimiter, out bool canClose) 69 | { 70 | canClose = true; 71 | var istack = seachBackwardsFrom; 72 | while (true) 73 | { 74 | if (istack == null) 75 | { 76 | // this cannot be a closer since there is no opener available. 77 | canClose = false; 78 | return null; 79 | } 80 | 81 | if (istack.Priority > priority || 82 | (istack.Delimiter == delimiter && 0 != (istack.Flags & InlineStackFlags.Closer))) 83 | { 84 | // there might be a closer further back but we cannot go there yet because a higher priority element is blocking 85 | // the other option is that the stack entry could be a closer for the same char - this means 86 | // that any opener we might find would first have to be matched against this closer. 87 | return null; 88 | } 89 | 90 | if (istack.Delimiter == delimiter) 91 | return istack; 92 | 93 | istack = istack.Previous; 94 | } 95 | } 96 | 97 | public static void AppendStackEntry(InlineStack entry, Subject subj) 98 | { 99 | if (subj.LastPendingInline != null) 100 | { 101 | entry.Previous = subj.LastPendingInline; 102 | subj.LastPendingInline.Next = entry; 103 | } 104 | 105 | if (subj.FirstPendingInline == null) 106 | subj.FirstPendingInline = entry; 107 | 108 | subj.LastPendingInline = entry; 109 | } 110 | 111 | /// 112 | /// Removes a subset of the stack. 113 | /// 114 | /// The first entry to be removed. 115 | /// The subject associated with this stack. Can be null if the pointers in the subject should not be updated. 116 | /// The last entry to be removed. Can be null if everything starting from has to be removed. 117 | public static void RemoveStackEntry(InlineStack first, Subject subj, InlineStack last) 118 | { 119 | var curPriority = first.Priority; 120 | 121 | if (last == null) 122 | { 123 | if (first.Previous != null) 124 | first.Previous.Next = null; 125 | else if (subj != null) 126 | subj.FirstPendingInline = null; 127 | 128 | if (subj != null) 129 | { 130 | last = subj.LastPendingInline; 131 | subj.LastPendingInline = first.Previous; 132 | } 133 | 134 | first = first.Next; 135 | } 136 | else 137 | { 138 | if (first.Previous != null) 139 | first.Previous.Next = last.Next; 140 | else if (subj != null) 141 | subj.FirstPendingInline = last.Next; 142 | 143 | if (last.Next != null) 144 | last.Next.Previous = first.Previous; 145 | else if (subj != null) 146 | subj.LastPendingInline = first.Previous; 147 | 148 | if (first == last) 149 | return; 150 | 151 | first = first.Next; 152 | last = last.Previous; 153 | } 154 | 155 | if (last == null || first == null) 156 | return; 157 | 158 | first.Previous = null; 159 | last.Next = null; 160 | 161 | // handle case like [*b*] (the whole [..] is being removed but the inner *..* must still be matched). 162 | // this is not done automatically because the initial * is recognized as a potential closer (assuming 163 | // potential scenario '*[*' ). 164 | if (curPriority > 0) 165 | PostProcessInlineStack(null, first, last, curPriority); 166 | } 167 | 168 | public static void PostProcessInlineStack(Subject subj, InlineStack first, InlineStack last, InlineStackPriority ignorePriority) 169 | { 170 | while (ignorePriority > 0) 171 | { 172 | var istack = first; 173 | while (istack != null) 174 | { 175 | if (istack.Priority >= ignorePriority) 176 | { 177 | RemoveStackEntry(istack, subj, istack); 178 | } 179 | else if (0 != (istack.Flags & InlineStackFlags.Closer)) 180 | { 181 | bool canClose; 182 | var iopener = FindMatchingOpener(istack.Previous, istack.Priority, istack.Delimiter, out canClose); 183 | if (iopener != null) 184 | { 185 | bool retry = false; 186 | if (iopener.Delimiter == '~') 187 | { 188 | InlineMethods.MatchInlineStack(iopener, subj, istack.DelimiterCount, istack, (InlineTag)0, InlineTag.Strikethrough); 189 | if (istack.DelimiterCount > 1) 190 | retry = true; 191 | } 192 | else 193 | { 194 | var useDelims = InlineMethods.MatchInlineStack(iopener, subj, istack.DelimiterCount, istack, InlineTag.Emphasis, InlineTag.Strong); 195 | if (istack.DelimiterCount > 0) 196 | retry = true; 197 | } 198 | 199 | if (retry) 200 | { 201 | // remove everything between opened and closer (not inclusive). 202 | if (istack.Previous != null && iopener.Next != istack.Previous) 203 | RemoveStackEntry(iopener.Next, subj, istack.Previous); 204 | 205 | continue; 206 | } 207 | else 208 | { 209 | // remove opener, everything in between, and the closer 210 | RemoveStackEntry(iopener, subj, istack); 211 | } 212 | } 213 | else if (!canClose) 214 | { 215 | // this case means that a matching opener does not exist 216 | // remove the Closer flag so that a future Opener can be matched against it. 217 | istack.Flags &= ~InlineStackFlags.Closer; 218 | } 219 | } 220 | 221 | if (istack == last) 222 | break; 223 | 224 | istack = istack.Next; 225 | } 226 | 227 | ignorePriority--; 228 | } 229 | } 230 | 231 | 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /CommonMark.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using CommonMark.Syntax; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | 7 | namespace CommonMark 8 | { 9 | class Program 10 | { 11 | static void PrintUsage(string invalidArg = null) 12 | { 13 | if (invalidArg != null) 14 | { 15 | Console.WriteLine("Invalid argument: " + invalidArg); 16 | Console.WriteLine(); 17 | } 18 | 19 | var fname = System.Diagnostics.Process.GetCurrentProcess().ProcessName; 20 | Console.WriteLine("Usage: " + fname + " [FILE*] [--out FILE]"); 21 | Console.WriteLine("Options: --help, -h Print usage information"); 22 | Console.WriteLine(" --ast Print AST instead of HTML"); 23 | Console.WriteLine(" --sourcepos Enable source position tracking and output"); 24 | Console.WriteLine(" --bench 20 Execute a benchmark on the given input, optionally"); 25 | Console.WriteLine(" specify the number of iterations, default is 20"); 26 | Console.WriteLine(" --perltest Changes console input handling for runtests.pl"); 27 | Console.WriteLine(" --version Print version"); 28 | Console.WriteLine(" --out The output file; if not specified, stdout is used"); 29 | } 30 | 31 | static int Main(string[] args) 32 | { 33 | var sources = new List(); 34 | var benchmark = false; 35 | var benchmarkIterations = 20; 36 | var target = Console.Out; 37 | var runPerlTests = false; 38 | var settings = CommonMarkSettings.Default.Clone(); 39 | 40 | try 41 | { 42 | for (var i = 0; i < args.Length; i++) 43 | { 44 | if (string.Equals(args[i], "--version", StringComparison.OrdinalIgnoreCase)) 45 | { 46 | Console.WriteLine("CommonMark.NET {0}", CommonMarkConverter.Version); 47 | Console.WriteLine(" - (c) 2014-2015 Kārlis Gaņģis"); 48 | return 0; 49 | } 50 | else if ((string.Equals(args[i], "--help", StringComparison.OrdinalIgnoreCase)) || 51 | (string.Equals(args[i], "-h", StringComparison.OrdinalIgnoreCase))) 52 | { 53 | PrintUsage(); 54 | return 0; 55 | } 56 | else if (string.Equals(args[i], "--perltest", StringComparison.OrdinalIgnoreCase)) 57 | { 58 | runPerlTests = true; 59 | } 60 | else if (string.Equals(args[i], "--ast", StringComparison.OrdinalIgnoreCase)) 61 | { 62 | settings.OutputFormat = OutputFormat.SyntaxTree; 63 | } 64 | else if (string.Equals(args[i], "--sourcepos", StringComparison.OrdinalIgnoreCase)) 65 | { 66 | settings.TrackSourcePosition = true; 67 | } 68 | else if (string.Equals(args[i], "--bench", StringComparison.OrdinalIgnoreCase)) 69 | { 70 | benchmark = true; 71 | if (i != args.Length - 1) 72 | { 73 | if (!int.TryParse(args[i + 1], System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out benchmarkIterations)) 74 | benchmarkIterations = 20; 75 | else 76 | i++; 77 | } 78 | } 79 | else if (string.Equals(args[i], "--out", StringComparison.OrdinalIgnoreCase)) 80 | { 81 | if (i == args.Length - 1 || args[i + 1].StartsWith("-")) 82 | { 83 | PrintUsage(); 84 | return 1; 85 | } 86 | 87 | i++; 88 | target = new System.IO.StreamWriter(args[i]); 89 | } 90 | else if (args[i].StartsWith("-")) 91 | { 92 | PrintUsage(args[i]); 93 | return 1; 94 | } 95 | else 96 | { 97 | // treat as file argument 98 | sources.Add(new System.IO.StreamReader(args[i])); 99 | } 100 | } 101 | 102 | if (sources.Count == 0) 103 | { 104 | if (runPerlTests) 105 | { 106 | Console.InputEncoding = Encoding.UTF8; 107 | Console.OutputEncoding = Encoding.UTF8; 108 | 109 | // runtests.pl writes directly to STDIN but will not send Ctrl+C and also 110 | // will get confused by the additional information written to STDOUT 111 | var sb = new StringBuilder(); 112 | while (Console.In.Peek() != -1) 113 | sb.AppendLine(Console.In.ReadLine()); 114 | 115 | sources.Add(new System.IO.StringReader(sb.ToString())); 116 | } 117 | else if (!Console.IsInputRedirected) 118 | { 119 | Console.InputEncoding = Encoding.Unicode; 120 | Console.OutputEncoding = Encoding.Unicode; 121 | 122 | Console.WriteLine("Enter the source. Press Enter after the last line and" + Environment.NewLine + "then press Ctrl+C to run parser."); 123 | Console.WriteLine(); 124 | 125 | Console.CancelKeyPress += (s, a) => 126 | { 127 | a.Cancel = true; 128 | Console.WriteLine("Output:"); 129 | Console.WriteLine(); 130 | }; 131 | 132 | sources.Add(Console.In); 133 | } 134 | else 135 | { 136 | Console.InputEncoding = Encoding.UTF8; 137 | Console.OutputEncoding = Encoding.UTF8; 138 | 139 | sources.Add(Console.In); 140 | } 141 | } 142 | 143 | if (benchmark) 144 | { 145 | Console.WriteLine("Running the benchmark..."); 146 | foreach (var source in sources) 147 | { 148 | // by using a in-memory source, the disparity of results is reduced. 149 | var data = source.ReadToEnd(); 150 | 151 | // in-memory source that gets reused further reduces the disparity. 152 | var builder = new StringBuilder(2 * 1024 * 1024); 153 | 154 | var sw = new System.Diagnostics.Stopwatch(); 155 | var mem = GC.GetTotalMemory(true); 156 | long mem2 = 0; 157 | 158 | for (var x = -1 - benchmarkIterations / 10; x < benchmarkIterations; x++) 159 | { 160 | if (x == 0) 161 | sw.Start(); 162 | 163 | builder.Length = 0; 164 | using (var reader = new System.IO.StringReader(data)) 165 | using (var twriter = new System.IO.StringWriter(builder)) 166 | CommonMarkConverter.Convert(reader, twriter, settings); 167 | 168 | if (mem2 == 0) 169 | mem2 = GC.GetTotalMemory(false); 170 | 171 | GC.Collect(); 172 | } 173 | 174 | sw.Stop(); 175 | target.WriteLine("Time spent: {0:0.#}ms Approximate memory usage: {1:0.000}MB", 176 | (decimal)sw.ElapsedMilliseconds / benchmarkIterations, 177 | (mem2 - mem) / 1024M / 1024M); 178 | } 179 | } 180 | else 181 | { 182 | foreach (var source in sources) 183 | CommonMarkConverter.Convert(source, target, settings); 184 | } 185 | 186 | if (System.Diagnostics.Debugger.IsAttached) 187 | { 188 | Console.WriteLine("Press any key to continue..."); 189 | Console.ReadKey(true); 190 | } 191 | 192 | return 0; 193 | } 194 | catch (Exception ex) 195 | { 196 | Console.Error.WriteLine(ex.ToString()); 197 | return -1; 198 | } 199 | finally 200 | { 201 | foreach (var s in sources) 202 | if (s != Console.In) 203 | s.Close(); 204 | 205 | if (target != Console.Out) 206 | target.Close(); 207 | } 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /CommonMark.Tests/Specification/Specs.tt: -------------------------------------------------------------------------------- 1 | <#/* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Morten Houston Ludvigsen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */#> 24 | <#@ template debug="false" hostspecific="true" language="C#" #> 25 | <#@ assembly name="System.Core" #> 26 | <#@ import namespace="System.Globalization" #> 27 | <#@ import namespace="System.IO" #> 28 | <#@ import namespace="System.Linq" #> 29 | <#@ import namespace="System.Text" #> 30 | <#@ import namespace="System.Text.RegularExpressions" #> 31 | <#@ import namespace="System.Collections.Generic" #> 32 | <#@ import namespace="System.CodeDom" #> 33 | <#@ import namespace="System.CodeDom.Compiler" #> 34 | <#@ output extension=".cs" #><# 35 | var specFile = "https://raw.githubusercontent.com/jgm/CommonMark/master/spec.txt"; 36 | var emptyLines = false; 37 | var displayEmptyLines = false; 38 | #> 39 | using System; 40 | using System.Collections.Generic; 41 | using System.Linq; 42 | using System.Text; 43 | using System.Text.RegularExpressions; 44 | using System.Threading.Tasks; 45 | using Microsoft.VisualStudio.TestTools.UnitTesting; 46 | 47 | namespace CommonMark.Tests.Specification 48 | { 49 | [TestClass] 50 | public class Specs 51 | { 52 | <# 53 | foreach (var item in ReadSpecs(specFile)) 54 | { 55 | if (item is string) 56 | { 57 | var line = (string)item; 58 | if (line == "") 59 | { 60 | emptyLines = true; 61 | } 62 | else 63 | { 64 | if (emptyLines && displayEmptyLines) 65 | { 66 | #> 67 | // 68 | <# 69 | } 70 | #> 71 | // <#= line #> 72 | <# 73 | displayEmptyLines = true; 74 | emptyLines = false; 75 | } 76 | } 77 | if (item is Spec) 78 | { 79 | var spec = (Spec)item; 80 | emptyLines = false; 81 | displayEmptyLines = false; 82 | #> 83 | [TestMethod] 84 | [TestCategory("<#= spec.SecHeading #>")] 85 | //[Timeout(1000)] 86 | public void <#= spec.Name #>() 87 | { 88 | // Example <#= spec.Example #> 89 | // Section: <#= spec.SecHeading #> 90 | // 91 | // The following CommonMark: 92 | <#= string.Join(Environment.NewLine, spec.CommonMark.Split('\n').Select(s => string.Format(" // {0}", s))) #> 93 | // 94 | // Should be rendered as: 95 | <#= string.Join(Environment.NewLine, spec.Html.Split('\n').Select(s => string.Format(" // {0}", s))) #> 96 | 97 | Helpers.Log("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, <#= spec.Example #>, "<#= Escape(spec.SecHeading) #>"); 98 | Helpers.ExecuteTest("<#= Escape(spec.CommonMark) #>", "<#= Escape(spec.Html) #>"); 99 | } 100 | 101 | <# 102 | } 103 | } 104 | #> 105 | } 106 | } 107 | <#+ 108 | private class Item 109 | { 110 | } 111 | 112 | private class Spec 113 | { 114 | public int Example { get; set; } 115 | public int Number { get; set; } 116 | public string SecHeading { get; set; } 117 | public string SectionId { get; set; } 118 | public string CommonMark { get; set; } 119 | public string Html { get; set; } 120 | 121 | public string Name 122 | { 123 | get { return string.Format("Example{0:000}", Example); } 124 | } 125 | } 126 | 127 | private bool Match(string value, string pattern, out string[] groups) 128 | { 129 | var match = Regex.Match(value, pattern); 130 | if (match.Success) 131 | { 132 | groups = match.Groups.Cast().Select(g => g.Value).ToArray(); 133 | return true; 134 | } 135 | groups = new string[] { }; 136 | return false; 137 | } 138 | 139 | private string GetSectionId(IEnumerable sectionHeadings) 140 | { 141 | var name = string.Join("", sectionHeadings.Select(s => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s))); 142 | name = Regex.Replace(name, @"\s+", ""); 143 | return name; 144 | } 145 | 146 | private IEnumerable ReadSpecs(string fileName) 147 | { 148 | var stage = 0; 149 | var commonMark = ""; 150 | var html = ""; 151 | var example = 0; 152 | var lineNum = 0; 153 | var exampleLine = 0; 154 | var sectionNums = new List(); 155 | var sectionHeadings = new Stack(); 156 | var sectionHeading = ""; 157 | var groups = new string[] { }; 158 | var line = ""; 159 | var sectionSpecNumbers = new Dictionary(); 160 | var endOfTests = false; 161 | 162 | using (var client = new System.Net.WebClient() { Encoding = Encoding.UTF8 }) 163 | using (var reader = new StringReader(client.DownloadString(fileName))) 164 | { 165 | while (!endOfTests && (line = reader.ReadLine()) != null) 166 | { 167 | lineNum += 1; 168 | if (line.StartsWith("````````````````````````````````") || line == ".") 169 | { 170 | stage = (stage + 1) % 3; 171 | if (stage == 1) 172 | { 173 | exampleLine = lineNum; 174 | } 175 | if (stage == 0) 176 | { 177 | var sectionId = GetSectionId(sectionHeadings.Reverse()); 178 | if (sectionSpecNumbers.ContainsKey(sectionId)) 179 | { 180 | sectionSpecNumbers[sectionId] += 1; 181 | } 182 | else 183 | { 184 | sectionSpecNumbers[sectionId] = 1; 185 | } 186 | 187 | example++; 188 | yield return new Spec 189 | { 190 | Example = example, 191 | Number = sectionSpecNumbers[sectionId], 192 | SecHeading = string.Join(" - ", sectionHeadings.Reverse()), 193 | SectionId = sectionId, 194 | CommonMark = commonMark, 195 | Html = html 196 | }; 197 | commonMark = ""; 198 | html = ""; 199 | } 200 | } 201 | else if (stage == 0 && line.StartsWith("")) 202 | { 203 | endOfTests = true; 204 | } 205 | else if (stage == 0 && Match(line, "^(#+) +(.*)", out groups)) 206 | { 207 | var sectionLevel = groups[1].Length; 208 | sectionHeading = groups[2]; 209 | 210 | while (sectionHeadings.Count() < sectionLevel) 211 | { 212 | sectionHeadings.Push(""); 213 | } 214 | while (sectionHeadings.Count() >= sectionLevel) 215 | { 216 | sectionHeadings.Pop(); 217 | } 218 | sectionHeadings.Push(sectionHeading); 219 | 220 | if (sectionNums.Count > 0 && sectionNums.Count == sectionLevel - 1) 221 | { 222 | sectionNums[sectionNums.Count - 1]++; 223 | } 224 | else if (sectionNums.Count > sectionLevel - 1) 225 | { 226 | sectionNums = Enumerable.Range(0, sectionLevel).ToList(); 227 | sectionNums[sectionNums.Count - 1]++; 228 | } 229 | else 230 | { 231 | while (sectionNums.Count < sectionLevel - 1) 232 | { 233 | sectionNums.Add(1); 234 | } 235 | } 236 | 237 | yield return line.Trim(); 238 | } 239 | else if (stage == 1) 240 | { 241 | if (commonMark.Length > 0) 242 | { 243 | commonMark += '\n'; 244 | } 245 | commonMark += line; 246 | } 247 | else if (stage == 2) 248 | { 249 | if (html.Length > 0) 250 | { 251 | html += '\n'; 252 | } 253 | html += line; 254 | } 255 | else 256 | { 257 | yield return line.Trim(); 258 | } 259 | } 260 | while ((line = reader.ReadLine()) != null) 261 | { 262 | yield return line.Trim(); 263 | } 264 | } 265 | } 266 | 267 | private static string Escape(string input) 268 | { 269 | return input 270 | .Replace("\\", "\\\\") 271 | .Replace("\"", "\\\"") 272 | .Replace("\0", "\\0") 273 | .Replace("\a", "\\a") 274 | .Replace("\b", "\\b") 275 | .Replace("\f", "\\f") 276 | .Replace("\n", "\\n") 277 | .Replace("\r", "\\r") 278 | .Replace("\t", "\\t") 279 | .Replace("\v", "\\v") 280 | ; 281 | } 282 | 283 | private static string ToLiteral(string input) 284 | { 285 | using (var writer = new StringWriter()) 286 | { 287 | using (var provider = CodeDomProvider.CreateProvider("CSharp")) 288 | { 289 | provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, null); 290 | return writer.ToString(); 291 | } 292 | } 293 | } 294 | #> -------------------------------------------------------------------------------- /CommonMark/Syntax/Block.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CommonMark.Syntax 5 | { 6 | /// 7 | /// Represents a block-level element of the parsed document. 8 | /// 9 | public sealed class Block 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The type of the element this instance represents. 15 | /// The position of the first character of this block in the source text. 16 | public Block(BlockTag tag, int sourcePosition) 17 | { 18 | this.Tag = tag; 19 | this.SourcePosition = sourcePosition; 20 | this.IsOpen = true; 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// The type of the element this instance represents. 27 | /// The number of the first line in the source text that contains this element. 28 | /// The number of the first column (within the first line) in the source text that contains this element. 29 | /// The position of the first character of this block in the source text. 30 | [Obsolete("StartLine/StartColumn are deprecated in favor of SourcePosition/SourceLength and will be removed in future. If you have a use case where this property cannot be replaced with the new ones, please log an issue at https://github.com/Knagis/CommonMark.NET", false)] 31 | public Block(BlockTag tag, int startLine, int startColumn, int sourcePosition) 32 | { 33 | this.Tag = tag; 34 | this.StartLine = startLine; 35 | this.EndLine = startLine; 36 | this.StartColumn = startColumn; 37 | this.SourcePosition = sourcePosition; 38 | this.IsOpen = true; 39 | } 40 | 41 | /// 42 | /// Returns an enumerable that allows the iteration over all block and inline elements within this 43 | /// instance. Note that the enumerator should not be used if optimal performance is desired and instead 44 | /// a custom implementation should be written. 45 | /// 46 | public IEnumerable AsEnumerable() 47 | { 48 | return new Enumerable(this); 49 | } 50 | 51 | /// 52 | /// Creates a new top-level document block. 53 | /// 54 | internal static Block CreateDocument() 55 | { 56 | #pragma warning disable 0618 57 | Block e = new Block(BlockTag.Document, 1, 1, 0); 58 | #pragma warning restore 0618 59 | e.Document = new DocumentData(); 60 | e.Document.ReferenceMap = new Dictionary(); 61 | e.Top = e; 62 | return e; 63 | } 64 | 65 | /// 66 | /// Gets or sets the type of the element this instance represents. 67 | /// 68 | public BlockTag Tag { get; set; } 69 | 70 | /// 71 | /// Gets or sets the type of the HTML block. Only applies when equals . 72 | /// 73 | public HtmlBlockType HtmlBlockType { get; set; } 74 | 75 | /// 76 | /// Gets or sets the number of the first line in the source text that contains this element. 77 | /// 78 | [Obsolete("This is deprecated in favor of SourcePosition/SourceLength and will be removed in future. If you have a use case where this property cannot be replaced with the new ones, please log an issue at https://github.com/Knagis/CommonMark.NET", false)] 79 | [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] 80 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 81 | public int StartLine { get; set; } 82 | 83 | /// 84 | /// Gets or sets the number of the first column (within the first line) in the source text that contains this element. 85 | /// 86 | [Obsolete("This is deprecated in favor of SourcePosition/SourceLength and will be removed in future. If you have a use case where this property cannot be replaced with the new ones, please log an issue at https://github.com/Knagis/CommonMark.NET", false)] 87 | [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] 88 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 89 | public int StartColumn { get; set; } 90 | 91 | /// 92 | /// Gets or sets the number of the last line in the source text that contains this element. 93 | /// 94 | [Obsolete("This is deprecated in favor of SourcePosition/SourceLength and will be removed in future. If you have a use case where this property cannot be replaced with the new ones, please log an issue at https://github.com/Knagis/CommonMark.NET", false)] 95 | [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] 96 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 97 | public int EndLine { get; set; } 98 | 99 | /// 100 | /// Gets or sets the position of the block element within the source data. This position is before 101 | /// any opening characters. must be enabled 102 | /// for this value to be defined. 103 | /// 104 | /// 105 | public int SourcePosition { get; set; } 106 | 107 | internal int SourceLastPosition { get; set; } 108 | 109 | /// 110 | /// Gets or sets the length of the block element within the source data. This includes also characters that 111 | /// close the block element and in most cases the newline characters right after the block element. 112 | /// must be enabled for this value to be defined. 113 | /// 114 | /// 115 | public int SourceLength 116 | { 117 | get { return this.SourceLastPosition - this.SourcePosition; } 118 | set { this.SourceLastPosition = this.SourcePosition + value; } 119 | } 120 | 121 | /// 122 | /// Gets or sets a value indicating whether this block element has been completed (and thus new lines cannot be added 123 | /// to it) or is still open. By default all elements are created as open and are closed when the parser detects it. 124 | /// 125 | public bool IsOpen { get; set; } 126 | 127 | /// 128 | /// Gets or sets a value indicating whether the last line parsed for this block element was blank (containing only 129 | /// whitespaces). 130 | /// 131 | public bool IsLastLineBlank { get; set; } 132 | 133 | /// 134 | /// Gets or sets the first child element of this instance. null if there are no children. 135 | /// 136 | public Block FirstChild { get; set; } 137 | 138 | /// 139 | /// Gets or sets the last child element (the last sibling of ) of this instance. 140 | /// null if there are no children. 141 | /// 142 | public Block LastChild { get; set; } 143 | 144 | /// 145 | /// Gets or sets the parent element of this block. 146 | /// 147 | public Block Parent { get; set; } 148 | 149 | /// 150 | /// Gets or sets the root element (that represents the document itself). 151 | /// 152 | public Block Top { get; set; } 153 | 154 | /// 155 | /// Gets or sets the string content of this block. The content consists of multiple string parts to avoid string concatenation. 156 | /// Note that some parts of the parser (for example, ) might assume that 157 | /// the parts are not split within certain objects, so it is advised that the parts are split on newline. 158 | /// 159 | public StringContent StringContent { get; set; } 160 | 161 | /// 162 | /// Gets or sets the first inline element that was parsed from property. 163 | /// Note that once the inlines are parsed, will be set to null. 164 | /// 165 | public Inline InlineContent { get; set; } 166 | 167 | /// 168 | /// Gets or sets the additional properties that apply to list elements. 169 | /// 170 | public ListData ListData { get; set; } 171 | 172 | /// 173 | /// Gets or sets the additional properties that apply to fenced code blocks. 174 | /// 175 | public FencedCodeData FencedCodeData { get; set; } 176 | 177 | /// 178 | /// Gets or sets the additional properties that apply to heading elements. 179 | /// 180 | public HeadingData Heading { get; set; } 181 | 182 | /// 183 | /// Gets or sets the heading level (as in <h1> or <h2>). 184 | /// 185 | [Obsolete("Use " + nameof(Heading) + " instead.")] 186 | public int HeaderLevel 187 | { 188 | get { return Heading.Level; } 189 | set { Heading = new HeadingData(value); } 190 | } 191 | 192 | /// 193 | /// Gets or sets the additional properties that apply to document nodes. 194 | /// 195 | public DocumentData Document { get; set; } 196 | 197 | /// 198 | /// Obsolete. Use instead. 199 | /// 200 | [Obsolete("Use " + nameof(Document) + " instead.")] 201 | public Dictionary ReferenceMap 202 | { 203 | get { return Document?.ReferenceMap; } 204 | set 205 | { 206 | if (Document == null) 207 | { 208 | if (value == null) 209 | { 210 | return; 211 | } 212 | Document = new DocumentData(); 213 | } 214 | Document.ReferenceMap = value; 215 | } 216 | } 217 | 218 | /// 219 | /// Gets or sets the next sibling of this block element. null if this is the last element. 220 | /// 221 | public Block NextSibling { get; set; } 222 | 223 | /// 224 | /// Gets or sets the previous sibling of this block element. null if this is the first element. 225 | /// 226 | [Obsolete("This property will be removed in future. If you have a use case where this property is required, please log an issue at https://github.com/Knagis/CommonMark.NET", false)] 227 | [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)] 228 | [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] 229 | public Block Previous { get; set; } 230 | } 231 | } 232 | --------------------------------------------------------------------------------