Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions csharp-version/src/MarkdownToDocx.CLI/Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Markdig.Extensions.CustomContainers;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
Expand Down Expand Up @@ -175,6 +176,51 @@ public static string ResolveRelativePath(string path, string basePath)
public static TableData GetTableData(Table table) =>
MarkdownToDocx.Core.Markdown.TableExtractor.Extract(table);

/// <summary>
/// Extracts structured child blocks from a Markdig CustomContainer (:::classname ... :::).
/// Supports paragraphs, headings, tables, and lists within the div.
/// Unrecognized child block types are silently skipped.
/// </summary>
public static FencedDivContent GetFencedDivContent(CustomContainer container)
{
var blocks = new List<FencedDivBlock>();

foreach (var child in container)
{
switch (child)
{
case ParagraphBlock paragraph:
var runs = new List<InlineRun>();
if (paragraph.Inline != null)
ExtractInlineRuns(paragraph.Inline, runs, bold: false, italic: false);
if (runs.Count > 0)
blocks.Add(new FencedDivParagraph { Runs = runs });
break;

case HeadingBlock heading:
var headingText = GetBlockText(heading);
if (!string.IsNullOrEmpty(headingText))
blocks.Add(new FencedDivHeading { Level = heading.Level, Text = headingText });
break;

case Table table:
blocks.Add(new FencedDivTable { Data = GetTableData(table) });
break;

case ListBlock list:
var items = GetListItems(list).ToList();
if (items.Count > 0)
{
var startNumber = int.TryParse(list.OrderedStart, out var parsed) ? parsed : 1;
blocks.Add(new FencedDivList { Items = items, IsOrdered = list.IsOrdered, StartNumber = startNumber });
}
break;
}
}

return new FencedDivContent { Blocks = blocks };
}

