Skip to content

Commit ce0e374

Browse files
authored
Merge pull request #157 from onizet/dev
Release 3.1.0
2 parents bd04f45 + a2878eb commit ce0e374

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+989
-729
lines changed

CHANGELOG.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
- Support MathMl
66
- Support SVG
77

8-
## 3.0.1
8+
## 3.1.0
99

10-
- Ensure to count existing images from header and footer too #113
11-
- Preserve line break pre for OSX/Windows
12-
- Prevent a crash when the provided style is missing its type
13-
- Defensive code to avoid 2 rowSpan+colSpan with a cell in between to crash #59
10+
- Fix table Cell borders are wrongly applied on the run #156
11+
- Correctly handle RTL layout for text, list, table and document scope #86 #66
12+
- Support property line-height #52
13+
- Fallback to `background` style attribute as many users use this simplified attribute version
14+
- In `HtmlDomExpression.CreateFromHtmlNode`, use the correct casting to `IElement` rather than `IHtmlElement`, to prevent crash if `svg` node is encountered
1415

1516
## 3.0.0
1617

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,5 @@ Logo provided with the permission of [Enhanced Labs Design Studio](http://www.en
6868

6969
## Support
7070

71-
This project is open source and I do my best to support it in my spare time. I'm always happy to receive Pull Request and grateful for the time you have taken
71+
This project is open source and I do my best to support it in my spare time. I'm always happy to receive Pull Request and grateful for the time you have taken. Please target branch `dev` only.
7272
If you have questions, don't hesitate to get in touch with me!

examples/Demo/Program.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,7 @@ static async Task Main(string[] args)
1717
const string filename = "test.docx";
1818
string html = ResourceHelper.GetString("Resources.CompleteRunTest.html");
1919
if (File.Exists(filename)) File.Delete(filename);
20-
const string preformattedText = @"
21-
^__^
22-
(oo)\_______
23-
(__)\ )\/\
24-
||----w |
25-
|| ||";
2620

27-
html = @$"<pre role='img' aria-label='ASCII COW'>
28-
{preformattedText}</pre>";
2921
using (MemoryStream generatedDocument = new MemoryStream())
3022
{
3123
// Uncomment and comment the second using() to open an existing template document
@@ -53,7 +45,7 @@ static async Task Main(string[] args)
5345
await converter.ParseHtml(html);
5446
mainPart.Document.Save();
5547

56-
//AssertThatOpenXmlDocumentIsValid(package);
48+
AssertThatOpenXmlDocumentIsValid(package);
5749
}
5850

5951
File.WriteAllBytes(filename, generatedDocument.ToArray());

src/Html2OpenXml/Collections/HtmlAttributeCollection.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ public HtmlColor GetColor(string name)
7171
/// Gets an attribute representing an unit: 120px, 10pt, 5em, 20%, ...
7272
/// </summary>
7373
/// <returns>If the attribute is misformed, the <see cref="Unit.IsValid"/> property is set to false.</returns>
74-
public Unit GetUnit(string name)
74+
public Unit GetUnit(string name, UnitMetric defaultMetric = UnitMetric.Unitless)
7575
{
76-
return Unit.Parse(this[name]);
76+
return Unit.Parse(this[name], defaultMetric);
7777
}
7878

7979
/// <summary>
@@ -86,13 +86,13 @@ public Margin GetMargin(string name)
8686
Margin margin = Margin.Parse(this[name]);
8787
Unit u;
8888

89-
u = GetUnit(name + "-top");
89+
u = GetUnit(name + "-top", UnitMetric.Pixel);
9090
if (u.IsValid) margin.Top = u;
91-
u = GetUnit(name + "-right");
91+
u = GetUnit(name + "-right", UnitMetric.Pixel);
9292
if (u.IsValid) margin.Right = u;
93-
u = GetUnit(name + "-bottom");
93+
u = GetUnit(name + "-bottom", UnitMetric.Pixel);
9494
if (u.IsValid) margin.Bottom = u;
95-
u = GetUnit(name + "-left");
95+
u = GetUnit(name + "-left", UnitMetric.Pixel);
9696
if (u.IsValid) margin.Left = u;
9797

