Hello World
Spiga

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

2010-05-27 12:37 by 老赵, 19075 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中也可正常使用。

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

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

85 条回复

  1. mikespoook
    219.137.51.*
    链接

    mikespoook 2010-05-27 12:49:23

    可能用 NetBeans 开发 OOo 的痛感比 Eclipse 会小一些……

  2. 老赵
    admin
    链接

    老赵 2010-05-27 13:11:04

    @mikespoook

    嗯?当时遇到的问题好像和IDE无关吧,我以前现在用的都是Ecplise,不知道NetBeans会有什么帮助呢?

  3. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-27 16:54:55

    MOSS的Document Conversions Frameworkout-of-box了doc(x) to html page功能,但没to pdf功能。需要如ASPOSE的3rd party,具体不详。

    另一个问题,与主题没太大关系,word,excel的object model(俗称二次开发接口)是不适合于大负载的server端的,OOXML(docx)的完美解决方案是Open XML SDK 2.0 for Microsoft Office,至于doc,这个,历史包袱是沉重的。

    推荐《Inside Microsoft Windows SharePoint Services 3.0》这本书(有电子版),特别是chater 7,看看m$的解题思路,但愿能对你有所启发。

  4. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-27 17:04:38

    也许根本没启发,因为终究要买ms的东西,老赵的“windows server真的不贵(大意)”这句话我印象深刻,我要是你的话,应该向老板大力布道:真的不贵

    该花不花的老板是没品位的老板:):):):):)

  5. 老赵
    admin
    链接

    老赵 2010-05-27 17:30:33

    @zzfff

    准确的说,我说的是“Windows Web Server真的不贵”,比如Windows Web Server 2008 R2。其他的,比如你说的这些是否涉及到Office和MOSS的授权,我就不知道了,你能不能提供更多一些资料?不过我估计如果只能转MS Office的东西,还是不如现在这个,毕竟OOo支持的格式更多。

    其实Aspose也不贵,虽然对我们来讲比Windows Server贵很多,不过其实需要的话也可以买,对公司来讲还是很值得的。

  6. 链接

    韦恩卑鄙 @waynebaby 2010-05-27 18:08:51

    Windows SharePoint Services 3.0 这个随便商用 MOSS就。。

  7. zzfff
    183.64.134.*
    链接

    zzfff 2010-05-27 18:16:37

    上面主要针对“文档库”跑题的,和格式转换关系不大。我基本不明白你们的需求(小需求——大需求,小计划——大计划,当前考量——长远考量...),而我又特别主观,再加上blog/bbs本来就是扯淡的场合(我以为)——so,跑跑跑~~~

    假设WSS/MOSS提供的Document Libraries/Content Management/Publishing Services/Workflow等等等等是高射炮,而你们现在只需要打蚊子的工具,且最终你们不过是打打麻雀,根本不会打飞机:),我也会像你这么做。

  8. 老赵
    admin
    链接

    老赵 2010-05-27 19:20:01

    @zzfff

    你跑的太厉害,越来越听不懂你的意思了。

  9. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-27 23:18:23

    说下格式转换。我认为有两种做法:

    1. 调用应用程序的object model(自动化接口)进行处理。这不适合于大负载的server-side,因为word,excel等是desktop application,天生不是为server-side准备的。具体原因我认为是word,excel等COM server生存在单线程套件(Single-Threaded Apartments)中,STA是并发的杀手,因为一切到STA中的调用都会被序列化处理(想象下windows窗口的message pump)。OOo我不清楚。

    2. 最好的做法,从souce document及target document的格式入手直接解决。要么自己做,要么用别人的,没太多说的。

    老赵,你不觉得格式转换不过是一个大计划的小插曲么?先要高屋建瓴,然后才细致入微,是不是?

  10. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-27 23:20:01

    单线程套 (哥们,灌水)

  11. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 00:06:51

    也就是,Word COM server是个单例(Singleton),并且加了锁,糟糕透了。

  12. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 02:38:12

    我不严谨,复习了下,Word COM server不是个单例的,对于每个CoCreateInstance,都会有一个Word COM server被创建。

    对于OOo,我觉得差不多。创建一个进程外COM服务器的开销是巨大的,可以用object pool方式。对于“追求编程之美”的人来说,始终觉得不是太爽。

  13. 老赵
    admin
    链接

    老赵 2010-05-28 09:17:17

    @zzfff

    格式转换本来就是CPU密集型,instance本就不会有多少的。

    没懂什么叫做不太爽,所谓“追求”就是创造的意思,不是等美妙的东西出来才去用,所以最爽的应该就是把丑陋的东西变漂亮。

  14. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 11:00:43

    把Word,Excel,OOo用在服务端一开始就是“错误”的

    例如我们还可以将一篇数百万字的小说加载为文档,再每十页导出一份图片

    假设是docx文档,Open XML SDK 2.0 for Microsoft Office难道不能做到吗?直接操作XML难道不比操作以一大堆笨重臃肿的COM对象更优雅吗?

  15. 老赵
    admin
    链接

    老赵 2010-05-28 13:22:40

    @zzfff

    假设就只要解析这一种格式或Office 07的格式,且你愿意花时间去理解这一份或多份Spec,那么自然可能更方便。但如果连Office 97的格式也不支持,那也真不够意思了。而且,我实在没觉得现在操作OOo有什么笨重的,虽然接口不够美观,但也至少比COM要强多了吧。

    还有我倒真不觉得在服务器端用MS Office或OOo有什么错误的,只要能漂亮的完成功能,而且没有授权问题,而且性能等各方面达标,错在什么地方?

  16. dreampuf
    110.53.208.*
    链接

    dreampuf 2010-05-28 15:21:37

    Aspose破解的路过. 不过商业程序真的很方便,doc2html包括格式设置以及导出图片一共刚才几行导入一个dll即可.

  17. chenyi1976
    203.206.180.*
    链接

    chenyi1976 2010-05-28 16:14:02

    Python之将Doc转化为PDF:http://bbs.chinaunix.net/archiver/tid-1646576.html

    你要比代码,那就和脚本语言比,Python/Ruby/Perl之类是无敌的。

  18. chenyi1976
    203.206.180.*
    链接

    chenyi1976 2010-05-28 16:20:09

    另一个办法是,使用Sikuli来做文档的转换。。。这玩意也是无敌的。几乎不用懂代码,阅读API文档可以做到任何事情。。。当然前提是你得把环境配好(广义的)

    http://sikuli.org

  19. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 17:09:07

    @老赵

    OOo和MSO比速度,这个,老许也许会含笑不语。OOo是C/C++写的吧,它的object model是怎么构造的呢?类似COM的东西?(我没精力去研究OOo)。用VSTO操作Word不是一样看上去很美么?根本看不到丑陋的COM的东东,但COM就在下面。OK,不谈信仰,有时这很SB,我们姑且脱离下现实,谈纯之又纯的程序设计。

    如果我们从“文档库”的角度看问题。docx是什么?——遵从标准(Schema)的XML文件。文档库,可以看作(广义的)数据库,不是任意什么文档都可以往里面塞的,得遵守Schema,传统数据库有严格且简单的schema,文档库有说简单也简单说复杂也复杂的schema——Open XML。把文档库安上的门槛,虽然入库不是那么方便随意,但好处是不言而喻的——操作遵守schema的XML有什么好处?:):)

  20. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 17:20:52

    如果把Open XML文档库看作核心的核心,convert to pdf,to picture这些就是些外围功能。

    我一开始就这么看问题的,所以一开始就跑题:)

  21. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 17:33:02

    “结构化”这个东东挺神奇的——我看《Inside WSS 3.0》chapter 7的深刻体会。

  22. 老赵
    admin
    链接

    老赵 2010-05-28 19:03:40

    @zzfff

    操作OOo就是普通的Java类库或代码,虽然API设计不佳,但还是比COM Interop要好不少的。总之我真的没听懂你的意思,你说了docx的很多好处,我似懂非懂,但是即便真的它有你说的那么好,但它还是不满足我的要求,我现在就至少还要支持Office 97的格式,不是吗?

  23. 老赵
    admin
    链接

    老赵 2010-05-28 19:12:44

    @chenyi1976

    Sikuli肯定不行,基于UI的,这个对环境的要求实在太高。你跑个Server上的自动工具用Sikuli,出了问题想解决都不容易。

    还有,Scala也是可以当脚本写的,它的语法简洁紧凑程度不差Python。你说比动态性啊,元编程之类的倒罢了,怎么每个地方都以为Python,Ruby无敌啊?静态语言不等于低生产力,不要以为每个静态语言都像Java那么啰嗦和恶心。Java生产力低这是没错,但以为只有脚本语言才高才紧凑,就是错误的说法了。

    就拿你给的Python代码来讲,我可以一行一行对应着转化为几乎一样的Scala代码,你信不信?再反过来说,OOo的API放在这里,我现在用Scala写的代码在文章里,你能用Python写出更省略的代码吗?

    随意走极端,实在没意思。

  24. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 20:59:51

    @老赵

    现在我只有C/C++的client,要自动化操作OOo,how?

  25. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 21:06:46

    我现在就至少还要支持Office 97的格式

    把OpenXML文档库看成一个hub、中枢。doc格式要入库的话,先转换(不管是手工转还是机器转)。

    OpenXML == 结构化数据,我说话一向比较煽情:),这是个见yin见智的问题,看具体需求了。

  26. 老赵
    admin
    链接

    老赵 2010-05-28 21:24:02

    @老赵: 现在我只有C/C++的client,要自动化操作OOo,how?

    OOo官方支持Java和C++的,也通过C++/CLI支持.NET上的C#等语言,接口几乎完全一致。

  27. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-28 23:45:33

    上面吹得比较玄,说个实际的问题:每10页将1页快照成图片。这是个有点变态但又实际的需求。我很怀疑OOo或Word的API提供了这个功能,万一它们没这个功能,只有傻眼。而Open XML SDK,本质就是字处理、电子表格的核心引擎,要玩熟需要不少时间。问题的关键是:OOo或Word的API的功能没Open XML SDK强大。

  28. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 00:12:55

    初略看了下Universal Network Objects,应该和COM是一路货色。

    对嘛,UNO,COM都是原生代码世界的组件模型(姑且认为UNO和COM一样牛逼),OOo的核心是原生代码,Java要与原生代码交互只有通过JNI,凭什么说JNI就好,COM Interop就不那么好呢?

  29. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 00:17:25

    老赵,我没说你的做法是错误的,我只是觉得,从自上谈兵的角度看,你的做法不是上策。

    人是感性动物,很容易被情绪左右。

  30. 老赵
    admin
    链接

    老赵 2010-05-29 01:18:54

    @zzfff

    关于这方面我再多说一些吧。

    Java操作OOo时并非使用JNI,这点C++或是C++/CLI也都一样,是将OOo作为一个Service开启,Client通过Socket与OOo通信,Client和OOo完全可以在不同的机器上。在服务器环境上使用时,可以开启多个OOo实例。格式转化是IO密集型操作,因此也只要与开启处理器数量相等的实例就够了。例如8个核就开启8个实例,可以同时转化8个文档,8个OOo实例占用不了多少资源。

    我还没有完整了解OOo的全部功能,但是从我对文档的粗略浏览下来看,对于文档的操作,比如创建、修改、打印、导出都是完全没有问题的,所以我觉得您所担心的“万一做不到”是大可不必的。至于它和Open XML SDK哪个功能强大,我觉得您不应该那么快的下结论。当然,我很乐意看到您来作一些详细的比较。

    我的做法的确不一定是上策,但事实上我也不知道有什么更好的做法,您不断强调的Open XML SDK更是无法满足我的要求。人是感性动物,但是从您的回复中我觉得您过于情绪化了,想法也过于发散和丰富。从您那里我得到了一些关于Open XML的信息或是所谓“理念”,我很欣赏,但是我并没有得到更好的解决办法。在这个问题上我欢迎您来讨论,但我目前无法同意您的看法。

    此外,我没有说过JNI,UNO或是COM Interop从技术角度说哪种更好或是不好,我一直是从API的设计,例如是否好用的角度来说的,您可以再去看一下我的说法。就我的使用体会,操作OOo比操作MS Office要简单的多。当然我没有去关注Open XML SDK,因为它实在无法满足我的要求。

    PS: 建议您以后可以先登录再回复,这样可以修改回复内容,不必每次补充都发新的回复,呵呵。

  31. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 01:54:26

    操作OOo比操作MS Office要简单的多

    VSTO也很简单啊,我觉得VSTO更简单

    每十页导出一份图片

    或者说“每10页将1页快照成图片”,Open XML SDK做起来好像也很难。走你的思路,大概只能用打印命令了,难道要自己实现个虚拟打印机?

  32. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 02:01:31

    word提供了save as pdf功能,才能用,但这无关“操作OOo比操作MS Office要简单的多”宏旨。罢了,挺无趣的。

  33. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 02:11:45

    自我检讨。老赵你是在做实事,我是在天马行空,并且我有点胡搅蛮缠了,对不住哈。

  34. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 02:26:11

    Aspose的方案就是从源及目标文件格式直接解决的,我觉得你可以就两种方案进行下负载测试。

  35. 老赵
    admin
    链接

    老赵 2010-05-29 02:26:28

    @zzfff

    VSTO可能是很简单吧,但是我不知道格式支持的如何,OOo的格式支持是很强的……还有便是我文章一开始说的,会不会有Licence问题?我还是不太清楚在服务器端用MS Office的功能在授权上该怎么搞,其实我一开始也是用Office的COM Interop的,但最终还是选择了OOo。

    当然,有一点几乎可以确定的是,对于MS Office的格式,MS Office本身肯定读取的更好,而OOo说不定会有点问题,虽然现在还没遇到。而且,MS Office其实是最最常见的,所以其实我也不排除以后是MS Office(或其他商业方案)再配合OOo转其他格式,呵呵。

    说起Aspose,我相信它的能力,毕竟是商业产品,而且许多人推荐。

  36. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 11:45:16

    纯粹服从于现实,现实最大。老赵,虽然我不完全明白需求,现在我觉得你的做法是最现实的。

    富士康的人为什么那么喜欢跳呢?因为他们太服从于逼仄的现实,自杀是对现实终极的反叛;而我又太愤世嫉俗、毫不妥协。

  37. 老赵
    admin
    链接

    老赵 2010-05-29 13:06:02

    @zzfff

    我不知道什么算是现实,我只知道我这个还是很追求完美的,属于很理想的人。我只是想尽量踏实一些,“现实”和“踏实”在我看来不是一回事情……虽然我还不够踏实,努力吧。

  38. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 17:14:45

    做这些,对我来说,也许2天就没劲了,因为到最后,不管自我感觉有多么良好,在老板眼里你不过是个可有可无的螺丝钉——这是做项目的宿命。也许说得太极端,中国的技术人员或多或少是这样。中国无技术。

    还是设计语言、实现编译器来劲,我是好高骛远、眼高手低的典型:)

  39. 老赵
    admin
    链接

    老赵 2010-05-29 21:56:58

    @zzfff

    嗯,你的确说的太极端了,说中国无技术的,在我看来就是很扯蛋的说法,具体展开说太花时间就懒得谈了。

    关于现在这些东西,随便想想当然容易,要做好还真不容易,比如你如果要做出个好用的Aspose系列产品,并且支持各种常见的格式,几个月都不够用。真要说起来,设计语言,写编译器又有什么困难的?大学里人人写过。但问题同样是,要搞的好就不容易。

    要搞好的话,做什么都不容易。

  40. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 22:17:59

    格式转换是不是CPU密集型还不好说,如果能流式增量转换(读多少源数据,就能写多少目标数据),格式转换基本上是IO密集的。

    因为我们不熟悉docx及pdf的格式,那就举XML转XML的例子:将attribute转换为child element(不知我说清楚没有?)。用XmlReader,XmlWriter而不是XmlDocument无疑。但XmlReader,XmlWriter居然没实现.NET APM模式(BeginRead,EndRead...),我有点不解,还得深入分析。

    即便用Aspose的方案,也应该仔细检查其源代码,看是否能实现最大化并发,若否,那就自己实现。

    BTW:追求完美是高代价的:)

  41. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 22:26:30

    哈哈,老赵,我们想到一块去了,我没看见你上条发言就写了条反省发言。

  42. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-29 22:29:52

    问题是:现实只给了“你”做项目的空间,“你”却完美得想做产品。

  43. 链接

    facingwaller 2010-05-30 09:03:32

    赵哥。博客上的搜索不能用啊,谷歌和IE8都不行啊

  44. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-30 11:08:09

    既然老赵“要求”"Why Java Sucks & C# Rocks”的读者能“类型推导”,我为什么就不能“要求”读者能“上下文推导、情绪压抑”呢?:)

    我说“中国无技术”,联系上下文,明显指的是因为现实的逼仄,中国的技术人员无法发挥出自己的潜能。我说“也许2天就没劲了”,因为在中国基本上是做项目(即便号称是做产品),在中国做项目的潜台词是什么?——凑合。我当然对凑合的东西没劲,而不是对格式转换、Open XML、并发、server-side这些技术没劲(尽管语言、编译器更带劲)。老赵若你觉得现实很美好,你该感到幸运。

    呵呵,在狡辩了,大家共勉吧。

    努力,奋斗!

  45. 老赵
    admin
    链接

    老赵 2010-05-30 12:54:19

    @zzfff

    我也不喜欢凑合,但我觉得你的“不喜欢凑合”已经做的有些过了。

    现实是有问题,但是还有一个关键是个人,个人有没有问题?我觉得你想的实在太多,而且过于理想化,我已经很理想化了,只是你比我理想化的太多。我不知道你有没有尝试着去实现,如果你觉得你的想法是正确的,是趋势,是美好,完全可以自己去设法做一下,而不说单单现实如何如何。我们没法让“中国技术人员”发挥出潜能,但是如果只是你个人的话,想要发挥出潜能并非完全不可能。

    好比说,中国人普遍穷,但是并非你个人无法赚到能够自在生活的钱。不过,如果说你一定要成为亿万富翁,否则就认为是恶劣的现实的逼仄,我就会是说你想法不对,脱离现实。在这个情况下,我觉得你不应该怪罪现实的恶劣,而是理想的虚无所造成的问题。这是心态问题不是现实问题。

    “现实”和“理想”都是中性词,既可能是贬,也肯能是褒,就看你怎么对待了。

  46. 老赵
    admin
    链接

    老赵 2010-05-30 14:08:17

    @facingwaller

    还没做呢,懒啊,有机会做上。

  47. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-30 15:16:05

    我又没从陈天桥那里领工资,所以在老赵的blog里,我只是个多嘴且“愤怒”的观棋者。

    我当然在下自己的棋,时机成熟时自会请老赵来观棋。很可能我自己走成了死棋,但至少我努力去下过棋。

  48. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-30 15:29:09

    现实世界并没有“项目”——“产品”这样的非黑及白的划分,一切都是相互妥协的结果。还是说实际的吧,我坚持我的观点:格式转换是IO密集的,desktop-application放在server-side是凑合。至于多“凑合”或多“完美”,这个,这个......

  49. 老赵
    admin
    链接

    老赵 2010-05-30 15:50:38

    @zzfff

    是IO还是CPU密集型,你做过实验吗?我无论使用OOo或是Aspose的类库进行实验,发现转PDF是CPU密集型操作。转一个几百K的文档都需要用“秒”级别的时间,无论是网络(千兆网)还是硬盘的IO速度都远超这点。

    当然有一点我同意:使用OOo进行转化“相对于”直接使用类库的确是种妥协。不过,我同样认为这个做法并没有太大的问题,目前也很难找到更好的做法。

  50. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-30 15:58:45

    我又没从陈天桥那里领工资,我为什么要做实验?你又剖析过Aspose的源码,研读过OpenXML,PDF的specs吗?我只是提出我的疑问,至于你感觉是否良好那是你的事了。

  51. 老赵
    admin
    链接

    老赵 2010-05-30 15:59:38

    @zzfff: 我又没从陈天桥那里领工资,所以在老赵的blog里,我只是个多嘴且“愤怒”的观棋者。

    听不懂你的意思,这和领谁的工资又有什么关系?难道说,如果你领陈天桥的工资,就会换种所说法了吗?不过我可以保证,我说的话和领谁的工资没有任何关系,不关领谁的工资我说的话都代表了我当下的真实想法。

  52. 老赵
    admin
    链接

    老赵 2010-05-30 16:03:28

    @zzfff: 我又没从陈天桥那里领工资,我为什么要做实验?你又剖析过Aspose的源码,研读过OpenXML,PDF的specs吗?我只是提出我的疑问,至于你感觉是否良好那是你的事了。

    您提出疑问,虽然我没有剖析过源码,但我实验过了,也观察结果了,这些都是技术上的探讨,所以我也希望您用实验或是其他正当的方式来讨论问题,这和领谁的工资有什么关系?实在是莫名其妙。

    您如果用这种方式来讨论问题,那么我也就不再奉陪下去了。

  53. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-30 18:33:10

    这只是观察结果,但你并没有说明为什么是CPU密集的。Aspose的东西也是可以怀疑的。

    我的看法是:格式转换是也应该是流式的。比如docx->html,为简化问题,把docx看作一巨型的XML文件,里面有如paragraph、run这样的标签,以及纯文本数据、base64编码的图片等等。那么转换可能是:

    1. 仅更换标签名,如paragraph->p
    2. 转换结构,如attribute转换成child element,拆分、合并结构等
    3. 更多的是copy as is,比如纯文本数据、图片

    我实现想不出来这些操作要用多少CPU,它们需要大量的IO。

    最极端的,想象成《Windows via C/C++》里的FileCopy 例子。

  54. 老赵
    admin
    链接

    老赵 2010-05-30 20:42:41

    @zzfff

    我说的是转PDF。我当然也知道文件复制,或是某些比如文本到HTML是不需要什么CPU的,但是谈这些极端的例子又有什么意义呢?你要举这样的例子,那么我是不是可以不做任何实验,直接找一个视频音频编码或是文件压缩作例子,就可以证明格式转化是CPU密集型应用了呢?

    现在我也不知道你是不是读过PDF的spec了,你似乎也只是猜想,而我至少还有观察和实验。我观察到的现象是:

    1. 转化过程用足(单个)CPU(核)。
    2. 转化的耗时远远超过IO传输所需要的时间,比如几百K的doc或ppt之类的,都要消耗“秒”级别的时间。
    3. 无论是OOo,MS Office还是Aspose转化成PDF,结果都是差不多的。

    我不知道我的实验和观察哪里有问题。当然,您可以怀疑OOo,MS Office和Aspose都实现的有问题,但我想还是多给些充分的理由比较妥当。

  55. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-30 20:57:13

    IO-bound和实际数据的大小有什么关系?想必老赵你也研究过IOCP,算了,自我感觉好就好。

  56. 老赵
    admin
    链接

    老赵 2010-05-31 09:26:48

    @zzfff

    IO-bound和实际数据大小没有关系,但是和处理这些数据的瓶颈是CPU还是IO有关系。

    你只能说,IO密集型应用可以利用IOCP(来降低开销),但并不是说(能够)利用IOCP它就是IO密集型应用了。区分IO密集还是CPU密集看的是,处理一定尺寸的数据,主要依赖的是CPU的能力还是IO的能力。例如复制文件是IO密集型操作,不需要多少CPU。但是视频编码,它只需要不多的IO,因为往往在IO上去之前,CPU早就用完了。这种操作很多,包括数据压缩等等。

    转PDF当然也可以用到流式的转化或是IOCP等等,但它还是一个CPU密集型操作。我说几百K,就是指IO瞬间能处理完的大小,却要花较长时间才能让CPU完成工作,此时要用到大量CPU,你说它是什么类型的操作?否则按照你的标准,又有什么操作算是CPU密集型的?包括视频编码,文件压缩,反正都是流式的,都要用到IO,有IO就能用到IOCP,照你说来就变成IO密集型应用了。

  57. zzfff
    113.141.29.*
    链接

    zzfff 2010-05-31 11:47:38

    说到底,最终的问题是:没有足够的资源去搞清楚为什么(Aspose)转PDF是CPU密集的,现在一切都是实验的观察结果。

    凑合着过日子吧,我不过是在纸上谈兵,假设我是你老大,我也不会给你6个月以上的时间去读PDF spec、剖析Aspose的源码、自己实现。硬件灰常便宜:)

  58. 链接

    陈梓瀚(vczh) 2010-05-31 18:47:00

    安装个PDF虚拟打印机就行了,什么软件都没关系

  59. 老赵
    admin
    链接

    老赵 2010-05-31 20:43:30

    @陈梓瀚(vczh)

    光有PDF打印机的话,又用什么来打开doc等文档呢?

  60. shuhen
    222.173.95.*
    链接

    shuhen 2010-06-01 10:37:28

    感觉zzfff说的稀里糊涂的,难得老赵能慢条斯理的分析

  61. oldrev
    221.213.44.*
    链接

    oldrev 2010-06-01 13:52:02

    OOo 可以“无头”运行通过 Socket 提供文档转换服务的

  62. 老赵
    admin
    链接

    老赵 2010-06-01 14:03:57

    @oldrev

    这就是我这篇文章里写的方法。

  63. min
    61.174.24.*
    链接

    min 2010-06-02 14:09:16

    我偷懒,直接用wps转换,开着wps进程效率低的可以... 要是金山公开他们的pdf那个api,想来是方便许多

  64. 老郑
    219.232.52.*
    链接

    老郑 2010-06-09 13:09:00

    老赵,看完你的文章感觉很舒服,主要是我也要完成这个功能。 我使用java,按照你的做法,我能过将doc文档转换成pdf文档,不过有个问题,提示“document closed ...”之后,程序并不能退出,通过office打开刚刚进行转换的doc文档,会提示doc文档被锁定。你遇到这样的问题吗?

  65. acylas
    221.224.29.*
    链接

    acylas 2010-07-30 16:48:44

    既然用openoffice,其实没必要自己用sdk写转换的程序。用JODConverter直接调用命令执行更方便,如果不是为了研究, 时间还是花在更有意义的地方比较好。

  66. 老赵
    admin
    链接

    老赵 2010-08-01 16:36:13

    @acylas

    这个还是很有用的,比如文末写的,而且我不还可以写文章嘛,哈哈。

  67. 老赵
    admin
    链接

    老赵 2010-08-01 16:36:36

    @老郑

    没遇到过……

  68. xijieqjx
    123.121.67.*
    链接

    xijieqjx 2010-09-09 22:53:06

    公司倒是有这个需求,openoffice一遇到复杂格式就挂了,最后用java调用本地office+pdf虚拟打印机勉强应付,但这个方案并不实用,格式是没问题了可是要调用本地命令用java还是很受限。

  69. robin
    116.232.79.*
    链接

    robin 2010-09-11 01:08:03

    这个东西压根就不会用到大并发上

  70. 老赵
    admin
    链接

    老赵 2010-09-11 15:11:14

    @robin

    那么大并发应该用什么呢?

  71. luan
    210.22.154.*
    链接

    luan 2010-09-16 16:59:24

    请问 你在Ubuntu server 下运行了吗,有没有乱码的情况

  72. kimmking
    58.33.116.*
    链接

    kimmking 2010-10-09 21:19:25

    大并发的,还是特殊问题特殊处理吧。

    转rtf,pdf,用itext之类的pdf 处理word/excel, poi/jxl(不是很完备) html的话(parse,然后转很麻烦),也有专门的转图片存pdf的组件

    少量的office,感觉可以用com方式或者vsto,或者vba,或者swt引入oleclient,blahbla,方式很多。

    pdf虚拟打印机,这个需要通过调用处理程序打开,然后调用print接口。也麻烦。

  73. 链接

    边宏源 2010-11-09 10:50:15

    你好,我非常幸运看到了你的这篇文章。我正要实现word转换pdf。 我如果用OpenOffice实现的话,必须装OpenOffice么?还是装一个插件?我不是很理解,您能指点我一下么。谢谢了

  74. 你好
    61.154.164.*
    链接

    你好 2011-01-10 17:00:56

    openoffice 文件名带有%,#转换不了

  75. Daniel Chow
    113.97.201.*
    链接

    Daniel Chow 2011-01-11 13:14:29

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

    老赵有C#版的import ooo.connector.BootstrapSocketConnector;吗? 不然跑不了啊!!!!

  76. 老赵
    admin
    链接

    老赵 2011-01-11 13:23:15

    @Daniel Chow

    好像SDK里就有啊,我用过的。

  77. 49694445
    183.37.229.*
    链接

    49694445 2011-01-11 15:09:46

    老赵你好, 我用在项目有问题如下: 在你代码中 close 那块,有 system.exit ,,而项目中肯定不能这样写,,而不写,,程序没有关闭,,而用在项目中具体表现是 , 正常启动后 ,使用正常,,如果我 重启tomcat ,, 则抛 java.net.BindException: Address already in use: bind. 然后 必须结束进程 soffice.exe 和 sofiice.bin 才能重启tomacat了,, 另外我把这段代码 放到线程中,,

  78. sacco
    123.126.50.*
    链接

    sacco 2011-08-06 15:01:13

    您好!为什么我将您的java代码放在一个java application中运行良好,却当我把它部署到Tomcat中调用就会报错呢?

  79. crepzl
    218.28.35.*
    链接

    crepzl 2011-08-08 18:06:04

    文章写得很好,结构清晰,一目了然。有个问题请教:如何实现PDF中的数字水印功能呢,如了解,请不吝赐教!crepzl@126.com

  80. nono_xh
    125.77.255.*
    链接

    nono_xh 2011-12-23 15:49:59

    老赵,你好。我最近在用openoffice做文件转换时,一次性处理1000个文件时,soffice.bin进程占用内存不断增加,最后进程就被停掉,差不多一次性只能转换130个文件。想请教是怎么回事?

  81. Eric
    110.185.146.*
    链接

    Eric 2012-01-19 14:58:04

    老赵,你好, 用openofice把文档转换成PDF格式我是勉强弄明白了,我同事也还学会了怎样把一个barcode加入Excel或Word。这对我更好的了解Microsoft都有很大的帮助,非常感谢!

  82. 鬼眼狂刀
    114.249.215.*
    链接

    鬼眼狂刀 2012-04-16 17:28:07

    我使用.net已经实现pdf的转换,但是一旦部署到IIS上,就会挂掉!出现问题的代码,在Bootstrap.bootstrap(),这个地方就死掉了 在国外网站也搜索了很久,但是没有找到合适的解决办法,请指教

  83. Levi
    64.104.125.*
    链接

    Levi 2012-07-25 10:40:33

    找文件,everything这个软件才是王道啊。

  84. rdongxie
    218.94.82.*
    链接

    rdongxie 2012-08-08 14:42:05

    @老赵

    您在ubuntu上部署成功了吗?我在windows上部署可以了,但是在linux上面还是抛 com.sun.star.comp.helper.BootstrapException at ooo.connector.BootstrapConnector.connect(BootstrapConnector.java:129)

  85. henry
    218.17.162.*
    链接

    henry 2013-06-14 15:38:14

    使用OOoSDK导出PDF也要依赖于OOo的主程序么?可不可以作为独立的程序存在

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我