http://blog.zhaojie.me/2010/05/convert-document-to-pdf-via-openoffice.html

——————————————————————————————————————————————————————————

使用OpenOffice.org将各类文档转为PDF

2010-05-27 12:37 by 老赵, 25682 visits

最近在项目中遇到一个需求,是要将各类文档转换为PDF。这应该是个很常见的工作,而且我也只需要支持MS Word,Excel,PowerPoint等常见的文档格式就行了。于是有朋友就建议了,可以使用MS Office转嘛。当然也可以使用其他方法,例如装一些PDF打印机,把文档打印成pdf文件。不过这些做法在“授权”方面似乎都有些问题。当然,我也找 了一些商业解决方案(如Aspose)保底,咋看之下它的授权方式也并不算贵。不过现在看来,OpenOffice.org已经能够满足我的需求了。如果您有更好的做法也请告诉我。

OpenOffice.org是个开源的办公套件,提供了与MS Word,Excel,PowerPoint等对应的多个软件,在很多时候倒也足够使用。更重要的是,它支持包括MS Office 2007在内的多种格式,并且能够将其导出为PDF文件,再加上它的授权方式是LGPL,在生产环境里使用自然也不会有什么明显的限制了。此外,OOo本身也有相当多的开发文档,我对完成这个工作还是很有信心的——但我没想到的是,这过程还真不如想象中那么顺利。

编译通过也不容易

首先,我安装了OpenOffice.org主程序以及SDK。SDK随带一些示例代码,其中DocumentHandling部分正 好包含一个我需要的DocumentConverter功能。于是我打开Eclipse,倒入这个文件,很显然会出现无数错误提示:还没有引入合适的类库 嘛。那么我该引用哪些jar包呢?根据其他一些搜索到的零碎的资料提示,我该引入的是一些放在~\Basis\program\classes下的几个 jar包,比如unoil.jar、juh.jar……等等,这个包在什么地方?事实上,我在这么目录下唯独只找到unoil.jar这个独苗。莫名之 余,我一股脑的将目录中的30多个jar包全部引入,可是错误依旧。

我就蒙了,在搜索引擎里不断地用juh.jar相关的关键字进行查询,希望可以找到一些提示,一无所获。然后我动用了系统中的文件搜索, 在~/Basis目录中查找*.jar,还是没有发现juh.jar的踪影。于是我很沮丧,怎么第一步也这么不顺利。直到大约过了一个小时后,我才无意间 在~\URE\java目录下发现了那几个关键的jar包。引入后我长吁一口气:示例代码终于编译通过了。概括来说,如果需要让 DocumentConverter.java编译通过,需要引入一下三个jar包:

  • ~\URE\java\juh.jar
  • ~\URE\java\jurt.jar
  • ~\Basis\program\classes\unoil.jar

真是痛恨文档和实际现象不符的情况,消耗时间不说,心情也变糟糕了。

整理示例代码

不得不说,DocumentConverter.java真不能算是段优秀的示例代码。首先,它并没有很好地起到示范的作用。我理想中的示例代码应 该能够清晰地说明工作的方式和步骤,而不会添加太多额外的内容。这段示例代码的效果是“转化指定目录中的所有文件”,还用到了递归。再加上它没有 import任何类型,每个类型在使用时都拖着长长的“com.sun.star”,这让原本就十分冗余的Java代码变得更为难以理解。更别说注释与代 码本身的冲突,还有多余的类型强制转换等问题。为此,我根据文档说明,重新改写了一下示例代码,将整个过程拆分为多个步骤。

首先,我们打开并连接一个OOo程序,这需要创建一个XComponentContext对象:

private static XComponentContext createContext() throws Exception {
// get the remote office component context
return Bootstrap.bootstrap();
}

然后创建一个XComponentLoader对象:

