提取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
相关产品推荐
相关产品推荐

