刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
阅读:29843回复:7

使用Java处理word2007(.docx)文档--docx4j的介绍和使用

楼主#
更多 发布于:2019-06-26 16:33
 很多项目中会遇到操作word文档或是使用java生成word文档的需求,而常用的poi对word2007的支持不是很好,经多方查找后找到了docx4j这个用于创建和操作Microsoft Open XML (Word docx、Powerpoint pptx和Excel xlsx)文件的Java库。本篇帖子主要对docx4j及其使用方法做一个简单的介绍。

最新喜欢:

李松鹤李松鹤
刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
沙发#
发布于:2019-06-26 17:11
  在介绍docx4j之前,我们还需要了解一下.docx文件的存储方式。
  .docx文件其实是一个压缩包,里面存放了若干xml文件以及文档中所用到的图片等资源文件。我们将一个.docx文件的后缀改为.zip之后进行解压操作就可以看到docx压缩包中存放的内容了。

图片:QQ图片20190626170035.png



  上图为.docx压缩包中的主要内容。文档的主体内容存储在document.xml文件中,文档样式存储在styles.xml文件中,编号信息存储在numbering.xml文件中,文档中的图片存储在media文件夹中。本帖主要涉及到对这些文件的操作,其他文件中的内容在此不做介绍,感兴趣的可以自己去了解下。
刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
板凳#
发布于:2019-06-26 17:42
  docx4j主要的工作就是将.docx中的各个xml文件以及文件中的xml标签、属性全部转换为java对象,通过使用docx4j,我们可以对java类的新增、修改去改变.docx文件中的内容,可以根据各种不同的数据源去制作一个全新的.docx文件,也可以将.docx文件中的特定内容进行替换,还可以将若干个.docx文件进行合并。
  本帖下面将会介绍向文档中插入内容、在文档中插入标题、为标题绑定数字编号、为文档中的图片和表格添加题注。
刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
地板#
发布于:2019-06-26 18:39
一.向文档中插入内容
 首先我们需要创建一个docx压缩包或是获得一个已存在的docx压缩包。
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
 上方代码为创建新的docx压缩包。
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxFile);
 上方代码为获得一个已存在的docx压缩包。方法参数为一个java.io.File类。

 之后我们需要获得压缩包中的document.xml文件并对其进行操作。

MainDocumentPart mainDoc = wordMLPackage.getMainDocumentPart();
Body body = mainDoc.getContents().getBody();
  上方代码中mainDoc为docx压缩包中的word文件夹对应的java类,而body为document.xml文件对应的java类。
  在为该文件插入内容之前,我们先了解一下document.xml文件中都有些什么内容。

图片:QQ图片20190626170035.png



  上图为一个docx文件中的内容,我们打开该文件压缩包中的document.xml文件后我们可以看到以下内容:

图片:QQ图片20190626170035.png



  <w:body>标签中的内容对应文档的主体内容。我们只需在<w:body>标签添加内容即可将对应的内容添加到word文档中去了。
List<Object> list = body.getContent();
  上方代码为获取document.xml文档中<w:body>标签中的内容,<w:body>标签中的各个标签被存放在一个list中,我们只需要给list中插入内容即可。
ObjectFactory factory = Context.getWmlObjectFactory();

P p = factory.createP();
R crun = factory.createR();
Text ct = factory.createText();
ct.setValue("def");
crun.getContent().add(ct);
p.getContent().add(crun);
list.add(p);
 上方代码可以向word文档中插入一个文字段落。ObjectFactory 为docx4j的工厂类,我们可以通过它创建各标签对应的java类;P为</w:p>标签对应的jave类;R为<w:r>标签对应的java类;Text<w:t>标签对应的java类。上方代码依次创建了</w:p>、<w:r>、<w:t>标签对应的的jave类,为<w:t>标签设置文字内容“def”之后将它们进行嵌套组合插入到了<w:body>标签中。(我们还可以设置文字字体、段落格式,相关内容网上资料较多在此不做详细介绍)