private static XComponentLoader createLoader(XComponentContext context) throws Exception {
// get the remote office service manager
XMultiComponentFactory mcf = context.getServiceManager();
Object desktop = mcf.createInstanceWithContext("com.sun.star.frame.Desktop", context);
return UnoRuntime.queryInterface(XComponentLoader.class, desktop);
}

从Loader对象可以加载一篇文档:

private static Object loadDocument(XComponentLoader loader, String inputFilePath) throws Exception {
// Preparing properties for loading the document
PropertyValue[] propertyValues = new PropertyValue[1];
propertyValues[0] = new PropertyValue();
propertyValues[0].Name = "Hidden";
propertyValues[0].Value = new Boolean(true); // Composing the URL by replacing all backslashs
File inputFile = new File(inputFilePath);
String inputUrl = "file:///" + inputFile.getAbsolutePath().replace('\\', '/'); return loader.loadComponentFromURL(inputUrl, "_blank", 0, propertyValues);
}

接着自然就是文档转换了:

private static void convertDocument(Object doc, String outputFilePath, String convertType) throws Exception {
// Preparing properties for converting the document
PropertyValue[] propertyValues = new PropertyValue[2];
// Setting the flag for overwriting
propertyValues[0] = new PropertyValue();
propertyValues[0].Name = "Overwrite";
propertyValues[0].Value = new Boolean(true);
// Setting the filter name
propertyValues[1] = new PropertyValue();
propertyValues[1].Name = "FilterName";
propertyValues[1].Value = convertType; // Composing the URL by replacing all backslashs
File outputFile = new File(outputFilePath);
String outputUrl = "file:///" + outputFile.getAbsolutePath().replace('\\', '/'); // Getting an object that will offer a simple way to store
// a document to a URL.
XStorable storable = UnoRuntime.queryInterface(XStorable.class, doc);
// Storing and converting the document
storable.storeAsURL(outputUrl, propertyValues);
}

最后还要关闭文档:

private static void closeDocument(Object doc) throws Exception {
// Closing the converted document. Use XCloseable.clsoe if the
// interface is supported, otherwise use XComponent.dispose
XCloseable closeable = UnoRuntime.queryInterface(XCloseable.class, doc); if (closeable != null) {
closeable.close(false);
} else {
XComponent component = UnoRuntime.queryInterface(XComponent.class, doc);
component.dispose();
}
}

最后便是将上面四个步骤串联起来:

public static void main(String args[]) {
String inputFilePath = "D:\\convert\\input.txt";
String outputFilePath = "D:\\convert\\output.doc"; // the given type to convert to
String convertType = "swriter: MS Word 97"; try {
XComponentContext context = createContext();
System.out.println("connected to a running office ..."); XComponentLoader compLoader = createLoader(context);
System.out.println("loader created ..."); Object doc = loadDocument(compLoader, inputFilePath);
System.out.println("document loaded ..."); convertDocument(doc, outputFilePath, convertType);
System.out.println("document converted ..."); closeDocument(doc);
System.out.println("document closed ..."); System.exit(0);
} catch (Exception e) {
e.printStackTrace(System.err);
System.exit(1);
}
}

总体来说,虽然OOo并没有提供优雅的API,但是它的主要“套路”还是比较容易摸索出来的:加载文档,使用 UnoRuntime.queryInterface方法获取各种操作接口,而各种参数都通过PropertyValue数组来提供。如果您像我一样感觉 不爽,重新作一层简单的封装也是十分容易的。

运行中的问题

到目前为止,我们只是重新整理了示例代码,还没有开始运行。当第一次运行的时候便发现有异常抛出:

com.sun.star.comp.helper.BootstrapException: no office executable found!
at com.sun.star.comp.helper.Bootstrap.bootstrap(Bootstrap.java:246)
at jeffz.practices.AnyToDoc.createContext(AnyToDoc.java:19)
at jeffz.practices.AnyToDoc.main(AnyToDoc.java:87)

