ONLYOFFICE Conversion APIでオンラインドキュメントの変換ツールを構築する方法

ドキュメント変換は、複数の種類のドキュメントを操作するのに役立つ、とても人気のある機能です。ONLYOFFICE エディターは、ドキュメントをさまざまなフォーマットに簡単に変換することができます。このブログ記事では、ONLYOFFICE Conversion APIで動作するオンライン変換ツールを構築する方法をご紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/04f9ba96-a2ba-8980-d9a9-4cd8b502e079.png)

## ONLYOFFICE Conversion APIについて

テキスト、シート、スライド、フォーム、PDF、電子書籍など、あらゆる種類のオフィス文書を変換することができる「文書変換」サービス。文書やシートからPDFを作成したり、教科書を電子書籍に変換したり、スライドを画像ファイルに変換したり、様々なことができます。ONLYOFFICEは、50種類以上のドキュメントタイプに対応しています。

## その仕組み

Conversion APIは、文書変換サービスを通じて動作します。ONLYOFFICEドキュメントサーバーの一部として機能し、様々なドキュメントファイルを適切なフォーマットに変換することができます。変換はいくつかのステップで実行されます。

* ユーザーは、ドキュメントマネージャにアップロードするファイルを選択する
* 文書マネージャは選択されたファイルを文書保管サービスにアップロードする
* 文書保管サービスは、アップロードされたファイルを、変換APIを用いて文書変換サービスに送る
* 文書変換サービスは、選択されたファイルを必要な形式に変換する
* 文書保管サービスは変換されたファイルをダウンロードする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/ca75666d-e603-aaa0-e5c5-08a891fd7567.png)

文書マネージャと文書保管サービスは、さらなる変換のためにドキュメントの選択と保存を容易にするクライアントとサーバー側のツールです。しかし、私たちのWebアプリケーションは、カスタム変換ツールを構築しているため、これらのタスクに取り組むことになります。

## 前提条件

私たちの変換ツールはNodeJSをベースとします。したがって、このプロジェクトでは、以下が必要です。

* [ONLYOFFICE Document Server](https://www.onlyoffice.com/ja/developer-edition.aspx?utm_source=qiita&utm_medium=article&utm_id=converter_app)
* 以下のパッケージがインストールされたNodeJSアプリケーション
  * Express
  * Axios
  * Jsonwebtoken

私たちは、ONLYOFFICE Document Serverへpostリクエストを送るためにAxiosパッケージを、JWTトークンに署名するためにJsonwebtokenパッケージを使用する予定です。バージョン7.2からは、JWT認証がデフォルトで有効になっています。

また、JWT認証を無効にしてONLYOFFICE Document Serverのイメージを実行するオプションも用意されています。その場合は、ターミナルで以下のコマンドを実行します。

```
sudo docker run -i -t -d -p 80:80 -e JWT_ENABLED=false onlyoffice/documentserver
```

当変換ツールがONLYOFFICE Document Serverへ送信しているポストリクエストは以下の通りです。

```javascript
{
"async": true,
"filetype": fileType,
"key": key, 
"outputtype": outputType,
"title": `Converted Document.${outputType}`,
"url": link
}
```

* *accept*パラメータでは、JSON形式のレスポンスを受け取ることを指定しています。
* *async* パラメーターは、リクエストが非同期であることを示します。
* *fileType*パラメータは、変換する元ファイルの種類を指定します。
* *key*パラメータは、現在のドキュメントのUID識別子を表す。
* *outputType*パラメータは、変換後のドキュメントの形式を表します。
* *title*パラメータは、変換後のドキュメントの名前を含みます。
* *url*パラメータには、変換したいファイルへのリンクが含まれます。

*fileType*、*outputType*、*url*パラメータの値は、アプリケーションのクライアント側から取得され、変数に格納されます。*key*パラメータの値はランダムに生成され、同様に変数に格納されます。

## プロジェクトのセットアップ

必要なパッケージをインストールした後、app.js ファイルに移動して、ポスト リクエスト データを処理するための bodyParser と共に初期化します。また、パブリックフォルダとビューエンジンをセットアップします。

```javascript
const express = require('express');
const app = express();
const axios = require('axios'); 
const jwt = require('jsonwebtoken');


app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(express.static("public"));
app.set('view engine', 'ejs');
```

次に、ルートを追加します。私たちのアプリには、getルートとpostルートが含まれます。 これらは、入力データを取得し、ONLYOFFICE Document Serverへのpostリクエストに渡すことができるようにします。

```javascript
app.get ('/', function (reg, response){ 
}

app.post ('/converter', function(req, response){
}
```

## クライアント側

さて、少し回り道をして、変換ツールのクライアント側を見てみましょう。ここで必要なデータをすべて入力することになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/7223b0fa-8949-dc5e-ce44-540adbd5192c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/b99862c4-1b63-5b16-6a5c-760719c2d146.png)