private static void ExtractInlineRuns(Inline? inline, List<InlineRun> runs, bool bold, bool italic)
{
if (inline == null) return;
Expand Down
10 changes: 10 additions & 0 deletions csharp-version/src/MarkdownToDocx.CLI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Markdig.Extensions.CustomContainers;
using Markdig.Extensions.Tables;
using Markdig.Syntax;
using MarkdownToDocx.CLI;
Expand Down Expand Up @@ -135,6 +136,15 @@
builder.AddTable(tableData, tableStyle);
break;

case CustomContainer container:
var className = container.Info ?? string.Empty;
if (!config.Styles.FencedDivs.TryGetValue(className, out var divConfig))
divConfig = new MarkdownToDocx.Styling.Models.FencedDivClassConfig();
var divStyle = styleApplicator.ApplyFencedDivStyle(divConfig, config.Styles);
var divContent = Helpers.GetFencedDivContent(container);
builder.AddFencedDiv(divContent, divStyle);
break;

case ThematicBreakBlock:
builder.AddThematicBreak();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ public interface IDocumentBuilder : IDisposable
/// <param name="style">Table style configuration</param>
void AddTable(TableData tableData, TableStyle style);

/// <summary>
/// Adds a fenced div block to the document.
/// Renders child blocks with background shading and optional top/bottom separator borders.
/// </summary>
/// <param name="content">Structured child blocks extracted from the fenced div</param>
/// <param name="style">Fenced div visual style</param>
void AddFencedDiv(FencedDivContent content, FencedDivStyle style);

/// <summary>
/// Adds a thematic break (horizontal rule) to the document
/// </summary>
Expand Down
77 changes: 77 additions & 0 deletions csharp-version/src/MarkdownToDocx.Core/Models/FencedDivContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
namespace MarkdownToDocx.Core.Models;

/// <summary>
/// Structured content extracted from a fenced div block.
/// Contains an ordered list of typed child blocks ready for rendering.
/// </summary>
public sealed class FencedDivContent
{
/// <summary>
/// Ordered sequence of child blocks within the fenced div.
/// </summary>
public IReadOnlyList<FencedDivBlock> Blocks { get; init; } = [];
}

/// <summary>
/// Base type for all child blocks within a fenced div.
/// </summary>
public abstract class FencedDivBlock { }

/// <summary>
/// A paragraph (with inline formatting) inside a fenced div.
/// </summary>
public sealed class FencedDivParagraph : FencedDivBlock
{
/// <summary>
/// Structured inline runs with bold/italic/code formatting.
/// </summary>
public IReadOnlyList<InlineRun> Runs { get; init; } = [];
}

/// <summary>
/// A heading inside a fenced div.
/// </summary>
public sealed class FencedDivHeading : FencedDivBlock
{
/// <summary>
/// Heading level (1–6).
/// </summary>
public int Level { get; init; }

/// <summary>
/// Plain text content of the heading.
/// </summary>
public string Text { get; init; } = string.Empty;
}

/// <summary>
/// A table inside a fenced div.
/// </summary>
public sealed class FencedDivTable : FencedDivBlock
{
/// <summary>
/// Structured table data with rows, cells, and alignment.
/// </summary>
public TableData Data { get; init; } = new();
}

/// <summary>
/// A list (ordered or unordered) inside a fenced div.
/// </summary>
public sealed class FencedDivList : FencedDivBlock
{
/// <summary>
/// List items.
/// </summary>
public IReadOnlyList<ListItem> Items { get; init; } = [];

/// <summary>
/// True for numbered list, false for bullet list.
/// </summary>
public bool IsOrdered { get; init; }

/// <summary>
/// First number for ordered lists.
/// </summary>
public int StartNumber { get; init; } = 1;
}
79 changes: 79 additions & 0 deletions csharp-version/src/MarkdownToDocx.Core/Models/FencedDivStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
namespace MarkdownToDocx.Core.Models;

/// <summary>
/// Styling configuration for a fenced div block (:::classname ... :::).
/// Background color is applied to all child paragraphs.
/// Top/bottom separator lines appear on the first and last paragraph-like elements.
/// </summary>
public sealed record FencedDivStyle
{
/// <summary>
/// Background fill color in hex (e.g., "F2F2F2"). Empty = no shading.
/// </summary>
public string BackgroundColor { get; init; } = string.Empty;

/// <summary>
/// Top separator line color in hex (e.g., "AAAAAA"). Empty = no top border.
/// </summary>
public string BorderTopColor { get; init; } = string.Empty;

/// <summary>
/// Top separator line thickness in eighths of a point (default: 4 = 0.5pt).
/// </summary>
public uint BorderTopSize { get; init; } = 4;

/// <summary>
/// Bottom separator line color in hex (e.g., "AAAAAA"). Empty = no bottom border.
/// </summary>
public string BorderBottomColor { get; init; } = string.Empty;

/// <summary>
/// Bottom separator line thickness in eighths of a point (default: 4 = 0.5pt).
/// </summary>
public uint BorderBottomSize { get; init; } = 4;

/// <summary>
/// Space between separator line and text in points (default: 0).
/// </summary>
public uint BorderSpace { get; init; } = 0;

/// <summary>
/// Spacing before the first block in the div in twips.
/// </summary>
public string SpaceBefore { get; init; } = "0";

/// <summary>
/// Spacing after the last block in the div in twips.
/// </summary>
public string SpaceAfter { get; init; } = "0";

/// <summary>
/// Left indent for div paragraphs in twips.
/// </summary>
public string LeftIndent { get; init; } = "0";

/// <summary>
/// Font size in half-points (inherited from paragraph style).
/// </summary>
public int FontSize { get; init; }

/// <summary>
/// Text color in hex (inherited from paragraph style).
/// </summary>
public string Color { get; init; } = string.Empty;

/// <summary>
/// Line spacing in twips (inherited from paragraph style).
/// </summary>
public string LineSpacing { get; init; } = "360";

/// <summary>
/// Monospace font for inline code ASCII characters.
/// </summary>
public string InlineCodeFontAscii { get; init; } = "Courier New";

/// <summary>
/// Monospace font for inline code East Asian characters.
/// </summary>
public string InlineCodeFontEastAsia { get; init; } = "Noto Sans Mono CJK JP";
}
7 changes: 7 additions & 0 deletions csharp-version/src/MarkdownToDocx.Core/Models/TableStyle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,11 @@ public sealed record TableStyle
/// Values below 100 leave horizontal space around the table.
/// </summary>
public int WidthPercent { get; init; } = 90;

/// <summary>
/// Background color applied to all body cells in hex (e.g., "F2F2F2").
/// When set (e.g., when the table is inside a fenced div), overrides the default
/// transparent body cell background. Null or empty means no shading.
/// </summary>
public string? BodyCellBackgroundColor { get; init; }
}
Loading
Loading