diff --git a/DocxTemplater.Markdown/DocxTemplater.Markdown.csproj b/DocxTemplater.Markdown/DocxTemplater.Markdown.csproj
new file mode 100644
index 0000000..4f8a152
--- /dev/null
+++ b/DocxTemplater.Markdown/DocxTemplater.Markdown.csproj
@@ -0,0 +1,15 @@
+
+
+ True
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DocxTemplater.Markdown/MarkDownFormatterConfiguration.cs b/DocxTemplater.Markdown/MarkDownFormatterConfiguration.cs
new file mode 100644
index 0000000..2d43a4b
--- /dev/null
+++ b/DocxTemplater.Markdown/MarkDownFormatterConfiguration.cs
@@ -0,0 +1,66 @@
+using DocumentFormat.OpenXml.Wordprocessing;
+using System.Collections.Generic;
+
+namespace DocxTemplater.Markdown
+{
+ public record ListLevelConfiguration(
+ string LevelText,
+ string FontOverride,
+ NumberFormatValues NumberingFormat,
+ int IndentPerLevel);
+
+ public class MarkDownFormatterConfiguration
+ {
+ public static readonly MarkDownFormatterConfiguration Default;
+
+ static MarkDownFormatterConfiguration()
+ {
+ Default = new MarkDownFormatterConfiguration();
+ }
+
+ public MarkDownFormatterConfiguration()
+ {
+ OrderedListLevelConfiguration = new List();
+ UnorderedListLevelConfiguration = new List();
+
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%1.", null, NumberFormatValues.Decimal, 720));
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%2.", null, NumberFormatValues.LowerLetter, 720));
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%3.", null, NumberFormatValues.LowerRoman, 720));
+
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%4.", null, NumberFormatValues.Decimal, 720));
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%5.", null, NumberFormatValues.LowerLetter, 720));
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%6.", null, NumberFormatValues.LowerRoman, 720));
+
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%7.", null, NumberFormatValues.Decimal, 720));
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%8.", null, NumberFormatValues.LowerLetter, 720));
+ OrderedListLevelConfiguration.Add(new ListLevelConfiguration("%9.", null, NumberFormatValues.LowerRoman, 720));
+
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("\uf0b7", "Symbol", NumberFormatValues.Bullet, 720));
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("o", "Courier New", NumberFormatValues.Bullet, 720));
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("\uf0a7", "Wingdings", NumberFormatValues.Bullet, 720));
+
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("\uf0b7", "Symbol", NumberFormatValues.Bullet, 720));
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("o", "Courier New", NumberFormatValues.Bullet, 720));
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("\uf0a7", "Wingdings", NumberFormatValues.Bullet, 720));
+
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("\uf0b7", "Symbol", NumberFormatValues.Bullet, 720));
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("o", "Courier New", NumberFormatValues.Bullet, 720));
+ UnorderedListLevelConfiguration.Add(new ListLevelConfiguration("\uf0a7", "Wingdings", NumberFormatValues.Bullet, 720));
+ }
+
+ public List OrderedListLevelConfiguration
+ {
+ get;
+ }
+
+ public List UnorderedListLevelConfiguration
+ {
+ get;
+ }
+
+ ///
+ /// Name of a table style in the template document applied to tables.
+ ///
+ public string TableStyle { get; set; }
+ }
+}
diff --git a/DocxTemplater.Markdown/MarkdownFormatter.cs b/DocxTemplater.Markdown/MarkdownFormatter.cs
new file mode 100644
index 0000000..82dacf2
--- /dev/null
+++ b/DocxTemplater.Markdown/MarkdownFormatter.cs
@@ -0,0 +1,52 @@
+using DocumentFormat.OpenXml;
+using DocumentFormat.OpenXml.Wordprocessing;
+using DocxTemplater.Formatter;
+using Markdig.Parsers;
+using System;
+using DocumentFormat.OpenXml.Packaging;
+using Markdig;
+
+namespace DocxTemplater.Markdown
+{
+ public class MarkdownFormatter : IFormatter
+ {
+ private readonly MarkDownFormatterConfiguration m_configuration;
+
+ public MarkdownFormatter(MarkDownFormatterConfiguration configuration = null)
+ {
+ m_configuration = configuration ?? MarkDownFormatterConfiguration.Default;
+ }
+
+ public bool CanHandle(Type type, string prefix)
+ {
+ string prefixUpper = prefix.ToUpper();
+ return prefixUpper is "MD" && type == typeof(string);
+ }
+
+ public void ApplyFormat(FormatterContext context, Text target)
+ {
+ if (context.Value is not string mdText)
+ {
+ return;
+ }
+
+ var root = target.GetRoot();
+ if (root is OpenXmlPartRootElement openXmlPartRootElement && openXmlPartRootElement.OpenXmlPart != null)
+ {
+
+ if (openXmlPartRootElement.OpenXmlPart is MainDocumentPart mainDocumentPart)
+ {
+ var pipeline = new MarkdownPipelineBuilder().UsePipeTables().Build();
+ var markdownDocument = MarkdownParser.Parse(mdText, pipeline);
+ var renderer = new MarkdownToOpenXmlRenderer(target, mainDocumentPart, m_configuration);
+ renderer.Render(markdownDocument);
+ }
+ else
+ {
+ throw new InvalidOperationException("Markdown currently only supported in MainDocument");
+ }
+ }
+ target.RemoveWithEmptyParent();
+ }
+ }
+}
diff --git a/DocxTemplater.Markdown/MarkdownToOpenXmlRenderer.cs b/DocxTemplater.Markdown/MarkdownToOpenXmlRenderer.cs
new file mode 100644
index 0000000..72c5b92
--- /dev/null
+++ b/DocxTemplater.Markdown/MarkdownToOpenXmlRenderer.cs
@@ -0,0 +1,183 @@
+
+using DocumentFormat.OpenXml;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using DocxTemplater.Markdown.Renderer;
+using DocxTemplater.Markdown.Renderer.Inlines;
+using Markdig.Helpers;
+using Markdig.Renderers;
+using Markdig.Syntax;
+using Markdig.Syntax.Inlines;
+using System;
+using System.Collections.Generic;
+
+namespace DocxTemplater.Markdown
+{
+ internal sealed class MarkdownToOpenXmlRenderer : RendererBase
+ {
+ private sealed record Format(bool Bold, bool Italic, string Style);
+
+ private readonly Stack m_formatStack = new();
+ private OpenXmlCompositeElement m_parentElement;
+ private bool m_lastElemntWasNewLine;
+
+ public MarkdownToOpenXmlRenderer(
+ Text previousOpenXmlNode,
+ MainDocumentPart mainDocumentPart,
+ MarkDownFormatterConfiguration configuration)
+ {
+ m_lastElemntWasNewLine = true;
+ m_formatStack.Push(new Format(false, false, null));
+ m_parentElement = previousOpenXmlNode.GetFirstAncestor();
+ ObjectRenderers.Add(new LiteralInlineRenderer());
+ ObjectRenderers.Add(new ParagraphRenderer());
+ ObjectRenderers.Add(new LineBreakLineRenderer());
+ ObjectRenderers.Add(new EmphasisInlineRenderer());
+ ObjectRenderers.Add(new TableRenderer(configuration, mainDocumentPart));
+ ObjectRenderers.Add(new ListRenderer(mainDocumentPart, configuration));
+ ObjectRenderers.Add(new HeadingRenderer());
+ ObjectRenderers.Add(new ThematicBreakRenderer());
+ }
+
+ public bool ExplicitParagraph { get; set; }
+
+ public Paragraph CurrentParagraph => m_parentElement as Paragraph;
+
+ public MarkdownToOpenXmlRenderer Write(ref StringSlice slice)
+ {
+ Write(slice.AsSpan());
+ return this;
+ }
+
+ public void Write(ReadOnlySpan content)
+ {
+ if (!content.IsEmpty)
+ {
+ var newRun = new Run(new Text(content.ToString()));
+ var format = m_formatStack.Peek();
+ if (format.Bold || format.Italic || format.Style != null)
+ {
+ RunProperties run1Properties = new();
+ if (format.Bold)
+ {
+ run1Properties.Append(new Bold());
+ }
+ if (format.Italic)
+ {
+ run1Properties.Append(new Italic());
+ }
+ newRun.RunProperties = run1Properties;
+
+ //add style
+ if (format.Style != null)
+ {
+ var runStyle = new RunStyle { Val = format.Style };
+ newRun.RunProperties.Append(runStyle);
+ }
+ }
+ m_parentElement.Append(newRun);
+ m_lastElemntWasNewLine = false;
+ }
+ }
+
+ public override object Render(MarkdownObject markdownObject)
+ {
+ Write(markdownObject);
+ return null;
+ }
+
+ public void WriteLeafInline(LeafBlock leafBlock)
+ {
+ Inline inline = leafBlock.Inline;
+ while (inline != null)
+ {
+ Write(inline);
+ inline = inline.NextSibling;
+ }
+ }
+
+ public IDisposable PushFormat(bool? bold, bool? italic)
+ {
+ var currentStyle = m_formatStack.Peek();
+ bold ??= currentStyle.Bold;
+ italic ??= currentStyle.Italic;
+ return new FormatScope(m_formatStack, bold.Value, italic.Value, currentStyle.Style);
+ }
+
+ public IDisposable PushStyle(string style)
+ {
+ return new FormatScope(m_formatStack, m_formatStack.Peek().Bold, m_formatStack.Peek().Italic, style);
+ }
+
+ public void NewLine()
+ {
+ m_parentElement.Append(new Run(new Break()));
+ m_lastElemntWasNewLine = true;
+ }
+
+ public void EnsureNewLine()
+ {
+ if (!m_lastElemntWasNewLine)
+ {
+ NewLine();
+ }
+ }
+
+ public void ReplaceIfCurrentParagraphIsEmpty(Paragraph newParagraph)
+ {
+ var lastParagraph = CurrentParagraph;
+ AddParagraph(newParagraph);
+ if (lastParagraph != null && lastParagraph.ChildElements.Count == 0)
+ {
+ lastParagraph.Remove();
+ }
+ }
+
+ public void AddParagraph(OpenXmlCompositeElement paragraph = null)
+ {
+ paragraph ??= new Paragraph();
+ m_parentElement = m_parentElement.InsertAfterSelf(paragraph);
+ m_lastElemntWasNewLine = false;
+ }
+
+ public IDisposable PushParagraph(Paragraph paragraph)
+ {
+ m_lastElemntWasNewLine = true;
+ return new ParagraphScope(this, paragraph);
+ }
+
+ private sealed class ParagraphScope : IDisposable
+ {
+ private readonly MarkdownToOpenXmlRenderer m_renderer;
+ private readonly OpenXmlCompositeElement m_previousParagraph;
+
+ public ParagraphScope(MarkdownToOpenXmlRenderer renderer, Paragraph element)
+ {
+ m_renderer = renderer;
+ m_previousParagraph = m_renderer.m_parentElement;
+ m_renderer.m_parentElement = element;
+ }
+
+ public void Dispose()
+ {
+ m_renderer.m_parentElement = m_previousParagraph;
+ }
+ }
+
+ private sealed class FormatScope : IDisposable
+ {
+ private readonly Stack m_formatStack;
+ public FormatScope(Stack formatStack, bool bold, bool italic, string style)
+ {
+ m_formatStack = formatStack;
+ m_formatStack.Push(new Format(bold, italic, style));
+ }
+
+ public void Dispose()
+ {
+ m_formatStack.Pop();
+ }
+ }
+ }
+
+}
diff --git a/DocxTemplater.Markdown/Renderer/HeadingRenderer.cs b/DocxTemplater.Markdown/Renderer/HeadingRenderer.cs
new file mode 100644
index 0000000..480f143
--- /dev/null
+++ b/DocxTemplater.Markdown/Renderer/HeadingRenderer.cs
@@ -0,0 +1,21 @@
+using DocumentFormat.OpenXml.Wordprocessing;
+using Markdig.Syntax;
+
+namespace DocxTemplater.Markdown.Renderer
+{
+ internal sealed class HeadingRenderer : OpenXmlObjectRenderer
+ {
+ protected override void Write(MarkdownToOpenXmlRenderer renderer, HeadingBlock heading)
+ {
+ var headingParagraph = new Paragraph();
+ // add heading style
+ var headingStyle = new ParagraphStyleId() { Val = $"Heading{heading.Level}" };
+ var paragraphProps = new ParagraphProperties();
+ paragraphProps.Append(headingStyle);
+ headingParagraph.ParagraphProperties = paragraphProps;
+ renderer.AddParagraph(headingParagraph);
+ renderer.WriteLeafInline(heading);
+ renderer.AddParagraph();
+ }
+ }
+}
diff --git a/DocxTemplater.Markdown/Renderer/Inlines/EmphasisInlineRenderer.cs b/DocxTemplater.Markdown/Renderer/Inlines/EmphasisInlineRenderer.cs
new file mode 100644
index 0000000..529058c
--- /dev/null
+++ b/DocxTemplater.Markdown/Renderer/Inlines/EmphasisInlineRenderer.cs
@@ -0,0 +1,26 @@
+using Markdig.Syntax.Inlines;
+
+namespace DocxTemplater.Markdown.Renderer.Inlines
+{
+ internal sealed class EmphasisInlineRenderer : OpenXmlObjectRenderer
+ {
+ protected override void Write(MarkdownToOpenXmlRenderer renderer, EmphasisInline obj)
+ {
+ bool? italic = null;
+ bool? bold = null;
+ if (obj.DelimiterChar is '_' or '*')
+ {
+ if (obj.DelimiterCount == 1)
+ {
+ italic = true;
+ }
+ else if (obj.DelimiterCount == 2)
+ {
+ bold = true;
+ }
+ }
+ using var format = renderer.PushFormat(bold, italic);
+ renderer.WriteChildren(obj);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocxTemplater.Markdown/Renderer/Inlines/LineBreakLineRenderer.cs b/DocxTemplater.Markdown/Renderer/Inlines/LineBreakLineRenderer.cs
new file mode 100644
index 0000000..7d19232
--- /dev/null
+++ b/DocxTemplater.Markdown/Renderer/Inlines/LineBreakLineRenderer.cs
@@ -0,0 +1,16 @@
+using Markdig.Syntax.Inlines;
+
+namespace DocxTemplater.Markdown.Renderer.Inlines
+{
+ internal sealed class LineBreakLineRenderer : OpenXmlObjectRenderer
+ {
+ protected override void Write(MarkdownToOpenXmlRenderer renderer, LineBreakInline obj)
+ {
+ if (renderer.IsLastInContainer)
+ {
+ return;
+ }
+ renderer.NewLine();
+ }
+ }
+}
diff --git a/DocxTemplater.Markdown/Renderer/Inlines/LiteralInlineRenderer.cs b/DocxTemplater.Markdown/Renderer/Inlines/LiteralInlineRenderer.cs
new file mode 100644
index 0000000..8f5d626
--- /dev/null
+++ b/DocxTemplater.Markdown/Renderer/Inlines/LiteralInlineRenderer.cs
@@ -0,0 +1,12 @@
+using Markdig.Syntax.Inlines;
+
+namespace DocxTemplater.Markdown.Renderer.Inlines
+{
+ internal sealed class LiteralInlineRenderer : OpenXmlObjectRenderer
+ {
+ protected override void Write(MarkdownToOpenXmlRenderer renderer, LiteralInline obj)
+ {
+ renderer.Write(ref obj.Content);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DocxTemplater.Markdown/Renderer/ListRenderer.cs b/DocxTemplater.Markdown/Renderer/ListRenderer.cs
new file mode 100644
index 0000000..d8c5772
--- /dev/null
+++ b/DocxTemplater.Markdown/Renderer/ListRenderer.cs
@@ -0,0 +1,200 @@
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using Markdig.Syntax;
+using System.Linq;
+
+namespace DocxTemplater.Markdown.Renderer
+{
+ internal sealed class ListRenderer : OpenXmlObjectRenderer
+ {
+ private int m_level = -1;
+ private int m_levelWithSameOrdering = -1;
+ private bool? m_lastLevelOrdered;
+
+ private readonly MainDocumentPart m_mainDocumentPart;
+ private readonly MarkDownFormatterConfiguration m_configuration;
+ private AbstractNum m_currentAbstractNumNotOrdered;
+ private AbstractNum m_currentAbstractNumOrdered;
+ private NumberingInstance m_currentNumberingInstanceOrdered;
+ private NumberingInstance m_currentNumberingInstanceNotOrdered;
+ private NumberingDefinitionsPart m_numberingDefinitionsPart;
+ private string m_listParagraphStyle;
+
+ public ListRenderer(MainDocumentPart mainDocumentPart, MarkDownFormatterConfiguration configuration)
+ {
+ m_mainDocumentPart = mainDocumentPart;
+ m_configuration = configuration;
+ }
+
+ protected override void Write(MarkdownToOpenXmlRenderer renderer, ListBlock listBlock)
+ {
+
+ StartListLevel(listBlock.IsOrdered);
+ try
+ {
+ var numberingInstance = listBlock.IsOrdered ? m_currentNumberingInstanceOrdered : m_currentNumberingInstanceNotOrdered;
+
+ foreach (var item in listBlock)
+ {
+ var numberingProps =
+ new NumberingProperties(
+ new NumberingLevelReference() { Val = m_levelWithSameOrdering },
+ new NumberingId() { Val = numberingInstance.NumberID }
+ );
+ var listItem = (ListItemBlock)item;
+ var listParagraph = new Paragraph();
+ var paragraphProperties = new ParagraphProperties(numberingProps)
+ {
+ ParagraphStyleId = new ParagraphStyleId() { Val = m_listParagraphStyle }
+ };
+ listParagraph.ParagraphProperties = paragraphProperties;
+ renderer.ReplaceIfCurrentParagraphIsEmpty(listParagraph);
+ renderer.ExplicitParagraph = true;
+ renderer.WriteChildren(listItem);
+ }
+ }
+ finally
+ {
+ EndListLevel();
+ }
+
+ if (m_level == -1)
+ {
+ renderer.AddParagraph();
+ }
+ }
+
+ private void CrateListParagraphStyleIfNotExistent()
+ {
+ if (m_listParagraphStyle == null)
+ {
+ var part = m_mainDocumentPart.StyleDefinitionsPart;
+ if (part == null)
+ {
+ part = m_mainDocumentPart.AddNewPart();
+ part.Styles = new Styles();
+ }
+
+ var style = part.Styles?.Elements