利用POI提取Word(.docx)文件的批注内容

一、POI下载地址

poi-bin-3.5-FINAL-20090928.zip( 26MB)

poi-src-3.5-FINAL-20090928.zip ( 51MB)

 

二:POI对doc文件的支持问题

若只想对DOCX文件操作,可以直接本段内容直接阅读第部分。如果想提取DOC文件批注相关信息,请看下面内容 —->>>

POI3.5现在对DOC文件的处理较弱,请原因是POI中对应的doc处理模 块(HWPF)作者Ryan Ackley已经离开APACHE 组织,此模块目前没有人更新、维护。以下为POI官网原文:

HWPF Pointman Needed!

At the moment we unfortunately do not have someone taking care for HWPF and fostering its development. What we need is someone to stand up, take this thing under his hood as his baby and push it forward. Ryan Ackley, who put a lot of effort into HWPF, is no longer on board, so HWPF is an orphan child waiting to be adopted.

详见:http://poi.apache.org/hwpf/index.html

正因为这个原因,我们没有完善的API来支持对DOC文件的操作。一个临时 的解决办法是利用jacob将doc文件转成docx文件,然后利用POI的docx API(XWPF)来对另存为的docx文件进行解析。这样做有一个很大的缺陷:jacob非纯Java的解决方案,它依赖于本地环境库和应用程序,不利 于代码移植。如果想利用JAVA的跨平台特性,只能期待POI更新了

JACOB一个Java-COM中间件.通过这个组件可以在Java应用程序中调用COM组件和Win32 libraries。如果需要打开或另存为docx,这就要求JVM所在机器必须安装Office2007.
主页:
http://danadler.com/jacob/

 

1、下载 Jacob相关lib

jacob-1.15-M2.zip ,解压文件,将其中jacob-1.15-M2-x86.dll文件放到%Path% 中:Windows/system32或JDK/bin下面;将jacob.jar配置到%CLASSPATH%中。

2、书写测试程序

import  java.io.File;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;

public class JacobTest {
	/**
	* 转换doc文件为docx文件
	* @param docPath doc源文件路径
	* @param docxPath docx目标文件路径
	* @return 目标docx文件
	* @throws Exception
	*/
	private File convertDoc2Docx(String docPath,String docxPath) throws Exception{
	   if(!insureNotNull(docPath,docxPath)){
	//   throw new Exception((docPath==null)?"Doc源文件路径为空":"Docx目标文件路径为空");
	   }
	   //Thread init
	   ComThread.InitSTA();
	    // Instantiate app
	        ActiveXComponent app = new ActiveXComponent("Word.Application"); // 启动word
	        try
	        {
	        // Set component to hide that is opened
	            app.setProperty("Visible", new Variant(false));
	            // Instantiate the Documents Property
	            Dispatch docs = app.getProperty("Documents").toDispatch();
	            // Open a word document
	            Dispatch doc = Dispatch.invoke( docs,
	                    "Open",
	                     Dispatch.Method,
	                     new Object[] { docPath, new Variant(true),
	                     new Variant(true) }, new int[1]).toDispatch();
	            //Save doc as docx
	            Dispatch.invoke( doc, "SaveAs",
	                 Dispatch.Method, new Object[] {docxPath,new Variant()}, new int[1]);
	            // Close doc
	            Dispatch.call(doc, "Close", new Variant(false));
	            return new File(docxPath);
	        }
	        catch (Exception e)
	        {
	           throw e;
	        }
	        finally
	        {
	        app.invoke("Quit", new Variant[] {});//ActiveXComponent quit
	        ComThread.Release();//Thread release
	        }  

	}

	/**
	* 确保此方法的所有参数均不为空
	* @param objects 对象参数
	* @return 所有参数均不为空返回true 否则为false
	*/
	private boolean insureNotNull(Object...objects){
	    for(Object object:objects){
	     if(object==null){
	      return false;
	     }
	    }
	    return true;
	}

	public static void main(String[] args) throws Exception {
	   new JacobTest().convertDoc2Docx("d:\\2009.doc", "d:\\2009.docx");
	}
}

三、Java提取 docx批注源码

 

