LibreOffice

LibreOfficeでPDF文書を自動生成

More than 1 year has passed since last update.

後からわかったこと

javaを使ったシステムの修復をしていましたが、こんなことしなくても今では

$ soffice --headless --convert-to pdf *.ods

とするだけでOKだったです。
やれやれ。

以下ムダな努力

10年以上前に、伝票をWebから自動生成したシステムを作っていた。
サーバを引っ越した時にこの仕組みは動かなくなっていたので修復。

ローカルのPCで動かす

まずはローカルのPCで動くところまで。
これがうまくいったら、Webシステムで動作するところまでやる予定。

過去のWebシステムとの違い

過去のWebシステムは2006年から稼働。
X-WindowとOpenOffice.orgを動作させていた。
なのだけれど基本sshなどでの操作前提のサーバだったので、OpenOffice.orgは
DISPLAY変数を指定してスクリプトで起動していました。

ちなみに、X-Windowのスクリーンセーバでリソース食われたりしてパフォーマンス低下したのもいい思い出です。

  • Ubuntu6ぐらい
  • OpenOffice.org

さて、現在はUbuntuは16ぐらい。OpenOffice.orgはLibreOfficeとして動かしたい。
今回動作させるローカルのPCにはリアルなX-Windowが動いているのでLibreOfficeを動かすのには問題がありまんが、この後動かすWebシステムは、リアルなX-windowを動かさない方法にする予定です。

javaのプログラムをサルベージ

昔作ったクラス。
PDFConverter.class

これはOpenOfficeで作ったodsファイルをPDFに変換して保存するもの。

odsファイルは、あらかじめ雛形を作っておき、XMLをコンテンツに応じて書き換えて生成する。

javaソースを復元

ソースが無かったので復元。
http://qiita.com/nanbuwks/items/76151b888dc8ca537228
「Java をディスコンパイル」

import com.sun.star.beans.PropertyValue;
import com.sun.star.comp.helper.Bootstrap;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XStorable;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.util.XCloseable;
import java.io.File;

public class PDFConverter
{
  public static void main(String[] paramArrayOfString)
  {
    try
    {
      String str1 = convertToURL(paramArrayOfString[0]);
      String str2 = getFilterName(paramArrayOfString[0]);
      String str3 = str1.replaceAll("\\..{3}\\Z", ".pdf");

      XComponentContext localXComponentContext = Bootstrap.bootstrap();
      XMultiComponentFactory localXMultiComponentFactory = localXComponentContext.getServiceManager();

      Object localObject1 = localXMultiComponentFactory.createInstanceWithContext("com.sun.star.frame.Desktop", localXComponentContext);

      XComponentLoader localXComponentLoader = (XComponentLoader)UnoRuntime.queryInterface(XComponentLoader.class, localObject1);

      PropertyValue[] arrayOfPropertyValue = new PropertyValue[2];

      arrayOfPropertyValue[0] = new PropertyValue();
      arrayOfPropertyValue[0].Name = "Hidden";
      arrayOfPropertyValue[0].Value = new Boolean(true);
      arrayOfPropertyValue[1] = new PropertyValue();
      arrayOfPropertyValue[1].Name = "ReadOnly";
      arrayOfPropertyValue[1].Value = new Boolean(true);

      XComponent localXComponent1 = localXComponentLoader.loadComponentFromURL(str1, "_blank", 0, arrayOfPropertyValue);

      arrayOfPropertyValue[0] = new PropertyValue();
      arrayOfPropertyValue[0].Name = "FilterName";
      arrayOfPropertyValue[0].Value = str2;
      arrayOfPropertyValue[1] = new PropertyValue();
      arrayOfPropertyValue[1].Name = "Overwrite";
      arrayOfPropertyValue[1].Value = new Boolean(true);

      XStorable localXStorable = (XStorable)UnoRuntime.queryInterface(XStorable.class, localXComponent1);

      localXStorable.storeToURL(str3, arrayOfPropertyValue);

      XCloseable localXCloseable = (XCloseable)UnoRuntime.queryInterface(XCloseable.class, localXStorable);
      if (localXCloseable != null)
      {
        localXCloseable.close(false);
      }
      else
      {
        XComponent localXComponent2 = (XComponent)UnoRuntime.queryInterface(XComponent.class, localXStorable);

        localXComponent2.dispose();
      }
    }
    catch (Exception localException)
    {
      localException.printStackTrace();
      System.exit(1);
    }
    finally
    {
      System.exit(0);
    }
  }

