Skip to content

Commit 994f5cf

Browse files
committed
Bordered container now use table instead of paragraphs with no spacing between lines #168
1 parent 8e00c09 commit 994f5cf

File tree

2 files changed

+102
-31
lines changed

2 files changed

+102
-31
lines changed

src/Html2OpenXml/Expressions/BlockElementExpression.cs

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ class BlockElementExpression: PhrasingElementExpression
2727
{
2828
private readonly OpenXmlLeafElement[]? defaultStyleProperties;
2929
protected readonly ParagraphProperties paraProperties = new();
30-
// some style attributes, such as borders, must be applied on multiple paragraphs
31-
// in order to render as one single block of content.
32-
protected bool renderAsOneBlock;
30+
// some style attributes, such as borders or bgcolor, will convert this node to a framed container
31+
protected bool renderAsFramed;
32+
private HtmlBorder styleBorder;
3333

3434

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

61-
if (!renderAsOneBlock || childElements.Count() < 2)
61+
if (!renderAsFramed)
6262
return childElements;
6363

64-
// to group together those paragraphs, we must force some indentation requirement
65-
foreach (var p in childElements.OfType<Paragraph>())
64+
var paragraphs = childElements.OfType<Paragraph>();
65+
if (!paragraphs.Any()) return childElements;
66+
67+
// if we have only 1 paragraph, just inline the styles
68+
if (paragraphs.Count() == 1)
6669
{
67-
p.ParagraphProperties ??= new();
68-
// do not override indentation if `text-indent` was previously set
69-
if ((p.ParagraphProperties.Indentation?.FirstLine?.HasValue) != true)
70+
var p = paragraphs.First();
71+
72+
if (!styleBorder.IsEmpty && p.ParagraphProperties?.ParagraphBorders is null)
7073
{
71-
p.ParagraphProperties.Indentation = new() { Right = "0" };
74+
p.ParagraphProperties ??= new();
75+
p.ParagraphProperties!.ParagraphBorders = new ParagraphBorders {
76+
LeftBorder = Converter.ToBorder<LeftBorder>(styleBorder.Left),
77+
RightBorder = Converter.ToBorder<RightBorder>(styleBorder.Right),
78+
TopBorder = Converter.ToBorder<TopBorder>(styleBorder.Top),
79+
BottomBorder = Converter.ToBorder<BottomBorder>(styleBorder.Bottom)
80+
};
7281
}
82+
83+
return childElements;
84+
}
85+
86+
// if we have 2+ paragraphs, we will embed them inside a stylised table
87+
return [CreateFrame(childElements)];
88+
}
89+
90+
/// <summary>
91+
/// Group all the paragraph inside a framed table.
92+
/// </summary>
93+
private Table CreateFrame(IEnumerable<OpenXmlElement> childElements)
94+
{
95+
TableCell cell;
96+
TableProperties tableProperties;
97+
Table framedTable = new(
98+
tableProperties = new TableProperties {
99+
TableWidth = new() { Type = TableWidthUnitValues.Auto, Width = "0" } // 100%
100+
},
101+
new TableGrid(
102+
new GridColumn() { Width = "5610" }),
103+
new TableRow(
104+
cell = new TableCell(childElements)
105+
)
106+
);
107+
108+
if (!styleBorder.IsEmpty)
109+
{
110+
tableProperties.TableBorders = new TableBorders {
111+
LeftBorder = Converter.ToBorder<LeftBorder>(styleBorder.Left),
112+
RightBorder = Converter.ToBorder<RightBorder>(styleBorder.Right),
113+
TopBorder = Converter.ToBorder<TopBorder>(styleBorder.Top),
114+
BottomBorder = Converter.ToBorder<BottomBorder>(styleBorder.Bottom)
115+
};
116+
}
117+
118+
if (runProperties.Shading != null)
119+
{
120+
cell.TableCellProperties = new() { Shading = (Shading?) runProperties.Shading.Clone() };
73121
}
74-
return childElements;
122+
123+
return framedTable;
75124
}
76125

77126
protected override IEnumerable<OpenXmlElement> Interpret (
@@ -163,18 +212,10 @@ protected override void ComposeStyles (ParsingContext context)
163212
}
164213

165214

166-
var styleBorder = styleAttributes.GetBorders();
215+
styleBorder = styleAttributes.GetBorders();
167216
if (!styleBorder.IsEmpty)
168217
{
169-
var borders = new ParagraphBorders {
170-
LeftBorder = Converter.ToBorder<LeftBorder>(styleBorder.Left),
171-
RightBorder = Converter.ToBorder<RightBorder>(styleBorder.Right),
172-
TopBorder = Converter.ToBorder<TopBorder>(styleBorder.Top),
173-
BottomBorder = Converter.ToBorder<BottomBorder>(styleBorder.Bottom)
174-
};
175-
176-
paraProperties.ParagraphBorders = borders;
177-
renderAsOneBlock = true;
218+
renderAsFramed = true;
178219
}
179220

