【py3o】py3oconverter (Javaパッケージ：odt等をpdfに変換)

**unoserver**

https://github.com/unoconv/unoserver

**py3o**

https://orus.io/florent.aide/py3o.renderers.juno

# LibreOfficeによるPDF変換（エクスポート）

https://wiki.documentfoundation.org/API/Tutorials/PDF_export

**フィルターオプション**

https://help.libreoffice.org/latest/ja/text/shared/guide/pdf_params.html

https://wiki.documentfoundation.org/Macros/Python_Guide/PDF_export_filter_data/ja

# py3oconverter.jar

`py3o.renderers.juno`に同梱されたJavaパッケージ

<details><summary>ソースコード</summary>

`py3o.renderers.juno-0.8.2.dev1-py2-none-any.whl`に同梱されている`py3oconverter.jar`

```java: py3oconverter
package py3oconverter;

import com.google.gson.Gson;
import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.XPropertySet;
import com.sun.star.bridge.XUnoUrlResolver;
import com.sun.star.comp.helper.Bootstrap;
import com.sun.star.connection.NoConnectException;
import com.sun.star.container.XIndexAccess;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XStorable;
import com.sun.star.io.ConnectException;
import com.sun.star.io.IOException;
import com.sun.star.lang.IndexOutOfBoundsException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.lang.XComponent;
import com.sun.star.text.XDocumentIndex;
import com.sun.star.text.XDocumentIndexesSupplier;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uri.ExternalUriReferenceTranslator;
import com.sun.star.util.CloseVetoException;
import com.sun.star.util.XCloseable;
import com.sun.star.util.XRefreshable;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Hashtable;
import java.util.Map;

/* loaded from: py3oconverter.jar:py3oconverter/Convertor.class */
public class Convertor {
    private XComponentContext remoteContext = null;
    private String serverHost;
    private String serverPort;

    public Convertor(String serverHost, String serverPort) {
        this.serverHost = serverHost;
        this.serverPort = serverPort;
    }

    public void convert(String inputFilePath, String outputFilePath, String filterName, String filterOptions, String filterData) throws ConnectException, Exception {
        convertDocument(outputFilePath, filterName, openDocument(inputFilePath, connect(serverHost, serverPort)), filterOptions, filterData);
    }

    private String createUNOFileURL(String filePath) {
        URL url = null;
        try {
            url = new File(filePath).toURI().toURL();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        String internalURL = ExternalUriReferenceTranslator.create(remoteContext).translateToInternal(url.toExternalForm());
        if (internalURL.length() == 0 && filePath.length() > 0) {
            System.out.println("File URL conversion failed. File location contains illegal characters: " + filePath);
        }
        return internalURL;
    }

    protected void refreshDocument(XComponent document) {
        XRefreshable refreshable = (XRefreshable) UnoRuntime.queryInterface(XRefreshable.class, document);
        if (refreshable != null) {
            refreshable.refresh();
        }
    }

    protected void refreshIndexes(XComponent document) {
        XDocumentIndexesSupplier indexesSupplier = (XDocumentIndexesSupplier) UnoRuntime.queryInterface(XDocumentIndexesSupplier.class, document);
        if (indexesSupplier != null) {
            XIndexAccess documentIndexes = indexesSupplier.getDocumentIndexes();
            for (int i = 0; i < documentIndexes.getCount(); i++) {
                try {
                    XDocumentIndex documentIndex = (XDocumentIndex) UnoRuntime.queryInterface(XDocumentIndex.class, documentIndexes.getByIndex(i));
                    if (documentIndex != null) {
                        documentIndex.update();
                    }
                } catch (WrappedTargetException | IndexOutOfBoundsException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void convertDocument(String outputFilePath, String filterName, XComponent document, String filterOptions, String filterData) {
        XStorable storable = (XStorable) UnoRuntime.queryInterface(XStorable.class, document);
        refreshDocument(document);
        refreshIndexes(document);

        PropertyValue[] storeProps = new PropertyValue[3];
        storeProps[0] = new PropertyValue();
        storeProps[0].Name = "Overwrite";
        storeProps[0].Value = new Boolean(true);
        storeProps[1] = new PropertyValue();
        storeProps[1].Name = "FilterName";
        storeProps[1].Value = filterName;

        Map<String, String> optionsMap = new Gson().fromJson(filterOptions, Map.class);
        Map<String, String> typesMap = new Gson().fromJson(filterData, Map.class);

        PropertyValue[] filterDataProps = new PropertyValue[optionsMap.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : optionsMap.entrySet()) {
            String key = entry.getKey();
            String type = typesMap.get(key);
            String value = entry.getValue();
            filterDataProps[i] = new PropertyValue();
            filterDataProps[i].Name = key;
            if ("boolean".equals(type)) {
                filterDataProps[i].Value = Boolean.parseBoolean(value);
            } else if ("integer".equals(type)) {
                filterDataProps[i].Value = Integer.valueOf(Integer.parseInt(value));
            } else if ("string".equals(type)) {
                filterDataProps[i].Value = value;
            }
            i++;
        }

        storeProps[2] = new PropertyValue();
        storeProps[2].Name = "FilterData";
        storeProps[2].Value = filterDataProps;

        try {
            storable.storeToURL(createUNOFileURL(outputFilePath), storeProps);
            XCloseable closeable = (XCloseable) UnoRuntime.queryInterface(XCloseable.class, storable);
            if (closeable != null) {
                try {
                    closeable.close(false);
                } catch (CloseVetoException e) {
                    e.printStackTrace();
                }
            } else {
                document.dispose();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private XComponent openDocument(String filePath, XDesktop desktop) {
        XComponentLoader loader = (XComponentLoader) UnoRuntime.queryInterface(XComponentLoader.class, desktop);
        PropertyValue[] loadProps = {new PropertyValue()};
        loadProps[0].Name = "Hidden";
        loadProps[0].Value = new Boolean(true);
        XComponent document = null;
        try {
            document = loader.loadComponentFromURL(createUNOFileURL(filePath), "_blank", 0, loadProps);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return document;
    }

    private XDesktop connect(String host, String port) throws ConnectException, Exception {
        try {
            XComponentContext initialContext = Bootstrap.createInitialComponentContext((Hashtable) null);
            remoteContext = (XComponentContext) UnoRuntime.queryInterface(XComponentContext.class, ((XPropertySet) UnoRuntime.queryInterface(XPropertySet.class, ((XUnoUrlResolver) UnoRuntime.queryInterface(XUnoUrlResolver.class, initialContext.getServiceManager().createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", initialContext))).resolve("uno:socket,host=" + host + ",port=" + port + ";urp;StarOffice.ServiceManager"))).getPropertyValue("DefaultContext"));
            return (XDesktop) UnoRuntime.queryInterface(XDesktop.class, remoteContext.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", remoteContext));
        } catch (NoConnectException e) {
            throw new ConnectException("Connection failed for host: " + host + ", and port: " + port + " : " + e.getMessage());
        } catch (Exception e) {
            throw new Exception("Open office exception : " + e);
        }
    }
}
```

