如何使用OpenXML .NET在表格单元格中重启编号列表
解决Word表格不同单元格中有序列表编号不重启的问题
问题概述
基于数据库数据生成Word文件,处理HTML的<ul>和<ol>标签时,无序列表正常,但有序列表在表格不同单元格中编号会连续显示,无法从1开始重启。
现有XML配置
numbering.xml中的编号定义
<w:abstractNum w:abstractNumId="2"> <w:lvl w:ilvl="0"> <w:start w:val="1"/> <w:numFmt w:val="decimal"/> <w:lvlText w:val="%1."/> <w:numRestart w:val="eachSect"/> <w:pPr> <w:ind w:left="360" w:hanging="360"/> </w:pPr> </w:lvl> </w:abstractNum>
编号实例映射
<w:num w:numId="7"> <w:abstractNumId w:val="2"/> </w:num> <w:num w:numId="8"> <w:abstractNumId w:val="2"/> </w:num>
document.xml中的单元格内容
单元格1
<w:p> <w:pPr> <w:numPr> <w:ilvl w:val="0"/> <w:numId w:val="7"/> </w:numPr> </w:pPr> <w:r> <w:t>SOME TEXT</w:t> </w:r> </w:p>
单元格2
<w:p> <w:pPr> <w:numPr> <w:ilvl w:val="0"/> <w:numId w:val="8"/> </w:numPr> </w:pPr> <w:r> <w:t>SOME TEXT</w:t> </w:r> </w:p>
现有实现代码
定义编号样式的方法
private void DefineNumberingStyles(MainDocumentPart mainPart) { NumberingDefinitionsPart numberingPart = mainPart.NumberingDefinitionsPart ?? mainPart.AddNewPart<NumberingDefinitionsPart>(); if (numberingPart.Numbering == null) { numberingPart.Numbering = new Numbering(); } Numbering numbering = numberingPart.Numbering; // Define Bullet List AbstractNum bulletAbstractNum = new AbstractNum( new Level( new NumberingFormat() { Val = NumberFormatValues.Bullet }, new LevelText() { Val = "•" }, // Bullet symbol new ParagraphProperties( new Indentation() { Left = "360", Hanging = "360" } ) ) { LevelIndex = 0 } ) { AbstractNumberId = 1 }; // Define Numbered List AbstractNum numberedAbstractNum = new AbstractNum( new Level( new StartNumberingValue() { Val = 1 }, new NumberingFormat() { Val = NumberFormatValues.Decimal }, new LevelText() { Val = "%1." }, // Number format new NumberingRestart() { Val = RestartNumberValues.EachSection }, new ParagraphProperties( new Indentation() { Left = "360", Hanging = "360" } ) ) { LevelIndex = 0 } ) { AbstractNumberId = 2 }; // Assign IDs numbering.Append(bulletAbstractNum, numberedAbstractNum); numbering.Append(new NumberingInstance(new AbstractNumId() { Val = 1 }) { NumberID = 1 }); // Bullets numbering.Append(new NumberingInstance(new AbstractNumId() { Val = 2 }) { NumberID = 2 }); // Numbers }
向单元格追加数据的逻辑
else if (node.Name == "ul" || node.Name == "ol") { bool isOrdered = node.Name == "ol"; //int numberingId = isOrdered ? CheckNumberListStyle(node) : CheckBulletListStyle(node); // 1 = Bullets, 2 = Numbers. NumberingDefinitionsPart numberingPart = mainPart.NumberingDefinitionsPart ?? mainPart.AddNewPart<NumberingDefinitionsPart>(); if (numberingPart.Numbering == null) { numberingPart.Numbering = new Numbering(); } Numbering numbering = numberingPart.Numbering; if (isOrdered) { // Create new numbering instance NumberingInstance numberingInstance = new NumberingInstance( new AbstractNumId() { Val = 2 } ) { NumberID = uniqueId }; numbering.Append(numberingInstance); } else { // Create new numbering instance NumberingInstance numberingInstance = new NumberingInstance( new AbstractNumId() { Val = 1 } ) { NumberID = uniqueId }; numbering.Append(numberingInstance); } var listItems = node.SelectNodes("li"); if (listItems != null) { foreach (HtmlNode li in listItems) { // ** Remove unnecessary bullets inside <li> ** HtmlNode bulletNode = li.SelectSingleNode(".//span[contains(@style, 'mso-list')]"); // Find bullet span if (bulletNode != null) { bulletNode.Remove(); // Remove bullet from text extraction } string childText = HttpUtility.HtmlDecode(li.InnerText.Trim()); Paragraph para = new Paragraph( new ParagraphProperties( new NumberingProperties( new NumberingLevelReference() { Val = 0 }, new NumberingId() { Val = uniqueId } ) ) ); Run run = new Run(); run.AppendChild(new Text(childText)); para.AppendChild(run); parentElement.AppendChild(para); } } }
解决方案
问题核心在于当前的w:numRestart设置为eachSect(仅在章节重启编号),表格单元格不属于章节,所以编号不会自动重启。同时,虽然为每个单元格的列表创建了独立的NumberingInstance,但未为每个实例指定强制从1开始的起始值。
修改步骤
- 调整抽象编号的重启规则:将
NumberingRestart的Val从RestartNumberValues.EachSection改为RestartNumberValues.EachParagraph,或直接移除该节点(后续会在实例中手动控制起始值)。 - 创建
NumberingInstance时覆盖起始值:每个新编号实例需添加LevelOverride,强制编号从1开始。
修改后的代码
1. 更新定义编号样式的方法(可选)
// Define Numbered List AbstractNum numberedAbstractNum = new AbstractNum( new Level( new StartNumberingValue() { Val = 1 }, new NumberingFormat() { Val = NumberFormatValues.Decimal }, new LevelText() { Val = "%1." }, // Number format // 修改为段落级重启 new NumberingRestart() { Val = RestartNumberValues.EachParagraph }, new ParagraphProperties( new Indentation() { Left = "360", Hanging = "360" } ) ) { LevelIndex = 0 } ) { AbstractNumberId = 2 };
2. 修改创建编号实例的逻辑(关键)
if (isOrdered) { // 创建带层级覆盖的编号实例,强制从1开始 NumberingInstance numberingInstance = new NumberingInstance( new AbstractNumId() { Val = 2 }, new LevelOverride( new StartNumberingValue() { Val = 1 } ) { LevelIndex = 0 } ) { NumberID = uniqueId }; numbering.Append(numberingInstance); } else { // Create new numbering instance NumberingInstance numberingInstance = new NumberingInstance( new AbstractNumId() { Val = 1 } ) { NumberID = uniqueId }; numbering.Append(numberingInstance); }
原理说明
LevelOverride会覆盖抽象编号的设置,为当前NumberingInstance单独指定起始值,确保每个单元格的列表从1开始计数。- 调整
NumberingRestart为EachParagraph可进一步避免段落间编号意外连续,但核心是每个实例的独立起始值设置。
内容的提问来源于stack exchange,提问作者Unknown
相关产品推荐
相关产品推荐

