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

基于HWPFDocument提取.doc中嵌入Office文件及PDF的技术咨询

提取.doc中嵌入的Office文件及PDF的解决方案

问题描述

我用HWPFDocument编写的代码目前能从.doc文件里提取.exe、.zip、.tar、.so、.xml格式的文件,现在需要修改代码以提取嵌入的.docx、.docm、.xlsx及PDF文件。核心问题在于:

  • 无法区分Ole10Native文件与这类Office文件
  • 不清楚提取逻辑的修改位置
  • 不知道PDF的提取判断条件

现有代码

document = new HWPFDocument(fs);
DirectoryNode docRootDir = fs.getRoot();
DirectoryNode objPoolDir;
// Read ROOT POIFS
Iterator<Entry> docRootDirIter = docRootDir.getEntries();
String fileName = null;
int fileNameIndex = 0;
while(docRootDirIter.hasNext()){
    Entry docRootDirEntry = docRootDirIter.next();

    if (docRootDirEntry.getName().contains("ObjectPool")){
        objPoolDir = (DirectoryNode) docRootDirEntry;

        //Read ObjectPool POIFS directory ---> ObjectPool is location of embedded files within a DOC file
        Iterator<Entry> objPoolDirIter = objPoolDir.getEntries();
        while(objPoolDirIter.hasNext()){
            Entry objPoolDirEntry = objPoolDirIter.next();

            // Read ObjectPool POIFS subdirectories
            if(objPoolDirEntry.isDirectoryEntry()){
                DirectoryNode objPoolSubDir = (DirectoryNode) objPoolDirEntry;
                Iterator<Entry> objPoolSubDirIter = objPoolSubDir.getEntries();
                while (objPoolSubDirIter.hasNext()){
                    Entry objPoolSubDirEntry = objPoolSubDirIter.next();

                    // Read embedded OLE10 files, create an OLE object, and write to the OS File system
                    if (objPoolSubDirEntry.isDocumentEntry()){
                        DocumentEntry objPoolSubDirFile = (DocumentEntry) objPoolSubDirEntry;

                        if(objPoolSubDirFile.getName().contains("Ole10Native")){

                            Ole10Native oleEntry = Ole10Native.createFromEmbeddedOleObject(objPoolSubDir);
                            fileName = oleEntry.getLabel();
                            if (fileName == null) 
                                fileName = "unknownOle10Native" + fileNameIndex++ + ".nul";
                            byte[] data =oleEntry.getDataBuffer();
                            FileOutputStream out = new FileOutputStream("./" + fileName);
                            out.write(data);
                            out.close();
                            if (fileName != null && !fileName.isEmpty()) {
                                File f = new File(fileName);
                                embeddedFilePaths.add(f.getAbsolutePath());
                            }
                        }
                    }
                }
            }else if(objPoolDirEntry.isDocumentEntry()){
                DocumentEntry documentEntry = (DocumentEntry) objPoolDirEntry;

                DirectoryEntry directoryEntry = documentEntry.getParent();

                directoryEntry.getEntryNames();

            }
        }
    }else if(docRootDirEntry.getName().contains("WordDocument")){
        DocumentEntry documentEntry = (DocumentEntry) docRootDirEntry;
        System.out.println(documentEntry.getName());
    }
}

解决方案

1. 区分Ole10Native与Office嵌入文件

Office文件(.docx/.docm/.xlsx)作为OLE嵌入时,不会以Ole10Native条目形式存在,而是在ObjectPool的子目录中包含完整的文件结构:

  • .docx/.docm是ZIP压缩包,文件头为PK\x03\x04
  • .xlsx同样是ZIP结构,文件头一致
  • 可直接读取字节流的前几个字节判断类型

2. 提取逻辑的修改位置

原代码仅处理Ole10Native类型的嵌入文件,需在遍历ObjectPool子目录时,增加分支处理非Ole10Native的情况:

  • 在if(objPoolSubDirFile.getName().contains("Ole10Native"))的else分支中,检查当前子目录是否包含Office或PDF文件
  • 对于Office文件,直接提取字节流保存;对于PDF,通过文件头验证后保存

3. PDF的提取判断条件

PDF文件的文件头固定为%PDF-,读取文件的前5个字节即可判断是否为PDF格式。

修改后的代码

import org.apache.commons.io.IOUtils;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.poifs.filesystem.*;
import org.apache.poi.ole2.Ole10Native;

import java.io.*;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

// 假设embeddedFilePaths是已定义的List<String>
// List<String> embeddedFilePaths = new ArrayList<>();

