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

使用C# OpenXml实现Word多表格列表按表重启编号并保留格式

需求与问题

现有一份含多表格的Word文档,表格为两列结构,Column2包含超链接、截图、嵌套表格、项目符号列表等内容。需要编写基于DocumentFormat.OpenXml的C#代码,读取Column2内容并写入另一份Word文档的编号列表中,且每个表格的编号列表需重新启动。

当前实现存在矛盾:

  • 实现编号列表时无法按表格重启编号
  • 实现重启编号时单元格原有格式(如超链接、图片、嵌套表格等)丢失

以下是能实现重启编号但格式丢失的代码:

public static void createDoc(string Filename, string opfolder)
{
    string b = DateTime.Now.ToString("ddMMyyyyhhmmsstt");
    b = opfolder + b + ".docx";
    byte[] byteArray = File.ReadAllBytes(Filename);
    using (MemoryStream memoryStream = new MemoryStream())
    {
        memoryStream.Write(byteArray, 0, byteArray.Length);
        using (WordprocessingDocument indoc = WordprocessingDocument.Open(memoryStream, true))
        using (WordprocessingDocument opdoc = WordprocessingDocument.Create(b, WordprocessingDocumentType.Document))
        {
            MainDocumentPart inMainPart = indoc.MainDocumentPart;
            Body inbody = inMainPart.Document.Body;
            MainDocumentPart opMainPart = opdoc.AddMainDocumentPart();
            opMainPart.Document = new Document();
            Body outputBody = new Body();
            IEnumerable<Table> tables = inbody.Descendants<Table>();
            NumberingDefinitionsPart numberingpart = opMainPart.AddNewPart<NumberingDefinitionsPart>();
            numberingpart.Numbering = new Numbering();
            Numbering numbering = numberingpart.Numbering;
            int listindex = 1;
            int columnIndex = 1;
            foreach (Table table in tables)
            {
                var columnCells = table.Elements<TableRow>()
                .Select(row => row.Elements<TableCell>().ElementAtOrDefault(columnIndex))
                .Where(cell => cell != null);
                foreach (var cell in columnCells)
                {
                    if (cell.InnerText.ToLower().StartsWith("column2"))
                    { continue; }
                    Paragraph newParagraph = new Paragraph();
                    newParagraph.ParagraphProperties = new ParagraphProperties(new NumberingProperties(
                    new NumberingLevelReference() { Val = 0 },
                    new NumberingId() { Val = listindex }));
                    foreach (Paragraph p in cell.Descendants<Paragraph>())
                    {
                        foreach (var run in p.Elements<Run>())
                        {
                            Run newrun = (Run)run.CloneNode(true);
                            newParagraph.Append(newrun);
                        }
                        outputBody.Append(newParagraph);
                    }
                }
                listindex++;
            }
            opMainPart.Document.Body = outputBody;
            opMainPart.Document.Save();
        }
    }
}

解决方案:同时保留格式与按表格重启编号

核心改进点:

  1. 为每个表格创建独立的编号定义实例,确保编号重启
  2. 直接克隆单元格内的所有内容元素(而非仅Run),完整保留格式、超链接、图片、嵌套表格等
  3. 为段落添加编号属性时,合并原段落的格式属性
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.IO;
using System.Linq;

public static void CreateFormattedNumberedDoc(string inputPath, string outputFolder)
{
    // 生成带时间戳的输出文件名
    string outputFileName = Path.Combine(outputFolder, $"{DateTime.Now:ddMMyyyyhhmmsstt}.docx");
    
    byte[] inputBytes = File.ReadAllBytes(inputPath);
    using (MemoryStream inputStream = new MemoryStream(inputBytes))
    using (WordprocessingDocument inputDoc = WordprocessingDocument.Open(inputStream, true))
    using (WordprocessingDocument outputDoc = WordprocessingDocument.Create(outputFileName, WordprocessingDocumentType.Document))
    {
        MainDocumentPart inputMainPart = inputDoc.MainDocumentPart;
        Body inputBody = inputMainPart.Document.Body;
        
        // 初始化输出文档主部件
        MainDocumentPart outputMainPart = outputDoc.AddMainDocumentPart();
        outputMainPart.Document = new Document();
        Body outputBody = new Body();
        
        // 添加编号定义部件并配置基础编号样式
        NumberingDefinitionsPart numberingPart = outputMainPart.AddNewPart<NumberingDefinitionsPart>();
        Numbering numbering = new Numbering();
        numberingPart.Numbering = numbering;
        
        // 定义通用的十进制编号样式(1, 2, 3...)
        AbstractNum abstractNum = new AbstractNum() { AbstractNumberId = 1 };
        abstractNum.AppendChild(new MultiLevelType() { Val = MultiLevelValues.HybridMultilevel });
        abstractNum.AppendChild(new Level()
        {
            LevelIndex = 0,
            StartNumberingValue = new StartNumberingValue() { Val = 1 },
            NumberFormat = new NumberFormat() { Val = NumberFormatValues.Decimal },
            LevelText = new LevelText() { Val = "%1." },
            LevelJustification = new LevelJustification() { Val = LevelJustificationValues.Left },
            ParagraphProperties = new ParagraphProperties(new Indentation() { Left = "720", Hanging = "360" })
        });
        numbering.AppendChild(abstractNum);
        
        int listIdCounter = 1;
        int columnIndex = 1; // Column2的索引
        
        foreach (Table table in inputBody.Descendants<Table>())
        {
            // 为当前表格创建独立的编号实例,确保编号从1重启
            NumberingInstance numInstance = new NumberingInstance() { NumberID = listIdCounter };
            numInstance.AppendChild(new AbstractNumId() { Val = 1 });
            numbering.AppendChild(numInstance);
            
            // 获取当前表格的Column2单元格,跳过表头
            var columnCells = table.Elements<TableRow>()
                .Select(row => row.Elements<TableCell>().ElementAtOrDefault(columnIndex))
                .Where(cell => cell != null && !cell.InnerText.Trim().Equals("Column2", StringComparison.OrdinalIgnoreCase));
            
            foreach (TableCell cell in columnCells)
            {
                // 遍历单元格内所有子元素(段落、嵌套表格、图片等)
                foreach (OpenXmlElement element in cell.ChildElements)
                {
                    // 克隆原元素,完整保留格式
                    OpenXmlElement clonedElement = element.CloneNode(true);
                    
                    // 如果是段落,添加编号属性并保留原段落格式
                    if (clonedElement is Paragraph para)
                    {
                        ParagraphProperties paraProps = para.ParagraphProperties ?? new ParagraphProperties();
                        
                        // 添加编号属性
                        paraProps.AppendChild(new NumberingProperties(
                            new NumberingLevelReference() { Val = 0 },
                            new NumberingId() { Val = listIdCounter }));
                        
                        para.ParagraphProperties = paraProps;
                    }
                    
                    outputBody.AppendChild(clonedElement);
                }
            }
            
            listIdCounter++;
        }
        
        outputMainPart.Document.Body = outputBody;
        outputMainPart.Document.Save();
    }
}

关键说明

  • 编号重启:每个表格对应一个独立的NumberingInstance,关联同一个基础编号样式,确保每个表格的编号从1开始计数。
  • 格式完整保留:直接克隆单元格内的所有OpenXmlElement,包括段落、嵌套表格、图片、超链接等元素,不丢失任何格式信息。
  • 属性合并:为段落添加编号属性时,保留原段落的格式属性(如缩进、字体样式等),避免覆盖原有格式。

内容的提问来源于stack exchange,提问作者Bubbles

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 02:33:10