不过有异常信息之后,查找解决方案一般也很容易(但就我个人经验来说, 还是有很多朋友会问“抛出XX异常该怎么办”之类的问题)。经过搜索,发现遇到这个问题的人还不少,他们把juh.jar等文件复制到OOo安装目录外 (这在生产环境中几乎是必然的)之后便会产生这个异常,但如果直接引用OOo安装目录内的jar便不会有问题了——但是我目前是直接引用OOo安装目录的 jar包,不是吗?但我转念一想,我当时为编译通过而挣扎的原因,不就是“juh.jar”等文件不在它本该在的位置吗?既然这个问题和jar包与OOo 程序的相对路径有关,那么如果我把jar包放回“原来”的位置,这个问题可能就不存在了。

不过这些只是推测,我没有去进行尝试。因为既然在生产环境中还是会破坏路径问题,那我还是找一下这个问题的解决方案吧。最终在OOo的论坛上找到了答案:有人提供了一个补充包bootstrapconnector.jar,其中提供了一个方法可以让我们指定OOo的程序目录。也就是说,我们需要把之前的createContext改写成:

private static XComponentContext createContext() throws Exception {
// get the remote office component context
// return Bootstrap.bootstrap();
String oooExeFolder = "C:/Program Files/OpenOffice.org 3/program/";
return BootstrapSocketConnector.bootstrap(oooExeFolder);
}

当然,生产环境中您一般不会使用硬编码的方式制定路径,您可以把它放在配置文件或是系统变量里。再次运行即告成功。这段代码会将一个txt文件转化成旧有的Word格式,事实上您可以将txt替换成OOo所支持的任何一种格式,比如rtf,docs,odt等等。

那么接下来的问题便是,如何将目标格式改为PDF文件?很显然,目标格式是Word文件,是因为我们将类型字符串指定为“swriter: MS Word 97”,那么PDF格式是多少?这靠猜测是没法得出结果的,最后还是从一篇文档中得到了答案:writer_pdf_Export。事实上,这么做还是不够,代码还是会在storeAsURL方法中抛出异常,而且这是一个泛泛的ErrorCodeIOException,没有具体信息(message为空)。又一阵好找,才发现storeAsURL对应着OOo的“Save as”功能,而如果是“Export”功能,则应该调用storeToURL方法。

最后,我们终于成功地将其他格式转化为PDF文件了。

完整代码

在这里贴出“txt转pdf”完整的可运行的示例代码:

import java.lang._;
import java.io.File;
import ooo.connector.BootstrapSocketConnector;
import com.sun.star.lang.XComponent;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.beans.PropertyValue;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XStorable;
import com.sun.star.util.XCloseable; object AnyToPdf extends Application { // get the remote office component context
def createContext() : XComponentContext = {
val oooExeFolder = "C:/Program Files/OpenOffice.org 3/program/"
BootstrapSocketConnector.bootstrap(oooExeFolder)
} def createComponentLoader(context: XComponentContext) : XComponentLoader = {
// get the remote office service manager
val mcf = context.getServiceManager()
val desktop = mcf.createInstanceWithContext("com.sun.star.frame.Desktop", context)
UnoRuntime.queryInterface(classOf[XComponentLoader], desktop)
} def loadDocument(loader: XComponentLoader, inputFilePath: String) : Object = {
// Preparing properties for loading the document
val propertyValue = new PropertyValue()
propertyValue.Name = "Hidden"
propertyValue.Value = new Boolean(true) // Composing the URL by replacing all backslashs
val inputFile = new File(inputFilePath)
val inputUrl = "file:///" + inputFile.getAbsolutePath().replace('\\', '/')
loader.loadComponentFromURL(inputUrl, "_blank", 0, Array(propertyValue))
} def convertDocument(doc: Object, outputFilePath: String, convertType: String) {
// Preparing properties for converting the document
// Setting the flag for overwriting
val overwriteValue = new PropertyValue()
overwriteValue.Name = "Overwrite"
overwriteValue.Value = new Boolean(true)
// Setting the filter name
val filterValue = new PropertyValue()
filterValue.Name = "FilterName"
filterValue.Value = convertType // Composing the URL by replacing all backslashs
val outputFile = new File(outputFilePath)
val outputUrl = "file:///" + outputFile.getAbsolutePath().replace('\\', '/') // Getting an object that will offer a simple way to store
// a document to a URL.
val storable = UnoRuntime.queryInterface(classOf[XStorable], doc)
// Storing and converting the document
storable.storeToURL(outputUrl, Array(overwriteValue, filterValue))
} def closeDocument(doc: Object) {
// Closing the converted document. Use XCloseable.clsoe if the
// interface is supported, otherwise use XComponent.dispose
val closeable = UnoRuntime.queryInterface(classOf[XCloseable], doc)
if (closeable != null) {
closeable.close(false)
} else {
val component = UnoRuntime.queryInterface(classOf[XComponent], doc)
component.dispose()
}
} val inputFilePath = "D:\\convert\\input.txt"
val outputFilePath = "D:\\convert\\output.pdf" // Getting the given type to convert to
val convertType = "writer_pdf_Export" val context = createContext()
println("connected to a running office ...") val loader = createComponentLoader(context)
println("loader created ...") val doc = loadDocument(loader, inputFilePath)
println("document loaded ...") convertDocument(doc, outputFilePath, convertType)
println("document converted ...") closeDocument(doc)
println("document closed ...")
}