wordMLPackage.save(docxFile);
 之后我们使用上方的代码将我们制作完成的docx包储存到文件中即可。下图为我们最终制作完成docx文件内容:

图片:QQ图片20190626170035.png


刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
4楼#
发布于:2019-06-27 16:52
二.在文档中插入标题

  在word文档中我们经常会使用到标题,将部分文字设置为标题后,word的导航窗格中会按照层级显示文档标题内容方便我们去阅览以及定位。那么使用docx4j要如何给文档中添加标题呢?下面将介绍相关内容。
  首先我们来了解一下word中加入了标题之后压缩包中的xml会如何变化。

图片:QQ图片20190627140912.png



  我们在之前的word文件中将两个文字段落分别设置为标题1和标题2。打开压缩包中的document.xml文件后我们可以看到如下内容:

图片:QQ图片20190627140912.png



  xml中被设置为标题的段落多出了<w:pstyle>标签,该标签的含义是为指定段落设置样式,标签中val的值代表的是样式的id。打开styles.xml文件后我们可以在文件中找到对应样式的内容。

图片:QQ图片20190627140912.png



  上图为styles.xml文件中对应样式的内容,用于将样式与导航窗格绑定的是<w:outlinelvl>标签,它指定了样式的大纲视图层级,val属性的值代表的是层级。
  我们要使用docx4j给文档加入标题的话仅需要在styles.xml中插入对应大纲视图层级的样式,再在document.xml中给对应段落绑定上样式id即可。
  具体代码如下所示:
                //新建word包
                WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
                //创建docx4j工厂
                ObjectFactory factory = Context.getWmlObjectFactory();
                //获得word包中document.xml文件内容
                MainDocumentPart main = wordMLPackage.getMainDocumentPart();
                //获得styles.xml文件内容
                StyleDefinitionsPart sdp = main.getStyleDefinitionsPart();
                //创建一个样式标签
                Style style = factory.createStyle();
                //设置样式标签的type属性
                style.setType("paragraph");
                //新建name标签
                Name name = new Name();
                //设置name标签的val属性
                name.setVal("Heading 1");
                //将设置好的name标签设置到样式标签中
                style.setName(name);
                //设置样式标签的id属性
                style.setStyleId("Heading1");
                //创建段落格式标签
                PPr ppr = factory.createPPr();
                //创建大纲级别标签
                OutlineLvl lvl = new OutlineLvl();
                //设置大纲级别标签的val属性为0
                lvl.setVal(BigInteger.valueOf(0));
                //将设置好的大纲级别标签设置到段落格式标签中
                ppr.setOutlineLvl(lvl);
                //将设置好的段落格式标签设置到样式标签中
                style.setPPr(ppr);
                //将设置好的段落标签加入到styles.xml文件中
                sdp.getContents().getStyle().add(style);
                //获得document.xml文件下body标签内容
                Body body = main.getContents().getBody();
                //创建段落标签
                P p = factory.createP();
                //创建段落格式标签
                PPr pPr = factory.createPPr();
                //创建段落样式标签
                PStyle ps = new PStyle();
                //设置段落样式标签的val属性值为前面创建的样式id
                ps.setVal("Heading1");
                //将设置好的段落样式标签设置到段落格式标签中
                pPr.setPStyle(ps);
                //将设置好的段落格式标签设置到段落标签中
                p.setPPr(pPr);
                //创建r标签
                R run = factory.createR();
                //创建t标签
                Text t = factory.createText();
                //设置t标签内的内容
                t.setValue("测试");
                //将设置好的t标签设置到r标签中
                run.getContent().add(t);
                //将设置好的r标签设置到段落标签中
                p.getContent().add(run);
                //将设置好的段落标签加入body标签中
                body.getContent().add(p);
                //设置word文档要存放的文件
                File file = new File("C:/Users/LiuGY/Desktop/test.docx");
                //将设置好的word包保存到指定文件中
                wordMLPackage.save(file);
  打开运行这段代码后得到的word文件如下图所示:

