Docx4J踩坑日志
先看Docx所需依赖:
<dependencies>
<!-- docx文档解析依赖 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>3.2.1</version>
</dependency>
<!-- docx4j依赖 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
踩坑一
javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
- with linked exception:
环境:jdk11
原因:直接使用该依赖,最高jdk只支持到jdk8
解决方法:
-
方法一:将jdk版本退回到jdk8
-
方法二:添加以下依赖:
<dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.3.2</version> </dependency>
踩坑二
先看代码:
@Test
public void test() throws IOException, JAXBException, Docx4JException {
String textNodesXPath = "//xmlns:w/xmlns:t";
// 或者是 String textNodesXPath = "//w:t/w:p";
FileInputStream is1 = new FileInputStream("C:\\Users\\User\\Desktop\\jl\\10.docx");
WordprocessingMLPackage load1 = WordprocessingMLPackage.load(is1);
List<Object> l1 = load1.getMainDocumentPart().getJAXBNodesViaXPath(textNodesXPath, false);
is1.close();
l1.forEach(o -> {
String value = ((Text) ((JAXBElement<?>) o).getValue()).getValue();
System.out.println("value = " + value);
});
}
以上代码困难会获取不到数据,如果获取不到数据,可能是变量:String textNodesXPath
设置的不对,试试换成String textNodesXPath = "//w:t"
,如果换完后出现新的错误,请看继续往下看(踩坑三)
踩坑三
先看代码:
@Test
public void test() throws IOException, JAXBException, Docx4JException {
String textNodesXPath = "//w:t";
FileInputStream is1 = new FileInputStream("C:\\Users\\User\\Desktop\\jl\\10.docx");
WordprocessingMLPackage load1 = WordprocessingMLPackage.load(is1);
List<Object> l1 = load1.getMainDocumentPart().getJAXBNodesViaXPath(textNodesXPath, false);
is1.close();
l1.forEach(o -> {
String value = ((Text) ((JAXBElement<?>) o).getValue()).getValue();
System.out.println("value = " + value);
});
FileInputStream is2 = new FileInputStream("C:\\Users\\User\\Desktop\\jl\\11.docx");
WordprocessingMLPackage load2 = WordprocessingMLPackage.load(is2);
List<Object> l2 = load2.getMainDocumentPart().getJAXBNodesViaXPath(textNodesXPath, false);
l2.forEach(o -> {
String value = ((Text) ((JAXBElement<?>) o).getValue()).getValue();
System.out.println("value = " + value);
});
is2.close();
}
相信不少人是直接从官网上直接抄来的代码,但大多数时候我们需要同时进行多次信息提取,也就是:WordprocessingMLPackage.load(is1)
这行代码要运行多次(例如如上代码),这时候就会报错:
org.docx4j.jaxb.XPathBinderAssociationIsPartialException: no object association for xpath result: w:t
解决方法:
将Docx4J版本换成3.3.7
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>3.3.7</version>
</dependency>
如果出现无法获取内容的,例如:
当上诉代码中的String textNodesXPath = "//w:t";
设置的是:String textNodesXPath = "//w:t/w:p"
或者是:String textNodesXPath = "//xmlns:w/xmlns:t
时,发现查不到数据的时候,也可以试试换成这个版本,说不定就好了。
排除日志
以下是踩坑三的差错思路,基本该解决的也都解决了,如果出现其他的错误,但是没有排错思路的话,可以看看我的思路(这个报错真的很离谱,花了我一整天的时间😓)
首先,我是直接从各个博主和教程网站Introduction To Docx4J | Baeldung上直接抄来的代码
File doc = new File("helloWorld.docx");
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage
.load(doc);
MainDocumentPart mainDocumentPart = wordMLPackage
.getMainDocumentPart();
String textNodesXPath = "//w:t";
List<Object> textNodes= mainDocumentPart
.getJAXBNodesViaXPath(textNodesXPath, true);
for (Object obj : textNodes) {
Text text = (Text) ((JAXBElement) obj).getValue();
String textValue = text.getValue();
System.out.println(textValue);
}
而且他们推荐我们导入的依赖刚好是:
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
这就很无语了,用他的这个6.1.2版本,刚好是有问题的,同时也看到有些博主用的版本是我上文所演示的3.2.1,好巧不巧的是,这俩个版本在maven中的使用人数是最多的
如果用上文的代码配上这俩个版本的话,不出意外的情况下是会出意外了:
org.docx4j.jaxb.XPathBinderAssociationIsPartialException: no object association for xpath result: w:t
这行报错的意思是:没有对象关联的xpath结果:w:t
,至于这个的报错的具体含义,可以了解以下docx的本质。在这里我简单描述一下:
docx本质上是一个压缩文件,当我们把xxx.docx的后缀改为xxx.zip后,发现他是可以被解压的,而且还能看到他的包里的内容:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
解压后可以看到:
其中有关于这个文档的相关信息,docProps中存放着有关于这个文档的所有消息,如创建时间,编辑软件等。word中存放着当前文档的具体信息,也就是文档的内容:
。document.xml文件中便是docx文件中的主要内容。由于docx的配置信息都是由XML文件存储的,符合XML规范,因此docx4j的解析过程实际上也就是解析该压缩包中不同XML文件的DOM标签,获取里面的属性和值。该知识点在之后的排错中将会很重要。
在之前的代码中,docx4j在读取文档时会先设置一个需要解析的根标签,这里具体为什么是w:t
,可以参考文章:DOCX (fileformat.com)。
同时我猜想,一般同样的方法第一次能解析,第二次不能解析,十有八九是因为对象之间出现了耦合。由于前面继续调用的方法之间完全没有耦合,那很有可能是因为他在调用的时候使用了类似于静态工厂之类的东西,或者是调用了某个静态成员变量了。刚好我看到他的WordprocessingMLPackage
对象不是new出来的,而是通过静态方法load出来的,那么就更有可能是走了静态工厂。
通过多次的debug,发现了这个类:XmlUtils
,他的成员变量里刚好有两个静态工厂:
public class XmlUtils {
private static Logger log = LoggerFactory.getLogger(XmlUtils.class);
public static String TRANSFORMER_FACTORY_PROCESSOR_XALAN = "org.apache.xalan.processor.TransformerFactoryImpl";
private static TransformerFactory transformerFactory;
private static final DocumentBuilderFactory documentBuilderFactory;
private static final String S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan";
private static final String S_BUILTIN_EXTENSIONS_UNIVERSAL = "{http://xml.apache.org/xalan}";
private static final String S_KEY_CONTENT_HANDLER = "{http://xml.apache.org/xalan}content-handler";
/* ... */
}
出错的地方也刚好是这个类中的这个方法:
public static List<Object> getJAXBNodesViaXPath(Binder<Node> binder, Object jaxbElement, String xpathExpr, boolean refreshXmlFirst) throws JAXBException, XPathBinderAssociationIsPartialException {
List<JAXBAssociation> associations = getJAXBAssociationsForXPath(binder, jaxbElement, xpathExpr, refreshXmlFirst);
List<Object> resultList = new ArrayList();
Iterator i$ = associations.iterator();
while(i$.hasNext()) {
JAXBAssociation association = (JAXBAssociation)i$.next();
// 这行代码有问题
if (association.getJaxbObject() == null) {
throw new XPathBinderAssociationIsPartialException("no object association for xpath result: " + association.getDomNode().getNodeName());
}
resultList.add(association.getJaxbObject());
}
return resultList;
}
到这里基本就能确定,可能是JAXBAssociation
出现的问题,association.getJaxbObject()
结果为NULL。
到了这里,我就在想,如果是我的问题,那么他在官网或者是教程平台上应该会给出批量获取doxc文档的方法,但是我找了很久都没找到。说明,这里可能是他(依赖本身)的问题,一般依赖本身的问题就要去换依赖的版本了。