diff --git a/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs b/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs
index f192e1a..acaf5e2 100644
--- a/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs
+++ b/src/Html2OpenXml/Expressions/Numbering/ListExpression.cs
@@ -207,10 +207,9 @@ private static string GetListName(IElement listNode, string? parentName = null)
bool orderedList = listNode.NodeName.Equals("ol", StringComparison.OrdinalIgnoreCase);
string? type = styleAttributes["list-style-type"].ToString();
- if(orderedList && string.IsNullOrEmpty(type))
- {
- type = ListTypeToListStyleType(listNode.GetAttribute("type"));
- }
+ // parse node "type" attribute, if inline style "list-style-type" attribute is null.
+ if (string.IsNullOrEmpty(type))
+ type = ListTypeToListStyleType(listNode.GetAttribute("type"), orderedList);
if (string.IsNullOrEmpty(type) || !supportedListTypes.Contains(type!))
{
@@ -233,18 +232,28 @@ private static string GetListName(IElement listNode, string? parentName = null)
///
/// Map ordered list style attribute values to css list-style-type.
- /// Valid types are "1|a|A|i|I": https://w3schools.com/tags/att_ol_type.asp
+ /// Ordered lists types are "1|a|A|i|I|α" (https://w3schools.com/tags/att_ol_type.asp).
+ /// Unordered lists types are "disc|circle|square" (https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/ul#type).
///
- private static string? ListTypeToListStyleType(string? type) => type switch
+ private static string? ListTypeToListStyleType(string? type, bool orderedList)
{
- "1" => "decimal",
- "a" => "lower-alpha",
- "A" => "upper-alpha",
- "i" => "lower-roman",
- "I" => "upper-roman",
- "lower-greek" or "upper-greek" => type,
- _ => null
- };
+ return (orderedList, type) switch
+ {
+ // un-ordered list marker style resolution
+ (false, string t) when t.Equals("disc", StringComparison.OrdinalIgnoreCase) => "disc",
+ (false, string t) when t.Equals("circle", StringComparison.OrdinalIgnoreCase) => "circle",
+ (false, string t) when t.Equals("square", StringComparison.OrdinalIgnoreCase) => "square",
+
+ // ordered list marker style resolution
+ (true, "1") => "decimal",
+ (true, "a") => "lower-alpha",
+ (true, "A") => "upper-alpha",
+ (true, "i") => "lower-roman",
+ (true, "I") => "upper-roman",
+
+ _ => null
+ };
+ }
///
/// Resolve the of a list element node,
diff --git a/test/HtmlToOpenXml.Tests/NumberingTests.cs b/test/HtmlToOpenXml.Tests/NumberingTests.cs
index 61a69d1..424ebd7 100644
--- a/test/HtmlToOpenXml.Tests/NumberingTests.cs
+++ b/test/HtmlToOpenXml.Tests/NumberingTests.cs
@@ -731,5 +731,61 @@ await converter.ParseBody(@"
AssertThatOpenXmlDocumentIsValid();
}
+
+ ///
+ /// A nested ul type attribute must use its own bullet, not the parent marker
+ ///
+ ///
+ ///
+ ///
+ [TestCase("disc", "square", ExpectedResult = "•,▪")]
+ [TestCase("disc", "circle", ExpectedResult = "•,\u25CB")]
+ public async Task NestedUnorderedList_TypeAttribute_UsesOwnBullet(string listType, string nestedListType)
+ {
+ await converter.ParseBody($@"
+ ");
+
+ var paragraphs = mainPart.Document!.Body!.Elements().ToList();
+ Assert.That(paragraphs, Has.Count.EqualTo(2));
+ AssertThatOpenXmlDocumentIsValid();
+
+ return string.Join(",", paragraphs.Select(GetBulletText).Select(BulletTextUnicode));
+ }
+
+ [Test(Description = "CSS list-style-type takes precedence over the ul type attribute")]
+ public async Task UnorderedList_CssListStyleType_OverridesTypeAttribute()
+ {
+ await converter.ParseBody(@"");
+
+ var paragraph = mainPart.Document!.Body!.Elements().Single();
+ Assert.That(GetBulletText(paragraph), Is.EqualTo("o"));
+ AssertThatOpenXmlDocumentIsValid();
+ }
+
+ private string? GetBulletText(Paragraph paragraph)
+ {
+ var numberingProperties = paragraph.ParagraphProperties?.NumberingProperties;
+ var numId = numberingProperties?.NumberingId?.Val?.Value;
+ var level = numberingProperties?.NumberingLevelReference?.Val?.Value;
+ var numbering = mainPart.NumberingDefinitionsPart!.Numbering!;
+ var instance = numbering.Elements().Single(i => i.NumberID?.Value == numId);
+ var abstractNum = numbering.Elements().Single(a => a.AbstractNumberId?.Value == instance.AbstractNumId!.Val!.Value);
+ return abstractNum.Elements().First(l => l.LevelIndex?.Value == level).LevelText?.Val?.Value;
+ }
+
+ private string? BulletTextUnicode(string? bulletText)
+ {
+ return bulletText switch
+ {
+ "o" => "\u25CB", // ○ circle
+ _ => bulletText
+ };
+ }
}
}