• Java将数据写入word文档(.doc)


    Java可用org.apache.poi包来操作word文档。org.apache.poi包可于官网上下载,解压后各jar作用如下图所示:
    poi包中的各jar用途
    可根据需求导入对应的jar。

    一、HWPFDocument类的使用
    用HWPFDocument类将数据写到指定的word文档中,基本思路是这样的:
    - 首先,建立一个HWPFDocument类的实例,关联到一个临时的word文档;
    - 然后,通过Range类实例,将数据写入这个word文档中;
    - 接着,将这个临时的word文档通过write函数写入指定的word文档中。
    - 最后,关闭所有资源。
    下面详细说明各个步骤。
    1.构造函数
    这里要说明一下,经过试验,暂时还没找到直接在程序中新建一个word文档并读取的方法,只能先创建好temp.doc,然后在程序中读取。(用File-createNewFile和POIFSFileSystem.create创建出来的.doc文件,都不能被正确读取)
    另外,其实选择哪种参数传入都是一样的,毕竟HWPFDocument关联的word文档是无法写入的,只是当作一个临时文件。所以,选择最为熟悉的InputStream较为合适。
    参数1:InputStream。可将word文档用FileInputStream流读取,然后传入HWPFDocument类。主要用于读取word文档中的数据。
    参数2:POIFSFileSystem。POIFSFileSystem的构造函数参数可以是(File,boolean)【这样的话file必须是已经存在的】,后者为false时可读写。这里可以写为

    HWPFDocument doc = new HWPFDocument(new POIFSFileSystem(new File("temp.doc"),false));

    2.Range类
    (1)获取Range类实例。
    HWPFDocument类中有一系列获取Range类实例以操作word文档的方法。比较常用的是getRange(),这个方法可以获取涵盖整个文档的范围,但不包括任何页眉和页脚。

    Range range = doc.getRange();

    此外,还有获取所有文本范围的getOverallRange()、获取所有文本框的getMainTextboxRange()等等,具体可以根据需求查阅文档。

    (2)Range类操作word文档
    Range类中有大量获取文档数据的方法,若有需要可以查阅文档。这里只说明与写入数据有关的方法。
    1. insertBefore(String),将字符串插入到此range的开头。返回值类型:CharacterRun
    2. insertAfter(String),将字符串插入到此range的结尾。返回值类型:CharacterRun
    3. insertTableBefore(short列数, int行数),在此range的开头插入一个指定行列数的表。返回值类型:Table
    4. text(),获取当前range的所有文本。返回值类型:String。虽然不是写入数据的方法,但是在调试过程中比较好用。

    3.write方法
    HWPFDocument类中的write方法有三种重载形式:(实际上可以理解为writeTo)
    参数1:空参数。将本对象关联的word文档写入另一个打开的可写的POIFSFileSystem文件中。
    参数2:File。将本对象关联的word文档写入指定的文件(newFile)中。如果该文件不存在,则创建;若存在,则覆盖。
    参数3:OutputStream。将本对象关联的word文档写入指定的字节输出流中。
    可以根据需求选择,但是最好还是选择OutputStream,因为输出流的操作空间更大。参数2的newFile不能续写,只能覆盖。
    可以将其直接写入目标文件的输出流,也可以先写入一个字节数组输出流,在通过字节数组输出流写入到目标文件输出流中。

    4.关闭资源
    - 关闭doc.close();,也即是关闭doc所使用的资源”temp.doc”
    - 关闭将数据写入指定word文档的输出流

    二、代码示例

        /**
         * @description 将数据归档到.doc的word文档中。数据续写到原目标文件末尾。
         * @param source
         *            源文件(必须存在!)
         * @param sourChs
         *            读取源文件要用的编码,若传入null,则默认是GBK编码
         * @param target
         *            目标word文档(必须存在!)
         */
        public static void storeDoc(File source, String sourChs, File target) {
            /*
             * 思路: 1.建立字符输入流,读取source中的数据。 2.在目标文件路径下new File:temp.doc
             * 3.将目标文件重命名为temp.doc,并用HWPFDocument类关联(temp.doc)。
             * 3.由temp.doc建立Range对象,写入source中的数据。 4.建立字节输出流,关联target。
             * 5.将range中的数据写入关联target的字节输出流。
             */
            if (!target.exists()) {
                throw new RuntimeException("目标文件不存在!");
            }
            if (sourChs == null) {
                sourChs = "GBK";
            }
            BufferedReader in = null;
            HWPFDocument temp = null;
            BufferedOutputStream out = null;
            String path = target.getParent();
            File tempDoc = new File(path, "temp.doc");
            target.renameTo(tempDoc);
            try {
                in = new BufferedReader(new InputStreamReader(new FileInputStream(source), sourChs));
                temp = new HWPFDocument(new BufferedInputStream(new FileInputStream(tempDoc)));
                out = new BufferedOutputStream(new FileOutputStream(target));
                Range range = temp.getRange();
                String line = null;
                range.insertAfter(getDate(12));
                range.insertAfter("
    ");
                while ((line = in.readLine()) != null) {
                    range.insertAfter(line);
                    range.insertAfter("
    "); // word中
    是换行符
                }
                range.insertAfter("
    ");
                range.insertAfter("
    ");
                temp.write(out);
            } catch (UnsupportedEncodingException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            } catch (IOException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }
                }
                if (temp != null) {
                    try {
                        temp.close();
                    } catch (IOException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }
                }
                tempDoc.deleteOnExit();
            }
        }

    三、调试记录
    1.关于创建临时temp.doc的尝试
    目的:在程序开始时创建一个temp.doc,程序结束后删除。

    尝试1:用File-createNewFile创建出文件,然后用POIFSFileSystem构造函数打开这个文件, 然后用HWPFDocument关联到这个POIFSFileSystem类实例。
    结果:org.apache.poi.EmptyFileException: The supplied file was empty (zero bytes long)。创建出的文件是0字节空文件,不能被POIFSFileSystem打开。

    尝试2:用POIFSFileSystem.create(file)静态函数创建POIFSFileSystem实例,然后用 HWPFDocument关联。
    结果:java.io.FileNotFoundException: no such entry: “WordDocument”, had: []。文件 是创建成功了,也用POIFSFileSystem成功载入,但是HWPFDocument无法接收这个参数。

    结论:目前只能用Office软件创建的word文档才行。也就是说暂时还没找到直接在程序中新建 一个word文档的方法,只能先创建好temp.doc,然后在程序中读取。

    2.无法写入temp.doc

    描述:用doc.getRange()方法获取文件的整个范围,然后用range.insertAfter(String)方法 插入数据。编译运行没有任何异常,但是打开文件发现还是原样。

    尝试:插入数据后用range.text()获取当前range的所有文本并显示在控制台上,发现数据的 确是成功插入到了range中,但是temp.doc依然没有任何变化。

    猜测:可能是文件读取到HWPFDocument的方式不对,只读不可写入。
    也有可能是range中的内容并不会改变.doc的内容,必须doc.write(*)写入到另一个文件中才 行。
    尝试:通过各种方式(inputstream,poifsfilesystem,(poifs,readonly))载入temp.doc,结 果都是一样。于是开始尝试第二种猜测。

    3.加入doc.write(*)方法后,运行报错,找不到需要的类文件(编译正常)。
    详情:只加了这一句话,这句话报错:doc.write(out):java.lang.NoClassDefFoundError错 误
    分析:NoClassDefFoundError发生在编译时对应的类可用,而运行时在Java的classpath路径 中,对应的类不可用导致的错误。
    解决:要注意看报错的提示:
    Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/collections4/bidimap/TreeBidiMap……
    可以看出,是org.apache.commons.collections4包找不到导致的。导入这个包即可。
    收获:使用外部jar包时,并不是只把所有代码里用到的类所在的jar包导入就万事大吉了,经 常是代码中用到的类里需要用到其他包中的类。如果在运行时报错,要注意看报错提示,根据 提示导入相关的包。
    就这样一个简答的小bug卡了我半天,以后代码出错时不要只看错误类型,一定要细看报错的 描述。

    4.无法写入目标文件
    详情:续2,通过doc.write(out)方法将数据写到字节输出流,目标文件毫无变化。

    尝试:用doc.write方法将数据写到字节数组,看看数据是否真的被输出了(如果是,就说明 是数据写入目标文件的过程中出了问题,而不是doc.write输出的问题)
    结果:在输出的内容中找到了想要输出的数据。由此说明,前面的一切都没问题了,问题出在 把数据写入word文档上。

    尝试:将目标文件删除,让程序创建出一个。结果,写入成功。那么问题来了:

    5.目标文件无法续写
    详情:由程序自己创建出的word文档可以写入,但已存在的word文档无法续写。即使是程序自 己创建出的word文档,也只能写入一次,无法续写。

    分析:Range输出的数据是带有word文档的创建信息和格式数据的,这些内容对于已存在的 word文档不适用。
    现在的情况是:用于创建HWPFDocument对象的temp.doc必须手动创建;目标文件必须由代码生 成,且生成后只能用代码写入一次。
    将数据写入指定word文档的流程是:用getRange()方法获得临时文件数据(其实是为了获取 word文档的创建信息、格式数据),然后将源文件数据写入range,最后将range写入目标文件 的字节输出流。
    既然如此,为何不直接将临时文件的来源设为目标文件呢?这样getRange所获取的range就能 同时包含目标文件中的原有文本数据,再在其后添加源文件中的内容,然后将整个range写入 由代码新创建的目标文件,不就是另一种意义上的续写吗?这样既避免了手动创建temp.doc, 又能实现续写,还能让避免产生垃圾文件(无意义的temp.doc)

    解决:考虑到输入输出的冲突问题,先将目标文件重命名为temp.doc,然后由程序新创建出一 个空的目标文件。如果需要续写,就直接用getRange方法获取原来的所有数据;如果不需要续 写,就用Range(0,0,tempdoc)获取一个空的range,只带有格式和创建信息。将所有源数据写 入range后,用temp.write(out)将range中的数据写入新创建的目标文件。

    6.如何不续写?
    思路:由传入的参数,如果不续写,就用range.replaceText(“”,false),将整个range清空, 然后再往后插入需要的内容。

    问题1:角标越界异常
    分析:将整个range清空会导致无法插入,可以将整个range改为”tobedeleted"range.replaceText("{tobedeleted}”,”“)删掉标志即可。

    问题2:做了上述操作后,仍然是续写,原range并没有清空。
    分析:经过一系列测试,发现原因:用程序写入的文本,用range.text()读取不到,当然用 range.replaceText也无法操作了。而在程序写入后,随便手动在文件中写点东西然后保存, 再用range.text()就可以读取到了。

    结论:这可能是包的固有bug之二,暂时无法解决。

    四、其他
    其实,poi包对于word文档来说,主要功能还是读取,写入功能很初级、不完善。poi只能操作最简单的word格式内容,当要求的样式复杂、文档长度较长时,用poi就较难完成要求。
    这时,Jacob是一个更好的选择。Jacob能完整保持复杂的格式内容,操作也更为方便。但Jacob也有个缺点:只能在Windows平台下实现,无法在linux平台下实现。
    此外,要生成标准格式的word文档,还有一种思路,是在另一篇博客上看到的:

    先用office2003或者2007编辑好word的样式,然后另存为xml,将xml翻译为FreeMarker模板,最后用java来解析FreeMarker模板并输出Doc。经测试这样方式生成的word文档完全符合office标准,样式、内容控制非常便利,打印也不会变形,生成的文档和office中编辑文档完全一样。

  • 相关阅读:
    python的字符串连接操作符+
    python-在定义函数时,不定长参数中,默认值参数不能放在必选参数前面
    python中的sort方法使用详解
    详解Python中的join()函数的用法
    python中map()函数
    python的匿名函数lambda解释及用法
    python 代码的缩进位置决定执行部分
    python代码位置引发的错误
    python中如何使输出不换行
    git stash
  • 原文地址:https://www.cnblogs.com/cage666/p/7295391.html
Copyright © 2020-2023  润新知