You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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开始的起始值。

修改步骤

  1. 调整抽象编号的重启规则:将NumberingRestart的Val从RestartNumberValues.EachSection改为RestartNumberValues.EachParagraph,或直接移除该节点(后续会在实例中手动控制起始值)。
  2. 创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 02:54:51