</details>

# 詳細

`py3oconverter` パッケージの `Convertor` クラスについて詳しく説明します。このクラスは、OpenOffice/LibreOffice を使用してドキュメントの変換を行うためのものです。

## クラスの概要
Convertor クラスは、リモートコンテキストを使用してOpenOffice/LibreOfficeサーバーに接続し、ドキュメントを変換します。このクラスは、ODTファイルを別のフォーマットに変換するための各種メソッドを提供します。

## 主なメンバー変数

### `xRemoteContext`: リモートコンテキストを格納します。

server_host と server_port: OpenOffice/LibreOfficeサーバーのホスト名とポート番号を格納します。

## 主なメソッド
### `Convertor` コンストラクタ

```java
public Convertor(String serverHost, String serverPort)
```

サーバーのホスト名とポート番号を設定します。

### `convert` メソッド

```java
public void convert(String inputFilePath, String outputFilePath, String filterName, String filterOptions, String filterData) throws ConnectException, Exception
```

ドキュメントを指定されたフォーマットに変換します。

パラメータ:

`inputFilePath`: 変換元ファイルのパス

`outputFilePath`: 変換後のファイルのパス

`filterName`: 変換に使用するフィルター名

`filterOptions`: フィルターオプションのJSON文字列

`filterData`: フィルターデータのJSON文字列

