Skip to content

Commit

Permalink
Bordered container now use table instead of paragraphs with no spacin…
Browse files Browse the repository at this point in the history
…g between lines #168
  • Loading branch information
onizet committed Oct 15, 2024
1 parent 8e00c09 commit 994f5cf
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 31 deletions.
90 changes: 67 additions & 23 deletions src/Html2OpenXml/Expressions/BlockElementExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ class BlockElementExpression: PhrasingElementExpression
{
private readonly OpenXmlLeafElement[]? defaultStyleProperties;
protected readonly ParagraphProperties paraProperties = new();
// some style attributes, such as borders, must be applied on multiple paragraphs
// in order to render as one single block of content.
protected bool renderAsOneBlock;
// some style attributes, such as borders or bgcolor, will convert this node to a framed container
protected bool renderAsFramed;
private HtmlBorder styleBorder;


public BlockElementExpression(IHtmlElement node, OpenXmlLeafElement? styleProperty) : base(node)
Expand Down Expand Up @@ -58,20 +58,69 @@ public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)
p.AppendChild(new BookmarkEnd() { Id = bookmarkId });
}

if (!renderAsOneBlock || childElements.Count() < 2)
if (!renderAsFramed)
return childElements;

// to group together those paragraphs, we must force some indentation requirement
foreach (var p in childElements.OfType<Paragraph>())
var paragraphs = childElements.OfType<Paragraph>();
if (!paragraphs.Any()) return childElements;

// if we have only 1 paragraph, just inline the styles
if (paragraphs.Count() == 1)
{
p.ParagraphProperties ??= new();
// do not override indentation if `text-indent` was previously set
if ((p.ParagraphProperties.Indentation?.FirstLine?.HasValue) != true)
var p = paragraphs.First();

if (!styleBorder.IsEmpty && p.ParagraphProperties?.ParagraphBorders is null)
{
p.ParagraphProperties.Indentation = new() { Right = "0" };
p.ParagraphProperties ??= new();
p.ParagraphProperties!.ParagraphBorders = new ParagraphBorders {
LeftBorder = Converter.ToBorder<LeftBorder>(styleBorder.Left),
RightBorder = Converter.ToBorder<RightBorder>(styleBorder.Right),
TopBorder = Converter.ToBorder<TopBorder>(styleBorder.Top),
BottomBorder = Converter.ToBorder<BottomBorder>(styleBorder.Bottom)
};
}

return childElements;
}

// if we have 2+ paragraphs, we will embed them inside a stylised table
return [CreateFrame(childElements)];
}

/// <summary>
/// Group all the paragraph inside a framed table.
/// </summary>
private Table CreateFrame(IEnumerable<OpenXmlElement> childElements)
{
TableCell cell;
TableProperties tableProperties;
Table framedTable = new(
tableProperties = new TableProperties {
TableWidth = new() { Type = TableWidthUnitValues.Auto, Width = "0" } // 100%
},
new TableGrid(
new GridColumn() { Width = "5610" }),
new TableRow(
cell = new TableCell(childElements)
)
);

if (!styleBorder.IsEmpty)
{
tableProperties.TableBorders = new TableBorders {
LeftBorder = Converter.ToBorder<LeftBorder>(styleBorder.Left),
RightBorder = Converter.ToBorder<RightBorder>(styleBorder.Right),
TopBorder = Converter.ToBorder<TopBorder>(styleBorder.Top),
BottomBorder = Converter.ToBorder<BottomBorder>(styleBorder.Bottom)
};
}

if (runProperties.Shading != null)
{
cell.TableCellProperties = new() { Shading = (Shading?) runProperties.Shading.Clone() };
}
return childElements;

return framedTable;
}

protected override IEnumerable<OpenXmlElement> Interpret (
Expand Down Expand Up @@ -163,18 +212,10 @@ protected override void ComposeStyles (ParsingContext context)
}


var styleBorder = styleAttributes.GetBorders();
styleBorder = styleAttributes.GetBorders();
if (!styleBorder.IsEmpty)
{
var borders = new ParagraphBorders {
LeftBorder = Converter.ToBorder<LeftBorder>(styleBorder.Left),
RightBorder = Converter.ToBorder<RightBorder>(styleBorder.Right),
TopBorder = Converter.ToBorder<TopBorder>(styleBorder.Top),
BottomBorder = Converter.ToBorder<BottomBorder>(styleBorder.Bottom)
};

paraProperties.ParagraphBorders = borders;
renderAsOneBlock = true;
renderAsFramed = true;
}

