diff --git a/src/Html2OpenXml/Collections/HtmlAttributeCollection.cs b/src/Html2OpenXml/Collections/HtmlAttributeCollection.cs
index 1ea4cc0..f149267 100755
--- a/src/Html2OpenXml/Collections/HtmlAttributeCollection.cs
+++ b/src/Html2OpenXml/Collections/HtmlAttributeCollection.cs
@@ -107,16 +107,37 @@ public static HtmlAttributeCollection ParseStyle(string? htmlStyles)
///
/// Gets the named attribute.
///
- public string? this[string name]
+ public ReadOnlySpan this[string name]
{
get
{
if (attributes.TryGetValue(name, out var range))
- return rawValue.AsSpan().Slice(range).ToString().Trim();
- return null;
+ return rawValue.AsSpan().Slice(range).Trim();
+ return [];
}
}
+ ///
+ /// Determines whether the collection contains the specified key.
+ ///
+ public bool ContainsKey(string name)
+ {
+ return attributes.ContainsKey(name);
+ }
+
+ ///
+ /// Efficient way to determine if a style is equals to a value.
+ ///
+ public bool HasKeyEqualsTo(string name, string value)
+ {
+ if (attributes.TryGetValue(name, out var range))
+ {
+ var span = rawValue.AsSpan().Slice(range).Trim();
+ return span.Equals(value.AsSpan(), StringComparison.InvariantCultureIgnoreCase);
+ }
+ return false;
+ }
+
///
/// Gets an attribute representing a color (named color, hexadecimal or hexadecimal
/// without the preceding # character).
@@ -147,6 +168,8 @@ public Unit GetUnit(string name, UnitMetric defaultMetric = UnitMetric.Unitless)
public Margin GetMargin(string name)
{
Margin margin = Margin.Empty;
+ if (IsEmpty) return margin;
+
if (attributes.TryGetValue(name, out var range))
margin = Margin.Parse(rawValue.AsSpan().Slice(range));
@@ -194,6 +217,8 @@ public HtmlBorder GetBorders()
public SideBorder GetSideBorder(string name)
{
SideBorder border = SideBorder.Empty;
+ if (IsEmpty) return border;
+
if (attributes.TryGetValue(name, out Range range))
border = SideBorder.Parse(rawValue.AsSpan().Slice(range));
@@ -224,6 +249,8 @@ public SideBorder GetSideBorder(string name)
public HtmlFont GetFont(string name)
{
HtmlFont font = HtmlFont.Empty;
+ if (IsEmpty) return font;
+
if (attributes.TryGetValue(name, out Range range))
font = HtmlFont.Parse(rawValue.AsSpan().Slice(range));
diff --git a/src/Html2OpenXml/Expressions/BlockElementExpression.cs b/src/Html2OpenXml/Expressions/BlockElementExpression.cs
index bf5de42..7973153 100644
--- a/src/Html2OpenXml/Expressions/BlockElementExpression.cs
+++ b/src/Html2OpenXml/Expressions/BlockElementExpression.cs
@@ -90,7 +90,7 @@ protected override IEnumerable Interpret (
{
return ComposeChildren(context, childNodes, paraProperties,
(runs) => {
- if ("always".Equals(styleAttributes!["page-break-before"], StringComparison.OrdinalIgnoreCase))
+ if (styleAttributes.HasKeyEqualsTo("page-break-before", "always"))
{
runs.Add(
new Run(
@@ -102,7 +102,7 @@ protected override IEnumerable Interpret (
}
},
(runs) => {
- if ("always".Equals(styleAttributes!["page-break-after"], StringComparison.OrdinalIgnoreCase))
+ if (styleAttributes.HasKeyEqualsTo("page-break-after", "always"))
{
runs.Add(new Run(
new Break() { Type = BreakValues.Page }));
@@ -186,8 +186,8 @@ protected override void ComposeStyles (ParsingContext context)
};
}
- JustificationValues? align = Converter.ToParagraphAlign(styleAttributes!["text-align"]);
- if (!align.HasValue) align = Converter.ToParagraphAlign(node.GetAttribute("align"));
+ JustificationValues? align = Converter.ToParagraphAlign(styleAttributes["text-align"]);
+ if (!align.HasValue) align = Converter.ToParagraphAlign(node.GetAttribute("align").AsSpan());
if (!align.HasValue) align = Converter.ToParagraphAlign(styleAttributes["justify-content"]);
if (align.HasValue)
{
@@ -256,7 +256,7 @@ protected override void ComposeStyles (ParsingContext context)
var lineHeight = styleAttributes.GetUnit("line-height");
if (!lineHeight.IsValid
- && "normal".Equals(styleAttributes["line-height"], StringComparison.OrdinalIgnoreCase))
+ && styleAttributes.HasKeyEqualsTo("line-height", "normal"))
{
// if `normal` is specified, reset any values
lineHeight = new Unit(UnitMetric.Unitless, 1);
diff --git a/src/Html2OpenXml/Expressions/BodyExpression.cs b/src/Html2OpenXml/Expressions/BodyExpression.cs
index 462d375..9b86a3a 100644
--- a/src/Html2OpenXml/Expressions/BodyExpression.cs
+++ b/src/Html2OpenXml/Expressions/BodyExpression.cs
@@ -69,10 +69,11 @@ protected override void ComposeStyles(ParsingContext context)
// Unsupported W3C attribute but claimed by users. Specified at level, the page
// orientation is applied on the whole document
- string? attr = styleAttributes!["page-orientation"];
- if (attr != null)
+ if (styleAttributes.ContainsKey("page-orientation"))
{
- PageOrientationValues orientation = Converter.ToPageOrientation(attr);
+ PageOrientationValues orientation = PageOrientationValues.Portrait;
+ if (styleAttributes.HasKeyEqualsTo("page-orientation", "landscape"))
+ orientation = PageOrientationValues.Landscape;
var sectionProperties = mainPart.Document.Body!.GetFirstChild();
if (sectionProperties == null || sectionProperties.GetFirstChild() == null)
diff --git a/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs b/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs
index 2d071b4..548ed09 100644
--- a/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs
+++ b/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs
@@ -214,7 +214,7 @@ private static string GetListName(IElement listNode, string? parentName = null)
{
var styleAttributes = listNode.GetStyles();
bool orderedList = listNode.NodeName.Equals("ol", StringComparison.OrdinalIgnoreCase);
- string? type = styleAttributes["list-style-type"];
+ string? type = styleAttributes["list-style-type"].ToString();
if(orderedList && string.IsNullOrEmpty(type))
{
diff --git a/src/Html2OpenXml/Expressions/Table/TableCaptionExpression.cs b/src/Html2OpenXml/Expressions/Table/TableCaptionExpression.cs
index 8a05e34..509be4c 100644
--- a/src/Html2OpenXml/Expressions/Table/TableCaptionExpression.cs
+++ b/src/Html2OpenXml/Expressions/Table/TableCaptionExpression.cs
@@ -54,8 +54,9 @@ public override IEnumerable Interpret (ParsingContext context)
}
p.Append(childElements);
- string? att = styleAttributes!["text-align"] ?? node.GetAttribute("align");
- if (!string.IsNullOrEmpty(att))
+ var att = styleAttributes["text-align"];
+ if (att.IsEmpty) att = node.GetAttribute("align").AsSpan();
+ if (!att.IsEmpty)
{
JustificationValues? align = Converter.ToParagraphAlign(att);
if (align.HasValue)
diff --git a/src/Html2OpenXml/Expressions/Table/TableCellExpression.cs b/src/Html2OpenXml/Expressions/Table/TableCellExpression.cs
index 6a4f94c..05c471f 100644
--- a/src/Html2OpenXml/Expressions/Table/TableCellExpression.cs
+++ b/src/Html2OpenXml/Expressions/Table/TableCellExpression.cs
@@ -62,7 +62,7 @@ protected override void ComposeStyles(ParsingContext context)
{
base.ComposeStyles(context);
- Unit width = styleAttributes!.GetUnit("width");
+ Unit width = styleAttributes.GetUnit("width");
if (!width.IsValid)
{
var widthValue = cellNode.GetAttribute("width");
@@ -84,23 +84,22 @@ protected override void ComposeStyles(ParsingContext context)
}
// Manage vertical text (only for table cell)
- string? direction = styleAttributes!["writing-mode"];
- if (direction != null)
+ var direction = styleAttributes["writing-mode"];
+ if (!direction.IsEmpty)
{
- switch (direction)
+ if (direction.Equals("tb-lr".AsSpan(), StringComparison.InvariantCultureIgnoreCase) ||
+ direction.Equals("vertical-lr".AsSpan(), StringComparison.InvariantCultureIgnoreCase))
{
- case "tb-lr":
- case "vertical-lr":
- cellProperties.TextDirection = new() { Val = TextDirectionValues.BottomToTopLeftToRight };
- cellProperties.TableCellVerticalAlignment = new() { Val = TableVerticalAlignmentValues.Center };
- paraProperties.Justification = new() { Val = JustificationValues.Center };
- break;
- case "tb-rl":
- case "vertical-rl":
- cellProperties.TextDirection = new() { Val = TextDirectionValues.TopToBottomRightToLeft };
- cellProperties.TableCellVerticalAlignment = new() { Val = TableVerticalAlignmentValues.Center };
- paraProperties.Justification = new() { Val = JustificationValues.Center };
- break;
+ cellProperties.TextDirection = new() { Val = TextDirectionValues.BottomToTopLeftToRight };
+ cellProperties.TableCellVerticalAlignment = new() { Val = TableVerticalAlignmentValues.Center };
+ paraProperties.Justification = new() { Val = JustificationValues.Center };
+ }
+ else if (direction.Equals("tb-rl".AsSpan(), StringComparison.InvariantCultureIgnoreCase) ||
+ direction.Equals("vertical-rl".AsSpan(), StringComparison.InvariantCultureIgnoreCase))
+ {
+ cellProperties.TextDirection = new() { Val = TextDirectionValues.TopToBottomRightToLeft };
+ cellProperties.TableCellVerticalAlignment = new() { Val = TableVerticalAlignmentValues.Center };
+ paraProperties.Justification = new() { Val = JustificationValues.Center };
}
}
}
diff --git a/src/Html2OpenXml/Expressions/Table/TableColExpression.cs b/src/Html2OpenXml/Expressions/Table/TableColExpression.cs
index c51269d..6edf0d1 100644
--- a/src/Html2OpenXml/Expressions/Table/TableColExpression.cs
+++ b/src/Html2OpenXml/Expressions/Table/TableColExpression.cs
@@ -35,7 +35,7 @@ public override IEnumerable Interpret(ParsingContext context)
ComposeStyles(context);
var column = new GridColumn();
- var width = styleAttributes!.GetUnit("width");
+ var width = styleAttributes.GetUnit("width");
if (width.IsValid)
{
if (width.IsFixed)
diff --git a/src/Html2OpenXml/Expressions/Table/TableElementExpressionBase.cs b/src/Html2OpenXml/Expressions/Table/TableElementExpressionBase.cs
index 27bef6f..f7614ce 100644
--- a/src/Html2OpenXml/Expressions/Table/TableElementExpressionBase.cs
+++ b/src/Html2OpenXml/Expressions/Table/TableElementExpressionBase.cs
@@ -71,8 +71,8 @@ protected override void ComposeStyles(ParsingContext context)
{
base.ComposeStyles(context);
- var valign = Converter.ToVAlign(styleAttributes!["vertical-align"]);
- if (!valign.HasValue) valign = Converter.ToVAlign(node.GetAttribute("valign"));
+ var valign = Converter.ToVAlign(styleAttributes["vertical-align"]);
+ if (!valign.HasValue) valign = Converter.ToVAlign(node.GetAttribute("valign").AsSpan());
if (!valign.HasValue)
{
// in Html, table cell are vertically centered by default
@@ -92,7 +92,7 @@ protected override void ComposeStyles(ParsingContext context)
}
var halign = Converter.ToParagraphAlign(styleAttributes["text-align"]);
- if (!halign.HasValue) halign = Converter.ToParagraphAlign(node.GetAttribute("align"));
+ if (!halign.HasValue) halign = Converter.ToParagraphAlign(node.GetAttribute("align").AsSpan());
if (halign.HasValue)
{
paraProperties.KeepNext = new();
diff --git a/src/Html2OpenXml/Expressions/Table/TableExpression.cs b/src/Html2OpenXml/Expressions/Table/TableExpression.cs
index dd0e54d..abcf010 100644
--- a/src/Html2OpenXml/Expressions/Table/TableExpression.cs
+++ b/src/Html2OpenXml/Expressions/Table/TableExpression.cs
@@ -278,7 +278,7 @@ protected override void ComposeStyles (ParsingContext context)
}
}
- var align = Converter.ToParagraphAlign(tableNode.GetAttribute("align"))
+ var align = Converter.ToParagraphAlign(tableNode.GetAttribute("align").AsSpan())
?? Converter.ToParagraphAlign(styleAttributes["justify-self"]);
if (!align.HasValue)
{
diff --git a/src/Html2OpenXml/Expressions/Table/TableRowExpression.cs b/src/Html2OpenXml/Expressions/Table/TableRowExpression.cs
index 25c7ce1..8b1223b 100644
--- a/src/Html2OpenXml/Expressions/Table/TableRowExpression.cs
+++ b/src/Html2OpenXml/Expressions/Table/TableRowExpression.cs
@@ -101,7 +101,7 @@ protected override void ComposeStyles(ParsingContext context)
{
base.ComposeStyles(context);
- Unit unit = styleAttributes!.GetUnit("height", UnitMetric.Pixel);
+ Unit unit = styleAttributes.GetUnit("height", UnitMetric.Pixel);
if (!unit.IsValid) unit = Unit.Parse(rowNode.GetAttribute("height").AsSpan(), UnitMetric.Pixel);
switch (unit.Metric)
diff --git a/src/Html2OpenXml/Primitives/HtmlColor.Named.cs b/src/Html2OpenXml/Primitives/HtmlColor.Named.cs
index b830189..400463d 100755
--- a/src/Html2OpenXml/Primitives/HtmlColor.Named.cs
+++ b/src/Html2OpenXml/Primitives/HtmlColor.Named.cs
@@ -15,7 +15,6 @@ private static HtmlColor GetNamedColor (ReadOnlySpan name)
{
// the longest built-in Color's name is much lower than this check, so we should not allocate here in a typical usage
Span loweredValue = name.Length <= 128 ? stackalloc char[name.Length] : new char[name.Length];
-
name.ToLowerInvariant(loweredValue);
namedColors.TryGetValue(loweredValue.ToString(), out var color);
diff --git a/src/Html2OpenXml/Utilities/AngleSharpExtensions.cs b/src/Html2OpenXml/Utilities/AngleSharpExtensions.cs
index 4caed8b..31763e8 100644
--- a/src/Html2OpenXml/Utilities/AngleSharpExtensions.cs
+++ b/src/Html2OpenXml/Utilities/AngleSharpExtensions.cs
@@ -156,9 +156,11 @@ public static string CollapseLineBreaks(this string str)
/// Determines whether the layout mode is inline vs block or flex.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsInlineLayout(string? displayMode, string defaultLayout)
+ public static bool IsInlineLayout(ReadOnlySpan displayMode, string defaultLayout)
{
- return (displayMode ?? defaultLayout)
- .StartsWith("inline", StringComparison.OrdinalIgnoreCase) == true;
+ if (displayMode.IsEmpty)
+ displayMode = defaultLayout.AsSpan();
+
+ return displayMode.StartsWith("inline".AsSpan(), StringComparison.InvariantCultureIgnoreCase);
}
}
\ No newline at end of file
diff --git a/src/Html2OpenXml/Utilities/Converter.cs b/src/Html2OpenXml/Utilities/Converter.cs
index 9d5907d..4d2290c 100755
--- a/src/Html2OpenXml/Utilities/Converter.cs
+++ b/src/Html2OpenXml/Utilities/Converter.cs
@@ -22,12 +22,13 @@ static class Converter
///
/// Convert the Html text align attribute (horizontal alignement) to its corresponding OpenXml value.
///
- public static JustificationValues? ToParagraphAlign(string? htmlAlign)
+ public static JustificationValues? ToParagraphAlign(ReadOnlySpan span)
{
- if (htmlAlign == null) return null;
- return htmlAlign.ToLowerInvariant() switch
+ Span loweredValue = span.Length <= 128 ? stackalloc char[span.Length] : new char[span.Length];
+ span.ToLowerInvariant(loweredValue);
+ return loweredValue switch
{
- "left" => JustificationValues.Left,
+ "left" => JustificationValues.Left,
"right" => JustificationValues.Right,
"center" => JustificationValues.Center,
"justify" => JustificationValues.Both,
@@ -38,10 +39,11 @@ static class Converter
///
/// Convert the Html vertical-align attribute to its corresponding OpenXml value.
///
- public static TableVerticalAlignmentValues? ToVAlign(string? htmlAlign)
+ public static TableVerticalAlignmentValues? ToVAlign(ReadOnlySpan span)
{
- if (htmlAlign == null) return null;
- return htmlAlign.ToLowerInvariant() switch
+ Span loweredValue = span.Length <= 128 ? stackalloc char[span.Length] : new char[span.Length];
+ span.ToLowerInvariant(loweredValue);
+ return loweredValue switch
{
"top" => TableVerticalAlignmentValues.Top,
"middle" => TableVerticalAlignmentValues.Center,
@@ -159,14 +161,6 @@ public static BorderValues ToBorderStyle(ReadOnlySpan span)
};
}
- public static PageOrientationValues ToPageOrientation(string? orientation)
- {
- if ( "landscape".Equals(orientation,StringComparison.OrdinalIgnoreCase))
- return PageOrientationValues.Landscape;
-
- return PageOrientationValues.Portrait;
- }
-
public static ICollection ToTextDecoration(ReadOnlySpan values)
{
// this style could take multiple values separated by a space
diff --git a/test/HtmlToOpenXml.Tests/Primitives/StyleParserTests.cs b/test/HtmlToOpenXml.Tests/Primitives/StyleParserTests.cs
index 73f02e8..ebdce13 100644
--- a/test/HtmlToOpenXml.Tests/Primitives/StyleParserTests.cs
+++ b/test/HtmlToOpenXml.Tests/Primitives/StyleParserTests.cs
@@ -9,21 +9,22 @@ namespace HtmlToOpenXml.Tests.Primitives
public class StyleParserTests
{
[TestCase("text-decoration:underline; color: red ")]
- [TestCase("text-decoration:underline;color:red")]
+ [TestCase("text-decoration : underline ;color :red")]
public void ParseStyle_ShouldSucceed(string htmlStyle)
{
var styles = HtmlAttributeCollection.ParseStyle(htmlStyle);
Assert.Multiple(() => {
- Assert.That(styles["text-decoration"], Is.EqualTo("underline"));
- Assert.That(styles["color"], Is.EqualTo("red"));
+ Assert.That(styles.HasKeyEqualsTo("text-decoration", "underline"), Is.True);
+ Assert.That(styles.HasKeyEqualsTo("color", "red"), Is.True);
+ Assert.That(styles["color"].ToString(), Is.EqualTo("red"));
});
}
[Test(Description = "Parser should consider the last occurence of a style")]
public void DuplicateStyle_ReturnsLatter()
{
- var styleAttributes = HtmlAttributeCollection.ParseStyle("color:red;color:blue");
- Assert.That(styleAttributes["color"], Is.EqualTo("blue"));
+ var styleAttributes = HtmlAttributeCollection.ParseStyle("color:red;color:BLUE");
+ Assert.That(styleAttributes.HasKeyEqualsTo("color", "blue"), Is.True);
}
[TestCase("color;color;")]
@@ -33,7 +34,7 @@ public void InvalidStyle_ShouldBeEmpty(string htmlStyle)
{
var styles = HtmlAttributeCollection.ParseStyle(htmlStyle);
Assert.That(styles.IsEmpty, Is.True);
- Assert.That(styles["color"], Is.Null);
+ Assert.That(styles.ContainsKey("color"), Is.False);
}
[Test]
diff --git a/test/HtmlToOpenXml.Tests/StyleTests.cs b/test/HtmlToOpenXml.Tests/StyleTests.cs
index 855b071..61b1d0f 100644
--- a/test/HtmlToOpenXml.Tests/StyleTests.cs
+++ b/test/HtmlToOpenXml.Tests/StyleTests.cs
@@ -163,23 +163,23 @@ public void ManualAddStyle_ThenRefreshStyles_ShouldSucceed()
public void DuplicateStyle_ReturnsLatter()
{
var styleAttributes = HtmlAttributeCollection.ParseStyle("color:red;color:blue");
- Assert.That(styleAttributes["color"], Is.EqualTo("blue"));
+ Assert.That(styleAttributes["color"].ToString(), Is.EqualTo("blue"));
}
[Test(Description = "Encoded ':' and ';' characters are valid")]
public void EncodedStyle_ShouldSucceed()
{
var styleAttributes = HtmlAttributeCollection.ParseStyle("text-decoration:underline;color:red");
- Assert.That(styleAttributes["text-decoration"], Is.EqualTo("underline"));
- Assert.That(styleAttributes["color"], Is.EqualTo("red"));
+ Assert.That(styleAttributes["text-decoration"].ToString(), Is.EqualTo("underline"));
+ Assert.That(styleAttributes["color"].ToString(), Is.EqualTo("red"));
}
[Test(Description = "Key style with no value should be ignored")]
public void EmptyStyle_ShouldBeIgnored()
{
var styleAttributes = HtmlAttributeCollection.ParseStyle("text-decoration;color:red");
- Assert.That(styleAttributes["text-decoration"], Is.Null);
- Assert.That(styleAttributes["color"], Is.EqualTo("red"));
+ Assert.That(styleAttributes.ContainsKey("text-decoration"), Is.False);
+ Assert.That(styleAttributes["color"].ToString(), Is.EqualTo("red"));
}
}
}