  private static String convertToURL(String paramString)
    throws Exception
  {
    File localFile = new File(paramString);
    StringBuffer localStringBuffer = new StringBuffer("file:///");
    localStringBuffer.append(localFile.getCanonicalPath().replace('\\', '/'));
    return localStringBuffer.toString();
  }


確か、このプログラムはサンプルプログラムを元にしたもの。
サンプルプログラムは後に述べるlibreoffice-dev-docをインストールすると /usr/share/doc/libreoffice-dev-doc/examples/java/DocumentHandling にある。ライセンスはBSDライセンス。

$ cat DocumentConverter.java 
/*************************************************************************
 *
 *  The Contents of this file are made available subject to the terms of
 *  the BSD license.
 *
 *  Copyright 2000, 2010 Oracle and/or its affiliates.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of Sun Microsystems, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *************************************************************************/

import com.sun.star.uno.UnoRuntime;

import java.io.File;


/** The class <CODE>DocumentConverter</CODE> allows you to convert all documents
 * in a given directory and in its subdirectories to a given type. A converted
 * document will be created in the same directory as the origin document.
 *
 */
public class DocumentConverter {
    /** Containing the loaded documents
     */
    static com.sun.star.frame.XComponentLoader xCompLoader = null;
    /** Containing the given type to convert to
     */
    static String sConvertType = "";
    /** Containing the given extension
     */
    static String sExtension = "";
    /** Containing the current file or directory
     */
    static String sIndent = "";
    /** Containing the directory where the converted files are saved
     */
    static String sOutputDir = "";

    /** Traversing the given directory recursively and converting their files to
     * the favoured type if possible
     * @param fileDirectory Containing the directory
     */
    static void traverse( File fileDirectory ) {
        // Testing, if the file is a directory, and if so, it throws an exception
        if ( !fileDirectory.isDirectory() ) {
            throw new IllegalArgumentException(
                "not a directory: " + fileDirectory.getName()
                );
        }

        // Prepare Url for the output directory
        File outdir = new File(DocumentConverter.sOutputDir);
        String sOutUrl = "file:///" + outdir.getAbsolutePath().replace( '\\', '/' );

        System.out.println("\nThe converted documents will stored in \""
                           + outdir.getPath() + "!");

        System.out.println(sIndent + "[" + fileDirectory.getName() + "]");
        sIndent += "  ";

        // Getting all files and directories in the current directory
        File[] entries = fileDirectory.listFiles();


        // Iterating for each file and directory
        for ( int i = 0; i < entries.length; ++i ) {
            // Testing, if the entry in the list is a directory
            if ( entries[ i ].isDirectory() ) {
                // Recursive call for the new directory
                traverse( entries[ i ] );
            } else {
                // Converting the document to the favoured type
                try {
                    // Composing the URL by replacing all backslashs
                    String sUrl = "file:///"
                        + entries[ i ].getAbsolutePath().replace( '\\', '/' );

                    // Loading the wanted document
                    com.sun.star.beans.PropertyValue propertyValues[] =
                        new com.sun.star.beans.PropertyValue[1];
                    propertyValues[0] = new com.sun.star.beans.PropertyValue();
                    propertyValues[0].Name = "Hidden";
                    propertyValues[0].Value = Boolean.TRUE;

                    Object oDocToStore =
                        DocumentConverter.xCompLoader.loadComponentFromURL(
                            sUrl, "_blank", 0, propertyValues);

                    // Getting an object that will offer a simple way to store
                    // a document to a URL.
                    com.sun.star.frame.XStorable xStorable =
                        UnoRuntime.queryInterface(
                        com.sun.star.frame.XStorable.class, oDocToStore );

                    // Preparing properties for converting the document
                    propertyValues = new com.sun.star.beans.PropertyValue[2];
                    // Setting the flag for overwriting
                    propertyValues[0] = new com.sun.star.beans.PropertyValue();
                    propertyValues[0].Name = "Overwrite";
                    propertyValues[0].Value = Boolean.TRUE;
                    // Setting the filter name
                    propertyValues[1] = new com.sun.star.beans.PropertyValue();
                    propertyValues[1].Name = "FilterName";
                    propertyValues[1].Value = DocumentConverter.sConvertType;

                    // Appending the favoured extension to the origin document name
                    int index1 = sUrl.lastIndexOf('/');
                    int index2 = sUrl.lastIndexOf('.');
                    String sStoreUrl = sOutUrl + sUrl.substring(index1, index2 + 1)
                        + DocumentConverter.sExtension;

                    // Storing and converting the document
                    xStorable.storeAsURL(sStoreUrl, propertyValues);

                    // Closing the converted document. Use XCloseable.close if the
                    // interface is supported, otherwise use XComponent.dispose
                    com.sun.star.util.XCloseable xCloseable =
                        UnoRuntime.queryInterface(
                        com.sun.star.util.XCloseable.class, xStorable);

                    if ( xCloseable != null ) {
                        xCloseable.close(false);
                    } else {
                        com.sun.star.lang.XComponent xComp =
                            UnoRuntime.queryInterface(
                            com.sun.star.lang.XComponent.class, xStorable);

                        xComp.dispose();
                    }
                }
                catch( Exception e ) {
                    e.printStackTrace(System.err);
                }

                System.out.println(sIndent + entries[ i ].getName());
            }
        }

        sIndent = sIndent.substring(2);
    }

