Java准确获取Word/Excel/PPT/PDF的页数(附Word页数读不准的处理办法)
1.需求背景
前两天接了个小需求,就是用Java准确地判断出Word(.doc和.docx)、Excel(.xls和.xlsx)、PPT(.ppt和.pptx)还有PDF的页数。开始觉得很简单,想都没想就用了Apache的POI,但后来发现个大问题:
Apache POI对于.doc(Office Word 1997- 2003的版本)后缀的word文件,页数读取不准!
度娘了一下,发现大家普遍都有这个问题,解决办法也是五花八门,有用POI的,有用JACOB将Word转PDF的,还有用第三方工具比如PageOffice/OpenOffice的,但是感觉都不是特别简洁。所以结合这些博客的经验及我自己的摸索,我终于找到了较为简单准确的解决方式,下面完整地分享给大家。
2.环境准备工作
2.1 JACOB介绍及安装
我的思路还是使用JACOB来操作Word,因为对于Office Word 97-03这种远古版本,除了软件自身提供的宏,似乎没有什么能对它进行直接的操作,而在Windows平台为了解决这种软件缺乏通用API的问题,推出了COM的解决方案。
而想通过Java来操作DOM,我们就需要一个JACOB(Java-COM Bridge)这样的桥梁。JACOB 开源项目提供的是一个 JVM 独立的自动化服务器实现,其核心是基于 JNI 技术实现的 Variant, Dispatch 等接口,从而调用Windows的COM(Component Object Model组对象模型)。
JACOB下载地址为:https://sourceforge.net/projects/jacob-project/
如下图:
点击Download,然后解压,将其中的jacob-1.19-x64.dll复制到System32文件夹下,如下图:
2.2 Microsoft Office Word的设置
因为是调用的Word Application自身,所以环境里必须得安装有Office Word,这个就不赘述。
Word里需要做如下两项设置,如下图:
原因是如果使用高版本的Office Word打开.doc文件,默认是预览视图,而不是编辑视图,这个预览视图会影响后面要介绍的方法的判断准确性,如下图:
3.代码
3.1 代码示例
3.1.1 pom.xml
<dependencies>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.8</version>
</dependency>
<!--用于操作Office系列-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.8</version>
</dependency>
<!--用于操作Word-->
<!-- https://mvnrepository.com/artifact/net.sf.jacob-project/jacob -->
<dependency>
<groupId>net.sf.jacob-project</groupId>
<artifactId>jacob</artifactId>
<version>1.14.3</version>
</dependency>
<!--用于操作PDF-->
<!-- https://mvnrepository.com/artifact/com.lowagie/itext -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.0.6</version>
</dependency>
</dependencies>
3.1.2 主要功能实现
package com.docreader;
import com.itextpdf.text.pdf.PdfReader;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.usermodel.SlideShow;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import java.io.FileInputStream;
import java.io.IOException;
public class reader {
/**
* description: 静态方法,用于判断文件类型,并返回页数
* @param filePath 文件完整路径
*/
public static int getFilePageNum(String filePath) throws IOException {
int pageNum = 0;
String lowerFilePath = filePath.toLowerCase();
if (lowerFilePath.endsWith(".xls")) {
HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(lowerFilePath));
Integer sheetNums = workbook.getNumberOfSheets();
if (sheetNums > 0) {
pageNum = workbook.getSheetAt(0).getRowBreaks().length + 1;
}
} else if (lowerFilePath.endsWith(".xlsx")) {
XSSFWorkbook xwb = new XSSFWorkbook(lowerFilePath);
Integer sheetNums = xwb.getNumberOfSheets();
if (sheetNums > 0) {
pageNum = xwb.getSheetAt(0).getRowBreaks().length + 1;
}
} else if (lowerFilePath.endsWith(".docx")) {
XWPFDocument docx = new XWPFDocument(POIXMLDocument.openPackage(lowerFilePath));
pageNum = docx.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
} else if (lowerFilePath.endsWith(".doc")) {
//下方的方法不好使,经常只统计出一页
// HWPFDocument wordDoc = new HWPFDocument(new FileInputStream(lowerFilePath));
// pageNum = wordDoc.getSummaryInformation().getPageCount();
//采用如下方法
pageNum = getDocPageNum(lowerFilePath);
} else if (lowerFilePath.endsWith(".ppt")) {
HSLFSlideShow document = new HSLFSlideShow(new FileInputStream(lowerFilePath));
SlideShow slideShow = new SlideShow(document);
pageNum = slideShow.getSlides().length;
} else if (lowerFilePath.endsWith(".pptx")) {
XMLSlideShow xslideShow = new XMLSlideShow(new FileInputStream(lowerFilePath));
pageNum = xslideShow.getSlides().length + 1;
} else if (lowerFilePath.endsWith(".pdf")){
PdfReader reader = new PdfReader(filePath);
pageNum = reader.getNumberOfPages();
}
return pageNum;
}
/**
* description: 静态方法,专门用于判断Office 2003版本之前的Word(格式为.doc)的页数
* @param filePath 文件完整路径
*/
private static int getDocPageNum(String filePath) {
int pageNum = 0;
try{
// 建立ActiveX部件
ActiveXComponent wordCom = new ActiveXComponent("Word.Application");
//word应用程序不可见
wordCom.setProperty("Visible", false);
// 返回wrdCom.Documents的Dispatch
Dispatch wrdDocs = wordCom.getProperty("Documents").toDispatch();//Documents表示word的所有文档窗口(word是多文档应用程序)
// 调用wrdCom.Documents.Open方法打开指定的word文档,返回wordDoc
Dispatch wordDoc = Dispatch.call(wrdDocs, "Open", filePath, false, true, false).toDispatch();
Dispatch selection = Dispatch.get(wordCom, "Selection").toDispatch();
pageNum = Integer.parseInt(Dispatch.call(selection,"information",4).toString());//总页数 //显示修订内容的最终状态
//关闭文档且不保存
Dispatch.call(wordDoc, "Close", new Variant(false));
//退出进程对象
wordCom.invoke("Quit", new Variant[] {});
} catch (Exception e) {
e.printStackTrace();
}
return pageNum;
}
}
3.1.3 Main方法
import com.docreader.reader;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
public class Main {
public static void main(String[] args) {
try{
int xlsNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\5 pages.xls");
System.out.println("Office2003之前版本的Excel:5 pages.xls 的页数为:" + xlsNum);
int xlsxNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\8 pages.xlsx");
System.out.println("Office2003之后版本的Excel:8 pages.xlsx 的页数为:" + xlsxNum);
int docNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\30 pages.doc");
System.out.println("Office2003之前版本的Word:30 pages.doc 的页数为:" + docNum);
int docxNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\133 pages.docx");
System.out.println("Office2003之后版本的Word:133 pages.docx 的页数为:" + docxNum);
int pptNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\35 pages.ppt");
System.out.println("Office2003之前版本的PPT:35 pages.ppt 的页数为:" + pptNum);
int pptxNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\95 pages.pptx");
System.out.println("Office2003之后版本的PPT:95 pages.pptx 的页数为:" + pptxNum);
int pdfNum = reader.getFilePageNum("E:\\workspace\\DocumentReader\\src\\main\\resources\\30 pages.pdf");
System.out.println("PDF:30 pages.pdf 的页数为:" + pdfNum);
} catch (Exception e){
e.printStackTrace();
}
}
}
3.1.4 运行结果
3.2 注意事项
3.2.1 获取Excel页数的注意事项
if (lowerFilePath.endsWith(".xls")) {
HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(lowerFilePath));
Integer sheetNums = workbook.getNumberOfSheets();
if (sheetNums > 0) {
pageNum = workbook.getSheetAt(0).getRowBreaks().length + 1;
}
} else if (lowerFilePath.endsWith(".xlsx")) {
XSSFWorkbook xwb = new XSSFWorkbook(lowerFilePath);
Integer sheetNums = xwb.getNumberOfSheets();
if (sheetNums > 0) {
pageNum = xwb.getSheetAt(0).getRowBreaks().length + 1;
}
}
仔细看代码,这里的:
getRowBreaks().length + 1
指的是Excel中的分页符的数量,如下图:
如果Excel中插入了7个分页符,那么总共可打印的页数就是7+1 = 8页,如下图:
注意,我看有的教程里,页数 = workbook.getNumberOfSheets(),这就错得太离谱了,这是sheet的数量,根本不是页数。
3.2.2 获取Word页数的注意事项
/**
* description: 静态方法,专门用于判断Office 2003版本之前的Word(格式为.doc)的页数
* @param filePath 文件完整路径
*/
private static int getDocPageNum(String filePath) {
int pageNum = 0;
try{
// 建立ActiveX部件
ActiveXComponent wordCom = new ActiveXComponent("Word.Application");
//word应用程序不可见
wordCom.setProperty("Visible", false);
// 返回wrdCom.Documents的Dispatch
Dispatch wrdDocs = wordCom.getProperty("Documents").toDispatch();//Documents表示word的所有文档窗口(word是多文档应用程序)
// 调用wrdCom.Documents.Open方法打开指定的word文档,返回wordDoc
Dispatch wordDoc = Dispatch.call(wrdDocs, "Open", filePath, false, true, false).toDispatch();
Dispatch selection = Dispatch.get(wordCom, "Selection").toDispatch();
pageNum = Integer.parseInt(Dispatch.call(selection,"information",4).toString());//总页数 //显示修订内容的最终状态
//关闭文档且不保存
Dispatch.call(wordDoc, "Close", new Variant(false));
//退出进程对象
wordCom.invoke("Quit", new Variant[] {});
} catch (Exception e) {
e.printStackTrace();
}
return pageNum;
}
这里的:
//word应用程序不可见
wordCom.setProperty("Visible", false);
如果设置成true,则会打开可见的Word应用,并打开指定的Word文档,对于调试时会有很大帮助。
3.2.3 获取PPT页数的注意事项
else if (lowerFilePath.endsWith(".pptx")) {
XMLSlideShow xslideShow = new XMLSlideShow(new FileInputStream(lowerFilePath));
pageNum = xslideShow.getSlides().length + 1;
}
仔细看代码,这里需要+1。具体原因未知,但是我经过多次测试,结果是准确的。