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

提取DOCX文件文本时的节点拆分与空格保留问题求助

解决DOCX文本提取的两个核心问题

一、自行解析XML时的问题修复

1. 正确获取xml:space="preserve"属性

你当前代码无法获取该属性,是因为xml:属于XML默认命名空间(http://www.w3.org/XML/1998/namespace),直接遍历node.Attributes无法拿到带命名空间的属性。修改文本节点的处理逻辑:

if (node.NodeType == XmlNodeType.Text)
{
    string text = node.Value;
    // 获取xml:space属性
    XmlAttribute spaceAttr = node.Attributes?.GetNamedItem("space", "http://www.w3.org/XML/1998/namespace") as XmlAttribute;
    if (spaceAttr != null && spaceAttr.Value == "preserve")
    {
        // 保留原始空格,不做修剪
        sb.Append(text);
    }
    else
    {
        // 非preserve模式下,仅保留必要空格(避免多余空白)
        sb.Append(text.Trim());
    }
}

2. 处理文本拆分与节点分隔

DOCX的文本会被拆分到多个<w:t>节点(分属不同<w:r>或<w:p>),需要根据父节点类型添加分隔符:

  • 遇到<w:p>(段落)节点:处理完子节点后添加换行符\n
  • 遇到<w:r>(文本块)节点:可根据前后节点判断是否添加空格,避免文本粘连

修改后的完整遍历方法:

private string ExtractAllText(XmlNode node)
{
    StringBuilder sb = new StringBuilder();
    string wordNs = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";

    if (node.NodeType == XmlNodeType.Text)
    {
        string text = node.Value;
        XmlAttribute spaceAttr = node.Attributes?.GetNamedItem("space", "http://www.w3.org/XML/1998/namespace") as XmlAttribute;
        if (spaceAttr != null && spaceAttr.Value == "preserve")
        {
            sb.Append(text);
        }
        else
        {
            sb.Append(text.Trim());
        }
    }
    else if (node.Name == "w:p" && node.NamespaceURI == wordNs)
    {
        // 处理段落内所有子节点文本
        foreach (XmlNode curr in node.ChildNodes)
        {
            sb.Append(ExtractAllText(curr));
        }
        // 段落结束添加换行
        sb.AppendLine();
    }
    else if (node.HasChildNodes)
    {
        foreach (XmlNode curr in node.ChildNodes)
        {
            sb.Append(ExtractAllText(curr));
        }
    }

    return sb.ToString();
}

二、使用DocumentFormat.OpenXml的正确姿势

直接调用Body.InnerText会忽略格式和空格规则,需要遍历具体文本节点并处理xml:space属性:

using (WordprocessingDocument doc = WordprocessingDocument.Open(_Filename, false))
{
    StringBuilder sb = new StringBuilder();
    var body = doc.MainDocumentPart.Document.Body;

    foreach (var paragraph in body.Elements<Paragraph>())
    {
        foreach (var run in paragraph.Elements<Run>())
        {
            foreach (var text in run.Elements<Text>())
            {
                // 获取xml:space属性
                var spaceAttr = text.GetAttribute("space", "http://www.w3.org/XML/1998/namespace");
                if (spaceAttr == "preserve")
                {
                    sb.Append(text.Text);
                }
                else
                {
                    sb.Append(text.Text.Trim());
                }
                // 不同文本块之间添加空格(可根据实际文档结构调整)
                sb.Append(" ");
            }
        }
        // 段落结束添加换行
        sb.AppendLine();
    }
    // 去除末尾多余的空格和换行
    return sb.ToString().TrimEnd();
}

关键注意事项

  • DOCX的文本布局依赖<w:p>(段落)、<w:r>(文本块)、<w:t>(文本节点)的层级结构,必须按层级处理才能还原合理的文本分隔
  • xml:space="preserve"仅作用于当前<w:t>节点内的空格,需要单独判断每个文本节点的该属性
  • 不同Run之间的空格是否需要添加,要根据实际文档结构调整(部分文档会用专门的空格节点,无需额外添加)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 18:38:22