9898
return margin;

src/Html2OpenXml/Expressions/BlockElementExpression.cs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,11 @@ protected override void ComposeStyles (ParsingContext context)
121121
}
122122

123123
// according to w3c, dir should be used in conjonction with lang. But whatever happens, we'll apply the RTL layout
124-
if ("rtl".Equals(node.Direction, StringComparison.OrdinalIgnoreCase))
125-
{
126-
paraProperties.Justification = new() { Val = JustificationValues.Right };
127-
}
128-
else if ("ltr".Equals(node.Direction, StringComparison.OrdinalIgnoreCase))
129-
{
130-
paraProperties.Justification = new() { Val = JustificationValues.Left };
124+
var dir = node.GetTextDirection();
125+
if (dir.HasValue) {
126+
paraProperties.BiDi = new() {
127+
Val = OnOffValue.FromBoolean(dir == AngleSharp.Dom.DirectionMode.Rtl)
128+
};
131129
}
132130

133131
var attrValue = styleAttributes!["text-align"];
@@ -200,6 +198,44 @@ protected override void ComposeStyles (ParsingContext context)
200198

201199
paraProperties.Indentation = indentation;
202200
}
201+
202+
var lineHeight = styleAttributes.GetUnit("line-height");
203+
if (!lineHeight.IsValid
204+
&& "normal".Equals(styleAttributes["line-height"], StringComparison.OrdinalIgnoreCase))
205+
{
206+
// if `normal` is specified, reset any values
207+
lineHeight = new Unit(UnitMetric.Unitless, 1);
208+
}
209+
210+
if (lineHeight.IsValid)
211+
{
212+
if (lineHeight.Type == UnitMetric.Unitless)
213+
{
214+
// auto should be considered as 240ths of a line
215+
// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.wordprocessing.spacingbetweenlines.line?view=openxml-3.0.1
216+
paraProperties.SpacingBetweenLines = new() {
217+
LineRule = LineSpacingRuleValues.Auto,
218+
Line = Math.Round(lineHeight.Value * 240).ToString(CultureInfo.InvariantCulture)
219+
};
220+
}
221+
else if (lineHeight.Type == UnitMetric.Percent)
222+
{
223+
// percentage depends on the font size which is hard to determine here
224+
// let's rely this to "auto" behaviour
225+
paraProperties.SpacingBetweenLines = new() {
226+
LineRule = LineSpacingRuleValues.Auto,
227+
Line = Math.Round(lineHeight.Value / 100 * 240).ToString(CultureInfo.InvariantCulture)
228+
};
229+
}
230+
else
231+
{
232+
// twentieths of a point
233+
paraProperties.SpacingBetweenLines = new() {
234+
LineRule = LineSpacingRuleValues.Exact,
235+
Line = Math.Round(lineHeight.ValueInPoint * 20).ToString(CultureInfo.InvariantCulture)
236+
};
237+
}
238+
}
203239
}
204240

205241
/// <summary>

src/Html2OpenXml/Expressions/BodyExpression.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ protected override void ComposeStyles(ParsingContext context)
5858
}
5959
}
6060
}
61+
62+
if (paraProperties.BiDi is not null)
63+
{
64+
var sectionProperties = context.MainPart.Document.Body!.GetFirstChild<SectionProperties>();
65+
if (sectionProperties == null || sectionProperties.GetFirstChild<PageSize>() == null)
66+
{
67+
context.MainPart.Document.Body.Append(sectionProperties = new());
68+
}
69+
70+
sectionProperties.AddChild(paraProperties.BiDi.CloneNode(true));
71+
}
6172
}
6273

6374
/// <summary>

