原文标题:Adding images and layout to your Docx4j-generated word documents, part 1
原文作者:lvdpal
发表日期:2012年10月22日
注:由于我对docx4j也不是很熟悉,所以很多专业名词不会翻译,如果文章内容使您感到困惑,请移步到原文。
在前一篇博客,我介绍了如何在docx文档中生成表格。这篇博客中我会展示一些关于图片、分页符和目录表的示例:
添加图片
本示例我们向新创建的word文档添加一张嵌入式图片。这并非很难,因为Docx4j示例包含了几种做这件事的方法。但是有几个问题我不是特别地清楚,所以我稍微重构了那个例子改为只添加一个内联图片并且添加了一些注释来解释发生了什么。
public class AddingAnInlineImage { /** * 像往常一样, 我们创建了一个包(package)来容纳文档. * 然后我们创建了一个指向将要添加到文档的图片的文件对象.为了能够对图片做一些操作, 我们将它转换 * 为字节数组. 最后我们将图片添加到包中并保存这个包(package). */ public static void main (String[] args) throws Exception { WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage(); File file = new File("src/main/resources/iProfsLogo.png"); byte[] bytes = convertImageToByteArray(file); addImageToPackage(wordMLPackage, bytes); wordMLPackage.save(new java.io.File("src/main/files/HelloWord7.docx")); } /** * Docx4j拥有一个由字节数组创建图片部件的工具方法, 随后将其添加到给定的包中. 为了能将图片添加 * 到一个段落中, 我们需要将图片转换成内联对象. 这也有一个方法, 方法需要文件名提示, 替换文本, * 两个id标识符和一个是嵌入还是链接到的指示作为参数. * 一个id用于文档中绘图对象不可见的属性, 另一个id用于图片本身不可见的绘制属性. 最后我们将内联 * 对象添加到段落中并将段落添加到包的主文档部件. * * @param wordMLPackage 要添加图片的包 * @param bytes 图片对应的字节数组 * @throws Exception 不幸的createImageInline方法抛出一个异常(没有更多具体的异常类型) */ private static void addImageToPackage(WordprocessingMLPackage wordMLPackage, byte[] bytes) throws Exception { BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes); int docPrId = 1; int cNvPrId = 2; Inline inline = imagePart.createImageInline("Filename hint","Alternative text", docPrId, cNvPrId, false); P paragraph = addInlineImageToParagraph(inline); wordMLPackage.getMainDocumentPart().addObject(paragraph); } /** * 创建一个对象工厂并用它创建一个段落和一个可运行块R. * 然后将可运行块添加到段落中. 接下来创建一个图画并将其添加到可运行块R中. 最后我们将内联 * 对象添加到图画中并返回段落对象. * * @param inline 包含图片的内联对象. * @return 包含图片的段落 */ private static P addInlineImageToParagraph(Inline inline) { // 添加内联对象到一个段落中 ObjectFactory factory = new ObjectFactory(); P paragraph = factory.createP(); R run = factory.createR(); paragraph.getContent().add(run); Drawing drawing = factory.createDrawing(); run.getContent().add(drawing); drawing.getAnchorOrInline().add(inline); return paragraph; } /** * 将图片从文件对象转换成字节数组. * * @param file 将要转换的文件 * @return 包含图片字节数据的字节数组 * @throws FileNotFoundException * @throws IOException */ private static byte[] convertImageToByteArray(File file) throws FileNotFoundException, IOException { InputStream is = new FileInputStream(file ); long length = file.length(); // 不能使用long类型创建数组, 需要用int类型. if (length > Integer.MAX_VALUE) { System.out.println("File too large!!"); } byte[] bytes = new byte[(int)length]; int offset = 0; int numRead = 0; while (offset < bytes.length && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) { offset += numRead; } // 确认所有的字节都没读取 if (offset < bytes.length) { System.out.println("Could not completely read file " +file.getName()); } is.close(); return bytes; } }
在表格中添加图片
现在我们知道了如何在文档中添加图片,如果在我们前面博客中的表格中添加图片将会使表格更漂亮。正如你将在下面看到的代码一样,并不比我们前面例子中所做的困难很多。
public class AddingAnInlineImageToTable { private static WordprocessingMLPackage wordMLPackage; private static ObjectFactory factory; /** * 首先我们创建包和对象工厂, 因此在类的随处我们都可以使用它们. 然后我们创建一个表格并添加 * 边框. 接下来我们创建一个表格行并在第一个域添加一些文本. * 对于第二个域, 我们用与前面一样的图片创建一个段落并添加进去. 最后把行添加到表格中, 并将 * 表格添加到包中, 然后保存这个包. */ public static void main (String[] args) throws Exception { wordMLPackage = WordprocessingMLPackage.createPackage(); factory = Context.getWmlObjectFactory(); Tbl table = factory.createTbl(); addBorders(table); Tr tr = factory.createTr(); P paragraphOfText = wordMLPackage.getMainDocumentPart().createParagraphOfText("Field 1"); addTableCell(tr, paragraphOfText); File file = new File("src/main/resources/iProfsLogo.png"); P paragraphWithImage = addInlineImageToParagraph(createInlineImage(file)); addTableCell(tr, paragraphWithImage); table.getContent().add(tr); wordMLPackage.getMainDocumentPart().addObject(table); wordMLPackage.save(new java.io.File("src/main/files/HelloWord8.docx")); } /** * 用给定的段落作为内容向给定的行中添加一个单元格. * * @param tr * @param paragraph */ private static void addTableCell(Tr tr, P paragraph) { Tc tc1 = factory.createTc(); tc1.getContent().add(paragraph); tr.getContent().add(tc1); } /** * 向新的段落中添加内联图片并返回这个段落. * 这个方法与前面例子中的方法没有区别. * * @param inline * @return */ private static P addInlineImageToParagraph(Inline inline) { // Now add the in-line image to a paragraph ObjectFactory factory = new ObjectFactory(); P paragraph = factory.createP(); R run = factory.createR(); paragraph.getContent().add(run); Drawing drawing = factory.createDrawing(); run.getContent().add(drawing); drawing.getAnchorOrInline().add(inline); return paragraph; } /** * 使用给定的文件创建一个内联图片. * 跟前面例子中一样, 我们将文件转换成字节数组, 并用它创建一个内联图片. * * @param file * @return * @throws Exception */ private static Inline createInlineImage(File file) throws Exception { byte[] bytes = convertImageToByteArray(file); BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes); int docPrId = 1; int cNvPrId = 2; return imagePart.createImageInline("Filename hint", "Alternative text", docPrId, cNvPrId, false); } /** * 将图片从文件转换成字节数组. * * @param file * @return * @throws FileNotFoundException * @throws IOException */ private static byte[] convertImageToByteArray(File file) throws FileNotFoundException, IOException { InputStream is = new FileInputStream(file ); long length = file.length(); // You cannot create an array using a long, it needs to be an int. if (length > Integer.MAX_VALUE) { System.out.println("File too large!!"); } byte[] bytes = new byte[(int)length]; int offset = 0; int numRead = 0; while (offset < bytes.length && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) { offset += numRead; } // Ensure all the bytes have been read if (offset < bytes.length) { System.out.println("Could not completely read file "+file.getName()); } is.close(); return bytes; } /** * 给表格添加简单的黑色边框. * * @param table */ private static void addBorders(Tbl table) { table.setTblPr(new TblPr()); CTBorder border = new CTBorder(); border.setColor("auto"); border.setSz(new BigInteger("4")); border.setSpace(new BigInteger("0")); border.setVal(STBorder.SINGLE); TblBorders borders = new TblBorders(); borders.setBottom(border); borders.setLeft(border); borders.setRight(border); borders.setTop(border); borders.setInsideH(border); borders.setInsideV(border); table.getTblPr().setTblBorders(borders); } }
添加换页符
添加换页符相当地简单。Docx4j拥有一个叫作Br的break对象,这个对象有一个type属性,这种情况下我们需要将其设置为page,type其它可选的值为column和textWrapping。这个break可以很简单地添加到段落中。
public class AddingAPageBreak { private static ObjectFactory factory; private static WordprocessingMLPackage wordMLPackage; public static void main (String[] args) throws Docx4JException { wordMLPackage = WordprocessingMLPackage.createPackage(); factory = Context.getWmlObjectFactory(); wordMLPackage.getMainDocumentPart().addParagraphOfText("Hello Word!"); addPageBreak(); wordMLPackage.getMainDocumentPart().addParagraphOfText("This is page 2!"); wordMLPackage.save(new java.io.File("src/main/files/HelloWord11.docx") ); } /** * 向文档添加一个换行符 */ private static void addPageBreak() { MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart(); Br breakObj = new Br(); breakObj.setType(STBrType.PAGE); P paragraph = factory.createP(); paragraph.getContent().add(breakObj); documentPart.getJaxbElement().getBody().getContent().add(paragraph); } }
添加目录表
添加目录十分地简单。在Word中这是一个可以被添加的域,在Docx4j中是一样的。仅有一个你需要知道的小问题,就是像这样创建的一个文档,在你第一次打开它的时候,Word会给你一个此文档可能包含指向其它文件的域的提示信息,询问你是否要更新这些域。如果你点击了yes回应询问,Word将会将目录表域转换为真正的目录。如果你点击了no,你会看到这个文档没有目录。
在点击yes之后,这个文档会变成这个样子:
public class AddingTableOfContent { private static ObjectFactory factory; /** * 首先我们创建对象工厂和包并从包中抽出文档部件. 然后我们添加目录表, 后面跟着一些带有分类 * 标题样式的段落. 最后我们保存包. */ public static void main(String[] args) throws Docx4JException { factory = Context.getWmlObjectFactory(); WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage(); MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart(); addTableOfContent(documentPart); documentPart.addStyledParagraphOfText("Heading1", "Hello 1"); documentPart.addStyledParagraphOfText("Heading2", "Hello 2"); documentPart.addStyledParagraphOfText("Heading3", "Hello 3"); documentPart.addStyledParagraphOfText("Heading1", "Hello 1"); wordMLPackage.save(new File("src/main/files/HelloWord10.docx")); } /** * 将目录表添加到文档. * * 首先我们创建段落. 然后添加标记域开始的指示符, 然后添加域内容(真正的目录表), 接着添加域 * 结束的指示符. 最后将段落添加到给定文档的JAXB元素中. * * @param documentPart */ private static void addTableOfContent(MainDocumentPart documentPart) { P paragraph = factory.createP(); addFieldBegin(paragraph); addTableOfContentField(paragraph); addFieldEnd(paragraph); documentPart.getJaxbElement().getBody().getContent().add(paragraph); } /** * (不知道该怎么翻译, 因此将英文原注释保留) * Adds the field that Word uses to create a table of content to the paragraph. * * First we create a run and a text. Then we indicate that all spaces in the * text are to be preserved and set the value to that of the TOC field. * This field definition takes some arguments. The exact definition can be * found in §17.16.5.58 of the Office Open XML standard. In this case we * specify that we want to include all paragrapsh formatted with headings of * levels 1-3 (\0 “1-3”). We also specify that we want all entries to be * hyperlinks (\h), that we want to hide tab leader and page numbers in Web * layout view (\z), and that we want to use the applied paragraph outline * level (\u). * Finally we take the text and use it to create a JAXB element containing text * and add this to the run, which we then add to the given paragraph. * * 将Word用于创建目录表的域添加到段落中. * * 首先创建一个可运行块和一个文本. 然后指出文本中所有的空格都被保护并给TOC域设置值. 这个域定义 * 需要一些参数, 确切定义可以在Office Open XML标准的§17.16.5.58找到, 这种情况我们指定所有 * 段落使用1-3级别的标题来格式化(\0 "1-3"). 我们同时指定所有的实体作为超链接(\h), 而且在Web * 视图中隐藏标签和页码(\z), 我们要使用段落大纲级别应用(\u). * 最后使用文本对象创建了一个JAXB元素包含文本并添加到随后被添加到段落中的可运行块中. * * @param paragraph */ private static void addTableOfContentField(P paragraph) { R run = factory.createR(); Text txt = new Text(); txt.setSpace("preserve"); txt.setValue("TOC \\o \"1-3\" \\h \\z \\u"); run.getContent().add(factory.createRInstrText(txt)); paragraph.getContent().add(run); } /** * 每个域都需要用复杂的域字符来确定界限. 本方法向给定段落添加在真正域之前的界定符. * * 再一次以创建一个可运行块开始, 然后创建一个域字符来标记域的起始并标记域是'脏的'因为我们想要 * 在整个文档生成之后进行内容更新. * 最后将域字符转换成JAXB元素并将其添加到可运行块, 然后将可运行块添加到段落中. * * @param paragraph */ private static void addFieldBegin(P paragraph) { R run = factory.createR(); FldChar fldchar = factory.createFldChar(); fldchar.setFldCharType(STFldCharType.BEGIN); fldchar.setDirty(true); run.getContent().add(getWrappedFldChar(fldchar)); paragraph.getContent().add(run); } /** * 每个域都需要用复杂的域字符来确定界限. 本方法向给定段落添加在真正域之后的界定符. * * 跟前面一样, 从创建可运行块开始, 然后创建域字符标记域的结束, 最后将域字符转换成JAXB元素并 * 将其添加到可运行块, 可运行块再添加到段落中. * * @param paragraph */ private static void addFieldEnd(P paragraph) { R run = factory.createR(); FldChar fldcharend = factory.createFldChar(); fldcharend.setFldCharType(STFldCharType.END); run.getContent().add(getWrappedFldChar(fldcharend)); paragraph.getContent().add(run); } /** * 创建包含给定复杂域字符的JAXBElement的便利方法. * * @param fldchar * @return */ public static JAXBElement getWrappedFldChar(FldChar fldchar) { return new JAXBElement(new QName(Namespaces.NS_WORD12, "fldChar"), FldChar.class, fldchar); } }
总结
在本篇博客中我展示了如何添加图片和word文档中布局的两个方面。在我的下一篇博客我会继续介绍更多布局相关的示例,我本来是将它们添加到了本篇博客中,但是博客有点太长了。