Skip to content

Commit 092980d

Browse files
committed
Ensure to apply default style for paragraphs, to avoid a paragraph between 2 list is mis-guessed
1 parent ba251d3 commit 092980d

File tree

8 files changed

+80
-17
lines changed

8 files changed

+80
-17
lines changed

src/Html2OpenXml/Expressions/BlockElementExpression.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,21 @@ namespace HtmlToOpenXml.Expressions;
2323
/// Process the parsing of block contents (like <c>p</c>, <c>span</c>, <c>heading</c>).
2424
/// A block-level element always starts on a new line, and the browsers automatically add some space (a margin) before and after the element.
2525
/// </summary>
26-
class BlockElementExpression(IHtmlElement node, params OpenXmlLeafElement[]? styleProperty) : PhrasingElementExpression(node)
26+
class BlockElementExpression: PhrasingElementExpression
2727
{
28-
private readonly OpenXmlLeafElement[]? defaultStyleProperties = styleProperty;
28+
private readonly OpenXmlLeafElement[]? defaultStyleProperties;
2929
protected readonly ParagraphProperties paraProperties = new();
3030

31+
public BlockElementExpression(IHtmlElement node, OpenXmlLeafElement? styleProperty) : base(node)
32+
{
33+
if (styleProperty is not null)
34+
defaultStyleProperties = [styleProperty];
35+
}
36+
public BlockElementExpression(IHtmlElement node, params OpenXmlLeafElement[]? styleProperty) : base(node)
37+
{
38+
defaultStyleProperties = styleProperty;
39+
}
40+
3141

3242
/// <inheritdoc/>
3343
public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)

src/Html2OpenXml/Expressions/BodyExpression.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ namespace HtmlToOpenXml.Expressions;
2323
/// Top parent expression, processing the <c>body</c> tag,
2424
/// even if it is not directly specified in the provided Html.
2525
/// </summary>
26-
sealed class BodyExpression(IHtmlElement node) : BlockElementExpression(node)
26+
sealed class BodyExpression(IHtmlElement node, ParagraphStyleId? defaultStyle)
27+
: BlockElementExpression(node, defaultStyle)
2728
{
2829
private bool shouldRegisterTopBookmark;
2930

src/Html2OpenXml/HtmlConverter.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,7 @@ public async Task ParseHeader(string html, HeaderFooterValues? headerType = null
127127
new ParallelOptions() { CancellationToken = cancellationToken },
128128
htmlStyles.GetParagraphStyle(htmlStyles.DefaultStyles.HeaderStyle));
129129

130-
foreach (var p in paragraphs)
131-
headerPart.Header.AddChild(p);
130+
headerPart.Header.Append(paragraphs);
132131
}
133132

134133
/// <summary>
@@ -152,8 +151,7 @@ public async Task ParseFooter(string html, HeaderFooterValues? footerType = null
152151
new ParallelOptions() { CancellationToken = cancellationToken },
153152
htmlStyles.GetParagraphStyle(htmlStyles.DefaultStyles.FooterStyle));
154153

155-
foreach (var p in paragraphs)
156-
footerPart.Footer.AddChild(p);
154+
footerPart.Footer.Append(paragraphs);
157155
}
158156

159157
/// <summary>
@@ -166,7 +164,8 @@ public async Task ParseBody(string html, CancellationToken cancellationToken = d
166164
{
167165
bodyImageLoader ??= new ImagePrefetcher<MainDocumentPart>(mainPart, webRequester);
168166
var paragraphs = await ParseCoreAsync(html, mainPart, bodyImageLoader,
169-
new ParallelOptions() { CancellationToken = cancellationToken });
167+
new ParallelOptions() { CancellationToken = cancellationToken },
168+
htmlStyles.GetParagraphStyle(htmlStyles.DefaultStyles.Paragraph));
170169

171170
if (!paragraphs.Any())
172171
return;
@@ -263,11 +262,9 @@ private async Task<IEnumerable<OpenXmlCompositeElement>> ParseCoreAsync(string h
263262

264263
Expressions.HtmlDomExpression expression;
265264
if (hostingPart is MainDocumentPart)
266-
expression = new Expressions.BodyExpression(htmlDocument.Body!);
267-
else if (defaultParagraphStyleId?.Val?.HasValue == true)
268-
expression = new Expressions.BlockElementExpression(htmlDocument.Body!, defaultParagraphStyleId);
265+
expression = new Expressions.BodyExpression(htmlDocument.Body!, defaultParagraphStyleId);
269266
else
270-
expression = new Expressions.BlockElementExpression(htmlDocument.Body!);
267+
expression = new Expressions.BlockElementExpression(htmlDocument.Body!, defaultParagraphStyleId);
271268

272269
var parsingContext = new ParsingContext(this, hostingPart, imageLoader);
273270
var paragraphs = expression.Interpret(parsingContext);

src/Html2OpenXml/PredefinedStyles.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ internal class PredefinedStyles
2424
public const string TableGrid = "TableGrid";
2525
public const string Header = "Header";
2626
public const string Footer = "Footer";
27+
public const string Paragraph = "Normal";
2728

2829

2930

src/Html2OpenXml/Primitives/DefaultStyles.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,10 @@ public class DefaultStyles
101101
/// </summary>
102102
/// <value>Footer</value>
103103
public string FooterStyle { get; set; } = PredefinedStyles.Footer;
104+
105+
/// <summary>
106+
/// Default style for body paragraph.
107+
/// </summary>
108+
/// <value>Normal</value>
109+
public string Paragraph { get; set; } = PredefinedStyles.Paragraph;
104110
}

src/Html2OpenXml/WordDocumentStyle.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ internal WordDocumentStyle(MainDocumentPart mainPart)
5353
PredefinedStyles.ListParagraph,
5454
PredefinedStyles.Quote,
5555
PredefinedStyles.QuoteChar,
56-
PredefinedStyles.TableGrid
56+
PredefinedStyles.TableGrid,
57+
PredefinedStyles.Paragraph
5758
];
5859
this.mainPart = mainPart;
5960
}