很显然,这里不是我所厌恶的Java语言。这是一段Scala代码,就从最基本的代码使用上看,Scala也已经比Java代码要节省许多了。

总结

其实解决这个问题还是走了不少弯路的,究其原因可能是从示例代码出发去寻找解决方案,而并没有去系统地阅读各种资料。在这个过程中,我找到了一些比较重要的文档:

当然,最详细文档莫过于完整的开发人员指南了,如果您想要详细了解这方面的内容,这应该也属于必读内容之一。

有了OpenOffice.org,就相当于我们拥有了一套完整的文档操作类库,可以用来实现各种功能。除了转PDF以外,例如我们还可以将一篇数 百万字的小说加载为文档,再每十页导出一份图片,方便用户在线预览顺便防点拷贝。此外,虽然我是在Windows下操作OOo,但是OOo和Java本身 都是跨平台的,因此同样的代码也可以运行在Linux平台上。我目前正在尝试在Ubuntu Server上部署一份OOo和代码,如果有什么特别的情况,我也会另行记录。

事实上有一点我之前一直没有提到:如果您使用Windows及.NET进行开发,OOo也提供了C++/CLI接口,可以使用C#、F#进行编程, 且代码与本文描述的几乎如出一辙(只要把queryInterface方法调用改成直接转换),在.NET 4.0中也可正常使用。

如果您有其他解决方案,也请一起交流一下。