クライアントサイドには、次の2つのEJSページがあります。

* *homepage.ejs* – ポストリクエストに必要なすべての値を取得することができます。
* *converter.ejs* – 変換されたファイルをダウンロードすることができます。

*homepage.ejs*を詳しく見てみましょう。ここでは、postルートにデータを送信するフォームを作成しました。まず、元のファイルへのURLを取得します。これをサーバー側の*link*変数に格納します。

```javascript
<h1 class="h3 mb-3 font-weight-normal">Convert your OOXML files here!</h1>
          <input type="text"  class="form-control" id="inputEmail" placeholder="Paste a link to the file" name="link" onchange="activateBox()">
```

そして、コンボボックスで元ファイルのタイプを選びます。後でこの値を取り出してinputType変数に格納します。

```javascript
<select class="form-control" id="inputType" input type="text" disabled="true" name="inputType" onchange="comboBox()">
          <option value="" selected>Input File Type</option>
          <option value="docx">docx</option>
          <option value="txt">txt</option>
          <option value="pdf">pdf</option>
          <option value="rtf">rtf</option>
          <option value="xml">xml</option>
          <option value="csv">csv</option>
          <option value="xlsx">xlsx</option>
          <option value="xls">xls</option>
          <option value="ppt">ppt</option>
          <option value="pptx">pptx</option>
</select>
```
次に、希望するファイルタイプを選択します。この値は、サーバー側の変数outputTypeに格納されます。

```javascript
<select class="form-control" id="outputType" input type="text" disabled="true" name="outputType" onchange="activateButton()">
      <option value="" disabled selected hidden>Output File Type</option>
    </select>
```

そして、変換ツールのpostルートにデータ全体を送信するためのボタンを使用します。

```javascript
<div class="button">
      <button type="submit" id="download" disabled="true" class="btn btn-lg btn-primary btn-block">Convert</button>
    </div>
    </form>
```

## 変換ツールの構築

取得したデータは、アプリケーションのサーバー側でパースされます。では、app.jsファイルに移動して、それを取得してみましょう。

```javascript
app.post ('/converter', function(req, response){
    let link = req.body.link;
    let outputType = req.body.outputType;
    let fileType = req.body.inputType;

});
```

次に、*outputType*の値をpostルートに送信する2番目のコンボボックスを見てみましょう。

```javascript
  <select class="form-control" id="outputType" input type="text" disabled="true" name="outputType" onchange="activateButton()">
      <option value="" disabled selected hidden>Output File Type</option>
    </select>
```

*outputType*変数は、サーバーへのポストリクエストに含まれる。これは変換されたファイルの形式を指定します。ページ要素と対話し、UIにダイナミックリストを組み込むことを可能にするJavaScriptのコードを調べてみましょう。

ドキュメント変換サービスは非常に強力なツールであり、さまざまな種類のファイルを変換することができます。そこで私たちの目標は、変換されたファイルのフォーマットを選択できる動的なリストを利用することです。このリストには、元のファイルの特定のタイプで利用可能なすべてのオプションが表示されます。

そのために、それぞれの特定のタイプに応じた値を格納する JSON ファイルを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/a24f7f02-204d-4354-5d81-f2f60259d549.png)