180221
foreach (string className in node.ClassList)
@@ -187,8 +228,8 @@ protected override void ComposeStyles (ParsingContext context)
187228
}
188229
}
189230

190-
Margin margin = styleAttributes.GetMargin("margin");
191-
Indentation? indentation = null;
231+
var margin = styleAttributes.GetMargin("margin");
232+
Indentation? indentation = null;
192233
if (!margin.IsEmpty)
193234
{
194235
if (margin.Top.IsFixed || margin.Bottom.IsFixed)
@@ -264,6 +305,9 @@ protected override void ComposeStyles (ParsingContext context)
264305
};
265306
}
266307
}
308+
309+
if (runProperties.Shading != null)
310+
renderAsFramed = true;
267311
}
268312

269313
/// <summary>

test/HtmlToOpenXml.Tests/DivTests.cs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,9 @@ public void WithOnlyLineBreak_ReturnsEmptyRun()
149149
}
150150

151151
[Test(Description = "Border defined on container should render its content with one bordered frame #168")]
152-
public async Task WithBorders_ReturnsAsOneFramedBlock()
152+
public async Task WithBorders_MultipleParagraphs_ReturnsAsOneFramedBlock()
153153
{
154-
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;'>
154+
await converter.ParseBody(@"<div style='margin-top: 20px; border: 1px dashed rgba(0, 0, 0, 0.4); padding: 6px 8px; font-size: 14px;'>
155155
<div>
156156
<p>Header placeholder:</p>
157157
<ol>
@@ -164,19 +164,46 @@ public async Task WithBorders_ReturnsAsOneFramedBlock()
164164
AssertThatOpenXmlDocumentIsValid();
165165

166166
var paragraphs = mainPart.Document.Body!.Elements<Paragraph>();
167-
Assert.That(paragraphs, Is.Not.Empty);
168-
Assert.That(paragraphs.Select(p => p.ParagraphProperties?.ParagraphBorders), Has.All.Not.Empty);
169-
Assert.That(paragraphs.SelectMany(p => p.ParagraphProperties?.ParagraphBorders!.Elements<BorderType>()!)
167+
Assert.That(paragraphs, Is.Empty, "Assert that all the paragraphs stand inside the framed table");
168+
169+
var framedTable = mainPart.Document.Body!.Elements<Table>().FirstOrDefault();
170+
Assert.That(framedTable, Is.Not.Null);
171+
172+
var borders = framedTable.GetFirstChild<TableProperties>()?.TableBorders;
173+
Assert.That(borders, Is.Not.Null, "Assert that border is applied on table scope");
174+
Assert.That(borders.Elements<BorderType>()!
170175
.Select(b => b.Val?.Value),
171176
Has.All.EqualTo(BorderValues.Dashed));
172177

173-
Assert.That(paragraphs.Take(paragraphs.Count() - 1)
174-
.Select(p => p.ParagraphProperties?.Indentation?.Right?.Value), Has.All.EqualTo("0"),
175-
"Assert that all paragraphs right indentation is reset");
178+
var cell = framedTable.GetFirstChild<TableRow>()?.GetFirstChild<TableCell>();
179+
Assert.That(cell, Is.Not.Null);
180+
paragraphs = cell.Elements<Paragraph>();
181+
Assert.That(paragraphs, Is.Not.Empty);
182+
176183
Assert.That(paragraphs.Last().ParagraphProperties?.Indentation?.FirstLine?.Value, Is.EqualTo("1080"),
177184
"Assert that paragraph with text-indent is preserved");
178185
Assert.That(paragraphs.Last().ParagraphProperties?.Indentation?.Right, Is.Null,
179186
"Assert that paragraph with right indentation is preserved");
180187
}
188+
189+
[Test(Description = "Background color defined on container should render its content with one bordered frame")]
190+
public async Task WithBgcolor_MultipleParagraphs_ReturnsAsOneFramedBlock()
191+
{
192+
await converter.ParseBody(@"<article style='background: orange'>
193+
<header>Header placeholder</header>
194+
<p>Body Placeholder</p>
195+
</article>");
196+
AssertThatOpenXmlDocumentIsValid();
197+
198+
var paragraphs = mainPart.Document.Body!.Elements<Paragraph>();
199+
Assert.That(paragraphs, Is.Empty, "Assert that all the paragraphs stand inside the framed table");
200+
201+
var framedTable = mainPart.Document.Body!.Elements<Table>().FirstOrDefault();
202+
Assert.That(framedTable, Is.Not.Null);
203+
204+
var shading = framedTable.GetFirstChild<TableRow>()?.GetFirstChild<TableCell>()?.TableCellProperties?.Shading;
205+
Assert.That(shading, Is.Not.Null, "Assert that background-color is applied on table scope");
206+
Assert.That(shading.Fill?.Value, Is.EqualTo("FFA500"));
207+
}
181208
}
182209
}

0 commit comments

Comments
 (0)