Skip to content

Commit 76ed4f8

Browse files
authored
Merge pull request #230 from AlexAbd1990/support-ul-list-styles
Add support for custom bullet symbols
2 parents 3e5700f + f0924cf commit 76ed4f8

4 files changed

Lines changed: 96 additions & 5 deletions

File tree

examples/Demo/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static class Program
1212
static async Task Main(string[] args)
1313
{
1414
const string filename = "test.docx";
15-
string html = ResourceHelper.GetString("Resources.CompleteRunTest.html");
15+
string html = ResourceHelper.GetString("Resources.UlStyles.html");
1616
if (File.Exists(filename)) File.Delete(filename);
1717

1818
using (MemoryStream generatedDocument = new())
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2+
<html>
3+
<head>
4+
<title></title>
5+
</head>
6+
<body>
7+
<ul style="list-style-type: '-';">
8+
<li style="text-align: justify;">test 2</li>
9+
<li style="text-align: justify;">test 3</li>
10+
<li style="text-align: justify;">&nbsp;</li>
11+
</ul>
12+
<p style="text-align: justify;">&nbsp;</p>
13+
<ul style="list-style-type: disc;">
14+
<li style="text-align: justify;">test 2</li>
15+
<li style="text-align: justify;">test 3</li>
16+
<li style="text-align: justify;">&nbsp;</li>
17+
</ul>
18+
<p style="text-align: justify;">&nbsp;</p>
19+
<ul style="list-style-type: '-';">
20+
<li style="text-align: justify;">test 2</li>
21+
<li style="text-align: justify;">test 3</li>
22+
<li style="text-align: justify;">&nbsp;</li>
23+
</ul>
24+
<p style="text-align: justify;">&nbsp;</p>
25+
<ul style="list-style-type: '😀';">
26+
<li style="text-align: justify;">test 2</li>
27+
<li style="text-align: justify;">test 3</li>
28+
<li style="text-align: justify;">&nbsp;</li>
29+
</ul>
30+
31+
<p style="text-align: justify;">&nbsp;</p>
32+
<ul style="list-style-type: dash;">
33+
<li style="text-align: justify;">test 2</li>
34+
<li style="text-align: justify;">test 3</li>
35+
<li style="text-align: justify;">&nbsp;</li>
36+
</ul>
37+
</body>
38+
</html>

src/Html2OpenXml/Expressions/Numbering/ListExpression.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ readonly struct ListContext(string listName, int absNumId, int instanceId, int l
3838
// https://answers.microsoft.com/en-us/msoffice/forum/all/custom-list-number-style/21a54399-4404-4c37-8843-2ccaaf827485
3939
// Image bullet: http://officeopenxml.com/WPnumbering-imagesAsSymbol.php
4040
private static readonly HashSet<string> supportedListTypes =
41-
["disc", "decimal", "square", "circle",
41+
["disc", "decimal", "square", "circle", "dash",
4242
"lower-alpha", "upper-alpha", "lower-latin", "upper-latin",
4343
"lower-greek", "upper-greek",
4444
"lower-roman", "upper-roman",
@@ -227,7 +227,15 @@ private static string GetListName(IElement listNode, string? parentName = null)
227227
if (parentName != null && IsCascadingStyle(parentName))
228228
return parentName!;
229229

230-
type = orderedList? "decimal" : "disc";
230+
// If a specific list-style-type is provided for an unordered list (e.g., a custom string like "'-'"),
231+
// we strip the surrounding quotes to extract the raw symbol.
232+
// Otherwise, we fallback to the default "decimal" or "disc" styles.
233+
if (!orderedList && !string.IsNullOrEmpty(type))
234+
{
235+
return type!.Trim('\'', '\"');
236+
}
237+
238+
return orderedList ? "decimal" : "disc";
231239
}
232240

233241
return type!;

src/Html2OpenXml/Expressions/Numbering/NumberingExpressionBase.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,20 @@ protected int GetOrCreateListTemplate(ParsingContext context, string listName)
5050

5151
Numbering numberingPart = context.MainPart.NumberingDefinitionsPart!.Numbering!;
5252

53+
AbstractNum abstractNum;
54+
5355
// at this stage, we have sanitized the list style so it's safe to grab them from the predefined template lists
54-
var abstractNum = predefinedNumberingLists[listName];
56+
if (predefinedNumberingLists.TryGetValue(listName, out var predefined))
57+
{
58+
// If it's a predefined style, we clone it as usual
59+
abstractNum = (AbstractNum)predefined.CloneNode(true);
60+
}
61+
else
62+
{
63+
// If not found in predefined lists, listName contains a custom bullet character (e.g., "-")
64+
abstractNum = CreateCustomBulletAbstractNum(listName);
65+
}
66+
5567
abstractNum = (AbstractNum) abstractNum.CloneNode(true);
5668
abstractNum.AbstractNumberId = IncrementAbstractNumId(context, numberingPart);
5769
var level1 = abstractNum.GetFirstChild<Level>()!;
@@ -216,6 +228,38 @@ private void InitNumberingIds(ParsingContext context)
216228
isInitialized = true;
217229
}
218230

231+
/// <summary>
232+
/// Generates a custom abstract numbering definition for unordered lists using a specific symbol.
233+
/// </summary>
234+
/// <param name="customSymbol">The custom character or string to be used as the list bullet.</param>
235+
/// <returns>An <see cref="AbstractNum"/> instance configured with the custom bullet symbol across all levels.</returns>
236+
private AbstractNum CreateCustomBulletAbstractNum(string customSymbol)
237+
{
238+
var abstractNum = new AbstractNum {
239+
AbstractNumDefinitionName = new() { Val = customSymbol },
240+
MultiLevelType = new() { Val = MultiLevelValues.HybridMultilevel }
241+
};
242+
243+
for (var lvlIndex = 0; lvlIndex <= MaxLevel; lvlIndex++)
244+
{
245+
abstractNum.Append(new Level {
246+
StartNumberingValue = new() { Val = 1 },
247+
NumberingFormat = new() { Val = NumberFormatValues.Bullet },
248+
LevelIndex = lvlIndex,
249+
LevelText = new() { Val = string.Format(customSymbol, lvlIndex+1) },
250+
LevelJustification = new() { Val = LevelJustificationValues.Left },
251+
PreviousParagraphProperties = new() {
252+
Indentation = new() {
253+
Left = ((lvlIndex + 1) * Indentation * 2).ToString(),
254+
Hanging = Indentation.ToString()
255+
}
256+
}
257+
});
258+
}
259+
260+
return abstractNum;
261+
}
262+
219263
/// <summary>
220264
/// Predefined template of lists.
221265
/// </summary>
@@ -230,6 +274,7 @@ private static IReadOnlyDictionary<string, AbstractNum> InitKnownLists()
230274
("disc", NumberFormatValues.Bullet, "•"),
231275
("square", NumberFormatValues.Bullet, "▪"),
232276
("circle", NumberFormatValues.Bullet, "o"),
277+
("dash", NumberFormatValues.Bullet, "-"),
233278
("upper-alpha", NumberFormatValues.UpperLetter, "%{0}."),
234279
("lower-alpha", NumberFormatValues.LowerLetter, "%{0}."),
235280
("upper-roman", NumberFormatValues.UpperRoman, "%{0}."),
@@ -299,4 +344,4 @@ private static IReadOnlyDictionary<string, AbstractNum> InitKnownLists()
299344
return knownAbstractNums;
300345
#endif
301346
}
302-
}
347+
}

0 commit comments

Comments
 (0)