test/HtmlToOpenXml.Tests/BodyTests.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ public class BodyTests : HtmlConverterTestBase
1212
{
1313
[TestCase("landscape", ExpectedResult = true)]
1414
[TestCase("portrait", ExpectedResult = false)]
15-
public bool PageOrientation_ReturnsLandscapeDimension(string orientation)
15+
public async Task<bool> PageOrientation_ReturnsLandscapeDimension(string orientation)
1616
{
17-
var _ = converter.Parse($@"<body style=""page-orientation:{orientation}""><body>");
17+
await converter.ParseBody($@"<body style=""page-orientation:{orientation}""><body>");
18+
AssertThatOpenXmlDocumentIsValid();
19+
1820
var sectionProperties = mainPart.Document.Body!.GetFirstChild<SectionProperties>();
1921
Assert.That(sectionProperties, Is.Not.Null);
2022
var pageSize = sectionProperties.GetFirstChild<PageSize>();
@@ -24,7 +26,7 @@ public bool PageOrientation_ReturnsLandscapeDimension(string orientation)
2426

2527
[TestCase("portrait", ExpectedResult = true)]
2628
[TestCase("landscape", ExpectedResult = false)]
27-
public bool PageOrientation_OverrideExistingLayout_ReturnsLandscapeDimension(string orientation)
29+
public async Task<bool> PageOrientation_OverrideExistingLayout_ReturnsLandscapeDimension(string orientation)
2830
{
2931
using var generatedDocument = new MemoryStream();
3032
using (var buffer = ResourceHelper.GetStream("Resources.DocWithLandscape.docx"))
@@ -35,7 +37,9 @@ public bool PageOrientation_OverrideExistingLayout_ReturnsLandscapeDimension(str
3537
MainDocumentPart mainPart = package.MainDocumentPart!;
3638
HtmlConverter converter = new(mainPart);
3739

38-
var _ = converter.Parse($@"<body style=""page-orientation:{orientation}""><body>");
40+
await converter.ParseBody($@"<body style=""page-orientation:{orientation}""><body>");
41+
AssertThatOpenXmlDocumentIsValid();
42+
3943
var sectionProperties = mainPart.Document.Body!.GetFirstChild<SectionProperties>();
4044
Assert.That(sectionProperties, Is.Not.Null);
4145
var pageSize = sectionProperties.GetFirstChild<PageSize>();

test/HtmlToOpenXml.Tests/HeaderFooterTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,48 @@ public async Task WithExistingHeader_Even_ReturnsAnotherHeaderPart()
109109
});
110110
AssertThatOpenXmlDocumentIsValid();
111111
}
112+
113+
[Test]
114+
public async Task Header_ReturnsStyleParagraphs()
115+
{
116+
await converter.ParseHeader(@"
117+
<header>
118+
<p>Placeholder
119+
<nav>
120+
<ul><li>Home</li><li>Contact</li></ul>
121+
</nav>
122+
</p>
123+
</header>
124+
");
125+
126+
var header = mainPart.HeaderParts.FirstOrDefault()?.Header;
127+
Assert.That(header, Is.Not.Null);
128+
var paragraphs = header.Elements<Paragraph>();
129+
Assert.That(paragraphs.Count(), Is.EqualTo(3));
130+
Assert.That(paragraphs.First().ParagraphProperties?.ParagraphStyleId?.Val?.Value,
131+
Is.EqualTo(converter.HtmlStyles.DefaultStyles.HeaderStyle));
132+
Assert.That(paragraphs.Skip(1).Select(p => p.ParagraphProperties?.ParagraphStyleId?.Val?.Value),
133+
Has.All.EqualTo(converter.HtmlStyles.DefaultStyles.ListParagraphStyle));
134+
}
135+
136+
[Test]
137+
public async Task Footer_ReturnsStyleParagraphs()
138+
{
139+
await converter.ParseFooter(@"
140+
<footer>
141+
<p>
142+
<a rel=""license"" href=""https://creativecommons.org/licenses/by/4.0/"">Copyrighted but you can use what's here as long as you credit me</a>
143+
<small>&copy; Copyright 2058, Company Inc.</small>
144+
</p>
145+
</footer>
146+
");
147+
148+
var footer = mainPart.FooterParts.FirstOrDefault()?.Footer;
149+
Assert.That(footer, Is.Not.Null);
150+
var paragraphs = footer.Elements<Paragraph>();
151+
Assert.That(paragraphs.Count(), Is.EqualTo(2));
152+
Assert.That(paragraphs.Select(p => p.ParagraphProperties?.ParagraphStyleId?.Val?.Value),
153+
Has.All.EqualTo(converter.HtmlStyles.DefaultStyles.FooterStyle));
154+
}
112155
}
113156
}

0 commit comments

Comments
 (0)