基于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
相关产品推荐
相关产品推荐

