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

Apache Tika无法读取嵌套内容控件内容的问题求助

带有嵌套占位符的Word文档内容提取问题

问题背景

尝试提取带有嵌套结构化文档标签(SDT,即Word占位符)的Word文档内容,其document.xml核心结构如下:

<w:document..>
    <w:body>
        <!-- Modified by docx4j 8.3.1 (Apache licensed) using REFERENCE JAXB in Eclipse Adoptium Java 17.0.7 on Windows Server 2022 -->
        <w:p w:rsidR="005C4CF0" w:rsidP="005C4CF0" w14:paraId="78C54BDB" w14:textId="77777777">
            <w:r>
                <w:t>Im</w:t>
            </w:r>
            <w:r>
                <w:t xml:space="preserve"> going to add some basic information that comes from the clause:</w:t>
            </w:r>
        </w:p>
        <w:p w:rsidR="005C4CF0" w:rsidP="005C4CF0" w14:paraId="4241C94B" w14:textId="77777777"/>
        <w:sdt>
            <w:sdtPr>
                <w:alias w:val="Standard :  LC Simple Clause"/>
                <w:tag w:val="CLS_171644_182392_1329803"/>
                <w:id w:val="849765303"/>
                <w:richText/>
            </w:sdtPr>
            <w:sdtContent>
                <w:p w:rsidR="005C4CF0" w14:paraId="6751289D" w14:textId="22A85AC8">
                    <w:r>
                        <w:t xml:space="preserve">This is a simple clause, for contract:</w:t>
                    </w:r>
                    <w:sdt>
                        <w:sdtPr>
                            <w:alias w:val="Contract Name"/>
                            <w:tag w:val="ContractMgmt_ContractName"/>
                            <w:id w:val="-1113981428"/>
                            <w:dataBinding w:prefixMappings="xmlns:sqph='http://schemas.sciquest.com/tcm/office/placeholders/v1'" w:xpath="/sqph:contractplaceholders[1]/sqph:ContractMgmt_ContractName[1]" w:storeItemID="{F2499371-A9EB-4C98-8057-F42491D7ED37}"/>
                            <w:text/>
                        </w:sdtPr>
                        <w:sdtContent>
                            <w:r>
                                <w:t>LC Chat 04</w:t>
                            </w:r>
                        </w:sdtContent>
                    </w:sdt>
                <!-- 原XML此处缺失<w:p>闭合标签 -->
            </w:sdtContent>
        </w:sdt>
        <w:r>
        <w:t xml:space="preserve"> with contract number</w:t>
        </w:r>
        <w:sdt>
            <w:sdtPr>
                <w:alias w:val="Work Group"/>
                <w:tag w:val="ContractConfigSection_ContractProject"/>
                <w:id w:val="-261304195"/>
                <w:dataBinding w:prefixMappings="xmlns:sqph='http://schemas.sciquest.com/tcm/office/placeholders/v1'" w:xpath="/sqph:contractplaceholders[1]/sqph:ContractConfigSection_ContractProject[1]" w:storeItemID="{F2499371-A9EB-4C98-8057-F42491D7ED37}"/>
                <w:text/>
            </w:sdtPr>
            <w:sdtContent>
                <w:r>
                    <w:t>TCMTEST</w:t>
                </w:r>
            </w:sdtContent>
        </w:sdt>
    </w:p>
    </w:body>
</w:document>

使用Apache Tika提取后得到结果:

Im going to add some basic information that comes from the clause<br />
This is a simple clause, for contract:   with contract number TCMTEST

可见顶层SDT外的内容和最后一个非嵌套SDT的内容被正确提取,但嵌套在另一个SDT内的"Contract Name"占位符内容LC Chat 04未被读取。

原因分析

  1. Tika默认解析逻辑限制:Apache Tika的DOCX解析器对嵌套SDT的递归处理不完整,默认只遍历顶层SDT节点,未深入解析嵌套在<w:sdtContent>内部的SDT节点,导致其文本内容被遗漏。
  2. XML结构错误:提供的document.xml存在标签闭合问题,外层SDT内的<w:p>标签未正确闭合,后续的<w:r>和<w:sdt>节点处于非法结构位置,干扰了Tika的解析器对节点层次的识别。

解决办法

方法1:修复XML结构异常

先修正document.xml的标签闭合问题,确保所有节点层次正确:

<!-- 修正后的核心结构片段 -->
<w:sdt>
    <w:sdtPr>
        <w:alias w:val="Standard :  LC Simple Clause"/>
        <w:tag w:val="CLS_171644_182392_1329803"/>
        <w:id w:val="849765303"/>
        <w:richText/>
    </w:sdtPr>
    <w:sdtContent>
        <w:p w:rsidR="005C4CF0" w14:paraId="6751289D" w14:textId="22A85AC8">
            <w:r>
                <w:t xml:space="preserve">This is a simple clause, for contract:</w:t>
            </w:r>
            <w:sdt>
                <w:sdtPr>
                    <w:alias w:val="Contract Name"/>
                    <w:tag w:val="ContractMgmt_ContractName"/>
                    <w:id w:val="-1113981428"/>
                    <w:dataBinding w:prefixMappings="xmlns:sqph='http://schemas.sciquest.com/tcm/office/placeholders/v1'" w:xpath="/sqph:contractplaceholders[1]/sqph:ContractMgmt_ContractName[1]" w:storeItemID="{F2499371-A9EB-4C98-8057-F42491D7ED37}"/>
                    <w:text/>
                </w:sdtPr>
                <w:sdtContent>
                    <w:r>
                        <w:t>LC Chat 04</w:t>
                    </w:r>
                </w:sdtContent>
            </w:sdt>
        </w:p> <!-- 补充缺失的段落闭合标签 -->
    </w:sdtContent>