document = new HWPFDocument(fs);
DirectoryNode docRootDir = fs.getRoot();
DirectoryNode objPoolDir;
Iterator<Entry> docRootDirIter = docRootDir.getEntries();
String fileName = null;
int fileNameIndex = 0;
while(docRootDirIter.hasNext()){
    Entry docRootDirEntry = docRootDirIter.next();

    if (docRootDirEntry.getName().contains("ObjectPool")){
        objPoolDir = (DirectoryNode) docRootDirEntry;
        Iterator<Entry> objPoolDirIter = objPoolDir.getEntries();
        while(objPoolDirIter.hasNext()){
            Entry objPoolDirEntry = objPoolDirIter.next();

            if(objPoolDirEntry.isDirectoryEntry()){
                DirectoryNode objPoolSubDir = (DirectoryNode) objPoolDirEntry;
                Iterator<Entry> objPoolSubDirIter = objPoolSubDir.getEntries();
                byte[] fileBytes = null;
                boolean isOle10Native = false;

                // 先处理Ole10Native类型文件
                while (objPoolSubDirIter.hasNext()){
                    Entry objPoolSubDirEntry = objPoolSubDirIter.next();
                    if (objPoolSubDirEntry.isDocumentEntry()){
                        DocumentEntry objPoolSubDirFile = (DocumentEntry) objPoolSubDirEntry;
                        if(objPoolSubDirFile.getName().contains("Ole10Native")){
                            isOle10Native = true;
                            Ole10Native oleEntry = Ole10Native.createFromEmbeddedOleObject(objPoolSubDir);
                            fileName = oleEntry.getLabel();
                            if (fileName == null) 
                                fileName = "unknownOle10Native" + fileNameIndex++ + ".nul";
                            fileBytes = oleEntry.getDataBuffer();
                            break;
                        }
                    }
                }

                // 处理非Ole10Native的嵌入文件(Office/PDF)
                if(!isOle10Native){
                    Iterator<Entry> subDirIter = objPoolSubDir.getEntries();
                    if(subDirIter.hasNext()){
                        Entry firstEntry = subDirIter.next();
                        if(firstEntry instanceof DocumentEntry){
                            DocumentEntry docEntry = (DocumentEntry) firstEntry;
                            InputStream is = docEntry.getInputStream();
                            fileBytes = IOUtils.toByteArray(is);
                            is.close();

                            // 判断文件类型并生成文件名
                            if(isPDF(fileBytes)){
                                fileName = "embedded_pdf_" + fileNameIndex++ + ".pdf";
                            } else if(isOOXML(fileBytes)){
                                if(isXLSX(fileBytes)){
                                    fileName = "embedded_xlsx_" + fileNameIndex++ + ".xlsx";
                                } else {
                                    fileName = "embedded_docx_" + fileNameIndex++ + ".docx";
                                }
                            } else {
                                fileName = "unknown_embedded_" + fileNameIndex++ + ".bin";
                            }
                        }
                    }
                }

                // 保存提取的文件
                if(fileBytes != null && fileName != null){
                    FileOutputStream out = new FileOutputStream("./" + fileName);
                    out.write(fileBytes);
                    out.close();
                    File f = new File(fileName);
                    embeddedFilePaths.add(f.getAbsolutePath());
                    fileName = null;
                }
            }
        }
    }
}

// 文件类型判断辅助方法
private static boolean isPDF(byte[] bytes){
    if(bytes.length < 5) return false;
    String header = new String(bytes, 0, 5);
    return "%PDF-".equals(header);
}

private static boolean isOOXML(byte[] bytes){
    if(bytes.length < 4) return false;
    return bytes[0] == 'P' && bytes[1] == 'K' && bytes[2] == 0x03 && bytes[3] == 0x04;
}

private static boolean isXLSX(byte[] bytes){
    try(ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bytes))){
        ZipEntry entry;
        while((entry = zis.getNextEntry()) != null){
            if("[Content_Types].xml".equals(entry.getName())){
                byte[] content = IOUtils.toByteArray(zis);
                String contentStr = new String(content);
                return contentStr.contains("spreadsheetml");
            }
            zis.closeEntry();
        }
    } catch (IOException e){
        return false;
    }
    return false;
}

代码说明:

  • 新增非Ole10Native嵌入文件的处理逻辑,直接读取子目录中的文档条目
  • 通过文件头判断PDF和OOXML格式,再通过ZIP内容区分xlsx和docx
  • 使用Apache Commons IO的IOUtils简化字节流读取(需引入对应依赖)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 14:25:03