图片:QQ图片20190627140912.png

刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
5楼#
发布于:2019-06-27 18:36
三.为标题绑定数字编号

 我们在文档中使用标题时通常还会为标题绑定多级编号,那么在docx4j中要如何为标题绑定编号呢?下文会对此进行详细介绍。
 首先我们还是了解一下绑定了多级编号的标题在xml中会如何进行表示。下图为文档中的内容:

图片:QQ图片20190627140912.png



 下图为endnotes.xml文件中的内容:

图片:QQ图片20190627140912.png



 我们会发现endnotes.xml中还是只为段落绑定了样式,并没有添加其他的内容,因此我们再打开styles.xml文件查看样式文件中发生的变化,如下图所示:

图片:QQ图片20190627140912.png



 我们会发现在标题编号的样式标签下多出了一个<w:numPr>标签,这个标签就是将设置好的编号与标题样式进行绑定的。<w:ilvl w:val="0"/>为设置编号层级,<w:numId w:val="1"/>为设置绑定的编号id。而编号内容是在另外的numbering.xml文件中进行设置的。我们打开numbering.xml文件可以看到文件内容如下图所示:

图片:QQ图片20190627140912.png



  在numbering.xml文件中我们可以找到styles.xml中绑定的编号标签<w:num>,我们发现<w:num>标签又另外绑定了一个<w:abstractNum>标签,在<w:abstractNum>标签中设置了各个层级的编号对应的格式。想要通过docx4j为文档中的标题绑定多级编号的话,我们首先需要制作numbering.xml文件中的内容,之后只需在styles.xml中的标题样式中将编号与其进行绑定即可。
 制作绑定了多级编号的文档的代码如下所示:

               //新建word包
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
//创建docx4j工厂
ObjectFactory factory = Context.getWmlObjectFactory();
//获得word包中document.xml文件内容
MainDocumentPart main = wordMLPackage.getMainDocumentPart();
//增加编号列表设置部分,注意,一定要增加到MainDocumentPart中,否则会出现引用不成功的情况
NumberingDefinitionsPart numberingPart = new NumberingDefinitionsPart();
//新建numbering标签
Numbering numbering = new Numbering();
//将numbering标签添加到编号列表设置文件中
numberingPart.setContents(numbering);
//将编号列表设置文件加入word文件夹下
main.addTargetPart(numberingPart);
// 首先定义AbstractNum部分
AbstractNum an = new AbstractNum();
an.setAbstractNumId(BigInteger.valueOf(1));
//设置编号类型为多级编号
MultiLevelType mlt = new MultiLevelType();
mlt.setVal("multilevel");
an.setMultiLevelType(mlt);
org.docx4j.wml.Numbering.AbstractNum.Name numberName = new org.docx4j.wml.Numbering.AbstractNum.Name();
numberName.setVal(NumberFormat.HEX.value());
an.setName(numberName);
Lvl numberlvl = new Lvl();
//设置为1级编号
numberlvl.setIlvl(BigInteger.valueOf(0));
// 设置开始序号,如果不设置为1的话默认为0,可能出现某些格式的序号没有0值显示
Start start = new Start();
start.setVal(BigInteger.valueOf(1));
numberlvl.setStart(start);
// 设置显示文本内容,如果不设置的话将不显示编号
LvlText lvltext = new LvlText();
// 表示显示第一级编号,后面跟一个“.”
lvltext.setVal("%1.");
numberlvl.setLvlText(lvltext);
// 标题增加加粗与字号
RPr rpr = new RPr();
rpr.setB(new BooleanDefaultTrue());
HpsMeasure sz = new HpsMeasure();
sz.setVal(BigInteger.valueOf(34));
rpr.setSz(sz);
rpr.setSzCs(sz);
numberlvl.setRPr(rpr);
// 设置编号格式
NumFmt numfmt = new NumFmt();
numfmt.setVal(NumberFormat.HEX);
numberlvl.setNumFmt(numfmt);
//将设置好的编号层级加入到<w:abstractNum>标签中
an.getLvl().add(numberlvl);
numberlvl = new Lvl();
//设置为2级编号
numberlvl.setIlvl(BigInteger.valueOf(1));
// 设置开始序号,如果不设置为1的话默认为0,可能出现某些格式的序号没有0值显示
start = new Start();
start.setVal(BigInteger.valueOf(1));
numberlvl.setStart(start);
// 设置显示文本内容,如果不设置的话将不显示编号
lvltext = new LvlText();
// 表示显示第二级编号,每一级后面跟一个“.”
lvltext.setVal("%1.%2.");
numberlvl.setLvlText(lvltext);
// 标题增加加粗与字号
rpr = new RPr();
rpr.setB(new BooleanDefaultTrue());
sz = new HpsMeasure();
sz.setVal(BigInteger.valueOf(34));
rpr.setSz(sz);
rpr.setSzCs(sz);
numberlvl.setRPr(rpr);
// 设置编号格式
numfmt = new NumFmt();
numfmt.setVal(NumberFormat.HEX);
numberlvl.setNumFmt(numfmt);
//将设置好的编号层级加入到<w:abstractNum>标签中
an.getLvl().add(numberlvl);
// 将AbstractNum增加到Numbering中
numbering.getAbstractNum().add(an);
// 设置一个Num的实例,通过AbstractNumId引用刚定义的AbstractNum
AbstractNumId anid = new AbstractNumId();
anid.setVal(BigInteger.valueOf(1));
Num num = new Num();
num.setAbstractNumId(anid);
num.setNumId(BigInteger.valueOf(1)); // 此处NumId不能为0,必须为正整数
// 将Num增加到Numbering中
numbering.getNum().add(num);
//获得styles.xml文件内容
StyleDefinitionsPart sdp = main.getStyleDefinitionsPart();
//清空styles.xml中styles标签下的内容
sdp.getContents().getStyle().clear();
//创建一个样式标签
Style style = factory.createStyle();
//设置样式标签的type属性
style.setType("paragraph");
//新建name标签
Name name = new Name();
//设置name标签的val属性
name.setVal("Heading 1");
//将设置好的name标签设置到样式标签中
style.setName(name);
//设置样式标签的id属性
style.setStyleId("Heading1");
//创建段落格式标签
PPr ppr = factory.createPPr();
//创建大纲级别标签
OutlineLvl lvl = new OutlineLvl();
//设置大纲级别标签的val属性为0
lvl.setVal(BigInteger.valueOf(0));
//将设置好的大纲级别标签设置到段落格式标签中
ppr.setOutlineLvl(lvl);
//绑定编号id
NumPr numpr = new NumPr();
NumId numid = new NumId();
numid.setVal(BigInteger.valueOf(1));
numpr.setNumId(numid);
Ilvl ilvl = new Ilvl();
ilvl.setVal(BigInteger.valueOf(0));
numpr.setIlvl(ilvl);
ppr.setNumPr(numpr);
//将设置好的段落格式标签设置到样式标签中
style.setPPr(ppr);
//将设置好的段落标签加入到styles.xml文件中的styles标签下
sdp.getContents().getStyle().add(style);
//创建一个样式标签
style = factory.createStyle();
//设置样式标签的type属性
style.setType("paragraph");
//新建name标签
name = new Name();
//设置name标签的val属性
name.setVal("Heading 2");
//将设置好的name标签设置到样式标签中
style.setName(name);
//设置样式标签的id属性
style.setStyleId("Heading2");
//创建段落格式标签
ppr = factory.createPPr();
//创建大纲级别标签
lvl = new OutlineLvl();
//设置大纲级别标签的val属性为1
lvl.setVal(BigInteger.valueOf(1));
//将设置好的大纲级别标签设置到段落格式标签中
ppr.setOutlineLvl(lvl);
//绑定编号id
numpr = new NumPr();
numid = new NumId();
numid.setVal(BigInteger.valueOf(1));
numpr.setNumId(numid);
ilvl = new Ilvl();
ilvl.setVal(BigInteger.valueOf(1));
numpr.setIlvl(ilvl);
ppr.setNumPr(numpr);
//将设置好的段落格式标签设置到样式标签中
style.setPPr(ppr);
//将设置好的段落标签加入到styles.xml文件中的styles标签下
sdp.getContents().getStyle().add(style);
//获得document.xml文件下body标签内容
Body body = main.getContents().getBody();
//创建段落标签
P p = factory.createP();
//创建段落格式标签
PPr pPr = factory.createPPr();
//创建段落样式标签
PStyle ps = new PStyle();
//设置段落样式标签的val属性值为前面创建的样式id
ps.setVal("Heading1");
//将设置好的段落样式标签设置到段落格式标签中
pPr.setPStyle(ps);
//将设置好的段落格式标签设置到段落标签中
p.setPPr(pPr);
//创建r标签
R run = factory.createR();
//创建t标签
Text t = factory.createText();
//设置t标签内的内容
t.setValue("测试");
//将设置好的t标签设置到r标签中
run.getContent().add(t);
//将设置好的r标签设置到段落标签中
p.getContent().add(run);
//将设置好的段落标签加入body标签中
body.getContent().add(p);
//创建段落标签
p = factory.createP();
//创建段落格式标签
pPr = factory.createPPr();
//创建段落样式标签
ps = new PStyle();
//设置段落样式标签的val属性值为前面创建的样式id
ps.setVal("Heading2");
//将设置好的段落样式标签设置到段落格式标签中
pPr.setPStyle(ps);
//将设置好的段落格式标签设置到段落标签中
p.setPPr(pPr);
//创建r标签
run = factory.createR();
//创建t标签
t = factory.createText();
//设置t标签内的内容
t.setValue("test");
//将设置好的t标签设置到r标签中
run.getContent().add(t);
//将设置好的r标签设置到段落标签中
p.getContent().add(run);
//将设置好的段落标签加入body标签中
body.getContent().add(p);
//设置word文档要存放的文件
File file = new File("C:/Users/LiuGY/Desktop/test.docx");
//将设置好的word包保存到指定文件中
wordMLPackage.save(file);
 打开代码生成的文件后如下图所示:

图片:QQ图片20190627140912.png


刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
6楼#
发布于:2019-06-28 10:55
四.在文档中添加题注

 在使用word时,我们常常会在表格前方插入表题注、在图片后方插入图题注,那么使用docx4j要如何为文档插入题注呢?下面将进行详细的介绍。
 我们还是先来了解一下在文档中插入了题注之后,文档xml内容会如何变化。我们在文档中加入了一个图片一张表格,并且分别为他们插入了题注,如下图所示:

图片:QQ图片20190627140912.png



  打开文档压缩包中的document.xml文件后可以看到下图内容:

图片:QQ图片20190627140912.png



  图中为表格题注的相关内容,我们可以看到表格题注中有两段域代码,第一段意为引用样式表中的样式,第二段表示统计"表"这一题注在之前出现的次数,分别对应文档中表1-1的前一个1和后一个1。在docx4j中我们也只需要制作相应的域代码并插入到对应位置即可。相关代码如下所示:
         /**
* 根据题注类型生成题注段落
* @param type 题注类型(TYPE_FIGURE或TYPE_TABLE)
* @return
*/
public static P getCaptions(CaptionsType type){
//创建段落
P para = factory.createP();
//创建R标签
R run = factory.createR();
//创建T标签
Text text = factory.createText();
if(type.equals(TYPE_FIGURE)){
//为T标签设置内容"图"
text.setValue(FIGURE_STRING);
}else if(type.equals(TYPE_TABLE)){
//为T标签设置内容"表"
text.setValue(TABLE_STRING);
}
//将T标签添加到R标签中
run.getContent().add(text);
//将R标签添加到段落中
para.getContent().add(run);
//创建段落格式
PPr ppr = factory.createPPr();
Jc jc = ppr.getJc();
if (jc == null) {
jc = new Jc();
}
//设置居中
jc.setVal(JcEnumeration.CENTER);
ppr.setJc(jc);
para.setPPr(ppr);
run = factory.createR();
text = factory.createText();
text.setSpace("preserve");
run.getContent().add(text);
para.getContent().add(run);
run = factory.createR();
//创建域标签
FldChar fldChar = new FldChar();
fldChar.setFldCharType(STFldCharType.BEGIN);
run.getContent().add(fldChar);
para.getContent().add(run);
run = factory.createR();
text = factory.createText();
text.setSpace("preserve");
//设置域中的内容
text.setValue(" STYLEREF 1 \\s ");
run.getContent().add(factory.createRInstrText(text));
para.getContent().add(run);
run = factory.createR();
fldChar = new FldChar();
fldChar.setFldCharType(STFldCharType.SEPARATE);
run.getContent().add(fldChar);
para.getContent().add(run);
run = factory.createR();
RPr rpr = factory.createRPr();
rpr.setNoProof(null);
run.setRPr(rpr);
text = factory.createText();
text.setValue(level+"");
run.getContent().add(text);
para.getContent().add(run);
run = factory.createR();
fldChar = new FldChar();
fldChar.setFldCharType(STFldCharType.END);
run.getContent().add(fldChar);
para.getContent().add(run);
run = factory.createR();
NoBreakHyphen nbh = new NoBreakHyphen();
run.getContent().add(nbh);
para.getContent().add(run);

run = factory.createR();
fldChar = new FldChar();
fldChar.setFldCharType(STFldCharType.BEGIN);
run.getContent().add(fldChar);
para.getContent().add(run);
run = factory.createR();
text = factory.createText();
text.setSpace("preserve");
text.setValue(" SEQ ");
run.getContent().add(factory.createRInstrText(text));
para.getContent().add(run);
run = factory.createR();
text = factory.createText();
if(type.equals(TYPE_FIGURE)){
text.setValue(FIGURE_STRING);
}else if(type.equals(TYPE_TABLE)){
text.setValue(TABLE_STRING);
}
run.getContent().add(factory.createRInstrText(text));
para.getContent().add(run);
run = factory.createR();
text = factory.createText();
text.setValue(" \\* ARABIC \\s 1 ");
run.getContent().add(factory.createRInstrText(text));
para.getContent().add(run);
run = factory.createR();
fldChar = new FldChar();
fldChar.setFldCharType(STFldCharType.SEPARATE);
run.getContent().add(fldChar);
para.getContent().add(run);
run = factory.createR();
rpr = factory.createRPr();
rpr.setNoProof(null);
run.setRPr(rpr);
text = factory.createText();
if(type.equals(TYPE_FIGURE)){
text.setValue(figureCount+++"");
}else if(type.equals(TYPE_TABLE)){
text.setValue(tableCount+++"");
}
run.getContent().add(text);
para.getContent().add(run);
run = factory.createR();
fldChar = new FldChar();
fldChar.setFldCharType(STFldCharType.END);
run.getContent().add(fldChar);
para.getContent().add(run);
return para;
}

  在使用上面代码中的getCaptions分别插入一个图题一个表题到文档中,最终生成的文档的效果如下所示:

图片:QQ图片20190627140912.png

刘广昱
骑士
骑士
  • UID363
  • 粉丝1
  • 关注1
  • 发帖数31
  • 社区居民
7楼#
发布于:2019-06-28 10:58
docx4j官方网站:http://www.docx4java.org/trac/docx4jdocx4j下载地址:http://www.docx4java.org/downloads.html

在使用docx4j中遇到什么问题欢迎大家来咨询讨论
游客

返回顶部