### `createUNOFileURL` メソッド

```java
private String createUNOFileURL(String filePath)
```

ファイルパスを内部URLに変換します。

### `refreshDocument` メソッド

```java
protected void refreshDocument(XComponent xComponent)
```

ドキュメントをリフレッシュします。

### `refreshIndexes` メソッド

```java
protected void refreshIndexes(XComponent xComponent)
```

ドキュメントのインデックスを更新します。

### `convert_document` メソッド

```java
private void convert_document(String outputFilePath, String filterName, XComponent xComponent, String filterOptions, String filterData)
```
ドキュメントの変換を実行します。

### `openDocument` メソッド

```java
private XComponent openDocument(String filePath, XDesktop xDesktop)
```

ドキュメントを開きます。

### `connect` メソッド

```java
private XDesktop connect(String serverHost, String serverPort) throws ConnectException, Exception
```

OpenOffice/LibreOfficeサーバーに接続します。

## メソッドの階層構造

これらのメソッドを使用して、OpenOffice/LibreOfficeを利用したドキュメント変換を実現します。例えば、ODTファイルをPDFに変換するなどの用途に利用できます。

```text
Convertor
├── Convertor(String serverHost, String serverPort)
└── convert(String inputFilePath, String outputFilePath, String filterName, String filterOptions, String filterData)
    ├── connect(String serverHost, String serverPort)
    │   └── XComponentContext initialContext
    │       └── XUnoUrlResolver resolve
    │           └── XDesktop getServiceManager
    ├── openDocument(String filePath, XDesktop desktop)
    │   └── createUNOFileURL(String filePath)
    │       └── URL toExternalForm
    └── convertDocument(String outputFilePath, String filterName, XComponent document, String filterOptions, String filterData)
        ├── refreshDocument(XComponent document)
        │   └── XRefreshable refresh
        ├── refreshIndexes(XComponent document)
        │   └── XDocumentIndexesSupplier getDocumentIndexes
        └── storeToURL(String createUNOFileURL, PropertyValue[] storeProps)
            └── XCloseable close
```

# pythonからの呼び出し

`py3o.renderers.juno` は py3oconverter パッケージを使用して、OpenOffice/LibreOffice サーバー経由でドキュメントの変換を自動化することができます。。以下に各部分の説明を階層化して示します。

## モジュールのインポート

```python
import jpype
from jpype import startJVM
from jpype import JPackage

import os
import json
import platform
import logging
import pkg_resources
```

jpype: Java仮想マシン (JVM) とPython間の相互運用をサポートするライブラリ。

os, json, platform, logging, pkg_resources: 標準ライブラリと追加パッケージ。

## ハードコードされたJARリソースの定義

```python
ureitems = [
    "juh.jar",
    "jurt.jar",
    "ridl.jar",
    "unoloader.jar",
    "java_uno.jar",
    "gson.jar",
]
basisitems = ["unoil.jar"]
```

## `get_oo_context` 関数

```python
def get_oo_context():
    ...
    return context
```

プラットフォームに応じて、Javaクラスパスのセパレータやサブパスを設定します。

## `start_jvm` 関数

```python
def start_jvm(jvm, oobase, urebase, max_mem):
    ...
    startJVM(jvm_abs, java_classpath, java_maxmem)
```

JVMを開始し、指定されたクラスパスとメモリ設定を適用します。

## `Convertor` クラス

```python
class Convertor(object):
    ...
    def convert(self, infilename, outfilename, filtername, pdf_options):
        ...
```

### `__init__` メソッド

```python
def __init__(self, host, port):
    ...
    jconvertor_package = JPackage('py3oconverter').Convertor
    self.jconvertor = jconvertor_package(host, port)
```

OpenOfficeサーバーのホストとポートを初期化し、Java側の Convertor クラスを使用します。

### `convert` メソッド

```python
def convert(self, infilename, outfilename, filtername, pdf_options):
    ...
    self.jconvertor.convert(
        infilename,
        outfilename,
        filtername,
        pdf_options_json,
        pdf_options_types_json)
```
入力ファイルを指定されたフィルターで変換し、出力ファイルに保存します。