foreach (string className in node.ClassList)
Expand All @@ -187,8 +228,8 @@ protected override void ComposeStyles (ParsingContext context)
}
}

Margin margin = styleAttributes.GetMargin("margin");
Indentation? indentation = null;
var margin = styleAttributes.GetMargin("margin");
Indentation? indentation = null;
if (!margin.IsEmpty)
{
if (margin.Top.IsFixed || margin.Bottom.IsFixed)
Expand Down Expand Up @@ -264,6 +305,9 @@ protected override void ComposeStyles (ParsingContext context)
};
}
}

if (runProperties.Shading != null)
renderAsFramed = true;
}

/// <summary>
Expand Down
43 changes: 35 additions & 8 deletions test/HtmlToOpenXml.Tests/DivTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ public void WithOnlyLineBreak_ReturnsEmptyRun()
}

[Test(Description = "Border defined on container should render its content with one bordered frame #168")]
public async Task WithBorders_ReturnsAsOneFramedBlock()
public async Task WithBorders_MultipleParagraphs_ReturnsAsOneFramedBlock()
{
await converter.ParseBody(@"<div style='margin-top: 20px; border: 1px dashed rgba(0, 0, 0, 0.4); display: flex; gap: 5px; padding: 6px 8px; font-size: 14px;'>
await converter.ParseBody(@"<div style='margin-top: 20px; border: 1px dashed rgba(0, 0, 0, 0.4); padding: 6px 8px; font-size: 14px;'>
<div>
<p>Header placeholder:</p>
<ol>
Expand All @@ -164,19 +164,46 @@ public async Task WithBorders_ReturnsAsOneFramedBlock()
AssertThatOpenXmlDocumentIsValid();

var paragraphs = mainPart.Document.Body!.Elements<Paragraph>();
Assert.That(paragraphs, Is.Not.Empty);
Assert.That(paragraphs.Select(p => p.ParagraphProperties?.ParagraphBorders), Has.All.Not.Empty);
Assert.That(paragraphs.SelectMany(p => p.ParagraphProperties?.ParagraphBorders!.Elements<BorderType>()!)
Assert.That(paragraphs, Is.Empty, "Assert that all the paragraphs stand inside the framed table");

var framedTable = mainPart.Document.Body!.Elements<Table>().FirstOrDefault();
Assert.That(framedTable, Is.Not.Null);

var borders = framedTable.GetFirstChild<TableProperties>()?.TableBorders;
Assert.That(borders, Is.Not.Null, "Assert that border is applied on table scope");
Assert.That(borders.Elements<BorderType>()!
.Select(b => b.Val?.Value),
Has.All.EqualTo(BorderValues.Dashed));

Assert.That(paragraphs.Take(paragraphs.Count() - 1)
.Select(p => p.ParagraphProperties?.Indentation?.Right?.Value), Has.All.EqualTo("0"),
"Assert that all paragraphs right indentation is reset");
var cell = framedTable.GetFirstChild<TableRow>()?.GetFirstChild<TableCell>();
Assert.That(cell, Is.Not.Null);
paragraphs = cell.Elements<Paragraph>();
Assert.That(paragraphs, Is.Not.Empty);

Assert.That(paragraphs.Last().ParagraphProperties?.Indentation?.FirstLine?.Value, Is.EqualTo("1080"),
"Assert that paragraph with text-indent is preserved");
Assert.That(paragraphs.Last().ParagraphProperties?.Indentation?.Right, Is.Null,
"Assert that paragraph with right indentation is preserved");
}

[Test(Description = "Background color defined on container should render its content with one bordered frame")]
public async Task WithBgcolor_MultipleParagraphs_ReturnsAsOneFramedBlock()
{
await converter.ParseBody(@"<article style='background: orange'>
<header>Header placeholder</header>
<p>Body Placeholder</p>
</article>");
AssertThatOpenXmlDocumentIsValid();

var paragraphs = mainPart.Document.Body!.Elements<Paragraph>();
Assert.That(paragraphs, Is.Empty, "Assert that all the paragraphs stand inside the framed table");

var framedTable = mainPart.Document.Body!.Elements<Table>().FirstOrDefault();
Assert.That(framedTable, Is.Not.Null);

var shading = framedTable.GetFirstChild<TableRow>()?.GetFirstChild<TableCell>()?.TableCellProperties?.Shading;
Assert.That(shading, Is.Not.Null, "Assert that background-color is applied on table scope");
Assert.That(shading.Fill?.Value, Is.EqualTo("FFA500"));
}
}
}

0 comments on commit 994f5cf

Please sign in to comment.