使用OpenOffice.org将各类文档转为PDF的更多相关文章

  1. 在linux中使用php将word文档转为pdf

    使用本教程需要在linux中安装openoffice,改页面中有详细的安装与使用教程(http://www.cnblogs.com/sustudy/p/3999628.html). 既然,你看了该教程 ...

  2. Java实现web在线预览office文档与pdf文档实例

    https://yq.aliyun.com/ziliao/1768?spm=5176.8246799.blogcont.24.1PxYoX 摘要: 本文讲的是Java实现web在线预览office文档 ...

  3. Java环境中,word文档转PDF直接打开浏览而非下载

    在平台上,需要把文档直接浏览而非下载,实现方法是先把文档转为PDF文件, 但在linux系统中确实汉字字库,所以转换失败,以下是解决方法 后面正式服务器也要添加字库,不然会转换出乱码文件,处理步骤如下 ...

  4. 使用OpenOffice实现各种文档转pdf或者html文档

    ---恢复内容开始--- 最近在做项目时需要写一个功能,将doc,ppt,xsl等文档做在线预览.网上查了很多资料,开始适用poi将文档转成pdf没成功,后来使用了OpenOffice4 + jodc ...

  5. Word文档转为MD

    最近整理近年的一些知识笔记,需要将一些之前用word写好的文档转为markdown格式,主要的方法是先将word转换为html格式,再将html转换为markdown格式. Step1. Word t ...

  6. 【技术博客】利用Python将markdown文档转为html文档

    利用Python将markdown文档转为html文档 v1.0 作者:FZK 元素简单的md文件 Python中自带有一个markdown库,你可以直接这样使用 md_file = open(&qu ...

  7. Java实现office文档与pdf文档的在线预览功能

    最近项目有个需求要java实现office文档与pdf文档的在线预览功能,刚刚接到的时候就觉得有点难,以自己的水平难以在三四天做完.压力略大.后面查找百度资料.以及在同事与网友的帮助下,四天多把它做完 ...

  8. office文档转pdf

    这里贴下代码吧,没啥好说的. using System; using System.Collections.Generic; using System.Linq; using System.Text; ...

  9. Java中几种office文档转pdf的方式

    最近公司要做office的文档,搜集了几种office文档转pdf的方式,简单的做下总结 我主要尝试了三种方式:openoffice,aspose,jacob 对他们进行了大文件,小文件,在linux ...

随机推荐

  1. Hadoop hostname: Unknown host

    本来下想在一台虚拟机上,搭建一个hadoop的测试hadoop,用于调试和阅读hadoop源代码,发现在虚拟机上执行: $hostname -i hostname: Unknown host 这个是因 ...

  2. ubuntu for win10 里运行net core

    花了点时间在ubuntu for win10里运行net core 按官网上ubuntun10.14装的net core指令 ...... ...... sudo apt-get install do ...

  3. 【Unity】第7章 输入控制

    分类:Unity.C#.VS2015 创建日期:2016-04-21 一.简介 Unity提供了-个非常易用和强大的用于处理输入信息的类:Input,利用该类可以处理鼠标.键盘.摇杆/方向盘/手柄等游 ...

  4. 《Android源代码设计模式解析》读书笔记——Android中你应该知道的设计模式

    断断续续的,<Android源代码设计模式解析>也看了一遍.书中提到了非常多的设计模式.可是有部分在开发中见到的几率非常小,所以掌握不了也没有太大影响. 我认为这本书的最大价值有两点,一个 ...

  5. 菜鸟学SSH(一)——Struts实现简单登录(附源码)

    从今天开始,一起跟各位聊聊java的三大框架——SSH.先从Struts开始说起,Struts对MVC进行了很好的封装,使用Struts的目的是为了帮助我们减少在运用MVC设计模型来开发Web应用的时 ...

  6. linux系统查毒软件ClamAV

    安装方法: 长久使用参考: http://www.cnblogs.com/kerrycode/archive/2015/08/24/4754820.html#undefined 临时使用参考: htt ...

  7. (转)Maven学习-处理资源文件

    转自:http://www.cnblogs.com/now-fighting/p/4888343.html 在前面两篇文章中,我们学习了Maven的基本使用方式和Maven项目的标准目录结构.接下来, ...

  8. Vue2键盘事件

    这两天学习了Vue.js 感觉组件这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记,学习一下Vue键盘事件 键盘事件 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 ...

  9. 分析jvm线程堆栈

    目录 一.java线程状态 二.使用jstack生成进程dump文件 三.统计dump文件中处于不同状态的线程数量 四.举例分析不同状态的线程 1.分析BLOCKED (on object monit ...

  10. 移动app传统测试流程优化

    [本文出自天外归云的博客园] 概述 在传统的软件测试流程中,每一期需求从开发到上线都要经历从需求分析与评审.测试用例评审.开发.测试.发布的流程.其中测试包含了后台测试.前端web测试.客户端测试.后 ...