mirror of
https://github.com/xoofx/markdig.git
synced 2026-02-12 13:54:45 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
446b1bcc0d | ||
|
|
ec7a4a6902 | ||
|
|
07a77142f4 | ||
|
|
3606f234b8 | ||
|
|
616eed62bd | ||
|
|
99f55e9ddc | ||
|
|
f8ab1cccc5 | ||
|
|
f24067cd16 | ||
|
|
9af96ba2b4 | ||
|
|
c99f7dd96a | ||
|
|
bf28cbd33f | ||
|
|
2040e23545 | ||
|
|
2761e36b6b | ||
|
|
891334134c | ||
|
|
0987fab6f2 | ||
|
|
ed5eea5e27 | ||
|
|
f73cbe4e76 | ||
|
|
aefad219cf | ||
|
|
76c3e88c58 | ||
|
|
afe4308e91 | ||
|
|
606556b692 | ||
|
|
253be5c362 | ||
|
|
33037d1034 | ||
|
|
f16ee828db | ||
|
|
a76305f39b | ||
|
|
f52a41e167 | ||
|
|
c818670919 | ||
|
|
a78a0b7016 | ||
|
|
6a0c9aeb47 | ||
|
|
b1cfcf2431 | ||
|
|
b411522a23 | ||
|
|
1d2977d47b | ||
|
|
ee8c87c357 | ||
|
|
25959174d5 | ||
|
|
033ddaf6a8 | ||
|
|
f3f7584c39 | ||
|
|
d00ca4acc1 | ||
|
|
d9663ef2e6 | ||
|
|
3106a49d02 | ||
|
|
a47a6890e7 | ||
|
|
7d21f8b003 | ||
|
|
15f6205adc | ||
|
|
e6dd2cf3d4 | ||
|
|
ea6592b773 | ||
|
|
5cd20efe3e | ||
|
|
31c7ba5862 | ||
|
|
0272840a62 | ||
|
|
f879d55b4a | ||
|
|
6d3a3584ac |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [xoofx]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
37
appveyor.yml
37
appveyor.yml
@@ -1,11 +1,15 @@
|
||||
version: 10.0.{build}
|
||||
image: Visual Studio 2017
|
||||
image: Visual Studio 2019
|
||||
configuration: Release
|
||||
|
||||
environment:
|
||||
COVERALLS_REPO_TOKEN:
|
||||
secure: /SEtLgIE6ZrJaWBC1xFZIeESiwfwiXEk9N4pSJ53rFhqBZC2sXJg7ZxZ1DBhnZGu
|
||||
install:
|
||||
- ps: >-
|
||||
cd src
|
||||
|
||||
dotnet tool install -g coveralls.net --version 1.0.0
|
||||
|
||||
nuget restore Markdig.sln
|
||||
|
||||
$env:MARKDIG_BUILD_NUMBER = ([int]$env:APPVEYOR_BUILD_NUMBER).ToString("000")
|
||||
@@ -31,22 +35,35 @@ build:
|
||||
project: src/Markdig.sln
|
||||
verbosity: minimal
|
||||
|
||||
after_build:
|
||||
- cmd: >-
|
||||
dotnet run --project SpecFileGen/SpecFileGen.csproj -c Release
|
||||
after_build: >
|
||||
dotnet SpecFileGen/bin/Release/netcoreapp2.2/SpecFileGen.dll
|
||||
|
||||
dotnet test -v n Markdig.Tests
|
||||
test_script:
|
||||
- cmd: >-
|
||||
dotnet test Markdig.Tests -c Release --no-build
|
||||
|
||||
dotnet test Markdig.Tests -c Debug
|
||||
|
||||
dotnet test Markdig.Tests -c Release -f netcoreapp2.1 /p:CollectCoverage=true /p:Include=\"[Markdig]*\" /p:CoverletOutputFormat=opencover /p:CoverletOutput=../../coverage.xml
|
||||
|
||||
after_test:
|
||||
- ps: >-
|
||||
if($env:APPVEYOR_REPO_BRANCH -eq "master") {
|
||||
cd ..
|
||||
if (Test-Path "./coverage.xml") {
|
||||
csmacnz.Coveralls --opencover -i "./coverage.xml" --repoToken $env:COVERALLS_REPO_TOKEN --basePath "$env:APPVEYOR_BUILD_FOLDER" --useRelativePath --commitId $env:APPVEYOR_REPO_COMMIT --commitBranch $env:APPVEYOR_REPO_BRANCH --commitAuthor $env:APPVEYOR_REPO_COMMIT_AUTHOR --commitEmail $env:APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL --commitMessage $env:APPVEYOR_REPO_COMMIT_MESSAGE --jobId $env:APPVEYOR_BUILD_NUMBER --serviceName appveyor
|
||||
}
|
||||
cd src
|
||||
}
|
||||
|
||||
before_package:
|
||||
- cmd: >-
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:Clean Markdig/Markdig.csproj
|
||||
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release;SignAssembly=true Markdig/Markdig.csproj
|
||||
msbuild /t:pack /p:VersionSuffix="%MARKDIG_VERSION_SUFFIX%" /p:Configuration=Release Markdig.Signed/Markdig.Signed.csproj
|
||||
|
||||
artifacts:
|
||||
- path: src\Markdig\Bin\Release\*.nupkg
|
||||
- path: src\**\*.nupkg
|
||||
name: Markdig Nugets
|
||||
|
||||
deploy:
|
||||
|
||||
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## 0.18.0 (24 Oct 2019)
|
||||
- Ignore backslashes in GFM AutoLinks ([(PR #357)](https://github.com/lunet-io/markdig/pull/357))
|
||||
- Fix SmartyPants quote matching ([(PR #360)](https://github.com/lunet-io/markdig/pull/360))
|
||||
- Fix generic attributes with values of length 1 ([(PR #361)](https://github.com/lunet-io/markdig/pull/361))
|
||||
- Fix link text balanced bracket matching ([(PR #375)](https://github.com/lunet-io/markdig/pull/375))
|
||||
- Improve overall performance and substantially reduce allocations ([(PR #377)](https://github.com/lunet-io/markdig/pull/377))
|
||||
|
||||
## 0.17.1 (04 July 2019)
|
||||
- Fix regression when escaping HTML characters ([(PR #340)](https://github.com/lunet-io/markdig/pull/340))
|
||||
- Update Emoji Dictionary ([(PR #346)](https://github.com/lunet-io/markdig/pull/346))
|
||||
|
||||
## 0.17.0 (10 May 2019)
|
||||
- Update to latest CommonMark specs 0.29 ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
|
||||
- Add `AutoLinkOptions` with `OpenInNewWindow`, `UseHttpsForWWWLinks` ([(PR #327)](https://github.com/lunet-io/markdig/pull/327))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
# Markdig [](https://ci.appveyor.com/project/xoofx/markdig) [](https://coveralls.io/github/lunet-io/markdig?branch=master) [](https://www.nuget.org/packages/Markdig/) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FRGHXBTP442JL)
|
||||
|
||||
<img align="right" width="160px" height="160px" src="img/markdig.png">
|
||||
|
||||
|
||||
@@ -1,43 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6A19F040-BC7C-4283-873A-177B5324F1ED}</ProjectGuid>
|
||||
<TargetFrameworks>net471</TargetFrameworks>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Markdig.Benchmarks</RootNamespace>
|
||||
<AssemblyName>Markdig.Benchmarks</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<CopyNuGetImplementations>true</CopyNuGetImplementations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="spec.md" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="CommonMarkNew, Version=0.1.0.0, Culture=neutral, PublicKeyToken=001ef8810438905d, processorArchitecture=MSIL">
|
||||
<HintPath>lib\CommonMarkNew.dll</HintPath>
|
||||
@@ -45,31 +14,12 @@
|
||||
<Aliases>newcmark</Aliases>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build" />
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="MoonShine">
|
||||
<HintPath>lib\MoonShine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MarkdownDeep">
|
||||
<HintPath>lib\MarkdownDeep.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CommonMarkLib.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestMatchPerf.cs" />
|
||||
<Compile Include="TestStringPerf.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="cmark.dll">
|
||||
@@ -83,21 +33,16 @@
|
||||
<Content Include="spec.md">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<None Include="app.config" />
|
||||
<None Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Markdig">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\Markdig\Bin\$(Configuration)\net40\Markdig.dll</HintPath>
|
||||
</Reference>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.10.6" />
|
||||
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.10.6" />
|
||||
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
|
||||
<PackageReference Include="MarkdownSharp" Version="1.13.0.0" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Runtime" Version="0.8.31-beta" />
|
||||
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="1.0.41.0" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Markdig\Markdig.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Testamina.Markdig.Benchmarks")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Testamina.Markdig.Benchmarks")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("6a19f040-bc7c-4283-873a-177b5324f1ed")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"CLASSIC": {
|
||||
"executablePath": "Testamina.Markdig.Benchmarks.dll",
|
||||
"workingDirectory": "..\\..\\artifacts\\bin\\Testamina.Markdig.Benchmarks\\Debug\\net45"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"runtimes": {
|
||||
"win-x86": {},
|
||||
"win-x64": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net46": {
|
||||
"compilationOptions": {
|
||||
"define": [
|
||||
"CLASSIC"
|
||||
]
|
||||
},
|
||||
"frameworkAssemblies": {
|
||||
"Microsoft.Build": "4.0.0.0",
|
||||
"Microsoft.Build.Framework": "4.0.0.0",
|
||||
"Microsoft.Build.Utilities.v4.0": "4.0.0.0",
|
||||
"System.Management": "4.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"BenchmarkDotNet": "0.10.6",
|
||||
"BenchmarkDotNet.Diagnostics.Windows": "0.10.6",
|
||||
"CommonMark.NET": "0.15.1",
|
||||
"MarkdownSharp": "1.13.0.0",
|
||||
"Microsoft.Diagnostics.Runtime": "0.8.31-beta",
|
||||
"Microsoft.Diagnostics.Tracing.TraceEvent": "1.0.41.0"
|
||||
}
|
||||
}
|
||||
13
src/Markdig.Signed/Markdig.Signed.csproj
Normal file
13
src/Markdig.Signed/Markdig.Signed.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<PackageId>Markdig.Signed</PackageId>
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Markdig\**\*.cs" Exclude="..\Markdig\obj\**;..\Markdig\bin\**">
|
||||
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<Import Project="..\Markdig\Markdig.targets" />
|
||||
</Project>
|
||||
@@ -6,8 +6,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.msbuild" Version="2.6.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
|
||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Markdig.Extensions.AutoLinks;
|
||||
using NUnit.Framework;
|
||||
@@ -8,6 +9,63 @@ namespace Markdig.Tests
|
||||
{
|
||||
public class MiscTests
|
||||
{
|
||||
[TestCase("link [foo [bar]]")] // https://spec.commonmark.org/0.29/#example-508
|
||||
[TestCase("link [foo][bar]")]
|
||||
[TestCase("link [][foo][bar][]")]
|
||||
[TestCase("link [][foo][bar][[]]")]
|
||||
[TestCase("link [foo] [bar]")]
|
||||
[TestCase("link [[foo] [] [bar] [[abc]def]]")]
|
||||
[TestCase("[]")]
|
||||
[TestCase("[ ]")]
|
||||
[TestCase("[bar][]")]
|
||||
[TestCase("[bar][ foo]")]
|
||||
[TestCase("[bar][foo ][]")]
|
||||
[TestCase("[bar][fo[ ]o ][][]")]
|
||||
[TestCase("[a]b[c[d[e]f]g]h")]
|
||||
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o")]
|
||||
[TestCase("a[b[c[d]e]f[g]h]i[] [][foo][bar][] foo [j]k[l[m]n]o")]
|
||||
[TestCase("a[b[c[d]e]f[g]h]i foo [j]k[l[m]n]o[][]")]
|
||||
public void LinkTextMayContainBalancedBrackets(string linkText)
|
||||
{
|
||||
string markdown = $"[{linkText}](/uri)";
|
||||
string expected = $@"<p><a href=""/uri"">{linkText}</a></p>";
|
||||
|
||||
TestParser.TestSpec(markdown, expected);
|
||||
|
||||
// Make the link text unbalanced
|
||||
foreach (var bracketIndex in linkText
|
||||
.Select((c, i) => new Tuple<char, int>(c, i))
|
||||
.Where(t => t.Item1 == '[' || t.Item1 == ']')
|
||||
.Select(t => t.Item2))
|
||||
{
|
||||
string brokenLinkText = linkText.Remove(bracketIndex, 1);
|
||||
|
||||
markdown = $"[{brokenLinkText}](/uri)";
|
||||
expected = $@"<p><a href=""/uri"">{brokenLinkText}</a></p>";
|
||||
|
||||
string actual = Markdown.ToHtml(markdown);
|
||||
Assert.AreNotEqual(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsIssue356Corrected()
|
||||
{
|
||||
string input = @"https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97";
|
||||
string expected = @"<p><a href=""https://foo.bar/path/%5C#m4mv5W0GYKZpGvfA.97"">https://foo.bar/path/\#m4mv5W0GYKZpGvfA.97</a></p>";
|
||||
|
||||
TestParser.TestSpec($"<{input}>", expected);
|
||||
TestParser.TestSpec(input, expected, "autolinks|advanced");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAltTextIsCorrectlyEscaped()
|
||||
{
|
||||
TestParser.TestSpec(
|
||||
@"",
|
||||
@"<p><img src=""girl.png"" alt=""This is image alt text with quotation ' and double quotation "hello" world"" /></p>");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangelogPRLinksMatchDescription()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-05 16:06:14
|
||||
// Generated: 2019-08-01 13:57:17
|
||||
|
||||
// --------------------------------
|
||||
// Generic Attributes
|
||||
@@ -79,5 +79,28 @@ namespace Markdig.Tests.Specs.GenericAttributes
|
||||
Console.WriteLine("Example 2\nSection Extensions / Generic Attributes\n");
|
||||
TestParser.TestSpec("{#fenced-id .fenced-class}\n~~~\nThis is a fenced with attached attributes\n~~~ ", "<pre><code id=\"fenced-id\" class=\"fenced-class\">This is a fenced with attached attributes\n</code></pre>", "attributes|advanced");
|
||||
}
|
||||
|
||||
// Attribute values can be one character long
|
||||
[Test]
|
||||
public void ExtensionsGenericAttributes_Example003()
|
||||
{
|
||||
// Example 3
|
||||
// Section: Extensions / Generic Attributes
|
||||
//
|
||||
// The following Markdown:
|
||||
// [Foo](url){data-x=1}
|
||||
//
|
||||
// [Foo](url){data-x='1'}
|
||||
//
|
||||
// [Foo](url){data-x=11}
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p><a href="url" data-x="1">Foo</a></p>
|
||||
// <p><a href="url" data-x="1">Foo</a></p>
|
||||
// <p><a href="url" data-x="11">Foo</a></p>
|
||||
|
||||
Console.WriteLine("Example 3\nSection Extensions / Generic Attributes\n");
|
||||
TestParser.TestSpec("[Foo](url){data-x=1}\n\n[Foo](url){data-x='1'}\n\n[Foo](url){data-x=11}", "<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"1\">Foo</a></p>\n<p><a href=\"url\" data-x=\"11\">Foo</a></p>", "attributes|advanced");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,3 +47,17 @@ This is a fenced with attached attributes
|
||||
<pre><code id="fenced-id" class="fenced-class">This is a fenced with attached attributes
|
||||
</code></pre>
|
||||
````````````````````````````````
|
||||
|
||||
Attribute values can be one character long
|
||||
|
||||
```````````````````````````````` example
|
||||
[Foo](url){data-x=1}
|
||||
|
||||
[Foo](url){data-x='1'}
|
||||
|
||||
[Foo](url){data-x=11}
|
||||
.
|
||||
<p><a href="url" data-x="1">Foo</a></p>
|
||||
<p><a href="url" data-x="1">Foo</a></p>
|
||||
<p><a href="url" data-x="11">Foo</a></p>
|
||||
````````````````````````````````
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated: 2019-04-05 16:06:14
|
||||
// Generated: 2019-08-01 12:33:23
|
||||
|
||||
// --------------------------------
|
||||
// Smarty Pants
|
||||
@@ -140,13 +140,13 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// This is a 'text <<with' a another text>>
|
||||
// This is 'a "text 'with" a another text'
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a ‘text <<with’ a another text>></p>
|
||||
// <p>This is ‘a “text 'with” a another text’</p>
|
||||
|
||||
Console.WriteLine("Example 8\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is a 'text <<with' a another text>>", "<p>This is a ‘text <<with’ a another text>></p>", "pipetables+smartypants|advanced+smartypants");
|
||||
TestParser.TestSpec("This is 'a \"text 'with\" a another text'", "<p>This is ‘a “text 'with” a another text’</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -156,20 +156,36 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// This is a 'text <<with' a another text>>
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a ‘text <<with’ a another text>></p>
|
||||
|
||||
Console.WriteLine("Example 9\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is a 'text <<with' a another text>>", "<p>This is a ‘text <<with’ a another text>></p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example010()
|
||||
{
|
||||
// Example 10
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// This is a <<text 'with>> a another text'
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>This is a «text 'with» a another text'</p>
|
||||
|
||||
Console.WriteLine("Example 9\nSection Extensions / SmartyPants Quotes\n");
|
||||
Console.WriteLine("Example 10\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is a <<text 'with>> a another text'", "<p>This is a «text 'with» a another text'</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Quotes requires to have the same rules than emphasis `_` regarding left/right frankling rules:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example010()
|
||||
public void ExtensionsSmartyPantsQuotes_Example011()
|
||||
{
|
||||
// Example 10
|
||||
// Example 11
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -178,24 +194,8 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>It's not quotes'</p>
|
||||
|
||||
Console.WriteLine("Example 10\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("It's not quotes'", "<p>It's not quotes'</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example011()
|
||||
{
|
||||
// Example 11
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// They are ' not matching quotes '
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>They are ' not matching quotes '</p>
|
||||
|
||||
Console.WriteLine("Example 11\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("They are ' not matching quotes '", "<p>They are ' not matching quotes '</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
TestParser.TestSpec("It's not quotes'", "<p>It's not quotes'</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -205,20 +205,36 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// They are ' not matching quotes '
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>They are ' not matching quotes '</p>
|
||||
|
||||
Console.WriteLine("Example 12\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("They are ' not matching quotes '", "<p>They are ' not matching quotes '</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example013()
|
||||
{
|
||||
// Example 13
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// They are' not matching 'quotes
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>They are' not matching 'quotes</p>
|
||||
|
||||
Console.WriteLine("Example 12\nSection Extensions / SmartyPants Quotes\n");
|
||||
Console.WriteLine("Example 13\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("They are' not matching 'quotes", "<p>They are' not matching 'quotes</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// An emphasis starting inside left/right quotes will span over the right quote:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example013()
|
||||
public void ExtensionsSmartyPantsQuotes_Example014()
|
||||
{
|
||||
// Example 13
|
||||
// Example 14
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -227,9 +243,26 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is “a <em>text” with an emphasis</em></p>
|
||||
|
||||
Console.WriteLine("Example 13\nSection Extensions / SmartyPants Quotes\n");
|
||||
Console.WriteLine("Example 14\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("This is \"a *text\" with an emphasis*", "<p>This is “a <em>text” with an emphasis</em></p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Multiple sets of quotes can be used
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsQuotes_Example015()
|
||||
{
|
||||
// Example 15
|
||||
// Section: Extensions / SmartyPants Quotes
|
||||
//
|
||||
// The following Markdown:
|
||||
// "aaa" "bbb" "ccc" "ddd"
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p>“aaa” “bbb” “ccc” “ddd”</p>
|
||||
|
||||
Console.WriteLine("Example 15\nSection Extensions / SmartyPants Quotes\n");
|
||||
TestParser.TestSpec("\"aaa\" \"bbb\" \"ccc\" \"ddd\"", "<p>“aaa” “bbb” “ccc” “ddd”</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
@@ -237,9 +270,9 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
{
|
||||
// ## SmartyPants Separators
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example014()
|
||||
public void ExtensionsSmartyPantsSeparators_Example016()
|
||||
{
|
||||
// Example 14
|
||||
// Example 16
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -248,14 +281,14 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is a – text</p>
|
||||
|
||||
Console.WriteLine("Example 14\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 16\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("This is a -- text", "<p>This is a – text</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example015()
|
||||
public void ExtensionsSmartyPantsSeparators_Example017()
|
||||
{
|
||||
// Example 15
|
||||
// Example 17
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -264,14 +297,14 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is a — text</p>
|
||||
|
||||
Console.WriteLine("Example 15\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 17\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("This is a --- text", "<p>This is a — text</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example016()
|
||||
public void ExtensionsSmartyPantsSeparators_Example018()
|
||||
{
|
||||
// Example 16
|
||||
// Example 18
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -280,15 +313,15 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>This is a en ellipsis…</p>
|
||||
|
||||
Console.WriteLine("Example 16\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 18\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("This is a en ellipsis...", "<p>This is a en ellipsis…</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Check that a smartypants are not breaking pipetable parsing:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example017()
|
||||
public void ExtensionsSmartyPantsSeparators_Example019()
|
||||
{
|
||||
// Example 17
|
||||
// Example 19
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -312,15 +345,15 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
Console.WriteLine("Example 17\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 19\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("a | b\n-- | --\n0 | 1", "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>0</td>\n<td>1</td>\n</tr>\n</tbody>\n</table>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
|
||||
// Check quotes and dash:
|
||||
[Test]
|
||||
public void ExtensionsSmartyPantsSeparators_Example018()
|
||||
public void ExtensionsSmartyPantsSeparators_Example020()
|
||||
{
|
||||
// Example 18
|
||||
// Example 20
|
||||
// Section: Extensions / SmartyPants Separators
|
||||
//
|
||||
// The following Markdown:
|
||||
@@ -329,7 +362,7 @@ namespace Markdig.Tests.Specs.SmartyPants
|
||||
// Should be rendered as:
|
||||
// <p>A “quote” with a —</p>
|
||||
|
||||
Console.WriteLine("Example 18\nSection Extensions / SmartyPants Separators\n");
|
||||
Console.WriteLine("Example 20\nSection Extensions / SmartyPants Separators\n");
|
||||
TestParser.TestSpec("A \"quote\" with a ---", "<p>A “quote” with a —</p>", "pipetables+smartypants|advanced+smartypants");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ This is a "text 'with" a another text'
|
||||
<p>This is a “text 'with” a another text'</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is 'a "text 'with" a another text'
|
||||
.
|
||||
<p>This is ‘a “text 'with” a another text’</p>
|
||||
````````````````````````````````
|
||||
|
||||
```````````````````````````````` example
|
||||
This is a 'text <<with' a another text>>
|
||||
.
|
||||
@@ -91,6 +97,14 @@ This is "a *text" with an emphasis*
|
||||
<p>This is “a <em>text” with an emphasis</em></p>
|
||||
````````````````````````````````
|
||||
|
||||
Multiple sets of quotes can be used
|
||||
|
||||
```````````````````````````````` example
|
||||
"aaa" "bbb" "ccc" "ddd"
|
||||
.
|
||||
<p>“aaa” “bbb” “ccc” “ddd”</p>
|
||||
````````````````````````````````
|
||||
|
||||
## SmartyPants Separators
|
||||
|
||||
```````````````````````````````` example
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace Markdig.Tests
|
||||
|
||||
// If file creation times aren't preserved by git, add some leeway
|
||||
// If specs have come from git, assume that they were regenerated since CI would fail otherwise
|
||||
testTime = testTime.AddSeconds(2);
|
||||
testTime = testTime.AddMinutes(3);
|
||||
|
||||
// This might not catch a changed spec every time, but should most of the time. Otherwise CI will catch it
|
||||
// This might not catch a changed spec every time, but should at least sometimes. Otherwise CI will catch it
|
||||
|
||||
// This could also trigger, if a user has modified the spec file but reverted the change - can't think of a good workaround
|
||||
Assert.Less(specTime, testTime,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Markdig.Helpers;
|
||||
@@ -81,8 +81,18 @@ namespace Markdig.Tests
|
||||
public void TestSkipWhitespaces()
|
||||
{
|
||||
var text = new StringLineGroup(" ABC").ToCharIterator();
|
||||
Assert.True(text.TrimStart());
|
||||
Assert.False(text.TrimStart());
|
||||
Assert.AreEqual('A', text.CurrentChar);
|
||||
|
||||
text = new StringLineGroup(" ").ToCharIterator();
|
||||
Assert.True(text.TrimStart());
|
||||
Assert.AreEqual('\0', text.CurrentChar);
|
||||
|
||||
var slice = new StringSlice(" ABC");
|
||||
Assert.False(slice.TrimStart());
|
||||
|
||||
slice = new StringSlice(" ");
|
||||
Assert.True(slice.TrimStart());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
{
|
||||
private const string AutoIdentifierKey = "AutoIdentifier";
|
||||
private readonly AutoIdentifierOptions options;
|
||||
private readonly StripRendererCache rendererCache = new StripRendererCache();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AutoIdentifierExtension"/> class.
|
||||
@@ -159,17 +160,11 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
}
|
||||
|
||||
// Use internally a HtmlRenderer to strip links from a heading
|
||||
var headingWriter = new StringWriter();
|
||||
var stripRenderer = new HtmlRenderer(headingWriter)
|
||||
{
|
||||
// Set to false both to avoid having any HTML tags in the output
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false
|
||||
};
|
||||
var stripRenderer = rendererCache.Get();
|
||||
|
||||
stripRenderer.Render(headingBlock.Inline);
|
||||
var headingText = headingWriter.ToString();
|
||||
headingWriter.GetStringBuilder().Length = 0;
|
||||
var headingText = stripRenderer.Writer.ToString();
|
||||
rendererCache.Release(stripRenderer);
|
||||
|
||||
// Urilize the link
|
||||
headingText = (options & AutoIdentifierOptions.GitHub) != 0
|
||||
@@ -195,5 +190,25 @@ namespace Markdig.Extensions.AutoIdentifiers
|
||||
|
||||
attributes.Id = headingId;
|
||||
}
|
||||
|
||||
private sealed class StripRendererCache : ObjectCache<HtmlRenderer>
|
||||
{
|
||||
protected override HtmlRenderer NewInstance()
|
||||
{
|
||||
var headingWriter = new StringWriter();
|
||||
var stripRenderer = new HtmlRenderer(headingWriter)
|
||||
{
|
||||
// Set to false both to avoid having any HTML tags in the output
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false
|
||||
};
|
||||
return stripRenderer;
|
||||
}
|
||||
|
||||
protected override void Reset(HtmlRenderer instance)
|
||||
{
|
||||
instance.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
/// <summary>
|
||||
/// The inline parser used to for autolinks.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
/// <seealso cref="InlineParser" />
|
||||
public class AutoLinkParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
@@ -30,10 +30,14 @@ namespace Markdig.Extensions.AutoLinks
|
||||
'f', // for ftp://
|
||||
'm', // for mailto:
|
||||
'w', // for www.
|
||||
};
|
||||
}
|
||||
|
||||
public readonly AutoLinkOptions Options;
|
||||
};
|
||||
|
||||
_listOfCharCache = new ListOfCharCache();
|
||||
}
|
||||
|
||||
public readonly AutoLinkOptions Options;
|
||||
|
||||
private readonly ListOfCharCache _listOfCharCache;
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
@@ -44,159 +48,162 @@ namespace Markdig.Extensions.AutoLinks
|
||||
return false;
|
||||
}
|
||||
|
||||
List<char> pendingEmphasis;
|
||||
// Check that an autolink is possible in the current context
|
||||
if (!IsAutoLinkValidInCurrentContext(processor, out pendingEmphasis))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
int domainOffset = 0;
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
// Precheck URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (slice.MatchLowercase("ttp://", 1))
|
||||
{
|
||||
domainOffset = 7; // http://
|
||||
}
|
||||
else if (slice.MatchLowercase("ttps://", 1))
|
||||
{
|
||||
domainOffset = 8; // https://
|
||||
}
|
||||
else return false;
|
||||
break;
|
||||
case 'f':
|
||||
if (!slice.MatchLowercase("tp://", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 6; // ftp://
|
||||
break;
|
||||
case 'm':
|
||||
if (!slice.MatchLowercase("ailto:", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 4; // www.
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
string link;
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out link, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
|
||||
if (pendingEmphasis != null)
|
||||
{
|
||||
for (int i = link.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pendingEmphasis.Contains(link[i]))
|
||||
{
|
||||
slice.Start--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < link.Length - 1)
|
||||
{
|
||||
link = link.Substring(0, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-check URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
int atIndex = link.IndexOf('@');
|
||||
if (atIndex == -1 ||
|
||||
atIndex == 7) // mailto:@ - no email part
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = atIndex + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!LinkHelper.IsValidDomain(link, domainOffset))
|
||||
List<char> pendingEmphasis = _listOfCharCache.Get();
|
||||
try
|
||||
{
|
||||
return false;
|
||||
// Check that an autolink is possible in the current context
|
||||
if (!IsAutoLinkValidInCurrentContext(processor, pendingEmphasis))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startPosition = slice.Start;
|
||||
int domainOffset = 0;
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
// Precheck URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (slice.MatchLowercase("ttp://", 1))
|
||||
{
|
||||
domainOffset = 7; // http://
|
||||
}
|
||||
else if (slice.MatchLowercase("ttps://", 1))
|
||||
{
|
||||
domainOffset = 8; // https://
|
||||
}
|
||||
else return false;
|
||||
break;
|
||||
case 'f':
|
||||
if (!slice.MatchLowercase("tp://", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 6; // ftp://
|
||||
break;
|
||||
case 'm':
|
||||
if (!slice.MatchLowercase("ailto:", 1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
if (!slice.MatchLowercase("ww.", 1)) // We won't match http:/www. or /www.xxx
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = 4; // www.
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse URL
|
||||
if (!LinkHelper.TryParseUrl(ref slice, out string link, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// If we have any pending emphasis, remove any pending emphasis characters from the end of the link
|
||||
if (pendingEmphasis.Count > 0)
|
||||
{
|
||||
for (int i = link.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pendingEmphasis.Contains(link[i]))
|
||||
{
|
||||
slice.Start--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i < link.Length - 1)
|
||||
{
|
||||
link = link.Substring(0, i + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-check URL
|
||||
switch (c)
|
||||
{
|
||||
case 'h':
|
||||
if (string.Equals(link, "http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(link, "https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
if (string.Equals(link, "ftp://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
int atIndex = link.IndexOf('@');
|
||||
if (atIndex == -1 ||
|
||||
atIndex == 7) // mailto:@ - no email part
|
||||
{
|
||||
return false;
|
||||
}
|
||||
domainOffset = atIndex + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!LinkHelper.IsValidDomain(link, domainOffset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var inline = new LinkInline()
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
|
||||
IsClosed = true,
|
||||
IsAutoLink = true,
|
||||
};
|
||||
|
||||
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
|
||||
|
||||
inline.Span.End = inline.Span.Start + link.Length - 1;
|
||||
inline.UrlSpan = inline.Span;
|
||||
inline.AppendChild(new LiteralInline()
|
||||
{
|
||||
Span = inline.Span,
|
||||
Line = line,
|
||||
Column = column,
|
||||
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
|
||||
IsClosed = true
|
||||
});
|
||||
processor.Inline = inline;
|
||||
|
||||
if (Options.OpenInNewWindow)
|
||||
{
|
||||
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var inline = new LinkInline()
|
||||
{
|
||||
Span =
|
||||
{
|
||||
Start = processor.GetSourcePosition(startPosition, out int line, out int column),
|
||||
},
|
||||
Line = line,
|
||||
Column = column,
|
||||
Url = c == 'w' ? ((Options.UseHttpsForWWWLinks ? "https://" : "http://") + link) : link,
|
||||
IsClosed = true,
|
||||
IsAutoLink = true,
|
||||
};
|
||||
|
||||
var skipFromBeginning = c == 'm' ? 7 : 0; // For mailto: skip "mailto:" for content
|
||||
|
||||
inline.Span.End = inline.Span.Start + link.Length - 1;
|
||||
inline.UrlSpan = inline.Span;
|
||||
inline.AppendChild(new LiteralInline()
|
||||
{
|
||||
Span = inline.Span,
|
||||
Line = line,
|
||||
Column = column,
|
||||
Content = new StringSlice(slice.Text, startPosition + skipFromBeginning, startPosition + link.Length - 1),
|
||||
IsClosed = true
|
||||
});
|
||||
processor.Inline = inline;
|
||||
|
||||
if (Options.OpenInNewWindow)
|
||||
finally
|
||||
{
|
||||
inline.GetAttributes().AddPropertyIfNotExist("target", "blank");
|
||||
_listOfCharCache.Release(pendingEmphasis);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, out List<char> pendingEmphasis)
|
||||
private bool IsAutoLinkValidInCurrentContext(InlineProcessor processor, List<char> pendingEmphasis)
|
||||
{
|
||||
pendingEmphasis = null;
|
||||
|
||||
// Case where there is a pending HtmlInline <a>
|
||||
var currentInline = processor.Inline;
|
||||
while (currentInline != null)
|
||||
{
|
||||
var htmlInline = currentInline as HtmlInline;
|
||||
if (htmlInline != null)
|
||||
if (currentInline is HtmlInline htmlInline)
|
||||
{
|
||||
// If we have a </a> we don't expect nested <a>
|
||||
if (htmlInline.Tag.StartsWith("</a", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -211,7 +218,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
}
|
||||
}
|
||||
|
||||
// Check previous sibling and parents in the tree
|
||||
// Check previous sibling and parents in the tree
|
||||
currentInline = currentInline.PreviousSibling ?? currentInline.Parent;
|
||||
}
|
||||
|
||||
@@ -221,8 +228,7 @@ namespace Markdig.Extensions.AutoLinks
|
||||
int countBrackets = 0;
|
||||
while (currentInline != null)
|
||||
{
|
||||
var linkDelimiterInline = currentInline as LinkDelimiterInline;
|
||||
if (linkDelimiterInline != null && linkDelimiterInline.IsActive)
|
||||
if (currentInline is LinkDelimiterInline linkDelimiterInline && linkDelimiterInline.IsActive)
|
||||
{
|
||||
if (linkDelimiterInline.Type == DelimiterType.Open)
|
||||
{
|
||||
@@ -236,14 +242,8 @@ namespace Markdig.Extensions.AutoLinks
|
||||
else
|
||||
{
|
||||
// Record all pending characters for emphasis
|
||||
var emphasisDelimiter = currentInline as EmphasisDelimiterInline;
|
||||
if (emphasisDelimiter != null)
|
||||
if (currentInline is EmphasisDelimiterInline emphasisDelimiter)
|
||||
{
|
||||
if (pendingEmphasis == null)
|
||||
{
|
||||
// Not optimized for GC, but we don't expect this case much
|
||||
pendingEmphasis = new List<char>();
|
||||
}
|
||||
if (!pendingEmphasis.Contains(emphasisDelimiter.DelimiterChar))
|
||||
{
|
||||
pendingEmphasis.Add(emphasisDelimiter.DelimiterChar);
|
||||
@@ -254,6 +254,14 @@ namespace Markdig.Extensions.AutoLinks
|
||||
}
|
||||
|
||||
return countBrackets <= 0;
|
||||
}
|
||||
|
||||
private sealed class ListOfCharCache : DefaultObjectCache<List<char>>
|
||||
{
|
||||
protected override void Reset(List<char> instance)
|
||||
{
|
||||
instance.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Parsers;
|
||||
|
||||
@@ -47,9 +47,9 @@ namespace Markdig.Extensions.Emoji
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
// Previous char must be a space
|
||||
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
|
||||
{
|
||||
return false;
|
||||
if (!slice.PeekCharExtra(-1).IsWhiteSpaceOrZero())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to match an emoji
|
||||
@@ -75,8 +75,8 @@ namespace Markdig.Extensions.Emoji
|
||||
slice.Start += match.Key.Length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Emojis and Smileys
|
||||
static EmojiParser()
|
||||
{
|
||||
@@ -961,6 +961,733 @@ namespace Markdig.Extensions.Emoji
|
||||
{ ":custom_arrow_left_strong:", "⇐"},
|
||||
{ ":custom_arrow_right_strong:", "⇒"},
|
||||
{ ":custom_arrow_left_right_strong:", "⇔"},
|
||||
{":rofl:","🤣"},
|
||||
{":slightly_smiling_face:","🙂"},
|
||||
{":upside_down_face:","🙃"},
|
||||
{":star_struck:","🤩"},
|
||||
{":zany_face:","🤪"},
|
||||
{":money_mouth_face:","🤑"},
|
||||
{":hugs:","🤗"},
|
||||
{":hand_over_mouth:","🤭"},
|
||||
{":shushing_face:","🤫"},
|
||||
{":thinking:","🤔"},
|
||||
{":zipper_mouth_face:","🤐"},
|
||||
{":raised_eyebrow:","🤨"},
|
||||
{":roll_eyes:","🙄"},
|
||||
{":lying_face:","🤥"},
|
||||
{":drooling_face:","🤤"},
|
||||
{":face_with_thermometer:","🤒"},
|
||||
{":face_with_head_bandage:","🤕"},
|
||||
{":nauseated_face:","🤢"},
|
||||
{":vomiting_face:","🤮"},
|
||||
{":sneezing_face:","🤧"},
|
||||
{":exploding_head:","🤯"},
|
||||
{":cowboy_hat_face:","🤠"},
|
||||
{":nerd_face:","🤓"},
|
||||
{":monocle_face:","🧐"},
|
||||
{":slightly_frowning_face:","🙁"},
|
||||
{":frowning_face:","☹️"},
|
||||
{":cursing_face:","🤬"},
|
||||
{":skull_and_crossbones:","☠️"},
|
||||
{":clown_face:","🤡"},
|
||||
{":robot:","🤖"},
|
||||
{":heavy_heart_exclamation:","❣️"},
|
||||
{":orange_heart:","🧡"},
|
||||
{":black_heart:","🖤"},
|
||||
{":hole:","🕳️"},
|
||||
{":eye_speech_bubble:","👁️🗨️"},
|
||||
{":left_speech_bubble:","🗨️"},
|
||||
{":right_anger_bubble:","🗯️"},
|
||||
{":raised_back_of_hand:","🤚"},
|
||||
{":raised_hand_with_fingers_splayed:","🖐️"},
|
||||
{":vulcan_salute:","🖖"},
|
||||
{":crossed_fingers:","🤞"},
|
||||
{":love_you_gesture:","🤟"},
|
||||
{":metal:","🤘"},
|
||||
{":call_me_hand:","🤙"},
|
||||
{":middle_finger:","🖕"},
|
||||
{":fist_raised:","✊"},
|
||||
{":fist_oncoming:","👊"},
|
||||
{":fist_left:","🤛"},
|
||||
{":fist_right:","🤜"},
|
||||
{":palms_up_together:","🤲"},
|
||||
{":handshake:","🤝"},
|
||||
{":writing_hand:","✍️"},
|
||||
{":selfie:","🤳"},
|
||||
{":brain:","🧠"},
|
||||
{":eye:","👁️"},
|
||||
{":child:","🧒"},
|
||||
{":adult:","🧑"},
|
||||
{":blond_haired_person:","👱"},
|
||||
{":bearded_person:","🧔"},
|
||||
{":blond_haired_man:","👱♂️"},
|
||||
{":blond_haired_woman:","👱♀️"},
|
||||
{":older_adult:","🧓"},
|
||||
{":frowning_person:","🙍"},
|
||||
{":frowning_man:","🙍♂️"},
|
||||
{":frowning_woman:","🙍♀️"},
|
||||
{":pouting_face:","🙎"},
|
||||
{":pouting_man:","🙎♂️"},
|
||||
{":pouting_woman:","🙎♀️"},
|
||||
{":no_good_man:","🙅♂️"},
|
||||
{":no_good_woman:","🙅♀️"},
|
||||
{":ok_person:","🙆"},
|
||||
{":ok_man:","🙆♂️"},
|
||||
{":tipping_hand_person:","💁"},
|
||||
{":tipping_hand_man:","💁♂️"},
|
||||
{":tipping_hand_woman:","💁♀️"},
|
||||
{":raising_hand_man:","🙋♂️"},
|
||||
{":raising_hand_woman:","🙋♀️"},
|
||||
{":bowing_man:","🙇♂️"},
|
||||
{":bowing_woman:","🙇♀️"},
|
||||
{":facepalm:","🤦"},
|
||||
{":man_facepalming:","🤦♂️"},
|
||||
{":woman_facepalming:","🤦♀️"},
|
||||
{":shrug:","🤷"},
|
||||
{":man_shrugging:","🤷♂️"},
|
||||
{":woman_shrugging:","🤷♀️"},
|
||||
{":man_health_worker:","👨⚕️"},
|
||||
{":woman_health_worker:","👩⚕️"},
|
||||
{":man_student:","👨🎓"},
|
||||
{":woman_student:","👩🎓"},
|
||||
{":man_teacher:","👨🏫"},
|
||||
{":woman_teacher:","👩🏫"},
|
||||
{":man_judge:","👨⚖️"},
|
||||
{":woman_judge:","👩⚖️"},
|
||||
{":man_farmer:","👨🌾"},
|
||||
{":woman_farmer:","👩🌾"},
|
||||
{":man_cook:","👨🍳"},
|
||||
{":woman_cook:","👩🍳"},
|
||||
{":man_mechanic:","👨🔧"},
|
||||
{":woman_mechanic:","👩🔧"},
|
||||
{":man_factory_worker:","👨🏭"},
|
||||
{":woman_factory_worker:","👩🏭"},
|
||||
{":man_office_worker:","👨💼"},
|
||||
{":woman_office_worker:","👩💼"},
|
||||
{":man_scientist:","👨🔬"},
|
||||
{":woman_scientist:","👩🔬"},
|
||||
{":man_technologist:","👨💻"},
|
||||
{":woman_technologist:","👩💻"},
|
||||
{":man_singer:","👨🎤"},
|
||||
{":woman_singer:","👩🎤"},
|
||||
{":man_artist:","👨🎨"},
|
||||
{":woman_artist:","👩🎨"},
|
||||
{":man_pilot:","👨✈️"},
|
||||
{":woman_pilot:","👩✈️"},
|
||||
{":man_astronaut:","👨🚀"},
|
||||
{":woman_astronaut:","👩🚀"},
|
||||
{":man_firefighter:","👨🚒"},
|
||||
{":woman_firefighter:","👩🚒"},
|
||||
{":police_officer:","👮"},
|
||||
{":policeman:","👮♂️"},
|
||||
{":policewoman:","👮♀️"},
|
||||
{":detective:","🕵️"},
|
||||
{":male_detective:","🕵️♂️"},
|
||||
{":female_detective:","🕵️♀️"},
|
||||
{":guard:","💂"},
|
||||
{":guardswoman:","💂♀️"},
|
||||
{":construction_worker_man:","👷♂️"},
|
||||
{":construction_worker_woman:","👷♀️"},
|
||||
{":prince:","🤴"},
|
||||
{":person_with_turban:","👳"},
|
||||
{":woman_with_turban:","👳♀️"},
|
||||
{":woman_with_headscarf:","🧕"},
|
||||
{":man_in_tuxedo:","🤵"},
|
||||
{":pregnant_woman:","🤰"},
|
||||
{":breast_feeding:","🤱"},
|
||||
{":mrs_claus:","🤶"},
|
||||
{":mage:","🧙"},
|
||||
{":mage_man:","🧙♂️"},
|
||||
{":mage_woman:","🧙♀️"},
|
||||
{":fairy:","🧚"},
|
||||
{":fairy_man:","🧚♂️"},
|
||||
{":fairy_woman:","🧚♀️"},
|
||||
{":vampire:","🧛"},
|
||||
{":vampire_man:","🧛♂️"},
|
||||
{":vampire_woman:","🧛♀️"},
|
||||
{":merperson:","🧜"},
|
||||
{":merman:","🧜♂️"},
|
||||
{":mermaid:","🧜♀️"},
|
||||
{":elf:","🧝"},
|
||||
{":elf_man:","🧝♂️"},
|
||||
{":elf_woman:","🧝♀️"},
|
||||
{":genie:","🧞"},
|
||||
{":genie_man:","🧞♂️"},
|
||||
{":genie_woman:","🧞♀️"},
|
||||
{":zombie:","🧟"},
|
||||
{":zombie_man:","🧟♂️"},
|
||||
{":zombie_woman:","🧟♀️"},
|
||||
{":massage_man:","💆♂️"},
|
||||
{":massage_woman:","💆♀️"},
|
||||
{":haircut_man:","💇♂️"},
|
||||
{":haircut_woman:","💇♀️"},
|
||||
{":walking_man:","🚶♂️"},
|
||||
{":walking_woman:","🚶♀️"},
|
||||
{":running_man:","🏃♂️"},
|
||||
{":running_woman:","🏃♀️"},
|
||||
{":woman_dancing:","💃"},
|
||||
{":man_dancing:","🕺"},
|
||||
{":business_suit_levitating:","🕴️"},
|
||||
{":dancing_men:","👯♂️"},
|
||||
{":dancing_women:","👯♀️"},
|
||||
{":sauna_person:","🧖"},
|
||||
{":sauna_man:","🧖♂️"},
|
||||
{":sauna_woman:","🧖♀️"},
|
||||
{":climbing:","🧗"},
|
||||
{":climbing_man:","🧗♂️"},
|
||||
{":climbing_woman:","🧗♀️"},
|
||||
{":person_fencing:","🤺"},
|
||||
{":skier:","⛷️"},
|
||||
{":golfing:","🏌️"},
|
||||
{":golfing_man:","🏌️♂️"},
|
||||
{":golfing_woman:","🏌️♀️"},
|
||||
{":surfing_man:","🏄♂️"},
|
||||
{":surfing_woman:","🏄♀️"},
|
||||
{":rowing_man:","🚣♂️"},
|
||||
{":rowing_woman:","🚣♀️"},
|
||||
{":swimming_man:","🏊♂️"},
|
||||
{":swimming_woman:","🏊♀️"},
|
||||
{":bouncing_ball_person:","⛹️"},
|
||||
{":bouncing_ball_man:","⛹️♂️"},
|
||||
{":bouncing_ball_woman:","⛹️♀️"},
|
||||
{":weight_lifting:","🏋️"},
|
||||
{":weight_lifting_man:","🏋️♂️"},
|
||||
{":weight_lifting_woman:","🏋️♀️"},
|
||||
{":biking_man:","🚴♂️"},
|
||||
{":biking_woman:","🚴♀️"},
|
||||
{":mountain_biking_man:","🚵♂️"},
|
||||
{":mountain_biking_woman:","🚵♀️"},
|
||||
{":cartwheeling:","🤸"},
|
||||
{":man_cartwheeling:","🤸♂️"},
|
||||
{":woman_cartwheeling:","🤸♀️"},
|
||||
{":wrestling:","🤼"},
|
||||
{":men_wrestling:","🤼♂️"},
|
||||
{":women_wrestling:","🤼♀️"},
|
||||
{":water_polo:","🤽"},
|
||||
{":man_playing_water_polo:","🤽♂️"},
|
||||
{":woman_playing_water_polo:","🤽♀️"},
|
||||
{":handball_person:","🤾"},
|
||||
{":man_playing_handball:","🤾♂️"},
|
||||
{":woman_playing_handball:","🤾♀️"},
|
||||
{":juggling_person:","🤹"},
|
||||
{":man_juggling:","🤹♂️"},
|
||||
{":woman_juggling:","🤹♀️"},
|
||||
{":lotus_position:","🧘"},
|
||||
{":lotus_position_man:","🧘♂️"},
|
||||
{":lotus_position_woman:","🧘♀️"},
|
||||
{":sleeping_bed:","🛌"},
|
||||
{":people_holding_hands:","🧑🤝🧑"},
|
||||
{":couplekiss_man_woman:","👩❤️💋👨"},
|
||||
{":couplekiss_man_man:","👨❤️💋👨"},
|
||||
{":couplekiss_woman_woman:","👩❤️💋👩"},
|
||||
{":couple_with_heart_woman_man:","👩❤️👨"},
|
||||
{":couple_with_heart_man_man:","👨❤️👨"},
|
||||
{":couple_with_heart_woman_woman:","👩❤️👩"},
|
||||
{":family_man_woman_boy:","👨👩👦"},
|
||||
{":family_man_woman_girl:","👨👩👧"},
|
||||
{":family_man_woman_girl_boy:","👨👩👧👦"},
|
||||
{":family_man_woman_boy_boy:","👨👩👦👦"},
|
||||
{":family_man_woman_girl_girl:","👨👩👧👧"},
|
||||
{":family_man_man_boy:","👨👨👦"},
|
||||
{":family_man_man_girl:","👨👨👧"},
|
||||
{":family_man_man_girl_boy:","👨👨👧👦"},
|
||||
{":family_man_man_boy_boy:","👨👨👦👦"},
|
||||
{":family_man_man_girl_girl:","👨👨👧👧"},
|
||||
{":family_woman_woman_boy:","👩👩👦"},
|
||||
{":family_woman_woman_girl:","👩👩👧"},
|
||||
{":family_woman_woman_girl_boy:","👩👩👧👦"},
|
||||
{":family_woman_woman_boy_boy:","👩👩👦👦"},
|
||||
{":family_woman_woman_girl_girl:","👩👩👧👧"},
|
||||
{":family_man_boy:","👨👦"},
|
||||
{":family_man_boy_boy:","👨👦👦"},
|
||||
{":family_man_girl:","👨👧"},
|
||||
{":family_man_girl_boy:","👨👧👦"},
|
||||
{":family_man_girl_girl:","👨👧👧"},
|
||||
{":family_woman_boy:","👩👦"},
|
||||
{":family_woman_boy_boy:","👩👦👦"},
|
||||
{":family_woman_girl:","👩👧"},
|
||||
{":family_woman_girl_boy:","👩👧👦"},
|
||||
{":family_woman_girl_girl:","👩👧👧"},
|
||||
{":speaking_head:","🗣️"},
|
||||
{":gorilla:","🦍"},
|
||||
{":fox_face:","🦊"},
|
||||
{":lion:","🦁"},
|
||||
{":unicorn:","🦄"},
|
||||
{":zebra:","🦓"},
|
||||
{":deer:","🦌"},
|
||||
{":giraffe:","🦒"},
|
||||
{":rhinoceros:","🦏"},
|
||||
{":chipmunk:","🐿️"},
|
||||
{":hedgehog:","🦔"},
|
||||
{":bat:","🦇"},
|
||||
{":turkey:","🦃"},
|
||||
{":dove:","🕊️"},
|
||||
{":eagle:","🦅"},
|
||||
{":duck:","🦆"},
|
||||
{":owl:","🦉"},
|
||||
{":lizard:","🦎"},
|
||||
{":sauropod:","🦕"},
|
||||
{":t-rex:","🦖"},
|
||||
{":shark:","🦈"},
|
||||
{":butterfly:","🦋"},
|
||||
{":cricket:","🦗"},
|
||||
{":spider:","🕷️"},
|
||||
{":spider_web:","🕸️"},
|
||||
{":scorpion:","🦂"},
|
||||
{":rosette:","🏵️"},
|
||||
{":wilted_flower:","🥀"},
|
||||
{":shamrock:","☘️"},
|
||||
{":kiwi_fruit:","🥝"},
|
||||
{":coconut:","🥥"},
|
||||
{":avocado:","🥑"},
|
||||
{":potato:","🥔"},
|
||||
{":carrot:","🥕"},
|
||||
{":hot_pepper:","🌶️"},
|
||||
{":cucumber:","🥒"},
|
||||
{":broccoli:","🥦"},
|
||||
{":peanuts:","🥜"},
|
||||
{":croissant:","🥐"},
|
||||
{":baguette_bread:","🥖"},
|
||||
{":pretzel:","🥨"},
|
||||
{":pancakes:","🥞"},
|
||||
{":cheese:","🧀"},
|
||||
{":cut_of_meat:","🥩"},
|
||||
{":bacon:","🥓"},
|
||||
{":hotdog:","🌭"},
|
||||
{":sandwich:","🥪"},
|
||||
{":taco:","🌮"},
|
||||
{":burrito:","🌯"},
|
||||
{":stuffed_flatbread:","🥙"},
|
||||
{":fried_egg:","🍳"},
|
||||
{":shallow_pan_of_food:","🥘"},
|
||||
{":bowl_with_spoon:","🥣"},
|
||||
{":green_salad:","🥗"},
|
||||
{":popcorn:","🍿"},
|
||||
{":canned_food:","🥫"},
|
||||
{":dumpling:","🥟"},
|
||||
{":fortune_cookie:","🥠"},
|
||||
{":takeout_box:","🥡"},
|
||||
{":crab:","🦀"},
|
||||
{":shrimp:","🦐"},
|
||||
{":squid:","🦑"},
|
||||
{":pie:","🥧"},
|
||||
{":milk_glass:","🥛"},
|
||||
{":champagne:","🍾"},
|
||||
{":clinking_glasses:","🥂"},
|
||||
{":tumbler_glass:","🥃"},
|
||||
{":cup_with_straw:","🥤"},
|
||||
{":chopsticks:","🥢"},
|
||||
{":plate_with_cutlery:","🍽️"},
|
||||
{":spoon:","🥄"},
|
||||
{":amphora:","🏺"},
|
||||
{":world_map:","🗺️"},
|
||||
{":mountain_snow:","🏔️"},
|
||||
{":mountain:","⛰️"},
|
||||
{":camping:","🏕️"},
|
||||
{":beach_umbrella:","🏖️"},
|
||||
{":desert:","🏜️"},
|
||||
{":desert_island:","🏝️"},
|
||||
{":national_park:","🏞️"},
|
||||
{":stadium:","🏟️"},
|
||||
{":classical_building:","🏛️"},
|
||||
{":building_construction:","🏗️"},
|
||||
{":houses:","🏘️"},
|
||||
{":derelict_house:","🏚️"},
|
||||
{":mosque:","🕌"},
|
||||
{":synagogue:","🕍"},
|
||||
{":shinto_shrine:","⛩️"},
|
||||
{":kaaba:","🕋"},
|
||||
{":cityscape:","🏙️"},
|
||||
{":racing_car:","🏎️"},
|
||||
{":motorcycle:","🏍️"},
|
||||
{":motor_scooter:","🛵"},
|
||||
{":kick_scooter:","🛴"},
|
||||
{":motorway:","🛣️"},
|
||||
{":railway_track:","🛤️"},
|
||||
{":oil_drum:","🛢️"},
|
||||
{":stop_sign:","🛑"},
|
||||
{":canoe:","🛶"},
|
||||
{":passenger_ship:","🛳️"},
|
||||
{":ferry:","⛴️"},
|
||||
{":motor_boat:","🛥️"},
|
||||
{":small_airplane:","🛩️"},
|
||||
{":flight_departure:","🛫"},
|
||||
{":flight_arrival:","🛬"},
|
||||
{":artificial_satellite:","🛰️"},
|
||||
{":flying_saucer:","🛸"},
|
||||
{":bellhop_bell:","🛎️"},
|
||||
{":stopwatch:","⏱️"},
|
||||
{":timer_clock:","⏲️"},
|
||||
{":mantelpiece_clock:","🕰️"},
|
||||
{":thermometer:","🌡️"},
|
||||
{":cloud_with_lightning_and_rain:","⛈️"},
|
||||
{":sun_behind_small_cloud:","🌤️"},
|
||||
{":sun_behind_large_cloud:","🌥️"},
|
||||
{":sun_behind_rain_cloud:","🌦️"},
|
||||
{":cloud_with_rain:","🌧️"},
|
||||
{":cloud_with_snow:","🌨️"},
|
||||
{":cloud_with_lightning:","🌩️"},
|
||||
{":tornado:","🌪️"},
|
||||
{":fog:","🌫️"},
|
||||
{":wind_face:","🌬️"},
|
||||
{":open_umbrella:","☂️"},
|
||||
{":parasol_on_ground:","⛱️"},
|
||||
{":snowman_with_snow:","☃️"},
|
||||
{":comet:","☄️"},
|
||||
{":reminder_ribbon:","🎗️"},
|
||||
{":tickets:","🎟️"},
|
||||
{":medal_military:","🎖️"},
|
||||
{":medal_sports:","🏅"},
|
||||
{":1st_place_medal:","🥇"},
|
||||
{":2nd_place_medal:","🥈"},
|
||||
{":3rd_place_medal:","🥉"},
|
||||
{":volleyball:","🏐"},
|
||||
{":cricket_game:","🏏"},
|
||||
{":field_hockey:","🏑"},
|
||||
{":ice_hockey:","🏒"},
|
||||
{":ping_pong:","🏓"},
|
||||
{":badminton:","🏸"},
|
||||
{":boxing_glove:","🥊"},
|
||||
{":martial_arts_uniform:","🥋"},
|
||||
{":goal_net:","🥅"},
|
||||
{":ice_skate:","⛸️"},
|
||||
{":sled:","🛷"},
|
||||
{":curling_stone:","🥌"},
|
||||
{":joystick:","🕹️"},
|
||||
{":chess_pawn:","♟️"},
|
||||
{":framed_picture:","🖼️"},
|
||||
{":dark_sunglasses:","🕶️"},
|
||||
{":scarf:","🧣"},
|
||||
{":gloves:","🧤"},
|
||||
{":coat:","🧥"},
|
||||
{":socks:","🧦"},
|
||||
{":shopping:","🛍️"},
|
||||
{":billed_cap:","🧢"},
|
||||
{":rescue_worker_helmet:","⛑️"},
|
||||
{":prayer_beads:","📿"},
|
||||
{":studio_microphone:","🎙️"},
|
||||
{":level_slider:","🎚️"},
|
||||
{":control_knobs:","🎛️"},
|
||||
{":drum:","🥁"},
|
||||
{":desktop_computer:","🖥️"},
|
||||
{":printer:","🖨️"},
|
||||
{":keyboard:","⌨️"},
|
||||
{":computer_mouse:","🖱️"},
|
||||
{":trackball:","🖲️"},
|
||||
{":film_strip:","🎞️"},
|
||||
{":film_projector:","📽️"},
|
||||
{":camera_flash:","📸"},
|
||||
{":candle:","🕯️"},
|
||||
{":newspaper_roll:","🗞️"},
|
||||
{":label:","🏷️"},
|
||||
{":ballot_box:","🗳️"},
|
||||
{":fountain_pen:","🖋️"},
|
||||
{":pen:","🖊️"},
|
||||
{":paintbrush:","🖌️"},
|
||||
{":crayon:","🖍️"},
|
||||
{":card_index_dividers:","🗂️"},
|
||||
{":spiral_notepad:","🗒️"},
|
||||
{":spiral_calendar:","🗓️"},
|
||||
{":paperclips:","🖇️"},
|
||||
{":card_file_box:","🗃️"},
|
||||
{":file_cabinet:","🗄️"},
|
||||
{":wastebasket:","🗑️"},
|
||||
{":old_key:","🗝️"},
|
||||
{":pick:","⛏️"},
|
||||
{":hammer_and_pick:","⚒️"},
|
||||
{":hammer_and_wrench:","🛠️"},
|
||||
{":dagger:","🗡️"},
|
||||
{":crossed_swords:","⚔️"},
|
||||
{":bow_and_arrow:","🏹"},
|
||||
{":shield:","🛡️"},
|
||||
{":gear:","⚙️"},
|
||||
{":clamp:","🗜️"},
|
||||
{":balance_scale:","⚖️"},
|
||||
{":chains:","⛓️"},
|
||||
{":alembic:","⚗️"},
|
||||
{":bed:","🛏️"},
|
||||
{":couch_and_lamp:","🛋️"},
|
||||
{":shopping_cart:","🛒"},
|
||||
{":coffin:","⚰️"},
|
||||
{":funeral_urn:","⚱️"},
|
||||
{":radioactive:","☢️"},
|
||||
{":biohazard:","☣️"},
|
||||
{":place_of_worship:","🛐"},
|
||||
{":atom_symbol:","⚛️"},
|
||||
{":om:","🕉️"},
|
||||
{":star_of_david:","✡️"},
|
||||
{":wheel_of_dharma:","☸️"},
|
||||
{":yin_yang:","☯️"},
|
||||
{":latin_cross:","✝️"},
|
||||
{":orthodox_cross:","☦️"},
|
||||
{":star_and_crescent:","☪️"},
|
||||
{":peace_symbol:","☮️"},
|
||||
{":menorah:","🕎"},
|
||||
{":next_track_button:","⏭️"},
|
||||
{":play_or_pause_button:","⏯️"},
|
||||
{":previous_track_button:","⏮️"},
|
||||
{":pause_button:","⏸️"},
|
||||
{":stop_button:","⏹️"},
|
||||
{":record_button:","⏺️"},
|
||||
{":eject_button:","⏏️"},
|
||||
{":female_sign:","♀️"},
|
||||
{":male_sign:","♂️"},
|
||||
{":medical_symbol:","⚕️"},
|
||||
{":infinity:","♾️"},
|
||||
{":fleur_de_lis:","⚜️"},
|
||||
{":asterisk:","*️⃣"},
|
||||
{":black_flag:","🏴"},
|
||||
{":white_flag:","🏳️"},
|
||||
{":rainbow_flag:","🏳️🌈"},
|
||||
{":pirate_flag:","🏴☠️"},
|
||||
{":ascension_island:","🇦🇨"},
|
||||
{":andorra:","🇦🇩"},
|
||||
{":united_arab_emirates:","🇦🇪"},
|
||||
{":afghanistan:","🇦🇫"},
|
||||
{":antigua_barbuda:","🇦🇬"},
|
||||
{":anguilla:","🇦🇮"},
|
||||
{":albania:","🇦🇱"},
|
||||
{":armenia:","🇦🇲"},
|
||||
{":angola:","🇦🇴"},
|
||||
{":antarctica:","🇦🇶"},
|
||||
{":argentina:","🇦🇷"},
|
||||
{":american_samoa:","🇦🇸"},
|
||||
{":austria:","🇦🇹"},
|
||||
{":australia:","🇦🇺"},
|
||||
{":aruba:","🇦🇼"},
|
||||
{":aland_islands:","🇦🇽"},
|
||||
{":azerbaijan:","🇦🇿"},
|
||||
{":bosnia_herzegovina:","🇧🇦"},
|
||||
{":barbados:","🇧🇧"},
|
||||
{":bangladesh:","🇧🇩"},
|
||||
{":belgium:","🇧🇪"},
|
||||
{":burkina_faso:","🇧🇫"},
|
||||
{":bulgaria:","🇧🇬"},
|
||||
{":bahrain:","🇧🇭"},
|
||||
{":burundi:","🇧🇮"},
|
||||
{":benin:","🇧🇯"},
|
||||
{":st_barthelemy:","🇧🇱"},
|
||||
{":bermuda:","🇧🇲"},
|
||||
{":brunei:","🇧🇳"},
|
||||
{":bolivia:","🇧🇴"},
|
||||
{":caribbean_netherlands:","🇧🇶"},
|
||||
{":brazil:","🇧🇷"},
|
||||
{":bahamas:","🇧🇸"},
|
||||
{":bhutan:","🇧🇹"},
|
||||
{":bouvet_island:","🇧🇻"},
|
||||
{":botswana:","🇧🇼"},
|
||||
{":belarus:","🇧🇾"},
|
||||
{":belize:","🇧🇿"},
|
||||
{":canada:","🇨🇦"},
|
||||
{":cocos_islands:","🇨🇨"},
|
||||
{":congo_kinshasa:","🇨🇩"},
|
||||
{":central_african_republic:","🇨🇫"},
|
||||
{":congo_brazzaville:","🇨🇬"},
|
||||
{":switzerland:","🇨🇭"},
|
||||
{":cote_divoire:","🇨🇮"},
|
||||
{":cook_islands:","🇨🇰"},
|
||||
{":chile:","🇨🇱"},
|
||||
{":cameroon:","🇨🇲"},
|
||||
{":colombia:","🇨🇴"},
|
||||
{":clipperton_island:","🇨🇵"},
|
||||
{":costa_rica:","🇨🇷"},
|
||||
{":cuba:","🇨🇺"},
|
||||
{":cape_verde:","🇨🇻"},
|
||||
{":curacao:","🇨🇼"},
|
||||
{":christmas_island:","🇨🇽"},
|
||||
{":cyprus:","🇨🇾"},
|
||||
{":czech_republic:","🇨🇿"},
|
||||
{":diego_garcia:","🇩🇬"},
|
||||
{":djibouti:","🇩🇯"},
|
||||
{":denmark:","🇩🇰"},
|
||||
{":dominica:","🇩🇲"},
|
||||
{":dominican_republic:","🇩🇴"},
|
||||
{":algeria:","🇩🇿"},
|
||||
{":ceuta_melilla:","🇪🇦"},
|
||||
{":ecuador:","🇪🇨"},
|
||||
{":estonia:","🇪🇪"},
|
||||
{":egypt:","🇪🇬"},
|
||||
{":western_sahara:","🇪🇭"},
|
||||
{":eritrea:","🇪🇷"},
|
||||
{":ethiopia:","🇪🇹"},
|
||||
{":eu:","🇪🇺"},
|
||||
{":finland:","🇫🇮"},
|
||||
{":fiji:","🇫🇯"},
|
||||
{":falkland_islands:","🇫🇰"},
|
||||
{":micronesia:","🇫🇲"},
|
||||
{":faroe_islands:","🇫🇴"},
|
||||
{":gabon:","🇬🇦"},
|
||||
{":grenada:","🇬🇩"},
|
||||
{":georgia:","🇬🇪"},
|
||||
{":french_guiana:","🇬🇫"},
|
||||
{":guernsey:","🇬🇬"},
|
||||
{":ghana:","🇬🇭"},
|
||||
{":gibraltar:","🇬🇮"},
|
||||
{":greenland:","🇬🇱"},
|
||||
{":gambia:","🇬🇲"},
|
||||
{":guinea:","🇬🇳"},
|
||||
{":guadeloupe:","🇬🇵"},
|
||||
{":equatorial_guinea:","🇬🇶"},
|
||||
{":greece:","🇬🇷"},
|
||||
{":south_georgia_south_sandwich_islands:","🇬🇸"},
|
||||
{":guatemala:","🇬🇹"},
|
||||
{":guam:","🇬🇺"},
|
||||
{":guinea_bissau:","🇬🇼"},
|
||||
{":guyana:","🇬🇾"},
|
||||
{":hong_kong:","🇭🇰"},
|
||||
{":heard_mcdonald_islands:","🇭🇲"},
|
||||
{":honduras:","🇭🇳"},
|
||||
{":croatia:","🇭🇷"},
|
||||
{":haiti:","🇭🇹"},
|
||||
{":hungary:","🇭🇺"},
|
||||
{":canary_islands:","🇮🇨"},
|
||||
{":indonesia:","🇮🇩"},
|
||||
{":ireland:","🇮🇪"},
|
||||
{":israel:","🇮🇱"},
|
||||
{":isle_of_man:","🇮🇲"},
|
||||
{":india:","🇮🇳"},
|
||||
{":british_indian_ocean_territory:","🇮🇴"},
|
||||
{":iraq:","🇮🇶"},
|
||||
{":iran:","🇮🇷"},
|
||||
{":iceland:","🇮🇸"},
|
||||
{":jersey:","🇯🇪"},
|
||||
{":jamaica:","🇯🇲"},
|
||||
{":jordan:","🇯🇴"},
|
||||
{":kenya:","🇰🇪"},
|
||||
{":kyrgyzstan:","🇰🇬"},
|
||||
{":cambodia:","🇰🇭"},
|
||||
{":kiribati:","🇰🇮"},
|
||||
{":comoros:","🇰🇲"},
|
||||
{":st_kitts_nevis:","🇰🇳"},
|
||||
{":north_korea:","🇰🇵"},
|
||||
{":kuwait:","🇰🇼"},
|
||||
{":cayman_islands:","🇰🇾"},
|
||||
{":kazakhstan:","🇰🇿"},
|
||||
{":laos:","🇱🇦"},
|
||||
{":lebanon:","🇱🇧"},
|
||||
{":st_lucia:","🇱🇨"},
|
||||
{":liechtenstein:","🇱🇮"},
|
||||
{":sri_lanka:","🇱🇰"},
|
||||
{":liberia:","🇱🇷"},
|
||||
{":lesotho:","🇱🇸"},
|
||||
{":lithuania:","🇱🇹"},
|
||||
{":luxembourg:","🇱🇺"},
|
||||
{":latvia:","🇱🇻"},
|
||||
{":libya:","🇱🇾"},
|
||||
{":morocco:","🇲🇦"},
|
||||
{":monaco:","🇲🇨"},
|
||||
{":moldova:","🇲🇩"},
|
||||
{":montenegro:","🇲🇪"},
|
||||
{":st_martin:","🇲🇫"},
|
||||
{":madagascar:","🇲🇬"},
|
||||
{":marshall_islands:","🇲🇭"},
|
||||
{":macedonia:","🇲🇰"},
|
||||
{":mali:","🇲🇱"},
|
||||
{":myanmar:","🇲🇲"},
|
||||
{":mongolia:","🇲🇳"},
|
||||
{":macau:","🇲🇴"},
|
||||
{":northern_mariana_islands:","🇲🇵"},
|
||||
{":martinique:","🇲🇶"},
|
||||
{":mauritania:","🇲🇷"},
|
||||
{":montserrat:","🇲🇸"},
|
||||
{":malta:","🇲🇹"},
|
||||
{":mauritius:","🇲🇺"},
|
||||
{":maldives:","🇲🇻"},
|
||||
{":malawi:","🇲🇼"},
|
||||
{":mexico:","🇲🇽"},
|
||||
{":malaysia:","🇲🇾"},
|
||||
{":mozambique:","🇲🇿"},
|
||||
{":namibia:","🇳🇦"},
|
||||
{":new_caledonia:","🇳🇨"},
|
||||
{":niger:","🇳🇪"},
|
||||
{":norfolk_island:","🇳🇫"},
|
||||
{":nigeria:","🇳🇬"},
|
||||
{":nicaragua:","🇳🇮"},
|
||||
{":netherlands:","🇳🇱"},
|
||||
{":norway:","🇳🇴"},
|
||||
{":nepal:","🇳🇵"},
|
||||
{":nauru:","🇳🇷"},
|
||||
{":niue:","🇳🇺"},
|
||||
{":new_zealand:","🇳🇿"},
|
||||
{":oman:","🇴🇲"},
|
||||
{":panama:","🇵🇦"},
|
||||
{":peru:","🇵🇪"},
|
||||
{":french_polynesia:","🇵🇫"},
|
||||
{":papua_new_guinea:","🇵🇬"},
|
||||
{":philippines:","🇵🇭"},
|
||||
{":pakistan:","🇵🇰"},
|
||||
{":poland:","🇵🇱"},
|
||||
{":st_pierre_miquelon:","🇵🇲"},
|
||||
{":pitcairn_islands:","🇵🇳"},
|
||||
{":puerto_rico:","🇵🇷"},
|
||||
{":palestinian_territories:","🇵🇸"},
|
||||
{":portugal:","🇵🇹"},
|
||||
{":palau:","🇵🇼"},
|
||||
{":paraguay:","🇵🇾"},
|
||||
{":qatar:","🇶🇦"},
|
||||
{":reunion:","🇷🇪"},
|
||||
{":romania:","🇷🇴"},
|
||||
{":serbia:","🇷🇸"},
|
||||
{":rwanda:","🇷🇼"},
|
||||
{":saudi_arabia:","🇸🇦"},
|
||||
{":solomon_islands:","🇸🇧"},
|
||||
{":seychelles:","🇸🇨"},
|
||||
{":sudan:","🇸🇩"},
|
||||
{":sweden:","🇸🇪"},
|
||||
{":singapore:","🇸🇬"},
|
||||
{":st_helena:","🇸🇭"},
|
||||
{":slovenia:","🇸🇮"},
|
||||
{":svalbard_jan_mayen:","🇸🇯"},
|
||||
{":slovakia:","🇸🇰"},
|
||||
{":sierra_leone:","🇸🇱"},
|
||||
{":san_marino:","🇸🇲"},
|
||||
{":senegal:","🇸🇳"},
|
||||
{":somalia:","🇸🇴"},
|
||||
{":suriname:","🇸🇷"},
|
||||
{":south_sudan:","🇸🇸"},
|
||||
{":sao_tome_principe:","🇸🇹"},
|
||||
{":el_salvador:","🇸🇻"},
|
||||
{":sint_maarten:","🇸🇽"},
|
||||
{":syria:","🇸🇾"},
|
||||
{":swaziland:","🇸🇿"},
|
||||
{":tristan_da_cunha:","🇹🇦"},
|
||||
{":turks_caicos_islands:","🇹🇨"},
|
||||
{":chad:","🇹🇩"},
|
||||
{":french_southern_territories:","🇹🇫"},
|
||||
{":togo:","🇹🇬"},
|
||||
{":thailand:","🇹🇭"},
|
||||
{":tajikistan:","🇹🇯"},
|
||||
{":tokelau:","🇹🇰"},
|
||||
{":timor_leste:","🇹🇱"},
|
||||
{":turkmenistan:","🇹🇲"},
|
||||
{":tunisia:","🇹🇳"},
|
||||
{":tonga:","🇹🇴"},
|
||||
{":tr:","🇹🇷"},
|
||||
{":trinidad_tobago:","🇹🇹"},
|
||||
{":tuvalu:","🇹🇻"},
|
||||
{":taiwan:","🇹🇼"},
|
||||
{":tanzania:","🇹🇿"},
|
||||
{":ukraine:","🇺🇦"},
|
||||
{":uganda:","🇺🇬"},
|
||||
{":us_outlying_islands:","🇺🇲"},
|
||||
{":united_nations:","🇺🇳"},
|
||||
{":uruguay:","🇺🇾"},
|
||||
{":uzbekistan:","🇺🇿"},
|
||||
{":vatican_city:","🇻🇦"},
|
||||
{":st_vincent_grenadines:","🇻🇨"},
|
||||
{":venezuela:","🇻🇪"},
|
||||
{":british_virgin_islands:","🇻🇬"},
|
||||
{":us_virgin_islands:","🇻🇮"},
|
||||
{":vietnam:","🇻🇳"},
|
||||
{":vanuatu:","🇻🇺"},
|
||||
{":wallis_futuna:","🇼🇫"},
|
||||
{":samoa:","🇼🇸"},
|
||||
{":kosovo:","🇽🇰"},
|
||||
{":yemen:","🇾🇪"},
|
||||
{":mayotte:","🇾🇹"},
|
||||
{":south_africa:","🇿🇦"},
|
||||
{":zambia:","🇿🇲"},
|
||||
{":zimbabwe:","🇿🇼"}
|
||||
};
|
||||
|
||||
SmileyToEmojiDefault = new Dictionary<string, string>()
|
||||
@@ -1039,31 +1766,31 @@ namespace Markdig.Extensions.Emoji
|
||||
{"<=", ":custom_arrow_left_strong:" },
|
||||
{"=>", ":custom_arrow_right_strong:" },
|
||||
{"<=>", ":custom_arrow_left_right_strong:" },
|
||||
};
|
||||
|
||||
// Build Emoji and Smiley CompactPrefixTree
|
||||
EmojiPrefixTreeDefault = new CompactPrefixTree<string>(EmojiToUnicodeDefault);
|
||||
|
||||
int jointCount = EmojiToUnicodeDefault.Count + SmileyToEmojiDefault.Count;
|
||||
// Count * 2 seems to be a good fit for the data set
|
||||
EmojiSmileyPrefixTreeDefault = new CompactPrefixTree<string>(jointCount, jointCount * 2, jointCount * 2);
|
||||
|
||||
// This is not the best data set for the prefix tree as it will have to check the first character linearly
|
||||
// A work-around would require a bunch of substrings / removing the leading ':' from emojis, neither one is pretty
|
||||
// This way we sacrifice a few microseconds for not introducing breaking changes, emojis aren't all that common anyhow
|
||||
|
||||
};
|
||||
|
||||
// Build Emoji and Smiley CompactPrefixTree
|
||||
EmojiPrefixTreeDefault = new CompactPrefixTree<string>(EmojiToUnicodeDefault);
|
||||
|
||||
int jointCount = EmojiToUnicodeDefault.Count + SmileyToEmojiDefault.Count;
|
||||
// Count * 2 seems to be a good fit for the data set
|
||||
EmojiSmileyPrefixTreeDefault = new CompactPrefixTree<string>(jointCount, jointCount * 2, jointCount * 2);
|
||||
|
||||
// This is not the best data set for the prefix tree as it will have to check the first character linearly
|
||||
// A work-around would require a bunch of substrings / removing the leading ':' from emojis, neither one is pretty
|
||||
// This way we sacrifice a few microseconds for not introducing breaking changes, emojis aren't all that common anyhow
|
||||
|
||||
var firstChars = new HashSet<char> { ':' };
|
||||
foreach (var emoji in EmojiToUnicodeDefault)
|
||||
foreach (var emoji in EmojiToUnicodeDefault)
|
||||
EmojiSmileyPrefixTreeDefault.Add(emoji);
|
||||
foreach (var smiley in SmileyToEmojiDefault)
|
||||
{
|
||||
if (!EmojiToUnicodeDefault.TryGetValue(smiley.Value, out string unicode))
|
||||
throw new ArgumentException("Invalid smiley target: {0} is not present in the emoji dictionary", smiley.Value);
|
||||
|
||||
firstChars.Add(smiley.Key[0]);
|
||||
|
||||
if (!EmojiSmileyPrefixTreeDefault.TryAdd(smiley.Key, unicode))
|
||||
throw new ArgumentException("Smiley {0} is already present in the Emoji dictionary", smiley.Key);
|
||||
foreach (var smiley in SmileyToEmojiDefault)
|
||||
{
|
||||
if (!EmojiToUnicodeDefault.TryGetValue(smiley.Value, out string unicode))
|
||||
throw new ArgumentException("Invalid smiley target: {0} is not present in the emoji dictionary", smiley.Value);
|
||||
|
||||
firstChars.Add(smiley.Key[0]);
|
||||
|
||||
if (!EmojiSmileyPrefixTreeDefault.TryAdd(smiley.Key, unicode))
|
||||
throw new ArgumentException("Smiley {0} is already present in the Emoji dictionary", smiley.Key);
|
||||
}
|
||||
|
||||
EmojiOpeningCharactersDefault = new[] { ':' };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
/// Extension that allows to attach HTML attributes to the previous <see cref="Inline"/> or current <see cref="Block"/>.
|
||||
/// This extension should be enabled last after enabling other extensions.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.IMarkdownExtension" />
|
||||
/// <seealso cref="IMarkdownExtension" />
|
||||
public class GenericAttributesExtension : IMarkdownExtension
|
||||
{
|
||||
public void Setup(MarkdownPipelineBuilder pipeline)
|
||||
@@ -29,8 +29,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
// Plug into all IAttributesParseable
|
||||
foreach (var parser in pipeline.BlockParsers)
|
||||
{
|
||||
var attributesParseable = parser as IAttributesParseable;
|
||||
if (attributesParseable != null)
|
||||
if (parser is IAttributesParseable attributesParseable)
|
||||
{
|
||||
attributesParseable.TryParseAttributes = TryProcessAttributesForHeading;
|
||||
}
|
||||
@@ -53,8 +52,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
var copy = line;
|
||||
copy.Start = indexOfAttributes;
|
||||
var startOfAttributes = copy.Start;
|
||||
HtmlAttributes attributes;
|
||||
if (GenericAttributesParser.TryParse(ref copy, out attributes))
|
||||
if (GenericAttributesParser.TryParse(ref copy, out HtmlAttributes attributes))
|
||||
{
|
||||
var htmlAttributes = block.GetAttributes();
|
||||
attributes.CopyTo(htmlAttributes);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
/// <summary>
|
||||
/// An inline parser used to parse a HTML attributes that can be attached to the previous <see cref="Inline"/> or current <see cref="Block"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Parsers.InlineParser" />
|
||||
/// <seealso cref="InlineParser" />
|
||||
public class GenericAttributesParser : InlineParser
|
||||
{
|
||||
/// <summary>
|
||||
@@ -27,9 +27,8 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
|
||||
public override bool Match(InlineProcessor processor, ref StringSlice slice)
|
||||
{
|
||||
HtmlAttributes attributes;
|
||||
var startPosition = slice.Start;
|
||||
if (TryParse(ref slice, out attributes))
|
||||
if (TryParse(ref slice, out HtmlAttributes attributes))
|
||||
{
|
||||
var inline = processor.Inline;
|
||||
|
||||
@@ -50,8 +49,10 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
|
||||
// If the current block is a Paragraph, but only the HtmlAttributes is used,
|
||||
// Try to attach the attributes to the following block
|
||||
var paragraph = objectToAttach as ParagraphBlock;
|
||||
if (paragraph != null && paragraph.Inline.FirstChild == null && processor.Inline == null && slice.IsEmptyOrWhitespace())
|
||||
if (objectToAttach is ParagraphBlock paragraph &&
|
||||
paragraph.Inline.FirstChild == null &&
|
||||
processor.Inline == null &&
|
||||
slice.IsEmptyOrWhitespace())
|
||||
{
|
||||
var parent = paragraph.Parent;
|
||||
var indexOfParagraph = parent.IndexOf(paragraph);
|
||||
@@ -67,9 +68,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
attributes.CopyTo(currentHtmlAttributes, true, false);
|
||||
|
||||
// Update the position of the attributes
|
||||
int line;
|
||||
int column;
|
||||
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out line, out column);
|
||||
currentHtmlAttributes.Span.Start = processor.GetSourcePosition(startPosition, out int line, out int column);
|
||||
currentHtmlAttributes.Line = line;
|
||||
currentHtmlAttributes.Column = column;
|
||||
currentHtmlAttributes.Span.End = currentHtmlAttributes.Span.Start + slice.Start - startPosition - 1;
|
||||
@@ -223,6 +222,7 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
{
|
||||
// Parse until we match a space or a special html character
|
||||
startValue = line.Start;
|
||||
bool valid = false;
|
||||
while (true)
|
||||
{
|
||||
if (c == '\0')
|
||||
@@ -234,9 +234,10 @@ namespace Markdig.Extensions.GenericAttributes
|
||||
break;
|
||||
}
|
||||
c = line.NextChar();
|
||||
valid = true;
|
||||
}
|
||||
endValue = line.Start - 1;
|
||||
if (endValue == startValue)
|
||||
if (!valid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System;
|
||||
@@ -10,7 +10,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
/// <summary>
|
||||
/// A HTML renderer for a <see cref="SmartyPant"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="Markdig.Renderers.Html.HtmlObjectRenderer{SmartyPant}" />
|
||||
/// <seealso cref="HtmlObjectRenderer{SmartyPant}" />
|
||||
public class HtmlSmartyPantRenderer : HtmlObjectRenderer<SmartyPant>
|
||||
{
|
||||
private static readonly SmartyPantOptions DefaultOptions = new SmartyPantOptions();
|
||||
@@ -21,17 +21,15 @@ namespace Markdig.Extensions.SmartyPants
|
||||
/// Initializes a new instance of the <see cref="HtmlSmartyPantRenderer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public HtmlSmartyPantRenderer(SmartyPantOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
this.options = options;
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
protected override void Write(HtmlRenderer renderer, SmartyPant obj)
|
||||
{
|
||||
string text;
|
||||
if (!options.Mapping.TryGetValue(obj.Type, out text))
|
||||
if (!options.Mapping.TryGetValue(obj.Type, out string text))
|
||||
{
|
||||
DefaultOptions.Mapping.TryGetValue(obj.Type, out text);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System.Diagnostics;
|
||||
@@ -45,5 +45,15 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
return OpeningCharacter != 0 ? OpeningCharacter.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
public LiteralInline AsLiteralInline()
|
||||
{
|
||||
return new LiteralInline(ToString())
|
||||
{
|
||||
Span = Span,
|
||||
Line = Line,
|
||||
Column = Column,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
|
||||
@@ -37,8 +37,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
|
||||
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
|
||||
{
|
||||
var htmlRenderer = renderer as HtmlRenderer;
|
||||
if (htmlRenderer != null)
|
||||
if (renderer is HtmlRenderer htmlRenderer)
|
||||
{
|
||||
if (!htmlRenderer.ObjectRenderers.Contains<HtmlSmartyPantRenderer>())
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
{
|
||||
case '\'':
|
||||
type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines
|
||||
if (slice.PeekChar(1) == '\'')
|
||||
if (slice.PeekChar() == '\'')
|
||||
{
|
||||
slice.NextChar();
|
||||
type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines
|
||||
@@ -95,9 +95,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
// Skip char
|
||||
c = slice.NextChar();
|
||||
|
||||
bool canOpen;
|
||||
bool canClose;
|
||||
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose);
|
||||
CharHelper.CheckOpenCloseDelimiter(pc, c, false, out bool canOpen, out bool canClose);
|
||||
|
||||
bool postProcess = false;
|
||||
|
||||
@@ -156,11 +154,9 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
|
||||
// Create the SmartyPant inline
|
||||
int line;
|
||||
int column;
|
||||
var pant = new SmartyPant()
|
||||
{
|
||||
Span = {Start = processor.GetSourcePosition(startingPosition, out line, out column)},
|
||||
Span = { Start = processor.GetSourcePosition(startingPosition, out int line, out int column) },
|
||||
Line = line,
|
||||
Column = column,
|
||||
OpeningCharacter = openingChar,
|
||||
@@ -195,96 +191,90 @@ namespace Markdig.Extensions.SmartyPants
|
||||
return quotePants;
|
||||
}
|
||||
|
||||
private readonly struct Opener
|
||||
{
|
||||
public readonly int Type;
|
||||
public readonly int Index;
|
||||
|
||||
public Opener(int type, int index)
|
||||
{
|
||||
Type = type;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
private void BlockOnProcessInlinesEnd(InlineProcessor processor, Inline inline)
|
||||
{
|
||||
processor.Block.ProcessInlinesEnd -= BlockOnProcessInlinesEnd;
|
||||
|
||||
var pants = (ListSmartyPants) processor.ParserStates[Index];
|
||||
|
||||
// We only change quote into left or right quotes if we find proper balancing
|
||||
var previousIndices = new int[3] {-1, -1, -1};
|
||||
Stack<Opener> openers = new Stack<Opener>(4);
|
||||
|
||||
for (int i = 0; i < pants.Count; i++)
|
||||
{
|
||||
var quote = pants[i];
|
||||
var quoteType = quote.Type;
|
||||
|
||||
int currentTypeIndex = -1;
|
||||
SmartyPantType expectedLeftQuote = 0;
|
||||
SmartyPantType expectedRightQuote = 0;
|
||||
int type;
|
||||
bool isLeft;
|
||||
|
||||
if (quote.Type == SmartyPantType.LeftQuote || quote.Type == SmartyPantType.RightQuote)
|
||||
if (quoteType == SmartyPantType.LeftQuote || quoteType == SmartyPantType.RightQuote)
|
||||
{
|
||||
currentTypeIndex = 0;
|
||||
expectedLeftQuote = SmartyPantType.LeftQuote;
|
||||
expectedRightQuote = SmartyPantType.RightQuote;
|
||||
type = 0;
|
||||
isLeft = quoteType == SmartyPantType.LeftQuote;
|
||||
}
|
||||
else if (quote.Type == SmartyPantType.LeftDoubleQuote || quote.Type == SmartyPantType.RightDoubleQuote)
|
||||
else if (quoteType == SmartyPantType.LeftDoubleQuote || quoteType == SmartyPantType.RightDoubleQuote)
|
||||
{
|
||||
currentTypeIndex = 1;
|
||||
expectedLeftQuote = SmartyPantType.LeftDoubleQuote;
|
||||
expectedRightQuote = SmartyPantType.RightDoubleQuote;
|
||||
type = 1;
|
||||
isLeft = quoteType == SmartyPantType.LeftDoubleQuote;
|
||||
}
|
||||
else if (quote.Type == SmartyPantType.LeftAngleQuote || quote.Type == SmartyPantType.RightAngleQuote)
|
||||
else if (quoteType == SmartyPantType.LeftAngleQuote || quoteType == SmartyPantType.RightAngleQuote)
|
||||
{
|
||||
currentTypeIndex = 2;
|
||||
expectedLeftQuote = SmartyPantType.LeftAngleQuote;
|
||||
expectedRightQuote = SmartyPantType.RightAngleQuote;
|
||||
}
|
||||
|
||||
if (currentTypeIndex < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int previousIndex = previousIndices[currentTypeIndex];
|
||||
var previousQuote = previousIndex >= 0 ? pants[previousIndex] : null;
|
||||
if (previousQuote == null)
|
||||
{
|
||||
if (quote.Type == expectedLeftQuote)
|
||||
{
|
||||
previousIndices[currentTypeIndex] = i;
|
||||
}
|
||||
type = 2;
|
||||
isLeft = quoteType == SmartyPantType.LeftAngleQuote;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (quote.Type == expectedRightQuote)
|
||||
{
|
||||
// Replace all intermediate unmatched left or right SmartyPants to their literal equivalent
|
||||
pants.RemoveAt(i);
|
||||
i--;
|
||||
for (int j = i; j > previousIndex; j--)
|
||||
{
|
||||
var toReplace = pants[j];
|
||||
pants.RemoveAt(j);
|
||||
toReplace.ReplaceBy(new LiteralInline(toReplace.ToString())
|
||||
{
|
||||
Span = toReplace.Span,
|
||||
Line = toReplace.Line,
|
||||
Column = toReplace.Column,
|
||||
});
|
||||
i--;
|
||||
}
|
||||
quote.ReplaceBy(quote.AsLiteralInline());
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we matched, we remove left/right quotes from the list
|
||||
pants.RemoveAt(previousIndex);
|
||||
previousIndices[currentTypeIndex] = -1;
|
||||
}
|
||||
else
|
||||
if (isLeft)
|
||||
{
|
||||
openers.Push(new Opener(type, i));
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
while (openers.Count > 0)
|
||||
{
|
||||
previousIndices[currentTypeIndex] = i;
|
||||
Opener opener = openers.Pop();
|
||||
var previousQuote = pants[opener.Index];
|
||||
|
||||
if (opener.Type == type)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
previousQuote.ReplaceBy(previousQuote.AsLiteralInline());
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
quote.ReplaceBy(quote.AsLiteralInline());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have any quotes lefts, replace them by there literal equivalent
|
||||
foreach (var quote in pants)
|
||||
foreach (var opener in openers)
|
||||
{
|
||||
quote.ReplaceBy(new LiteralInline(quote.ToString())
|
||||
{
|
||||
Span = quote.Span,
|
||||
Line = quote.Line,
|
||||
Column = quote.Column,
|
||||
});
|
||||
var quote = pants[opener.Index];
|
||||
quote.ReplaceBy(quote.AsLiteralInline());
|
||||
}
|
||||
|
||||
pants.Clear();
|
||||
@@ -294,8 +284,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
bool isFinalProcessing)
|
||||
{
|
||||
// Don't try to process anything if there are no dash
|
||||
var quotePants = state.ParserStates[Index] as ListSmartyPants;
|
||||
if (quotePants == null || !quotePants.HasDash)
|
||||
if (!(state.ParserStates[Index] is ListSmartyPants quotePants) || !quotePants.HasDash)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -309,10 +298,8 @@ namespace Markdig.Extensions.SmartyPants
|
||||
{
|
||||
var next = child.NextSibling;
|
||||
|
||||
if (child is LiteralInline)
|
||||
if (child is LiteralInline literal)
|
||||
{
|
||||
var literal = (LiteralInline) child;
|
||||
|
||||
var startIndex = 0;
|
||||
|
||||
var indexOfDash = literal.Content.IndexOf("--", startIndex);
|
||||
@@ -372,7 +359,7 @@ namespace Markdig.Extensions.SmartyPants
|
||||
}
|
||||
|
||||
|
||||
private class ListSmartyPants : List<SmartyPant>
|
||||
private sealed class ListSmartyPants : List<SmartyPant>
|
||||
{
|
||||
public bool HasDash { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
@@ -138,7 +138,7 @@ namespace Markdig.Extensions.Tables
|
||||
private static void SetRowSpanState(List<GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan)
|
||||
{
|
||||
var lineStart = line.Start;
|
||||
isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '=';
|
||||
isHeaderRow = line.PeekChar() == '=' || line.PeekChar(2) == '=';
|
||||
hasRowSpan = false;
|
||||
foreach (var columnSlice in columns)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Markdig.Extensions.Tables
|
||||
/// <summary>
|
||||
/// Internal state used by the <see cref="GridTableParser"/>
|
||||
/// </summary>
|
||||
internal class GridTableState
|
||||
internal sealed class GridTableState
|
||||
{
|
||||
public int Start { get; set; }
|
||||
|
||||
|
||||
@@ -36,7 +36,8 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
@@ -130,9 +131,9 @@ namespace Markdig.Helpers
|
||||
int result = 0;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
var character = Char.ToUpperInvariant(text[i]);
|
||||
var character = char.ToUpperInvariant(text[i]);
|
||||
var candidate = romanMap[character];
|
||||
if (i + 1 < text.Length && candidate < romanMap[Char.ToUpperInvariant(text[i + 1])])
|
||||
if (i + 1 < text.Length && candidate < romanMap[char.ToUpperInvariant(text[i + 1])])
|
||||
{
|
||||
result -= candidate;
|
||||
}
|
||||
@@ -147,7 +148,9 @@ namespace Markdig.Helpers
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static int AddTab(int column)
|
||||
{
|
||||
return ((column + TabSize) / TabSize) * TabSize;
|
||||
// return ((column + TabSize) / TabSize) * TabSize;
|
||||
Debug.Assert(TabSize == 4, "Change the AddTab implementation if TabSize is no longer a power of 2");
|
||||
return TabSize + (column & ~(TabSize - 1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
@@ -180,7 +183,7 @@ namespace Markdig.Helpers
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsControl(this char c)
|
||||
{
|
||||
return c < ' ' || Char.IsControl(c);
|
||||
return c < ' ' || char.IsControl(c);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
@@ -193,7 +196,7 @@ namespace Markdig.Helpers
|
||||
//[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsWhiteSpaceOrZero(this char c)
|
||||
{
|
||||
return IsWhitespace(c) || IsZero(c);
|
||||
return IsZero(c) || IsWhitespace(c);
|
||||
}
|
||||
|
||||
// Note that we are not considering the character & as a punctuation in HTML
|
||||
@@ -294,14 +297,14 @@ namespace Markdig.Helpers
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsAlphaUpper(this char c)
|
||||
{
|
||||
return c >= 'A' && c <= 'Z';
|
||||
{
|
||||
return (uint)(c - 'A') <= ('Z' - 'A');
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsAlpha(this char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
return ((uint)(c - 'a') <= ('z' - 'a')) || ((uint)(c - 'A') <= ('Z' - 'A'));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
@@ -313,7 +316,7 @@ namespace Markdig.Helpers
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public static bool IsDigit(this char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
return (uint)(c - '0') <= ('9' - '0');
|
||||
}
|
||||
|
||||
public static bool IsAsciiPunctuation(this char c)
|
||||
|
||||
@@ -118,20 +118,11 @@ namespace Markdig.Helpers
|
||||
public int IndexOfOpeningCharacter(string text, int start, int end)
|
||||
{
|
||||
var maxChar = isOpeningCharacter.Length;
|
||||
#if SUPPORT_UNSAFE
|
||||
|
||||
unsafe
|
||||
#endif
|
||||
{
|
||||
#if SUPPORT_FIXED_STRING
|
||||
fixed (char* pText = text)
|
||||
#else
|
||||
var pText = text;
|
||||
#endif
|
||||
#if SUPPORT_UNSAFE
|
||||
fixed (char* pText = text)
|
||||
fixed (bool* openingChars = isOpeningCharacter)
|
||||
#else
|
||||
var openingChars = isOpeningCharacter;
|
||||
#endif
|
||||
{
|
||||
if (nonAsciiMap == null)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Markdig.Helpers
|
||||
/// <para>Something between a Trie and a full Radix tree, but stored linearly in memory</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The value associated with the key</typeparam>
|
||||
internal class CompactPrefixTree<TValue>
|
||||
internal sealed class CompactPrefixTree<TValue>
|
||||
//#if !LEGACY
|
||||
// : IReadOnlyDictionary<string, TValue>, IReadOnlyList<KeyValuePair<string, TValue>>
|
||||
//#endif
|
||||
|
||||
86
src/Markdig/Helpers/CustomArrayPool.cs
Normal file
86
src/Markdig/Helpers/CustomArrayPool.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Threading;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
internal sealed class CustomArrayPool<T>
|
||||
{
|
||||
private sealed class Bucket
|
||||
{
|
||||
private readonly T[][] _buffers;
|
||||
|
||||
private int _index;
|
||||
private int _lock;
|
||||
|
||||
public Bucket(int numberOfBuffers)
|
||||
{
|
||||
_buffers = new T[numberOfBuffers][];
|
||||
}
|
||||
|
||||
public T[] Rent()
|
||||
{
|
||||
T[][] buffers = _buffers;
|
||||
T[] buffer = null;
|
||||
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
|
||||
{
|
||||
int index = _index;
|
||||
if ((uint)index < (uint)buffers.Length)
|
||||
{
|
||||
buffer = buffers[index];
|
||||
buffers[index] = null;
|
||||
_index = index + 1;
|
||||
}
|
||||
Interlocked.Decrement(ref _lock);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void Return(T[] array)
|
||||
{
|
||||
var buffers = _buffers;
|
||||
if (Interlocked.CompareExchange(ref _lock, 1, 0) == 0)
|
||||
{
|
||||
int index = _index - 1;
|
||||
if ((uint)index < (uint)buffers.Length)
|
||||
{
|
||||
buffers[index] = array;
|
||||
_index = index;
|
||||
}
|
||||
Interlocked.Decrement(ref _lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bucket _bucket4, _bucket8, _bucket16, _bucket32;
|
||||
|
||||
public CustomArrayPool(int size4, int size8, int size16, int size32)
|
||||
{
|
||||
_bucket4 = new Bucket(size4);
|
||||
_bucket8 = new Bucket(size8);
|
||||
_bucket16 = new Bucket(size16);
|
||||
_bucket32 = new Bucket(size32);
|
||||
}
|
||||
|
||||
private Bucket SelectBucket(int length)
|
||||
{
|
||||
switch (length)
|
||||
{
|
||||
case 4: return _bucket4;
|
||||
case 8: return _bucket8;
|
||||
case 16: return _bucket16;
|
||||
case 32: return _bucket32;
|
||||
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Rent(int length)
|
||||
{
|
||||
return SelectBucket(length)?.Rent() ?? new T[length];
|
||||
}
|
||||
|
||||
public void Return(T[] array)
|
||||
{
|
||||
SelectBucket(array.Length)?.Return(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,7 +373,7 @@ namespace Markdig.Helpers
|
||||
}
|
||||
builder.Append('-');
|
||||
builder.Append('-');
|
||||
if (text.PeekChar(1) == '>')
|
||||
if (text.PeekChar() == '>')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -597,20 +597,23 @@ namespace Markdig.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
if (!isAutoLink)
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
if (hasEscape && !c.IsAsciiPunctuation())
|
||||
{
|
||||
buffer.Append('\\');
|
||||
}
|
||||
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
c = text.NextChar();
|
||||
continue;
|
||||
}
|
||||
// If we have an escape
|
||||
if (c == '\\')
|
||||
{
|
||||
hasEscape = true;
|
||||
c = text.NextChar();
|
||||
continue;
|
||||
}
|
||||
|
||||
hasEscape = false;
|
||||
hasEscape = false;
|
||||
}
|
||||
|
||||
if (IsEndOfUri(c, isAutoLink))
|
||||
{
|
||||
@@ -622,10 +625,7 @@ namespace Markdig.Helpers
|
||||
{
|
||||
if (c == '&')
|
||||
{
|
||||
int entityNameStart;
|
||||
int entityNameLength;
|
||||
int entityValue;
|
||||
if (HtmlHelper.ScanEntity(text, out entityValue, out entityNameStart, out entityNameLength) > 0)
|
||||
if (HtmlHelper.ScanEntity(text, out _, out _, out _) > 0)
|
||||
{
|
||||
isValid = true;
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Text;
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for StringBuilder with <see cref="StringSlice"/>
|
||||
/// Extensions for StringBuilder
|
||||
/// </summary>
|
||||
public static class StringBuilderExtensions
|
||||
{
|
||||
@@ -19,5 +19,12 @@ namespace Markdig.Helpers
|
||||
{
|
||||
return builder.Append(slice.Text, slice.Start, slice.Length);
|
||||
}
|
||||
|
||||
internal static string GetStringAndReset(this StringBuilder builder)
|
||||
{
|
||||
string text = builder.ToString();
|
||||
builder.Length = 0;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ namespace Markdig.Helpers
|
||||
return line.Slice;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return Slice.ToString();
|
||||
}
|
||||
|
||||
@@ -5,26 +5,28 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Markdig.Extensions.Tables;
|
||||
|
||||
namespace Markdig.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// A group of <see cref="StringLine"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Collections.IEnumerable" />
|
||||
/// <seealso cref="IEnumerable" />
|
||||
public struct StringLineGroup : IEnumerable
|
||||
{
|
||||
// Feel free to change these numbers if you see a positive change
|
||||
private static readonly CustomArrayPool<StringLine> _pool
|
||||
= new CustomArrayPool<StringLine>(512, 386, 128, 64);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StringLineGroup"/> class.
|
||||
/// </summary>
|
||||
/// <param name="capacity"></param>
|
||||
public StringLineGroup(int capacity)
|
||||
public StringLineGroup(int capacity, bool willRelease = false)
|
||||
{
|
||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||
Lines = new StringLine[capacity];
|
||||
Lines = _pool.Rent(willRelease ? Math.Max(8, capacity) : capacity);
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
@@ -100,7 +102,7 @@ namespace Markdig.Helpers
|
||||
Lines[Count++] = new StringLine(ref slice);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return ToSlice().ToString();
|
||||
}
|
||||
@@ -110,7 +112,7 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <param name="lineOffsets">The position of the `\n` line offsets from the beginning of the returned slice.</param>
|
||||
/// <returns>A single slice concatenating the lines of this instance</returns>
|
||||
public StringSlice ToSlice(List<LineOffset> lineOffsets = null)
|
||||
public readonly StringSlice ToSlice(List<LineOffset> lineOffsets = null)
|
||||
{
|
||||
// Optimization case when no lines
|
||||
if (Count == 0)
|
||||
@@ -128,6 +130,11 @@ namespace Markdig.Helpers
|
||||
return Lines[0];
|
||||
}
|
||||
|
||||
if (lineOffsets != null && lineOffsets.Capacity < lineOffsets.Count + Count)
|
||||
{
|
||||
lineOffsets.Capacity = Math.Max(lineOffsets.Count + Count, lineOffsets.Capacity * 2);
|
||||
}
|
||||
|
||||
// Else use a builder
|
||||
var builder = StringBuilderCache.Local();
|
||||
int previousStartOfLine = 0;
|
||||
@@ -151,16 +158,14 @@ namespace Markdig.Helpers
|
||||
{
|
||||
lineOffsets.Add(new LineOffset(Lines[Count - 1].Position, Lines[Count - 1].Column, Lines[Count - 1].Slice.Start - Lines[Count - 1].Position, previousStartOfLine, builder.Length));
|
||||
}
|
||||
var str = builder.ToString();
|
||||
builder.Length = 0;
|
||||
return new StringSlice(str);
|
||||
return new StringSlice(builder.GetStringAndReset());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this instance into a <see cref="ICharIterator"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Iterator ToCharIterator()
|
||||
public readonly Iterator ToCharIterator()
|
||||
{
|
||||
return new Iterator(this);
|
||||
}
|
||||
@@ -183,14 +188,24 @@ namespace Markdig.Helpers
|
||||
|
||||
private void IncreaseCapacity()
|
||||
{
|
||||
var newItems = new StringLine[Lines.Length * 2];
|
||||
var newItems = _pool.Rent(Lines.Length * 2);
|
||||
if (Count > 0)
|
||||
{
|
||||
Array.Copy(Lines, 0, newItems, 0, Count);
|
||||
Array.Clear(Lines, 0, Count);
|
||||
}
|
||||
_pool.Return(Lines);
|
||||
Lines = newItems;
|
||||
}
|
||||
|
||||
internal void Release()
|
||||
{
|
||||
Array.Clear(Lines, 0, Count);
|
||||
_pool.Return(Lines);
|
||||
Lines = null;
|
||||
Count = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The iterator used to iterate other the lines.
|
||||
/// </summary>
|
||||
@@ -207,7 +222,7 @@ namespace Markdig.Helpers
|
||||
_offset = -1;
|
||||
SliceIndex = 0;
|
||||
CurrentChar = '\0';
|
||||
End = -2;
|
||||
End = -2;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
End += lines.Lines[i].Slice.Length + 1; // Add chars
|
||||
@@ -221,7 +236,7 @@ namespace Markdig.Helpers
|
||||
|
||||
public int End { get; private set; }
|
||||
|
||||
public bool IsEmpty => Start > End;
|
||||
public readonly bool IsEmpty => Start > End;
|
||||
|
||||
public int SliceIndex { get; private set; }
|
||||
|
||||
@@ -277,7 +292,7 @@ namespace Markdig.Helpers
|
||||
return CurrentChar;
|
||||
}
|
||||
|
||||
public char PeekChar(int offset = 1)
|
||||
public readonly char PeekChar(int offset = 1)
|
||||
{
|
||||
if (offset < 0) throw new ArgumentOutOfRangeException("Negative offset are not supported for StringLineGroup", nameof(offset));
|
||||
|
||||
@@ -298,17 +313,15 @@ namespace Markdig.Helpers
|
||||
public bool TrimStart()
|
||||
{
|
||||
var c = CurrentChar;
|
||||
bool hasSpaces = false;
|
||||
while (c.IsWhitespace())
|
||||
{
|
||||
hasSpaces = true;
|
||||
c = NextChar();
|
||||
}
|
||||
return hasSpaces;
|
||||
return IsEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
public struct LineOffset
|
||||
public readonly struct LineOffset
|
||||
{
|
||||
public LineOffset(int linePosition, int column, int offset, int start, int end)
|
||||
{
|
||||
|
||||
@@ -38,8 +38,7 @@ namespace Markdig.Helpers
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public StringSlice(string text, int start, int end)
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
Text = text;
|
||||
Text = text ?? throw new ArgumentNullException(nameof(text));
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
@@ -62,24 +61,40 @@ namespace Markdig.Helpers
|
||||
/// <summary>
|
||||
/// Gets the length.
|
||||
/// </summary>
|
||||
public int Length => End - Start + 1;
|
||||
public readonly int Length => End - Start + 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current character.
|
||||
/// </summary>
|
||||
public char CurrentChar => Start <= End ? this[Start] : '\0';
|
||||
public readonly char CurrentChar
|
||||
{
|
||||
get
|
||||
{
|
||||
int start = Start;
|
||||
return start <= End ? Text[start] : '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty => Start > End;
|
||||
public readonly bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
get => Start > End;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="System.Char"/> at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>A character in the slice at the specified index (not from <see cref="Start"/> but from the begining of the slice)</returns>
|
||||
public char this[int index] => Text[index];
|
||||
public readonly char this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
get => Text[index];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Goes to the next character, incrementing the <see cref="Start" /> position.
|
||||
@@ -90,13 +105,27 @@ namespace Markdig.Helpers
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char NextChar()
|
||||
{
|
||||
Start++;
|
||||
if (Start > End)
|
||||
int start = Start;
|
||||
if (start >= End)
|
||||
{
|
||||
Start = End + 1;
|
||||
return '\0';
|
||||
}
|
||||
return Text[Start];
|
||||
start++;
|
||||
Start = start;
|
||||
return Text[start];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Peeks a character at the offset of 1 from the current <see cref="Start"/> position
|
||||
/// inside the range <see cref="Start"/> and <see cref="End"/>, returns `\0` if outside this range.
|
||||
/// </summary>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public readonly char PeekChar()
|
||||
{
|
||||
int index = Start + 1;
|
||||
return index <= End ? Text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,10 +135,10 @@ namespace Markdig.Helpers
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekChar(int offset = 1)
|
||||
public readonly char PeekChar(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= Start && index <= End ? Text[index] : (char) 0;
|
||||
return index >= Start && index <= End ? Text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -117,9 +146,10 @@ namespace Markdig.Helpers
|
||||
/// </summary>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekCharAbsolute(int index)
|
||||
public readonly char PeekCharAbsolute(int index)
|
||||
{
|
||||
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
|
||||
string text = Text;
|
||||
return (uint)index < (uint)text.Length ? text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,10 +159,11 @@ namespace Markdig.Helpers
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>The character at offset, returns `\0` if none.</returns>
|
||||
[MethodImpl(MethodImplOptionPortable.AggressiveInlining)]
|
||||
public char PeekCharExtra(int offset)
|
||||
public readonly char PeekCharExtra(int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
return index >= 0 && index < Text.Length ? Text[index] : (char)0;
|
||||
var text = Text;
|
||||
return (uint)index < (uint)text.Length ? text[index] : '\0';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,7 +172,7 @@ namespace Markdig.Helpers
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool Match(string text, int offset = 0)
|
||||
public readonly bool Match(string text, int offset = 0)
|
||||
{
|
||||
return Match(text, End, offset);
|
||||
}
|
||||
@@ -153,19 +184,22 @@ namespace Markdig.Helpers
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool Match(string text, int end, int offset)
|
||||
public readonly bool Match(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
|
||||
if (end - index + 1 < text.Length)
|
||||
return false;
|
||||
|
||||
string sliceText = Text;
|
||||
for (int i = 0; i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != Text[index])
|
||||
if (text[i] != sliceText[index])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +230,7 @@ namespace Markdig.Helpers
|
||||
/// <param name="text">The text.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool MatchLowercase(string text, int offset = 0)
|
||||
public readonly bool MatchLowercase(string text, int offset = 0)
|
||||
{
|
||||
return MatchLowercase(text, End, offset);
|
||||
}
|
||||
@@ -208,19 +242,22 @@ namespace Markdig.Helpers
|
||||
/// <param name="end">The end.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns><c>true</c> if the text matches; <c>false</c> otherwise</returns>
|
||||
public bool MatchLowercase(string text, int end, int offset)
|
||||
public readonly bool MatchLowercase(string text, int end, int offset)
|
||||
{
|
||||
var index = Start + offset;
|
||||
int i = 0;
|
||||
for (; index <= end && i < text.Length; i++, index++)
|
||||
|
||||
if (end - index + 1 < text.Length)
|
||||
return false;
|
||||
|
||||
string sliceText = Text;
|
||||
for (int i = 0; i < text.Length; i++, index++)
|
||||
{
|
||||
if (text[i] != char.ToLowerInvariant(Text[index]))
|
||||
if (text[i] != char.ToLowerInvariant(sliceText[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return i == text.Length;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,46 +267,41 @@ namespace Markdig.Helpers
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="ignoreCase">true if ignore case</param>
|
||||
/// <returns><c>true</c> if the text was found; <c>false</c> otherwise</returns>
|
||||
public int IndexOf(string text, int offset = 0, bool ignoreCase = false)
|
||||
public readonly int IndexOf(string text, int offset = 0, bool ignoreCase = false)
|
||||
{
|
||||
var end = End - text.Length + 1;
|
||||
if (ignoreCase)
|
||||
{
|
||||
for (int i = Start + offset; i <= end; i++)
|
||||
{
|
||||
if (MatchLowercase(text, End, i - Start))
|
||||
{
|
||||
return i; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = Start + offset; i <= end; i++)
|
||||
{
|
||||
if (Match(text, End, i - Start))
|
||||
{
|
||||
return i; ;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
offset += Start;
|
||||
int length = End - offset + 1;
|
||||
|
||||
if (length <= 0)
|
||||
return -1;
|
||||
|
||||
#if NETCORE
|
||||
var span = Text.AsSpan(offset, length);
|
||||
int index = ignoreCase ? span.IndexOf(text, StringComparison.OrdinalIgnoreCase) : span.IndexOf(text);
|
||||
return index == -1 ? index : index + offset;
|
||||
#else
|
||||
return Text.IndexOf(text, offset, length, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for the specified character within this slice.
|
||||
/// </summary>
|
||||
/// <returns>A value >= 0 if the character was found, otherwise < 0</returns>
|
||||
public int IndexOf(char c)
|
||||
public readonly int IndexOf(char c)
|
||||
{
|
||||
for (int i = Start; i <= End; i++)
|
||||
{
|
||||
if (Text[i] == c)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
int start = Start;
|
||||
int length = End - start + 1;
|
||||
|
||||
if (length <= 0)
|
||||
return -1;
|
||||
|
||||
#if NETCORE
|
||||
int index = Text.AsSpan(start, length).IndexOf(c);
|
||||
return index == -1 ? index : index + start;
|
||||
#else
|
||||
return Text.IndexOf(c, start, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -281,7 +313,6 @@ namespace Markdig.Helpers
|
||||
public bool TrimStart()
|
||||
{
|
||||
// Strip leading spaces
|
||||
var start = Start;
|
||||
for (; Start <= End; Start++)
|
||||
{
|
||||
if (!Text[Start].IsWhitespace())
|
||||
@@ -289,7 +320,7 @@ namespace Markdig.Helpers
|
||||
break;
|
||||
}
|
||||
}
|
||||
return start != Start;
|
||||
return IsEmpty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -336,31 +367,29 @@ namespace Markdig.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String" /> that represents this instance.
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String" /> that represents this instance.
|
||||
/// A <see cref="string" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
public readonly override string ToString()
|
||||
{
|
||||
if (Text != null && Start <= End)
|
||||
{
|
||||
var length = Length;
|
||||
if (Start == 0 && Text.Length == length)
|
||||
{
|
||||
return Text;
|
||||
}
|
||||
string text = Text;
|
||||
int start = Start;
|
||||
int length = End - start + 1;
|
||||
|
||||
return Text.Substring(Start, length);
|
||||
if (text is null || length <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
return text.Substring(start, length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this slice is empty or made only of whitespaces.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if this slice is empty or made only of whitespaces; <c>false</c> otherwise</returns>
|
||||
public bool IsEmptyOrWhitespace()
|
||||
public readonly bool IsEmptyOrWhitespace()
|
||||
{
|
||||
for (int i = Start; i <= End; i++)
|
||||
{
|
||||
|
||||
@@ -1,84 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
|
||||
<Copyright>Alexandre Mutel</Copyright>
|
||||
<AssemblyTitle>Markdig</AssemblyTitle>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.17.0</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
|
||||
<AssemblyName>Markdig</AssemblyName>
|
||||
<PackageId>Markdig</PackageId>
|
||||
<PackageId Condition="'$(SignAssembly)' == 'true'">Markdig.Signed</PackageId>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
|
||||
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
|
||||
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netstandard1.1' ">1.6.0</NetStandardImplicitPackageVersion>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'portable40-net40+sl5+win8+wp8+wpa81' ">
|
||||
<Reference Include="mscorlib" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net35' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'net40' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||
<DefineConstants>$(DefineConstants);SUPPORT_FIXED_STRING;SUPPORT_UNSAFE;NETCORE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.17763.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
|
||||
<DefineConstants>$(DefineConstants);NETSTANDARD_11;SUPPORT_UNSAFE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(SignAssembly)' == 'true' ">
|
||||
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
|
||||
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Special packages and imports for UWP support -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
|
||||
<Import Project="Markdig.targets" />
|
||||
</Project>
|
||||
|
||||
43
src/Markdig/Markdig.targets
Normal file
43
src/Markdig/Markdig.targets
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Description>A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET with 20+ builtin extensions (pipetables, footnotes, definition lists... etc.)</Description>
|
||||
<Copyright>Alexandre Mutel</Copyright>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<VersionPrefix>0.18.0</VersionPrefix>
|
||||
<Authors>Alexandre Mutel</Authors>
|
||||
<TargetFrameworks>net35;net40;netstandard2.0;uap10.0;netcoreapp2.1</TargetFrameworks>
|
||||
<PackageTags>Markdown CommonMark md html md2html</PackageTags>
|
||||
<PackageReleaseNotes>https://github.com/lunet-io/markdig/blob/master/changelog.md</PackageReleaseNotes>
|
||||
<PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/lunet-io/markdig/master/img/markdig.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://github.com/lunet-io/markdig</PackageProjectUrl>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
|
||||
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
|
||||
<TargetPlatformVersion Condition="'$(TargetPlatformVersion)' == ''">10.0.17763.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="'$(TargetPlatformMinVersion)' == ''">10.0.10240.0</TargetPlatformMinVersion>
|
||||
<DefineConstants>$(DefineConstants);UAP</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Special packages and imports for UWP support -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSBuild.Sdk.Extras" Version="1.0.9" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(TargetFramework)' == 'uap10.0' ">
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform " Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildSDKExtrasTargets)" Condition="Exists('$(MSBuildSDKExtrasTargets)')" />
|
||||
</Project>
|
||||
@@ -17,7 +17,7 @@ namespace Markdig
|
||||
/// </summary>
|
||||
public static partial class Markdown
|
||||
{
|
||||
#if NETSTANDARD_11
|
||||
#if UAP
|
||||
public static readonly string Version = typeof(Markdown).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
|
||||
#else
|
||||
public static readonly string Version = ((AssemblyFileVersionAttribute) typeof(Markdown).Assembly.GetCustomAttributes(typeof(AssemblyFileVersionAttribute), false)[0]).Version;
|
||||
@@ -73,9 +73,18 @@ namespace Markdig
|
||||
public static string ToHtml(string markdown, MarkdownPipeline pipeline = null)
|
||||
{
|
||||
if (markdown == null) throw new ArgumentNullException(nameof(markdown));
|
||||
var writer = new StringWriter();
|
||||
ToHtml(markdown, writer, pipeline);
|
||||
return writer.ToString();
|
||||
pipeline = pipeline ?? new MarkdownPipelineBuilder().Build();
|
||||
pipeline = CheckForSelfPipeline(pipeline, markdown);
|
||||
|
||||
var renderer = pipeline.GetCacheableHtmlRenderer();
|
||||
|
||||
var document = Parse(markdown, pipeline);
|
||||
renderer.Render(document);
|
||||
renderer.Writer.Flush();
|
||||
|
||||
string html = renderer.Writer.ToString();
|
||||
pipeline.ReleaseCacheableHtmlRenderer(renderer);
|
||||
return html;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -184,7 +193,8 @@ namespace Markdig
|
||||
var renderer = new HtmlRenderer(writer)
|
||||
{
|
||||
EnableHtmlForBlock = false,
|
||||
EnableHtmlForInline = false
|
||||
EnableHtmlForInline = false,
|
||||
EnableHtmlEscape = false,
|
||||
};
|
||||
pipeline.Setup(renderer);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System;
|
||||
@@ -63,5 +63,42 @@ namespace Markdig
|
||||
extension.Setup(this, renderer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HtmlRendererCache _rendererCache = null;
|
||||
|
||||
internal HtmlRenderer GetCacheableHtmlRenderer()
|
||||
{
|
||||
if (_rendererCache is null)
|
||||
{
|
||||
_rendererCache = new HtmlRendererCache
|
||||
{
|
||||
OnNewInstanceCreated = Setup
|
||||
};
|
||||
}
|
||||
return _rendererCache.Get();
|
||||
}
|
||||
internal void ReleaseCacheableHtmlRenderer(HtmlRenderer renderer)
|
||||
{
|
||||
_rendererCache.Release(renderer);
|
||||
}
|
||||
|
||||
private sealed class HtmlRendererCache : ObjectCache<HtmlRenderer>
|
||||
{
|
||||
public Action<HtmlRenderer> OnNewInstanceCreated;
|
||||
|
||||
protected override HtmlRenderer NewInstance()
|
||||
{
|
||||
var writer = new StringWriter();
|
||||
var renderer = new HtmlRenderer(writer);
|
||||
OnNewInstanceCreated(renderer);
|
||||
return renderer;
|
||||
}
|
||||
|
||||
protected override void Reset(HtmlRenderer instance)
|
||||
{
|
||||
instance.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,6 +500,11 @@ namespace Markdig.Parsers
|
||||
if (!block.Parser.Close(this, block))
|
||||
{
|
||||
block.Parent?.Remove(block);
|
||||
|
||||
if (block is LeafBlock leaf)
|
||||
{
|
||||
leaf.Lines.Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -873,7 +878,7 @@ namespace Markdig.Parsers
|
||||
NewBlocks.Clear();
|
||||
}
|
||||
|
||||
private class BlockParserStateCache : ObjectCache<BlockProcessor>
|
||||
private sealed class BlockParserStateCache : ObjectCache<BlockProcessor>
|
||||
{
|
||||
private readonly BlockProcessor root;
|
||||
|
||||
|
||||
@@ -124,7 +124,9 @@ namespace Markdig.Parsers
|
||||
}
|
||||
else
|
||||
{
|
||||
infoString = line.ToString().Trim();
|
||||
var lineCopy = line;
|
||||
lineCopy.Trim();
|
||||
infoString = lineCopy.ToString();
|
||||
}
|
||||
|
||||
fenced.Info = HtmlHelper.Unescape(infoString);
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Markdig.Parsers
|
||||
if (c == '!')
|
||||
{
|
||||
c = line.NextChar();
|
||||
if (c == '-' && line.PeekChar(1) == '-')
|
||||
if (c == '-' && line.PeekChar() == '-')
|
||||
{
|
||||
return CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition); // group 2
|
||||
}
|
||||
@@ -140,7 +140,7 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
if (
|
||||
!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar(1) == '>') || c.IsWhitespace() ||
|
||||
!(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() ||
|
||||
c == '\0'))
|
||||
{
|
||||
return BlockState.None;
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace Markdig.Parsers
|
||||
previousLineIndexForSliceOffset = 0;
|
||||
lineOffsets.Clear();
|
||||
var text = leafBlock.Lines.ToSlice(lineOffsets);
|
||||
leafBlock.Lines = new StringLineGroup();
|
||||
leafBlock.Lines.Release();
|
||||
|
||||
int previousStart = -1;
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@ namespace Markdig.Parsers.Inlines
|
||||
|
||||
var c = slice.CurrentChar;
|
||||
|
||||
int line;
|
||||
int column;
|
||||
var startPosition = processor.GetSourcePosition(slice.Start, out line, out column);
|
||||
var startPosition = processor.GetSourcePosition(slice.Start, out int line, out int column);
|
||||
|
||||
bool isImage = false;
|
||||
if (c == '!')
|
||||
@@ -49,11 +47,9 @@ namespace Markdig.Parsers.Inlines
|
||||
// If this is not an image, we may have a reference link shortcut
|
||||
// so we try to resolve it here
|
||||
var saved = slice;
|
||||
string label;
|
||||
|
||||
SourceSpan labelSpan;
|
||||
// If the label is followed by either a ( or a [, this is not a shortcut
|
||||
if (LinkHelper.TryParseLabel(ref slice, out label, out labelSpan))
|
||||
if (LinkHelper.TryParseLabel(ref slice, out string label, out SourceSpan labelSpan))
|
||||
{
|
||||
if (!processor.Document.ContainsLinkReferenceDefinition(label))
|
||||
{
|
||||
@@ -97,230 +93,208 @@ namespace Markdig.Parsers.Inlines
|
||||
|
||||
private bool ProcessLinkReference(InlineProcessor state, string label, bool isShortcut, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition)
|
||||
{
|
||||
bool isValidLink = false;
|
||||
LinkReferenceDefinition linkRef;
|
||||
if (state.Document.TryGetLinkReferenceDefinition(label, out linkRef))
|
||||
if (!state.Document.TryGetLinkReferenceDefinition(label, out LinkReferenceDefinition linkRef))
|
||||
{
|
||||
Inline link = null;
|
||||
// Try to use a callback directly defined on the LinkReferenceDefinition
|
||||
if (linkRef.CreateLinkInline != null)
|
||||
{
|
||||
link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a default link if the callback was not found
|
||||
if (link == null)
|
||||
Inline link = null;
|
||||
// Try to use a callback directly defined on the LinkReferenceDefinition
|
||||
if (linkRef.CreateLinkInline != null)
|
||||
{
|
||||
link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild);
|
||||
}
|
||||
|
||||
// Create a default link if the callback was not found
|
||||
if (link == null)
|
||||
{
|
||||
// Inline Link
|
||||
link = new LinkInline()
|
||||
{
|
||||
// Inline Link
|
||||
link = new LinkInline()
|
||||
Url = HtmlHelper.Unescape(linkRef.Url),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title),
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
UrlSpan = linkRef.UrlSpan,
|
||||
IsImage = parent.IsImage,
|
||||
IsShortcut = isShortcut,
|
||||
Reference = linkRef,
|
||||
Span = new SourceSpan(parent.Span.Start, endPosition),
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
}
|
||||
|
||||
if (link is ContainerInline containerLink)
|
||||
{
|
||||
var child = parent.FirstChild;
|
||||
if (child == null)
|
||||
{
|
||||
child = new LiteralInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(linkRef.Url),
|
||||
Title = HtmlHelper.Unescape(linkRef.Title),
|
||||
Label = label,
|
||||
LabelSpan = labelSpan,
|
||||
UrlSpan = linkRef.UrlSpan,
|
||||
IsImage = parent.IsImage,
|
||||
IsShortcut = isShortcut,
|
||||
Reference = linkRef,
|
||||
Span = new SourceSpan(parent.Span.Start, endPosition),
|
||||
Content = StringSlice.Empty,
|
||||
IsClosed = true,
|
||||
// Not exact but we leave it like this
|
||||
Span = parent.Span,
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
containerLink.AppendChild(child);
|
||||
}
|
||||
|
||||
var containerLink = link as ContainerInline;
|
||||
if (containerLink != null)
|
||||
else
|
||||
{
|
||||
var child = parent.FirstChild;
|
||||
if (child == null)
|
||||
// Insert all child into the link
|
||||
while (child != null)
|
||||
{
|
||||
child = new LiteralInline()
|
||||
{
|
||||
Content = StringSlice.Empty,
|
||||
IsClosed = true,
|
||||
// Not exact but we leave it like this
|
||||
Span = parent.Span,
|
||||
Line = parent.Line,
|
||||
Column = parent.Column,
|
||||
};
|
||||
var next = child.NextSibling;
|
||||
child.Remove();
|
||||
containerLink.AppendChild(child);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert all child into the link
|
||||
while (child != null)
|
||||
{
|
||||
var next = child.NextSibling;
|
||||
child.Remove();
|
||||
containerLink.AppendChild(child);
|
||||
child = next;
|
||||
}
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
|
||||
link.IsClosed = true;
|
||||
|
||||
// Process emphasis delimiters
|
||||
state.PostProcessInlines(0, link, null, false);
|
||||
|
||||
state.Inline = link;
|
||||
isValidLink = true;
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// // Else output a literal, leave it opened as we may have literals after
|
||||
// // that could be append to this one
|
||||
// var literal = new LiteralInline()
|
||||
// {
|
||||
// ContentBuilder = processor.StringBuilders.Get().Append('[').Append(label).Append(']')
|
||||
// };
|
||||
// processor.Inline = literal;
|
||||
//}
|
||||
return isValidLink;
|
||||
|
||||
link.IsClosed = true;
|
||||
|
||||
// Process emphasis delimiters
|
||||
state.PostProcessInlines(0, link, null, false);
|
||||
|
||||
state.Inline = link;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text)
|
||||
{
|
||||
LinkDelimiterInline openParent = null;
|
||||
foreach (var parent in inlineState.Inline.FindParentOfType<LinkDelimiterInline>())
|
||||
LinkDelimiterInline openParent = inlineState.Inline.FirstParentOfType<LinkDelimiterInline>();
|
||||
|
||||
if (openParent is null)
|
||||
{
|
||||
openParent = parent;
|
||||
break;
|
||||
}
|
||||
|
||||
if (openParent != null)
|
||||
{
|
||||
// If we do find one, but it’s not active,
|
||||
// we remove the inactive delimiter from the stack,
|
||||
// and return a literal text node ].
|
||||
if (!openParent.IsActive)
|
||||
{
|
||||
inlineState.Inline = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice("["),
|
||||
Span = openParent.Span,
|
||||
Line = openParent.Line,
|
||||
Column = openParent.Column,
|
||||
};
|
||||
openParent.ReplaceBy(inlineState.Inline);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we find one and it’s active,
|
||||
// then we parse ahead to see if we have
|
||||
// an inline link/image, reference link/image,
|
||||
// compact reference link/image,
|
||||
// or shortcut reference link/image
|
||||
var parentDelimiter = openParent.Parent;
|
||||
var savedText = text;
|
||||
switch (text.CurrentChar)
|
||||
{
|
||||
case '(':
|
||||
string url;
|
||||
string title;
|
||||
SourceSpan linkSpan;
|
||||
SourceSpan titleSpan;
|
||||
if (LinkHelper.TryParseInlineLink(ref text, out url, out title, out linkSpan, out titleSpan))
|
||||
{
|
||||
// Inline Link
|
||||
var link = new LinkInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(url),
|
||||
Title = HtmlHelper.Unescape(title),
|
||||
IsImage = openParent.IsImage,
|
||||
LabelSpan = openParent.LabelSpan,
|
||||
UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
|
||||
TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan),
|
||||
Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start -1)),
|
||||
Line = openParent.Line,
|
||||
Column = openParent.Column,
|
||||
};
|
||||
|
||||
openParent.ReplaceBy(link);
|
||||
// Notifies processor as we are creating an inline locally
|
||||
inlineState.Inline = link;
|
||||
|
||||
// Process emphasis delimiters
|
||||
inlineState.PostProcessInlines(0, link, null, false);
|
||||
|
||||
// If we have a link (and not an image),
|
||||
// we also set all [ delimiters before the opening delimiter to inactive.
|
||||
// (This will prevent us from getting links within links.)
|
||||
if (!openParent.IsImage)
|
||||
{
|
||||
MarkParentAsInactive(parentDelimiter);
|
||||
}
|
||||
|
||||
link.IsClosed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
text = savedText;
|
||||
goto default;
|
||||
default:
|
||||
var labelSpan = SourceSpan.Empty;
|
||||
string label = null;
|
||||
bool isLabelSpanLocal = true;
|
||||
|
||||
bool isShortcut = false;
|
||||
// Handle Collapsed links
|
||||
if (text.CurrentChar == '[')
|
||||
{
|
||||
if (text.PeekChar(1) == ']')
|
||||
{
|
||||
label = openParent.Label;
|
||||
labelSpan = openParent.LabelSpan;
|
||||
isLabelSpanLocal = false;
|
||||
text.NextChar(); // Skip [
|
||||
text.NextChar(); // Skip ]
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
label = openParent.Label;
|
||||
isShortcut = true;
|
||||
}
|
||||
|
||||
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
|
||||
{
|
||||
if (isLabelSpanLocal)
|
||||
{
|
||||
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
|
||||
}
|
||||
|
||||
if (ProcessLinkReference(inlineState, label, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
|
||||
{
|
||||
// Remove the open parent
|
||||
openParent.Remove();
|
||||
if (!openParent.IsImage)
|
||||
{
|
||||
MarkParentAsInactive(parentDelimiter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// We have a nested [ ]
|
||||
// firstParent.Remove();
|
||||
// The opening [ will be transformed to a literal followed by all the children of the [
|
||||
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Span = openParent.Span,
|
||||
Content = new StringSlice(openParent.IsImage ? "![" : "[")
|
||||
};
|
||||
|
||||
inlineState.Inline = openParent.ReplaceBy(literal);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we do find one, but it’s not active,
|
||||
// we remove the inactive delimiter from the stack,
|
||||
// and return a literal text node ].
|
||||
if (!openParent.IsActive)
|
||||
{
|
||||
inlineState.Inline = new LiteralInline()
|
||||
{
|
||||
Content = new StringSlice("["),
|
||||
Span = openParent.Span,
|
||||
Line = openParent.Line,
|
||||
Column = openParent.Column,
|
||||
};
|
||||
openParent.ReplaceBy(inlineState.Inline);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we find one and it’s active,
|
||||
// then we parse ahead to see if we have
|
||||
// an inline link/image, reference link/image,
|
||||
// compact reference link/image,
|
||||
// or shortcut reference link/image
|
||||
var parentDelimiter = openParent.Parent;
|
||||
var savedText = text;
|
||||
|
||||
if (text.CurrentChar == '(')
|
||||
{
|
||||
if (LinkHelper.TryParseInlineLink(ref text, out string url, out string title, out SourceSpan linkSpan, out SourceSpan titleSpan))
|
||||
{
|
||||
// Inline Link
|
||||
var link = new LinkInline()
|
||||
{
|
||||
Url = HtmlHelper.Unescape(url),
|
||||
Title = HtmlHelper.Unescape(title),
|
||||
IsImage = openParent.IsImage,
|
||||
LabelSpan = openParent.LabelSpan,
|
||||
UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan),
|
||||
TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan),
|
||||
Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)),
|
||||
Line = openParent.Line,
|
||||
Column = openParent.Column,
|
||||
};
|
||||
|
||||
openParent.ReplaceBy(link);
|
||||
// Notifies processor as we are creating an inline locally
|
||||
inlineState.Inline = link;
|
||||
|
||||
// Process emphasis delimiters
|
||||
inlineState.PostProcessInlines(0, link, null, false);
|
||||
|
||||
// If we have a link (and not an image),
|
||||
// we also set all [ delimiters before the opening delimiter to inactive.
|
||||
// (This will prevent us from getting links within links.)
|
||||
if (!openParent.IsImage)
|
||||
{
|
||||
MarkParentAsInactive(parentDelimiter);
|
||||
}
|
||||
|
||||
link.IsClosed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
text = savedText;
|
||||
}
|
||||
|
||||
var labelSpan = SourceSpan.Empty;
|
||||
string label = null;
|
||||
bool isLabelSpanLocal = true;
|
||||
|
||||
bool isShortcut = false;
|
||||
// Handle Collapsed links
|
||||
if (text.CurrentChar == '[')
|
||||
{
|
||||
if (text.PeekChar() == ']')
|
||||
{
|
||||
label = openParent.Label;
|
||||
labelSpan = openParent.LabelSpan;
|
||||
isLabelSpanLocal = false;
|
||||
text.NextChar(); // Skip [
|
||||
text.NextChar(); // Skip ]
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
label = openParent.Label;
|
||||
isShortcut = true;
|
||||
}
|
||||
|
||||
if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan))
|
||||
{
|
||||
if (isLabelSpanLocal)
|
||||
{
|
||||
labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan);
|
||||
}
|
||||
|
||||
if (ProcessLinkReference(inlineState, label, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1)))
|
||||
{
|
||||
// Remove the open parent
|
||||
openParent.Remove();
|
||||
if (!openParent.IsImage)
|
||||
{
|
||||
MarkParentAsInactive(parentDelimiter);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (text.CurrentChar != ']' && text.CurrentChar != '[')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We have a nested [ ]
|
||||
// firstParent.Remove();
|
||||
// The opening [ will be transformed to a literal followed by all the children of the [
|
||||
|
||||
var literal = new LiteralInline()
|
||||
{
|
||||
Span = openParent.Span,
|
||||
Content = new StringSlice(openParent.IsImage ? "![" : "[")
|
||||
};
|
||||
|
||||
inlineState.Inline = openParent.ReplaceBy(literal);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ namespace Markdig.Parsers
|
||||
private readonly ProcessDocumentDelegate documentProcessed;
|
||||
private readonly bool preciseSourceLocation;
|
||||
|
||||
private readonly int roughLineCountEstimate;
|
||||
|
||||
private LineReader lineReader;
|
||||
|
||||
/// <summary>
|
||||
@@ -42,6 +44,8 @@ namespace Markdig.Parsers
|
||||
{
|
||||
if (text == null) throw new ArgumentNullException(nameof(text));
|
||||
if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
|
||||
|
||||
roughLineCountEstimate = text.Length / 40;
|
||||
text = FixupZero(text);
|
||||
lineReader = new LineReader(text);
|
||||
preciseSourceLocation = pipeline.PreciseSourceLocation;
|
||||
@@ -82,13 +86,16 @@ namespace Markdig.Parsers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the current <see cref="Reader"/> into a Markdown <see cref="MarkdownDocument"/>.
|
||||
/// Parses the current <see cref="lineReader"/> into a Markdown <see cref="MarkdownDocument"/>.
|
||||
/// </summary>
|
||||
/// <returns>A document instance</returns>
|
||||
private MarkdownDocument Parse()
|
||||
{
|
||||
if (preciseSourceLocation)
|
||||
document.LineStartIndexes = new List<int>();
|
||||
{
|
||||
// Save some List resizing allocations
|
||||
document.LineStartIndexes = new List<int>(Math.Min(512, roughLineCountEstimate));
|
||||
}
|
||||
|
||||
ProcessBlocks();
|
||||
ProcessInlines();
|
||||
@@ -127,7 +134,7 @@ namespace Markdig.Parsers
|
||||
return text.Replace('\0', CharHelper.ZeroSafeChar);
|
||||
}
|
||||
|
||||
private class ContainerItemCache : DefaultObjectCache<ContainerItem>
|
||||
private sealed class ContainerItemCache : DefaultObjectCache<ContainerItem>
|
||||
{
|
||||
protected override void Reset(ContainerItem instance)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Markdig.Renderers.Html.Inlines
|
||||
{
|
||||
protected override void Write(HtmlRenderer renderer, HtmlEntityInline obj)
|
||||
{
|
||||
if (renderer.EnableHtmlForInline)
|
||||
if (renderer.EnableHtmlEscape)
|
||||
{
|
||||
renderer.WriteEscape(obj.Transcoded);
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ namespace Markdig.Renderers.Html.Inlines
|
||||
{
|
||||
protected override void Write(HtmlRenderer renderer, LiteralInline obj)
|
||||
{
|
||||
if (renderer.EnableHtmlForInline)
|
||||
if (renderer.EnableHtmlEscape)
|
||||
{
|
||||
renderer.WriteEscape(obj.Content);
|
||||
renderer.WriteEscape(ref obj.Content);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Write(obj.Content);
|
||||
renderer.Write(ref obj.Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@ namespace Markdig.Renderers
|
||||
public abstract class TextRendererBase<T> : TextRendererBase where T : TextRendererBase<T>
|
||||
{
|
||||
private bool previousWasLine;
|
||||
#if !NETCORE
|
||||
private char[] buffer;
|
||||
#endif
|
||||
private readonly List<string> indents;
|
||||
|
||||
/// <summary>
|
||||
@@ -79,12 +81,29 @@ namespace Markdig.Renderers
|
||||
/// <param name="writer">The writer.</param>
|
||||
protected TextRendererBase(TextWriter writer) : base(writer)
|
||||
{
|
||||
#if !NETCORE
|
||||
buffer = new char[1024];
|
||||
#endif
|
||||
// We assume that we are starting as if we had previously a newline
|
||||
previousWasLine = true;
|
||||
indents = new List<string>();
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
if (Writer is StringWriter stringWriter)
|
||||
{
|
||||
stringWriter.GetStringBuilder().Length = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Cannot reset this TextWriter instance");
|
||||
}
|
||||
|
||||
previousWasLine = true;
|
||||
indents.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a newline.
|
||||
/// </summary>
|
||||
@@ -193,6 +212,10 @@ namespace Markdig.Renderers
|
||||
|
||||
WriteIndent();
|
||||
previousWasLine = false;
|
||||
|
||||
#if NETCORE
|
||||
Writer.Write(content.AsSpan(offset, length));
|
||||
#else
|
||||
if (offset == 0 && content.Length == length)
|
||||
{
|
||||
Writer.Write(content);
|
||||
@@ -210,6 +233,7 @@ namespace Markdig.Renderers
|
||||
Writer.Write(buffer, 0, length);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
|
||||
@@ -189,12 +189,17 @@ namespace Markdig.Syntax
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return children[index];
|
||||
var array = children;
|
||||
if ((uint)index >= (uint)array.Length || index >= Count)
|
||||
{
|
||||
ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
return null;
|
||||
}
|
||||
return array[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if ((uint)index >= (uint)Count) ThrowHelper.ThrowIndexOutOfRangeException();
|
||||
children[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,15 +215,28 @@ namespace Markdig.Syntax.Inlines
|
||||
var inline = this;
|
||||
while (inline != null)
|
||||
{
|
||||
var delimiter = inline as T;
|
||||
if (delimiter != null)
|
||||
if (inline is T inlineOfT)
|
||||
{
|
||||
yield return delimiter;
|
||||
yield return inlineOfT;
|
||||
}
|
||||
inline = inline.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
internal T FirstParentOfType<T>() where T : Inline
|
||||
{
|
||||
var inline = this;
|
||||
while (inline != null)
|
||||
{
|
||||
if (inline is T inlineOfT)
|
||||
{
|
||||
return inlineOfT;
|
||||
}
|
||||
inline = inline.Parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Inline FindBestParent()
|
||||
{
|
||||
var current = this;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// Copyright (c) Alexandre Mutel. All rights reserved.
|
||||
// This file is licensed under the BSD-Clause 2 license.
|
||||
// See the license.txt file in the project root for more information.
|
||||
using System.Diagnostics;
|
||||
@@ -51,7 +51,7 @@ namespace Markdig.Syntax
|
||||
{
|
||||
if (Lines.Lines == null)
|
||||
{
|
||||
Lines = new StringLineGroup(4);
|
||||
Lines = new StringLineGroup(4, ProcessInlines);
|
||||
}
|
||||
|
||||
var stringLine = new StringLine(ref slice, line, column, sourceLinePosition);
|
||||
@@ -64,12 +64,9 @@ namespace Markdig.Syntax
|
||||
{
|
||||
// We need to expand tabs to spaces
|
||||
var builder = StringBuilderCache.Local();
|
||||
for (int i = column; i < CharHelper.AddTab(column); i++)
|
||||
{
|
||||
builder.Append(' ');
|
||||
}
|
||||
builder.Append(' ', CharHelper.AddTab(column) - column);
|
||||
builder.Append(slice.Text, slice.Start + 1, slice.Length - 1);
|
||||
stringLine.Slice = new StringSlice(builder.ToString());
|
||||
stringLine.Slice = new StringSlice(builder.GetStringAndReset());
|
||||
Lines.Add(ref stringLine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,12 +103,12 @@ namespace SpecFileGen
|
||||
continue;
|
||||
}
|
||||
|
||||
string source = ParseSpecification(spec, File.ReadAllText(spec.Path));
|
||||
string source = ParseSpecification(spec, File.ReadAllText(spec.Path)).Replace("\r\n", "\n", StringComparison.Ordinal);
|
||||
totalTests += spec.TestCount;
|
||||
|
||||
if (File.Exists(spec.OutputPath)) // If the source hasn't changed, don't bump the generated tag
|
||||
{
|
||||
string previousSource = File.ReadAllText(spec.OutputPath);
|
||||
string previousSource = File.ReadAllText(spec.OutputPath).Replace("\r\n", "\n", StringComparison.Ordinal);
|
||||
int start = previousSource.IndexOf('\n', StringComparison.Ordinal) + 1;
|
||||
int previousLength = previousSource.Length - start;
|
||||
if (start != 0 && previousLength == source.Length)
|
||||
|
||||
@@ -13,12 +13,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{061866E2
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig", "Markdig\Markdig.csproj", "{8A58A7E2-627C-4F41-933F-5AC92ADFAB48}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Signed", "Markdig.Signed\Markdig.Signed.csproj", "{C37C7B94-1219-4ED9-ABAC-C0B4B8FE8750}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdig.Tests", "Markdig.Tests\Markdig.Tests.csproj", "{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{8A58A7E2-627C-4F41-933F-5AC92ADFAB48} = {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Markdig.Benchmarks", "Markdig.Benchmarks\Markdig.Benchmarks.csproj", "{6A19F040-BC7C-4283-873A-177B5324F1ED}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Markdig.Benchmarks", "Markdig.Benchmarks\Markdig.Benchmarks.csproj", "{6A19F040-BC7C-4283-873A-177B5324F1ED}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{8A58A7E2-627C-4F41-933F-5AC92ADFAB48} = {8A58A7E2-627C-4F41-933F-5AC92ADFAB48}
|
||||
EndProjectSection
|
||||
@@ -41,6 +43,10 @@ Global
|
||||
{8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8A58A7E2-627C-4F41-933F-5AC92ADFAB48}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C37C7B94-1219-4ED9-ABAC-C0B4B8FE8750}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C37C7B94-1219-4ED9-ABAC-C0B4B8FE8750}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C37C7B94-1219-4ED9-ABAC-C0B4B8FE8750}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C37C7B94-1219-4ED9-ABAC-C0B4B8FE8750}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0C5CB5F-5568-40AB-B945-D6D2664D51B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
Reference in New Issue
Block a user