    /** Bootstrap UNO, getting the remote component context, getting a new instance
     * of the desktop (used interface XComponentLoader) and calling the
     * static method traverse
     * @param args The array of the type String contains the directory, in which
     *             all files should be converted, the favoured converting type
     *             and the wanted extension
     */
    public static void main( String args[] ) {
        if ( args.length < 3 ) {
            System.out.println("usage: java -jar DocumentConverter.jar " +
                "\"<directory to convert>\" \"<type to convert to>\" " +
                "\"<extension>\" \"<output_directory>\"");
            System.out.println("\ne.g.:");
            System.out.println("usage: java -jar DocumentConverter.jar " +
                "\"c:/myoffice\" \"swriter: MS Word 97\" \"doc\"");
            System.exit(1);
        }

        com.sun.star.uno.XComponentContext xContext = null;

        try {
            // get the remote office component context
            xContext = com.sun.star.comp.helper.Bootstrap.bootstrap();
            System.out.println("Connected to a running office ...");

            // get the remote office service manager
            com.sun.star.lang.XMultiComponentFactory xMCF =
                xContext.getServiceManager();

            Object oDesktop = xMCF.createInstanceWithContext(
                "com.sun.star.frame.Desktop", xContext);

            xCompLoader = UnoRuntime.queryInterface(com.sun.star.frame.XComponentLoader.class,
                                      oDesktop);

            // Getting the given starting directory
            File file = new File(args[0]);

            // Getting the given type to convert to
            sConvertType = args[1];

            // Getting the given extension that should be appended to the
            // origin document
            sExtension = args[2];

            // Getting the given type to convert to
            sOutputDir = args[3];

            // Starting the conversion of documents in the given directory
            // and subdirectories
            traverse(file);

            System.exit(0);
        } catch( Exception e ) {
            e.printStackTrace(System.err);
            System.exit(1);
        }
    }
}

環境を整える

Ubuntu Linux 16.04 において、

Java のインストール

今のJava環境、ちょっと複雑で良くわからない。動かそうとしたら
```

$ java
プログラム 'java' は以下のパッケージで見つかりました:
* default-jre
* gcj-5-jre-headless
* openjdk-8-jre-headless
* gcj-4.8-jre-headless
* gcj-4.9-jre-headless
* openjdk-9-jre-headless
次の操作を試してください: sudo apt install <選択したパッケージ>
```

うーん、default-jreをインストールしてみよう。

$ sudo apt-get install default-jre
[sudo] nanbuwks のパスワード: 
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  gyp libatk-wrapper-java libatk-wrapper-java-jni libbonobo2-0 libbonobo2-common libgnome-2-0 libgnome2-common
  libjs-inherits libjs-node-uuid liborbit-2-0 libssl-dev libssl-doc libuv1 libuv1-dev linux-headers-4.4.0-57
  linux-headers-4.4.0-57-generic linux-headers-4.4.0-59 linux-headers-4.4.0-59-generic linux-headers-4.4.0-62
  linux-headers-4.4.0-62-generic linux-headers-4.4.0-63 linux-headers-4.4.0-63-generic linux-headers-4.4.0-64
  linux-headers-4.4.0-64-generic linux-headers-4.4.0-66 linux-headers-4.4.0-66-generic linux-headers-4.4.0-71
  linux-headers-4.4.0-71-generic linux-headers-4.4.0-72 linux-headers-4.4.0-72-generic linux-headers-4.4.0-75
  linux-headers-4.4.0-75-generic linux-headers-4.4.0-78 linux-headers-4.4.0-78-generic linux-headers-4.4.0-79
  linux-headers-4.4.0-79-generic linux-headers-4.4.0-81 linux-headers-4.4.0-81-generic linux-headers-4.4.0-83
  linux-headers-4.4.0-83-generic linux-headers-4.4.0-87 linux-headers-4.4.0-87-generic linux-image-4.4.0-57-generic
  linux-image-4.4.0-59-generic linux-image-4.4.0-62-generic linux-image-4.4.0-63-generic linux-image-4.4.0-64-generic
  linux-image-4.4.0-66-generic linux-image-4.4.0-71-generic linux-image-4.4.0-72-generic linux-image-4.4.0-75-generic
  linux-image-4.4.0-78-generic linux-image-4.4.0-79-generic linux-image-4.4.0-81-generic linux-image-4.4.0-83-generic
  linux-image-4.4.0-87-generic linux-image-extra-4.4.0-57-generic linux-image-extra-4.4.0-59-generic
  linux-image-extra-4.4.0-62-generic linux-image-extra-4.4.0-63-generic linux-image-extra-4.4.0-64-generic
  linux-image-extra-4.4.0-66-generic linux-image-extra-4.4.0-71-generic linux-image-extra-4.4.0-72-generic
  linux-image-extra-4.4.0-75-generic linux-image-extra-4.4.0-78-generic linux-image-extra-4.4.0-79-generic
  linux-image-extra-4.4.0-81-generic linux-image-extra-4.4.0-83-generic linux-image-extra-4.4.0-87-generic
これを削除するには 'sudo apt autoremove' を利用してください。
以下の追加パッケージがインストールされます:
  default-jre-headless
提案パッケージ:
  default-java-plugin
以下のパッケージが新たにインストールされます:
  default-jre default-jre-headless
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 250 個。
5,360 B のアーカイブを取得する必要があります。
この操作後に追加で 28.7 kB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://jp.archive.ubuntu.com/ubuntu xenial/main amd64 default-jre-headless amd64 2:1.8-56ubuntu2 [4,380 B]
取得:2 http://jp.archive.ubuntu.com/ubuntu xenial/main amd64 default-jre amd64 2:1.8-56ubuntu2 [980 B]
5,360 B を 0秒 で取得しました (14.3 kB/s)
以前に未選択のパッケージ default-jre-headless を選択しています。
(データベースを読み込んでいます ... 現在 794349 個のファイルとディレクトリがインストールされています。)
.../default-jre-headless_2%3a1.8-56ubuntu2_amd64.deb を展開する準備をしています ...
default-jre-headless (2:1.8-56ubuntu2) を展開しています...
以前に未選択のパッケージ default-jre を選択しています。
.../default-jre_2%3a1.8-56ubuntu2_amd64.deb を展開する準備をしています ...
default-jre (2:1.8-56ubuntu2) を展開しています...
default-jre-headless (2:1.8-56ubuntu2) を設定しています ...
default-jre (2:1.8-56ubuntu2) を設定しています ...

としたがうまくいかない

$ java PDFConverter 1500336208000000004.ods
プログラム 'java' は以下のパッケージで見つかりました:
 * default-jre
 * gcj-5-jre-headless
 * openjdk-8-jre-headless
 * gcj-4.8-jre-headless
 * gcj-4.9-jre-headless
 * openjdk-9-jre-headless
次の操作を試してください: sudo apt install <選択したパッケージ>


$ jre PDFConverter 1500336208000000004.ods
コマンド 'jre' は見つかりませんでした。もしかして:
 コマンド 're' - パッケージ 're' (universe)
 コマンド 'joe' - パッケージ 'joe' (universe)
 コマンド 'joe' - パッケージ 'joe-jupp' (universe)
jre: コマンドが見つかりません

仕方がないのでopenjdk-8を明示的にインストール・・・したがなんだか調子が悪いので更に remove して再度install

$ sudo apt-get install  openjdk-8-jdk openjdk-8-jre openjdk-8-jre-headless

$ sudo apt-get remove  openjdk-8-jdk openjdk-8-jre openjdk-8-jre-headless
パッケージリストを読み込んでいます... 完了

$ sudo apt-get install  openjdk-8-jdk openjdk-8-jre openjdk-8-jre-headless

これで、java自体は動作するようになりました。

待ち受け

soffice with  "-accept=socket,host=localhost,port=8100;urp" option

とドキュメントに書いてあったけれども、

$ soffice -accept=socket,host=localhost,port=8100;urp
コマンド 'urp' は見つかりませんでした。もしかして:
 コマンド 'arp' - パッケージ 'net-tools' (main)
 コマンド 'unp' - パッケージ 'unp' (universe)
 コマンド 'ur' - パッケージ 'libur-perl' (universe)
 コマンド 'burp' - パッケージ 'burp' (universe)
 コマンド 'rup' - パッケージ 'rstat-client' (universe)
urp: コマンドが見つかりません

となる。ダブルクォーテーション付きでしないといけないのね。

$ soffice "-accept=socket,host=localhost,port=8100;urp"

別端末で、

$ netstat -a
稼働中のインターネット接続 (サーバと確立)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態      
tcp        0      0 localhost:8100          *:*                     LISTEN    
・
・
・

待ち受けしてます。

動かしてみる→失敗

$ java PDFConverter 1500336208000000004.ods
$

何も起こらずに終了。
元々のシステムの呼び出しスクリプトを見てみる。

$ cat PDFConverterWenv.sh 
export CLASSPATH=.:/usr/lib/openoffice/program/classes/juh.jar:/usr/lib/openoffice/program/classes/jurt.jar:/usr/lib/openoffice/program/classes/ridl.jar:/usr/lib/openoffice/program/classes/unoloader.jar:/usr/lib/openoffice/program/classes/unoil.jar:/usr/lib/openoffice/program
export OFFICE_PROGRAM_PATH=/usr/bin
export OO_SDK_CPP_HOME=/usr/bin
export OO_SDK_HOME=/usr/lib/openoffice_sdk
export OO_SDK_JAVA_HOME=/usr//bin/java
export OO_SDK_MAKE_HOME=/usr/bin
export OO_SDK_NAME=openoffice_sdk
export OO_SDK_OUTPUT_DIR=/home/nanbuwks
export OO_SDK_URE_BIN_DIR=/usr/lib/openoffice/program
export OO_SDK_URE_HOME=
export OO_SDK_URE_JAVA_DIR=/usr/lib/openoffice/program/classes
export OO_SDK_URE_LIB_DIR=/usr/lib/openoffice/program
export OO_SDK_ZIP_HOME=/usr/bin
export export DISPLAY=localhost:0
# set
cp ../$1.ods .
/usr/bin/java PDFConverter $1.ods 

sdkとかないといけなかったかな?

sdkインストール

LibreOfficeのSDKってどんな名前?
パッケージを探してみる

$ sudo apt-cache search LibreOffice-SDK

見つからない

$ sudo apt-cache search Libreoffice | grep -i sdk
libreoffice-dev - office productivity suite -- SDK
libreoffice-dev-doc - office productivity suite -- SDK documentation

なるほど。ではこれらをインストール。


$ sudo apt-get install libreoffice-dev libreoffice-dev-doc

先ほどのPDFConverterWebv.shをLibreOffice用に書き換える。

$ cat PDFConverterWenv.sh 
export CLASSPATH=.:/usr/lib/libreoffice/program/classes/juh.jar:/usr/lib/libreoffice/program/classes/jurt.jar:/usr/lib/libreoffice/program/classes/ridl.jar:/usr/lib/libreoffice/program/classes/unoloader.jar:/usr/lib/libreoffice/program/classes/unoil.jar:/usr/lib/libreoffice/program
export OFFICE_PROGRAM_PATH=/usr/bin
export OO_SDK_CPP_HOME=/usr/bin
export OO_SDK_HOME=/usr/lib/libreoffice/sdk
export OO_SDK_JAVA_HOME=/usr//bin/java
export OO_SDK_MAKE_HOME=/usr/bin
export OO_SDK_NAME=libreoffice_sdk
export OO_SDK_OUTPUT_DIR=/home/nanbuwks
export OO_SDK_URE_BIN_DIR=/usr/lib/libreoffice/program
export OO_SDK_URE_HOME=
export OO_SDK_URE_JAVA_DIR=/usr/lib/libreoffice/program/classes
export OO_SDK_URE_LIB_DIR=/usr/lib/libreoffice/program
export OO_SDK_ZIP_HOME=/usr/bin
export export DISPLAY=localhost:0
# set
cp ../$1.ods .
/usr/bin/java PDFConverter $1.ods 

変換

$./PDFConverterWenv.sh 1500336208000000004
$ ls -alh 1500336208000000004.pdf
-rw-rw-r-- 1 nanbuwks nanbuwks 124K  8月 14 18:02 1500336208000000004.pdf

できた。