Skip to content

Commit

Permalink
HTML Parsing Extension for preview pane markdown renderer (microsoft#…
Browse files Browse the repository at this point in the history
…1108)

* Added Extension for html post processing

* Added unit test poroject for preview pane

* Added pipline test and base test function

* Added Tests for extension

* Added tests for url slashes

* Added tests for url and figure caption
  • Loading branch information
dsrivastavv authored and udit3333 committed Feb 19, 2020
1 parent bce2b98 commit eba08e5
Show file tree
Hide file tree
Showing 9 changed files with 428 additions and 5 deletions.
16 changes: 15 additions & 1 deletion PowerToys.sln
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowWalker", "src\modules
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "previewpane", "previewpane", "{2F305555-C296-497E-AC20-5FA1B237996A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "src\modules\previewpane\common\Common.csproj", "{AF2349B8-E5B6-4004-9502-687C1C7730B1}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "src\modules\previewpane\Common\Common.csproj", "{AF2349B8-E5B6-4004-9502-687C1C7730B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MarkDownPreviewHandler", "src\modules\previewpane\MarkDownPreviewHandler\MarkDownPreviewHandler.csproj", "{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PreviewPaneUnitTests", "src\modules\previewpane\PreviewPaneUnitTests\PreviewPaneUnitTests.csproj", "{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -204,6 +208,14 @@ Global
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Debug|x64.Build.0 = Debug|x64
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.ActiveCfg = Release|x64
{AF2349B8-E5B6-4004-9502-687C1C7730B1}.Release|x64.Build.0 = Release|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.ActiveCfg = Debug|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Debug|x64.Build.0 = Debug|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.ActiveCfg = Release|x64
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}.Release|x64.Build.0 = Release|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.ActiveCfg = Debug|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Debug|x64.Build.0 = Debug|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.ActiveCfg = Release|x64
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -229,6 +241,8 @@ Global
{0485F45C-EA7A-4BB5-804B-3E8D14699387} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
{2F305555-C296-497E-AC20-5FA1B237996A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{AF2349B8-E5B6-4004-9502-687C1C7730B1} = {2F305555-C296-497E-AC20-5FA1B237996A}
{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB} = {2F305555-C296-497E-AC20-5FA1B237996A}
{A2B51B8B-8F90-424E-BC97-F9AB7D76CA1A} = {2F305555-C296-497E-AC20-5FA1B237996A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.IO;
using Markdig;
using Markdig.Extensions.Figures;
using Markdig.Extensions.Tables;
using Markdig.Renderers;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace MarkDownPreviewHandler
{
/// <summary>
/// Markdig Extension to process html nodes in markdown AST.
/// </summary>
internal class HTMLParsingExtension : IMarkdownExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="HTMLParsingExtension"/> class.
/// </summary>
public HTMLParsingExtension(string baseUrl = "")
{
this.BaseUrl = baseUrl;
}

/// <summary>
/// Gets or sets path to directory containing markdown file.
/// </summary>
public string BaseUrl { get; set; }

/// <inheritdoc/>
public void Setup(MarkdownPipelineBuilder pipeline)
{
// Make sure we don't have a delegate twice
pipeline.DocumentProcessed -= this.PipelineOnDocumentProcessed;
pipeline.DocumentProcessed += this.PipelineOnDocumentProcessed;
}

/// <inheritdoc/>
public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
{
}

/// <summary>
/// Process nodes in markdown AST.
/// </summary>
/// <param name="document">Markdown Document.</param>
public void PipelineOnDocumentProcessed(MarkdownDocument document)
{
foreach (var node in document.Descendants())
{
if (node is Block)
{
if (node is Table)
{
node.GetAttributes().AddClass("table table-striped table-bordered");
}
else if (node is QuoteBlock)
{
node.GetAttributes().AddClass("blockquote");
}
else if (node is Figure)
{
node.GetAttributes().AddClass("figure");
}
else if (node is FigureCaption)
{
node.GetAttributes().AddClass("figure-caption");
}
}
else if (node is Inline)
{
if (node is LinkInline link && link.IsImage)
{
if (!Uri.TryCreate(link.Url, UriKind.Absolute, out Uri uriLink))
{
link.Url = link.Url.TrimStart('/', '\\');
this.BaseUrl = this.BaseUrl.TrimEnd('/', '\\');
uriLink = new Uri(Path.Combine(this.BaseUrl, link.Url));
link.Url = uriLink.ToString();
}

link.GetAttributes().AddClass("img-fluid");
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6A71162E-FC4C-4A2C-B90F-3CF94F59A9BB}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MarkDownPreviewHandler</RootNamespace>
<AssemblyName>MarkDownPreviewHandler</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin/Debug/MarkdownPreviewPaneDocumentation.xml</DocumentationFile>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DocumentationFile>bin/Release/commonDocumentation.xml</DocumentationFile>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<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="HTMLParsingExtension.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Markdig.Signed">
<Version>0.18.0</Version>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers">
<Version>1.1.118</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\codeAnalysis\StyleCop.json">
<Link>StyleCop.json</Link>
</AdditionalFiles>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\common\Common.csproj">
<Project>{af2349b8-e5b6-4004-9502-687c1c7730b1}</Project>
<Name>Common</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

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("MarkDownPreviewHandler")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MarkDownPreviewHandler")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Make assembly visible to UnitTest Project
[assembly: InternalsVisibleTo("PreviewPaneUnitTests")]

// 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("6a71162e-fc4c-4a2c-b90f-3cf94f59a9bb")]

// 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")]
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using Markdig;
using MarkDownPreviewHandler;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace PreviewPaneUnitTests
{
[TestClass]
public class MarkDownPreviewHandlerHTMLParsingExtensionTest
{
public MarkdownPipeline BuidPipeline(IMarkdownExtension extension)
{
MarkdownPipelineBuilder pipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions();
pipelineBuilder.Extensions.Add(extension);
return pipelineBuilder.Build();
}

[TestMethod]
public void Extension_UpdatesTablesClass_WhenUsed()
{
// Arrange
String mdString = "| A | B |\n| -- | -- | ";
HTMLParsingExtension htmlParsingExtension = new HTMLParsingExtension();
MarkdownPipeline markdownPipeline = BuidPipeline(htmlParsingExtension);

// Act
String html = Markdown.ToHtml(mdString, markdownPipeline);

// Assert
Assert.AreEqual(html, "<table class=\"table table-striped table-bordered\">\n<thead>\n<tr>\n<th>A</th>\n<th>B</th>\n</tr>\n</thead>\n</table>\n");
}


[TestMethod]
public void Extension_UpdatesBlockQuotesClass_WhenUsed()
{
// Arrange
String mdString = "> Blockquotes.";
HTMLParsingExtension htmlParsingExtension = new HTMLParsingExtension();
MarkdownPipeline markdownPipeline = BuidPipeline(htmlParsingExtension);

// Act
String html = Markdown.ToHtml(mdString, markdownPipeline);

// Assert
Assert.AreEqual(html, "<blockquote class=\"blockquote\">\n<p>Blockquotes.</p>\n</blockquote>\n");
}

[TestMethod]
public void Extension_UpdatesFigureClassAndRelativeUrltoAbsolute_WhenUsed()
{
// arrange
String mdString = "![text](a.jpg \"Figure\")";
HTMLParsingExtension htmlParsingExtension = new HTMLParsingExtension("C:\\Users\\");
MarkdownPipeline markdownPipeline = BuidPipeline(htmlParsingExtension);

// Act
String html = Markdown.ToHtml(mdString, markdownPipeline);

// Assert
Assert.AreEqual(html, "<p><img src=\"file:///C:/Users/a.jpg\" class=\"img-fluid\" alt=\"text\" title=\"Figure\" /></p>\n");
}

[TestMethod]
public void Extension_CreatesCorrectAbsoluteLinkByTrimmingForwardSlash_WhenUsed()
{
// arrange
String mdString = "![text](\\document\\a.jpg \"Figure\")";
HTMLParsingExtension htmlParsingExtension = new HTMLParsingExtension("C:\\Users\\");
MarkdownPipeline markdownPipeline = BuidPipeline(htmlParsingExtension);

// Act
String html = Markdown.ToHtml(mdString, markdownPipeline);

// Assert
Assert.AreEqual(html, "<p><img src=\"file:///C:/Users/document/a.jpg\" class=\"img-fluid\" alt=\"text\" title=\"Figure\" /></p>\n");
}

[TestMethod]
public void Extension_CreatesCorrectAbsoluteLinkByTrimmingBackwardSlash_WhenUsed()
{
// arrange
String mdString = "![text](/document/a.jpg \"Figure\")";
HTMLParsingExtension htmlParsingExtension = new HTMLParsingExtension("C:/Users/");
MarkdownPipeline markdownPipeline = BuidPipeline(htmlParsingExtension);

// Act
String html = Markdown.ToHtml(mdString, markdownPipeline);

// Assert
Assert.AreEqual(html, "<p><img src=\"file:///C:/Users/document/a.jpg\" class=\"img-fluid\" alt=\"text\" title=\"Figure\" /></p>\n");
}

[TestMethod]
public void Extension_AddsClassToFigureCaption_WhenUsed()
{
// arrange
String mdString = "^^^ This is a caption";
HTMLParsingExtension htmlParsingExtension = new HTMLParsingExtension("C:/Users/");
MarkdownPipeline markdownPipeline = BuidPipeline(htmlParsingExtension);

// Act
String html = Markdown.ToHtml(mdString, markdownPipeline);

// Assert
Assert.AreEqual(html, "<figure class=\"figure\">\n<figcaption class=\"figure-caption\">This is a caption</figcaption>\n</figure>\n");
}
}
}
Loading

0 comments on commit eba08e5

Please sign in to comment.