import java.io.File;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.poi.POIXMLDocument;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.xwpf.usermodel.XWPFComment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CommentsDocument;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.sun.star.lang.IllegalArgumentException;

public class WordHelper {

	private File file;
	/** Word document */
	private XWPFDocument docx;
	/** 批注内容数组 */
	private XWPFComment[] comments;//

	/** 批注引用正文map,结构-<批注Id,正文text> */
	private Map commentRefs;//

	/** 日期格式化类型 */
	private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

	/** 批注所引用正文装配Map完毕标识 */
	private static final String COMMENT_REF_FILLED_OK = "OK";
	/** 批注最大下标 */
	private String maxCommentIndex;

	/**
	* @param filePath Word文件路径
	* @throws Exception Word缺陷导入异常
	*/
	public WordHelper(String filePath) throws Exception{
	   file = new File(filePath);
	   initAttributes();
	}

	/**
	* 初始化成员变量
	* @throws Exception Word缺陷导入异常
	*/
	private void initAttributes() throws Exception{
	   try {
	    docx = new XWPFDocument(POIXMLDocument.openPackage(file.getCanonicalPath()));
	    comments = docx.getComments();
	    maxCommentIndex = String.valueOf(comments.length-1);
	    commentRefs = new HashMap();
	    fillCommentRef(docx.getDocument().getDomNode(), new StringBuilder(), new StringBuilder(), new StringBuilder(),commentRefs);
	   } catch (Exception e) {
	    throw new Exception(new StringBuilder()
	       .append("Word文件格式错误")
	       .append("-").append(e.getMessage()).toString(), e);
	   }

	}

	/**
	* 获取批注内容
	*/
	public XWPFComment[] getComments() {
	   return comments;
	}

	/**
	* 获取批注引用正文
	*/
	public Map getCommentRefs() {
	   return commentRefs;
	}

	/**
	* 获取日期格式化类型
	*/
	public SimpleDateFormat getSdf() {
	   return sdf;
	}

	/*
	 *@See XWPFDocument.onDocumentRead()
	* 获取批注日期List
	*/
	public List getSubmitDateList() {
	   Map dateMap = new HashMap();
	   List dateList = new ArrayList();
	   try {
	    Iterator iter = docx.getRelations().iterator();
	    do {
	     if (!iter.hasNext())
	      break;
	     POIXMLDocumentPart p = (POIXMLDocumentPart) iter.next();
	     String relation = p.getPackageRelationship().getRelationshipType();
	     if (relation.equals(XWPFRelation.COMMENT.getRelation())) {
	      CommentsDocument cmntdoc;
	       cmntdoc = org.openxmlformats.schemas.wordprocessingml.x2006.main.CommentsDocument.Factory
	         .parse(p.getPackagePart().getInputStream());
	      org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment arr[] = cmntdoc
	        .getComments().getCommentArray();
	      int len = arr.length;
	      int j = 0;
	      while (j < len) {
	       org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment ctcomment = arr[j];
	       dateMap.put(ctcomment.getId().toString(), ctcomment.getDate().getTime());
	       j++;
	      }
	     }
	    } while (true);
	    if(dateMap!=null){
	     for(XWPFComment comment:comments){
	      dateList.add(dateMap.get(comment.getId()));
	     }
	    }
	   } catch (Exception e) {}
	   return dateList;
	}

	/*
	* 获取批注作者List
	*/
	public List getSubmitterList() {
	   List list = new ArrayList();
	   for(XWPFComment comment:comments){
	    list.add(comment.getAuthor().trim());
	   }
	   return list;
	}