:::note warn
**ご注意**！デモンストレーションのため、最も一般的なOOXMLフォーマットのみを掲載しています。サポートされているすべての変換オプションについては、[ドキュメントページ](https://api.onlyoffice.com/editors/conversionapi?_ga=2.76528981.1756320700.1678693495-520645956.1673247425)をご覧ください。
:::

次に、このダイナミックリストを生成する関数を追加します。

```javascript
 function comboBox () {
     let type = document.querySelector("#inputType").value;
     let listDropDown = document.querySelector("#outputType");
     fetch("formats.json")
    .then(function(response){
      return response.json();
    })
    .then(function(data){
     let options = data[type];
     let out = "";
     out += `<option value="">Output File Type</option>`;
     Object.values(options).map(function(format){
        out += '<option value=' + format + '>' + format + '</option>';
      });
     listDropDown.innerHTML = out;
     listDropDown.disabled = false;
               });  
    };
        };
```

まず、この関数は、元のファイルのフォーマットを選択したinputTypeコンボボックスの値を取得します。この値を type 変数に代入して、この値を下層で使用します。次に、AJAXリクエストを行い、JSONデータを読み込みます。その後、type 変数の値をインデックスとして使用し、読み込んだデータを繰り返し処理して、ドロップダウン リストのオプションにその値を挿入します。

これで、元のファイルの種類を選択するたびにスクリプトが実行され、元のファイル形式に従って、利用可能な変換オプションが表示されるようになります。

そして、必要なデータがすべて揃ったら、「変換」ボタンをクリックして変換ルートに送信します。

しかし、これだけではありません。 このアプリをよりインタラクティブにするために、コンボボックスとボタン要素を正しい順序で作動させる関数を追加する予定です。

```javascript
 function activateBox() {
      $("#inputType").prop('disabled', false)
    };
    
    function activateButton() {
      $("#download").prop('disabled', false)
      $("#outputTypeForm").hide();
    };
```

JavaScriptのコード全体は以下の通りです。

```javascript
     function comboBox () {
     let type = document.querySelector("#inputType").value;
     let listDropDown = document.querySelector("#outputType");
     fetch("formats.json")
    .then(function(response){
      return response.json();
    })
    .then(function(data){
     let options = data[type];
     let out = "";
     out += `<option value="">Output File Type</option>`;
     Object.values(options).map(function(format){
        out += '<option value=' + format + '>' + format + '</option>';
      });
     listDropDown.innerHTML = out;
     listDropDown.disabled = false;
               });  
    };
    function activateBox() {
      $("#inputType").prop('disabled', false)
    };
    
    function activateButton() {
      $("#download").prop('disabled', false)
      $("#outputTypeForm").hide();
    };
        }
```

サーバー側で何が起こっているのかを見てみましょう。

```javascript
app.post ('/converter', function(req, response){
    let link = req.body.link;
    let outputType = req.body.outputType;
    let fileType = req.body.inputType;
    let key =  function () {
        var key = '';
        var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 
                'abcdefghijklmnopqrstuvwxyz0123456789';
          
        for (let i = 1; i <= 12; i++) {
            var char = Math.floor(Math.random()
                        * str.length + 1);
              
            key += str.charAt(char);
        }
          
        return key;
    };
    const payload =  { 
        "async": true,
        "filetype": fileType,
        "key": key, 
        "outputtype": outputType,
        "title": `Converted Document.${outputType}`,
        "url": link       
    }
    let token = jwt.sign(payload, secret, options);
    
    axios.post( 
        'http://127.0.0.1:83/ConvertService.ashx',
        { 
           "token": token
        }) 
        .then((res) => response.render('converter.ejs', {
            link: res.data.fileUrl
        })) 
});
```

変換ツールのルートでは、ONLYOFFICE Document Serverへのポストリクエストを形成し、ペイロード変数に格納しました。フェッチしたデータを格納するfileType、outputType、link変数を使用しました。しかし、我々はまた、現在のドキュメントのユニークな識別子を含むkey変数を持っています。そこで、それを生成するための小さな関数を上に追加しました。

```javascript
    let key =  function () {
        var key = '';
        var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 
                'abcdefghijklmnopqrstuvwxyz0123456789';
          
        for (let i = 1; i <= 12; i++) {
            var char = Math.floor(Math.random()
                        * str.length + 1);
              
            key += str.charAt(char)
        }   
        return key;
    };
```

さて、リクエストに必要なすべての値が揃ったので、*jwt.sign*を使ってそれらをトークンに包みます。

```javascript
  let token = jwt.sign(payload, secret, options);
```
jwt.signメソッドは3つのパラメータを取ります。
* Payload：変換に必要なすべてのパラメータを含む。
* Header：暗号化アルゴリズムと有効期限に関する情報を含む。これらのパラメータはoptions変数でラップしています。
* ONLYOFFICE Document Serverによって生成されたシクレットキーを表すシークレット。このシークレットは、[local.jsonファイル](https://api.onlyoffice.com/editors/conversionapi?utm_source=blog&utm_medium=article&utm_id=converter_app&_ga=2.122534347.1756320700.1678693495-520645956.1673247425)、またはターミナルで次のコマンドを実行することで見つけることができます。

```
sudo docker exec <dockerID> /var/www/onlyoffice/documentserver/npm/json -f /etc/onlyoffice/documentserver/local.json 'services.CoAuthoring.secret.session.string'
```

トークンに署名した後、axios の post request を使ってサーバーに送信します。そして、ONLYOFFICE Document Serverからレスポンスを受け取るconverter.ejsのページをレンダリングします。

```javascript
 axios.post( 
        'http://127.0.0.1:83/ConvertService.ashx',
        { 
           "token": token
        }) 
        .then((res) => response.render('converter.ejs', {
            link: res.data.fileUrl
        }))
```

以下は、JSON形式のレスポンスの例です。

```javascript
{     "endConvert": true,     "fileType": "docx",     "fileUrl": "https://documentserver/url-to-converted-document.pdf",     "percent": 100 }
```

ここで必要なのは、fileUrl要素です。これは変換されたファイルへのリンクです。そこで、converter.ejsのページにアクセスし、それを送信します。

```javascript
.then((res) => response.render('converter.ejs', {
            link: res.data.fileUrl
        })) 
});
```

そのページには、2つのボタンを作成しました。「戻る」ボタンはhomepage.ejsのページに戻り、「ダウンロード」ボタンはそのページに送ったリンクを開いて変換されたファイルをダウンロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2655664/174e6193-8c82-7a3b-1973-dd9a44e14083.png)

:::note warn
**ご注意**！JWTについてより詳しくは、[ドキュメントページ](https://api.onlyoffice.com/editors/conversionapi?utm_source=blog&utm_medium=article&utm_id=converter_app&_ga=2.9369333.1756320700.1678693495-520645956.1673247425)をご覧ください。サーバーサイドのコード全体は以下の通りです。
:::

```javascript
const express = require('express');
const app = express();
const axios = require('axios'); 
const jwt = require('jsonwebtoken');
const options = {algorithm: "HS256", expiresIn: "5m"};
const secret = "k1gWQdmDX6ZGiWw5r3g2";
app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(express.static("public"));
app.set('view engine', 'ejs');
app.get ('/', function (reg, response){ 
    response.render('homepage.ejs', {
    })
});
app.post ('/converter', function(req, response){
    let link = req.body.link;
    let outputType = req.body.outputType;
    let fileType = req.body.inputType;
    let key =  function () {
        var key = '';
        var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 
                'abcdefghijklmnopqrstuvwxyz0123456789';
          
        for (let i = 1; i <= 12; i++) {
            var char = Math.floor(Math.random()
                        * str.length + 1);
              
            key += str.charAt(char);
        }
          
        return key;
    };
    const payload =  { 
        "async": true,
        "filetype": fileType,
        "key": key, 
        "outputtype": outputType,
        "title": `Converted Document.${outputType}`,
        "url": link       
    }
    let token = jwt.sign(payload, secret, options);
    
    axios.post( 
        'http://127.0.0.1:83/ConvertService.ashx',
        { 
           "token": token
        }) 
        .then((res) => response.render('converter.ejs', {
            link: res.data.fileUrl
        })) 
});
app.listen(3000,() => console.log('the server is up and running'));
```

ONLYOFFICE ソリューションは、非常に多機能です。それらはユーザーと開発者にユニークな経験を提供し、様々な方法でOOXMLドキュメントを操作することを可能にします。私たちは、上記の情報を有用と判断し、皆さんの将来のプロジェクトに適用することを望みます。コメント、質問、アイデアの共有など、遠慮なくお寄せください。ご提案とご協力をいつも歓迎します！

## お役立ちリンク

* [ONLYOFFICE Document Server](https://www.onlyoffice.com/ja/developer-edition.aspx?utm_source=qiita&utm_medium=article&utm_id=converter_app)
* [Conversion APIのドキュメント](https://api.onlyoffice.com/editors/conversionapi?utm_source=blog&utm_medium=article&utm_id=converter_app&_ga=2.49723750.1756320700.1678693495-520645956.1673247425)
* [署名のドキュメント](https://api.onlyoffice.com/editors/conversionapi?utm_source=blog&utm_medium=article&utm_id=converter_app&_ga=2.49723750.1756320700.1678693495-520645956.1673247425)
* [JWTについて](https://jwt.io/)