</w:sdt>
<!-- 将后续内容放入合法的段落节点 -->
<w:p w:rsidR="005C4CF0" w:rsidP="005C4CF0" w14:paraId="XXXXXX" w14:textId="XXXXXX">
    <w:r>
        <w:t xml:space="preserve"> with contract number</w:t>
    </w:r>
    <w:sdt>
        <w:sdtPr>
            <w:alias w:val="Work Group"/>
            <w:tag w:val="ContractConfigSection_ContractProject"/>
            <w:id w:val="-261304195"/>
            <w:dataBinding w:prefixMappings="xmlns:sqph='http://schemas.sciquest.com/tcm/office/placeholders/v1'" w:xpath="/sqph:contractplaceholders[1]/sqph:ContractConfigSection_ContractProject[1]" w:storeItemID="{F2499371-A9EB-4C98-8057-F42491D7ED37}"/>
            <w:text/>
        </w:sdtPr>
        <w:sdtContent>
            <w:r>
                <w:t>TCMTEST</w:t>
            </w:r>
        </w:sdtContent>
    </w:sdt>
</w:p>

修正后重新用Tika提取,大概率能正常识别嵌套SDT的内容。

方法2:自定义Tika解析器扩展嵌套SDT处理

扩展Tika的DocxParser类,重写SDT节点处理逻辑,增加递归遍历嵌套SDT的逻辑:

import org.apache.tika.parser.microsoft.ooxml.DocxParser;
import org.apache.tika.sax.XHTMLContentHandler;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;

public class NestedSDTDocxParser extends DocxParser {
    @Override
    protected void processSDT(XMLStreamReader reader, XHTMLContentHandler xhtml) throws XMLStreamException, IOException {
        // 先处理当前SDT的内容
        super.processSDT(reader, xhtml);
        // 遍历当前节点的子节点,检查是否有嵌套的SDT
        while (reader.hasNext()) {
            int event = reader.next();
            if (event == XMLStreamReader.START_ELEMENT && "w:sdt".equals(reader.getLocalName())) {
                // 递归处理嵌套的SDT
                processSDT(reader, xhtml);
            } else if (event == XMLStreamReader.END_ELEMENT && "w:sdtContent".equals(reader.getLocalName())) {
                // 退出当前SDT内容节点
                break;
            }
        }
    }
}

使用自定义解析器替换默认的DocxParser,即可提取嵌套SDT的内容。

方法3:直接用Docx4j解析XML

利用Docx4j库直接解析document.xml,递归遍历所有SDT节点提取内容,避免Tika的限制:

import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.SdtBlock;
import org.docx4j.wml.SdtRun;
import org.docx4j.wml.P;
import java.util.List;
import java.util.stream.Collectors;

public class SDTContentExtractor {
    public static String extractAllSDTContent(String docxPath) throws Exception {
        WordprocessingMLPackage pkg = WordprocessingMLPackage.load(new java.io.File(docxPath));
        StringBuilder content = new StringBuilder();
        
        // 提取顶层SDT
        List<SdtBlock> topLevelSDTs = pkg.getMainDocumentPart().getContent().stream()
                .filter(obj -> obj instanceof SdtBlock)
                .map(obj -> (SdtBlock) obj)
                .collect(Collectors.toList());
        
        for (SdtBlock sdt : topLevelSDTs) {
            // 提取当前SDT的文本内容
            content.append(org.docx4j.Docx4J.extractText(sdt.getSdtContent()));
            // 递归处理SDT内容内的嵌套SDT
            processNestedSDT(sdt.getSdtContent().getContent(), content);
        }
        
        // 提取非SDT的普通段落内容
        List<P> paragraphs = pkg.getMainDocumentPart().getContent().stream()
                .filter(obj -> obj instanceof P)
                .map(obj -> (P) obj)
                .collect(Collectors.toList());
        for (P p : paragraphs) {
            content.append(org.docx4j.Docx4J.extractText(p));
        }
        
        return content.toString();
    }
    
    private static void processNestedSDT(List<Object> contentList, StringBuilder result) {
        for (Object obj : contentList) {
            if (obj instanceof SdtRun) {
                // 处理行内嵌套SDT
                result.append(org.docx4j.Docx4J.extractText(((SdtRun) obj).getSdtContent()));
            } else if (obj instanceof P) {
                // 处理段落内的嵌套内容
                processNestedSDT(((P) obj).getContent(), result);
            } else if (obj instanceof SdtBlock) {
                // 处理块级嵌套SDT
                result.append(org.docx4j.Docx4J.extractText(((SdtBlock) obj).getSdtContent()));
                processNestedSDT(((SdtBlock) obj).getSdtContent().getContent(), result);
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Luis Alfredo Conde Vela

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 17:14:53