src/Html2OpenXml/Expressions/HorizontalLineExpression.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,8 @@ namespace HtmlToOpenXml.Expressions;
2020
/// Process the parsing of a <c>hr</c> element
2121
/// by inserting an horizontal line as it stands in many emails.
2222
/// </summary>
23-
sealed class HorizontalLineExpression(IHtmlElement node) : HtmlElementExpression(node)
23+
sealed class HorizontalLineExpression(IHtmlElement node) : HtmlDomExpression
2424
{
25-
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
26-
public override void CascadeStyles(OpenXmlElement element)
27-
{
28-
throw new System.NotSupportedException();
29-
}
30-
3125
/// <inheritdoc/>
3226
public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)
3327
{

src/Html2OpenXml/Expressions/HtmlDomExpression.cs

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -24,53 +24,53 @@ namespace HtmlToOpenXml.Expressions;
2424
abstract class HtmlDomExpression
2525
{
2626
protected const string InternalNamespaceUri = "https://github.com/onizet/html2openxml";
27-
static readonly Dictionary<string, Func<IHtmlElement, HtmlElementExpression>> knownTags = InitKnownTags();
27+
static readonly Dictionary<string, Func<IElement, HtmlDomExpression>> knownTags = InitKnownTags();
2828
static readonly HashSet<string> ignoreTags = new(StringComparer.OrdinalIgnoreCase) {
2929
TagNames.Xml, TagNames.AnnotationXml, TagNames.Button, TagNames.Progress,
3030
TagNames.Select, TagNames.Input, TagNames.Textarea, TagNames.Meter };
3131

32-
private static Dictionary<string, Func<IHtmlElement, HtmlElementExpression>> InitKnownTags()
32+
private static Dictionary<string, Func<IElement, HtmlDomExpression>> InitKnownTags()
3333
{
3434
// A complete list of HTML tags can be found here: http://www.w3schools.com/tags/default.asp
3535

36-
var knownTags = new Dictionary<string, Func<IHtmlElement, HtmlElementExpression>>(StringComparer.InvariantCultureIgnoreCase) {
37-
{ TagNames.A, el => new HyperlinkExpression(el) },
38-
{ TagNames.Abbr, el => new AbbreviationExpression(el) },
39-
{ "acronym", el => new AbbreviationExpression(el) },
40-
{ TagNames.B, el => new PhrasingElementExpression(el, new Bold()) },
41-
{ TagNames.BlockQuote, el => new BlockQuoteExpression(el) },
42-
{ TagNames.Br, el => new LineBreakExpression(el) },
43-
{ TagNames.Cite, el => new CiteElementExpression(el) },
44-
{ TagNames.Dd, el => new BlockElementExpression(el, new Indentation() { FirstLine = "708" }, new SpacingBetweenLines() { After = "0" }) },
45-
{ TagNames.Del, el => new PhrasingElementExpression(el, new Strike()) },
46-
{ TagNames.Dfn, el => new AbbreviationExpression(el) },
47-
{ TagNames.Em, el => new PhrasingElementExpression(el, new Italic()) },
48-
{ TagNames.Figcaption, el => new FigureCaptionExpression(el) },
49-
{ TagNames.Font, el => new FontElementExpression(el) },
50-
{ TagNames.H1, el => new HeadingElementExpression(el) },
51-
{ TagNames.H2, el => new HeadingElementExpression(el) },
52-
{ TagNames.H3, el => new HeadingElementExpression(el) },
53-
{ TagNames.H4, el => new HeadingElementExpression(el) },
54-
{ TagNames.H5, el => new HeadingElementExpression(el) },
55-
{ TagNames.H6, el => new HeadingElementExpression(el) },
56-
{ TagNames.I, el => new PhrasingElementExpression(el, new Italic()) },
57-
{ TagNames.Hr, el => new HorizontalLineExpression(el) },
58-
{ TagNames.Img, el => new ImageExpression(el) },
59-
{ TagNames.Ins, el => new PhrasingElementExpression(el, new Underline() { Val = UnderlineValues.Single }) },
60-
{ TagNames.Ol, el => new ListExpression(el) },
61-
{ TagNames.Pre, el => new PreElementExpression(el) },
62-
{ TagNames.Q, el => new QuoteElementExpression(el) },
63-
{ TagNames.Quote, el => new QuoteElementExpression(el) },
64-
{ TagNames.Span, el => new PhrasingElementExpression(el) },
65-
{ TagNames.S, el => new PhrasingElementExpression(el, new Strike()) },
66-
{ TagNames.Strike, el => new PhrasingElementExpression(el, new Strike()) },
67-
{ TagNames.Strong, el => new PhrasingElementExpression(el, new Bold()) },
68-
{ TagNames.Sub, el => new PhrasingElementExpression(el, new VerticalTextAlignment() { Val = VerticalPositionValues.Subscript }) },
69-
{ TagNames.Sup, el => new PhrasingElementExpression(el, new VerticalTextAlignment() { Val = VerticalPositionValues.Superscript }) },
70-
{ TagNames.Table, el => new TableExpression(el) },
71-
{ TagNames.Time, el => new PhrasingElementExpression(el) },
72-
{ TagNames.U, el => new PhrasingElementExpression(el, new Underline() { Val = UnderlineValues.Single }) },
73-
{ TagNames.Ul, el => new ListExpression(el) },
36+
var knownTags = new Dictionary<string, Func<IElement, HtmlDomExpression>>(StringComparer.InvariantCultureIgnoreCase) {
37+
{ TagNames.A, el => new HyperlinkExpression((IHtmlAnchorElement) el) },
38+
{ TagNames.Abbr, el => new AbbreviationExpression((IHtmlElement) el) },
39+
{ "acronym", el => new AbbreviationExpression((IHtmlElement) el) },
40+
{ TagNames.B, el => new PhrasingElementExpression((IHtmlElement) el, new Bold()) },
41+
{ TagNames.BlockQuote, el => new BlockQuoteExpression((IHtmlElement) el) },
42+
{ TagNames.Br, _ => new LineBreakExpression() },
43+
{ TagNames.Cite, el => new CiteElementExpression((IHtmlElement) el) },
44+
{ TagNames.Dd, el => new BlockElementExpression((IHtmlElement) el, new Indentation() { FirstLine = "708" }, new SpacingBetweenLines() { After = "0" }) },
45+
{ TagNames.Del, el => new PhrasingElementExpression((IHtmlElement) el, new Strike()) },
46+
{ TagNames.Dfn, el => new AbbreviationExpression((IHtmlElement) el) },
47+
{ TagNames.Em, el => new PhrasingElementExpression((IHtmlElement) el, new Italic()) },
48+
{ TagNames.Figcaption, el => new FigureCaptionExpression((IHtmlElement) el) },
49+
{ TagNames.Font, el => new FontElementExpression((IHtmlElement) el) },
50+
{ TagNames.H1, el => new HeadingElementExpression((IHtmlElement) el) },
51+
{ TagNames.H2, el => new HeadingElementExpression((IHtmlElement) el) },
52+
{ TagNames.H3, el => new HeadingElementExpression((IHtmlElement) el) },
53+
{ TagNames.H4, el => new HeadingElementExpression((IHtmlElement) el) },
54+
{ TagNames.H5, el => new HeadingElementExpression((IHtmlElement) el) },
55+
{ TagNames.H6, el => new HeadingElementExpression((IHtmlElement) el) },
56+
{ TagNames.I, el => new PhrasingElementExpression((IHtmlElement) el, new Italic()) },
57+
{ TagNames.Hr, el => new HorizontalLineExpression((IHtmlElement) el) },
58+
{ TagNames.Img, el => new ImageExpression((IHtmlImageElement) el) },
59+
{ TagNames.Ins, el => new PhrasingElementExpression((IHtmlElement) el, new Underline() { Val = UnderlineValues.Single }) },
60+
{ TagNames.Ol, el => new ListExpression((IHtmlElement) el) },
61+
{ TagNames.Pre, el => new PreElementExpression((IHtmlElement) el) },
62+
{ TagNames.Q, el => new QuoteElementExpression((IHtmlElement) el) },
63+
{ TagNames.Quote, el => new QuoteElementExpression((IHtmlElement) el) },
64+
{ TagNames.Span, el => new PhrasingElementExpression((IHtmlElement) el) },
65+
{ TagNames.S, el => new PhrasingElementExpression((IHtmlElement) el, new Strike()) },
66+
{ TagNames.Strike, el => new PhrasingElementExpression((IHtmlElement) el, new Strike()) },
67+
{ TagNames.Strong, el => new PhrasingElementExpression((IHtmlElement) el, new Bold()) },
68+
{ TagNames.Sub, el => new PhrasingElementExpression((IHtmlElement) el, new VerticalTextAlignment() { Val = VerticalPositionValues.Subscript }) },
69+
{ TagNames.Sup, el => new PhrasingElementExpression((IHtmlElement) el, new VerticalTextAlignment() { Val = VerticalPositionValues.Superscript }) },
70+
{ TagNames.Table, el => new TableExpression((IHtmlTableElement) el) },
71+
{ TagNames.Time, el => new PhrasingElementExpression((IHtmlElement) el) },
72+
{ TagNames.U, el => new PhrasingElementExpression((IHtmlElement) el, new Underline() { Val = UnderlineValues.Single }) },
73+
{ TagNames.Ul, el => new ListExpression((IHtmlElement) el) },
7474
};
7575

7676
return knownTags;
@@ -93,8 +93,8 @@ private static Dictionary<string, Func<IHtmlElement, HtmlElementExpression>> Ini
9393
else if (node.NodeType == NodeType.Element
9494
&& !ignoreTags.Contains(node.NodeName))
9595
{
96-
if (knownTags.TryGetValue(node.NodeName, out Func<IHtmlElement, HtmlElementExpression>? handler))
97-
return handler((IHtmlElement) node);
96+
if (knownTags.TryGetValue(node.NodeName, out Func<IElement, HtmlDomExpression>? handler))
97+
return handler((IElement) node);
9898

9999
// fallback on the flow element which will cover all the semantic Html5 tags
100100
return new BlockElementExpression((IHtmlElement) node);

src/Html2OpenXml/Expressions/HtmlElementExpression.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,15 @@
99
* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
1010
* PARTICULAR PURPOSE.
1111
*/
12-
using System;
13-
using AngleSharp.Html.Dom;
1412
using DocumentFormat.OpenXml;
1513

1614
namespace HtmlToOpenXml.Expressions;
1715

1816
/// <summary>
1917
/// Represents the base definition of the processor of an HTML tag.
2018
/// </summary>
21-
abstract class HtmlElementExpression(IHtmlElement node) : HtmlDomExpression
19+
abstract class HtmlElementExpression : HtmlDomExpression
2220
{
23-
protected readonly IHtmlElement node = node ?? throw new ArgumentNullException(nameof(node));
24-
2521
/// <summary>
2622
/// Apply the style properties on the provided element.
2723
/// </summary>

src/Html2OpenXml/Expressions/HyperlinkExpression.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ namespace HtmlToOpenXml.Expressions;
2424
/// <summary>
2525
/// Process the parsing of a link element.
2626
/// </summary>
27-
sealed class HyperlinkExpression(IHtmlElement node) : PhrasingElementExpression(node)
27+
sealed class HyperlinkExpression(IHtmlAnchorElement node) : PhrasingElementExpression(node)
2828
{
29-
private readonly IHtmlAnchorElement linkNode = (IHtmlAnchorElement) node;
29+
private readonly IHtmlAnchorElement linkNode = node;
3030

3131

3232
/// <inheritdoc/>
3333
public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)
3434
{
3535
var h = CreateHyperlink(context);
36-
var childElements = Interpret(context.CreateChild(this), node.ChildNodes);
36+
var childElements = Interpret(context.CreateChild(this), linkNode.ChildNodes);
3737
if (h is null)
3838
{
3939
return childElements;
@@ -112,8 +112,8 @@ public override IEnumerable<OpenXmlElement> Interpret (ParsingContext context)
112112
return null;
113113
}
114114

115-
if (!string.IsNullOrEmpty(node.Title))
116-
h.Tooltip = node.Title;
115+
if (!string.IsNullOrEmpty(linkNode.Title))
116+
h.Tooltip = linkNode.Title;
117117
return h;
118118
}
119119
}

0 commit comments

Comments
 (0)