	/**
	* 组装批注引用文本Map,Map结构-
	* @param node WordProcessingML node
	* @param id 批注ID
	* @param value 批注引用正文文本
	* @param convertOK 正文组装完毕标识 ,组装完毕 = “OK”
	* @param map 要填充的目标Map
	*/
	private void fillCommentRef(Node node,StringBuilder id,StringBuilder value,StringBuilder convertOK,Map map) throws Exception{
	//fillCommentRef方法要求所有参数不能为null,如果为null,抛出异常
	   if(!insureNotNull(node,id,value,convertOK,map)){
	    throw new IllegalArgumentException(new StringBuilder().append(this.getClass().getName()).append("fillCommentRef(")
	      .append(node).append(",").append(id).append(",").append(value).append(",")
	      .append(convertOK).append(",").append(map).append(")").toString());
	   }
	/*
	   * docx文件批注所引用的正文保存在document.xml中,可以通过重命名xx.docx为xx.zip来查看
	   * 其中如果某段正文文本内容有批注,那么会在document.xml这样保存
	   *   
	     ......
	     正文文本
	     ......
	     
	     
	     如果被批注的是在图片上加批注,那么会在document.xml中这样保存(仅限真正docx文件,如果是doc文件另存为docx文件,<wp:docpr节点中是没有属性的) 
	     ......
	     
	     ......
	    
	   * 要解析批注所引用的内容,思路如下:
	   * 1、id初始值为空,如果解析到节点w:commentRangeStart,就代表是有批注的部分,需要把参数id设为节点的id属性值。
	   * 2、顺次解析下面节点,如果此时的id不为空,就代表进入批注引用部分,w:t是文本内容,直接append;wp:docPr是图片内容,用" [xxx]"来区分是图片,然后append.
	   * 3、如果解析到节点w:commentRangeEnd,就代表一个批注引用完毕,这时需要向Map中put(id,value)值;
	   *    判断当前的批注Id是不是最大,如果为最大批注Id,convertOK置为“OK”,用此标识来说明批注引用提取完毕,退出节点for循环。例如一个很大的Word文件,只在第2页做了一个批注,前面的做法会很有用;
	   *    同时还要做好一条批注引用解析完毕的收尾工作:id清空,代表下面节点又是无批注的部分;value清空,待下次新的批注append.
	   */
	   if("w:t".equals(node.getNodeName()) && id.length()>0){
	    value.append(node.getFirstChild().getNodeValue());
	   }else if("wp:docPr".equals(node.getNodeName()) && id.length()>0){
	    value.append("[").append(getAttribute(node,"name")).append("]");
	   }else if("w:commentRangeStart".equals(node.getNodeName())){
	    id.delete(0, id.length());
	    id.append(getAttribute(node,"w:id"));
	   }else if("w:commentRangeEnd".equals(node.getNodeName()) && id.length()>0 ){
	    if(id.toString().equals(getAttribute(node,"w:id"))){
	     map.put(id.toString(), value.toString());
	     if(id.toString().equals(maxCommentIndex)){
	      convertOK.delete(0, convertOK.length());
	      convertOK.append(COMMENT_REF_FILLED_OK);
	     }
	     id.delete(0, id.length());
	     value.delete(0, value.length());
	    }
	   }
	   if(node.hasChildNodes()){
	    NodeList temp = node.getChildNodes();
	    for(int i=0;i<temp.getlength();i++){ 
				if(convertok.tostring().endswith(comment_ref_filled_ok)){="" break;="" }="" fillcommentref(temp.item(i),="" id,value,convertok,map);="" **="" *="" 取节点的属性值="" @param="" node="" 当前的node="" attname="" 要获取的属性名="" @return="" 属性值,没有该属性时返回null="" private="" static="" string="" getattribute(node="" node,string="" attname){="" return="" (node.hasattributes()="" &&="" node.getattributes().getnameditem(attname)!="null)?" node.getattributes().getnameditem(attname).getnodevalue():null;="" 确保此方法的所有参数均不为空="" objects="" 对象参数="" 所有参数均不为空返回true="" 否则为false="" boolean="" insurenotnull(object...objects){="" for(object="" object:objects){="" if(object="=null){" false;="" true;="" public="" void="" main(string[]="" args)="" throws="" exception="" {="" stringbuffer="" value="new" stringbuffer();="" wordhelper="" wh="new" wordhelper("d:\\2009.docx");="" xwpfcomment[]="" comments="wh.getComments();" map commenRefMap = wh.getCommentRefs();
	   List l = wh.getSubmitDateList();
	   SimpleDateFormat sdf= wh.getSdf();
	   XWPFComment comment;

	   for(int i=0;i
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值