ReactでJavaScriptを使ってWord文書に目次を挿入する

Reactアプリケーションで目次を含むWord文書を動的に生成することは、データ報告書、技術文書、またはビジネス資料の専門性と可読性を大幅に向上させます。特に、複数の章から成る長文書を扱う場合、目次の自動生成機能は、手動で章のページ番号を管理する煩わしさを解消するだけでなく、文書が更新されるたびに目次と本文をリアルタイムで同期させることができます。JavaScriptを使用すれば、フロントエンドで直接Word文書を操作することができ、特にオフラインでの操作やデータ管理が厳密に要求される企業向けアプリケーションに最適です。本記事では、**ReactアプリケーションにおいてJavaScriptを使用してWord文書に目次を挿入する方法**について紹介します。

- JavaScriptでWord文書にデフォルトスタイルの目次を挿入する方法
- JavaScriptでWord文書にカスタムスタイルの目次を挿入する方法

本記事で使用する方法は、[Spire.Doc for JavaScript](https://www.e-iceblue.com/Introduce/doc-for-javascript.html)を利用します。npmパッケージは`npm i spire.doc`です。

## JavaScriptでWord文書にデフォルトスタイルの目次を挿入する方法
Spire.Doc for JavaScriptはWebAssemblyモジュールを使用してWord文書を処理します。このモジュールを利用することで、文書を直接作成することも、仮想ファイルシステムに読み込んで修正を加えることもできます。Word文書に目次を挿入する際、設定したタイトルを基に目次を挿入できます。もし文書にタイトルレベルが設定されていない場合は、目次挿入前に`Paragraph.ApplyStyle(BuiltinStyle)`メソッドでタイトルレベルを設定することができます。
`Paragraph.AppendTOC(lowerLevel: int, upperLevel: int)`メソッドを使用すると、Word文書の任意の段落に目次を挿入でき、表示するタイトルの範囲を指定できます。目次を挿入した後、`Document.UpdateTableOfContents()`メソッドを使用して目次を更新することを忘れないようにしましょう。操作手順の一例を以下に示します：

1. `Spire.Doc.Base.js`ファイルを読み込んでWebAssemblyモジュールを初期化します。
2. `wasmModule.FetchFileToVFS()`メソッドを使って、Wordファイルを仮想ファイルシステムにロードします。
3. `wasmModule.Document.Create()`メソッドを呼び出して、`Document`インスタンスを作成します。
4. `Document.LoadFromFile()`メソッドを使用して、Word文書を`Document`インスタンスに読み込みます。
5. `Document.AddSection()`メソッドを使って新しいセクションを作成します。
6. `Section.AddParagraph()`メソッドを使って、セクション内に段落を追加します。
7. `Paragraph.AppendTOC()`メソッドで目次を段落に追加し、目次に表示する最大および最小のタイトルレベルを設定します。
8. `Document.Sections.Insert()`メソッドを使って、目次のセクションを文書の適切な位置に挿入します。
9. `Document.UpdateTableOfContents()`メソッドで目次を更新します。
10. `Document.SaveToFile()`メソッドを使って、Word文書を仮想ファイルシステムに保存します。
11. Word文書を読み込んでダウンロードするか、他の処理を行います。

**コード例**
```js
import React, { useState, useEffect } from 'react';

function App() {

  // ロードされたWASMモジュールを保存するための状態
  const [wasmModule, setWasmModule] = useState(null);

  // コンポーネントがマウントされたときにWASMモジュールをロードするuseEffectフック
  useEffect(() => {
    const loadWasm = async () => {
      try {
        // グローバルなwindowオブジェクトからModuleとspiredocを取得
        const { Module, spiredoc } = window;

        // ランタイムが初期化されたときにwasmModule状態を設定
        Module.onRuntimeInitialized = () => {
          setWasmModule(spiredoc);
        };
      } catch (err) {
        // モジュールのロード中に発生したエラーをログに出力
        console.error('WASMモジュールのロードに失敗しました:', err);
      }
    };

    // WASM JavaScriptファイルをロードするためのscript要素を作成
    const script = document.createElement('script');
    script.src = `${process.env.PUBLIC_URL}/Spire.Doc.Base.js`;
    script.onload = loadWasm;

    // スクリプトをドキュメントのボディに追加
    document.body.appendChild(script);

    // コンポーネントがアンマウントされたときにスクリプトを削除するクリーンアップ関数
    return () => {
      document.body.removeChild(script);
    };
  }, []);

  // Word文書にデフォルトの目次を挿入するメソッド
  const InsertTOCWord = async () => {
    if (wasmModule) {
      // 入力ファイル名と出力ファイル名を指定
      const inputFileName = 'Sample94.docx';
      const outputFileName = 'デフォルト目次.docx';

      // フォントファイルを取得してVFSに追加
      await wasmModule.FetchFileToVFS('HarmonyOS_Sans_SC_Regular.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/`);

      // 入力ファイルを取得してVFSに追加
      await wasmModule.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/`);

      // Documentオブジェクトを作成
      const doc = wasmModule.Document.Create();

      // Word文書をロード
      doc.LoadFromFile({ fileName: inputFileName });

      // 新しいセクションを作成
      const section = doc.AddSection();
      // セクションに段落を追加
      const paragraph = section.AddParagraph();

      // 段落に目次を挿入
      paragraph.AppendTOC(1, 2);

      // セクションをカバーページの後に挿入
      doc.Sections.Insert(1, section);

      // 目次を更新
      doc.UpdateTableOfContents();

      // VFSに文書を保存
      doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2019 });

      // VFSから文書を読み取る
      const wordArray = await wasmModule.FS.readFile(outputFileName);

      // Blobオブジェクトを生成し、Wordファイルのダウンロードをトリガー
      const blob = new Blob([wordArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `${outputFileName}`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }
  };

  return (
      <div style={{ textAlign: 'center', height: '300px' }}>
        <h1>ReactでJavaScriptを使用してWord文書にデフォルト目次を挿入する</h1>
        <button onClick={InsertTOCWord} disabled={!wasmModule}>
          挿入してダウンロード
        </button>
      </div>
  );
}

export default App;
```

結果
![2025-02-14_dsd152720.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/988fd61b-1259-4343-bdfb-22423d475e3a.png)

**コード例**
また、`TableOfContent`オブジェクトを初期化して目次を作成し、`switch`でカスタマイズすることもできます。例えば、`"{\o \”1-2\” \\1-1}"`は、目次にレベル1からレベル3のタイトルを表示し、レベル1のタイトルのページ番号を省略することを意味します。手順の一例を以下に示します：

1. `Spire.Doc.Base.js`ファイルを読み込んでWebAssemblyモジュールを初期化します。
2. `wasmModule.FetchFileToVFS()`メソッドを使って、Wordファイルを仮想ファイルシステムにロードします。
3. `wasmModule.Document.Create()`メソッドを呼び出して、`Document`インスタンスを作成します。
4. `Document.LoadFromFile()`メソッドを使用して、Word文書を`Document`インスタンスに読み込みます。
5. `Document.AddSection()`メソッドを使って新しいセクションを作成します。
6. `Section.AddParagraph()`メソッドを使って、セクション内に段落を追加します。
7. `Document.Sections.Insert()`メソッドを使ってセクションを文書に挿入します。
8. `wasmModule.TableOfContent.Create()`メソッドを使用して、`TableOfContent`オブジェクトを作成します。
9. `Paragraph.Items.Add()`メソッドで目次を新しい段落に追加します。
10. `Paragraph.AppendFieldMark()`メソッドで、フィールド区切りマークと終了マークを追加します。
11. `Document.TOC`プロパティで、現在の文書の目次として設定します。
12. `Document.UpdateTableOfContents()`メソッドで目次を更新します。
13. `Document.SaveToFile()`メソッドで、Word文書を仮想ファイルシステムに保存します。
14. Word文書を読み込んでダウンロードするか、他の処理を行います。

**コード例**
```js
import React, { useState, useEffect } from 'react';

function App() {

  // ロードされたWASMモジュールを保存するための状態
  const [wasmModule, setWasmModule] = useState(null);

  // コンポーネントがマウントされたときにWASMモジュールをロードするuseEffectフック
  useEffect(() => {
    const loadWasm = async () => {
      try {
        // グローバルなwindowオブジェクトからModuleとspiredocを取得
        const { Module, spiredoc } = window;

        // ランタイムが初期化されたときにwasmModule状態を設定
        Module.onRuntimeInitialized = () => {
          setWasmModule(spiredoc);
        };
      } catch (err) {
        // モジュールのロード中に発生したエラーをログに出力
        console.error('WASMモジュールのロードに失敗しました:', err);
      }
    };

    // WASM JavaScriptファイルをロードするためのscript要素を作成
    const script = document.createElement('script');
    script.src = `${process.env.PUBLIC_URL}/Spire.Doc.Base.js`;
    script.onload = loadWasm;

    // スクリプトをドキュメントのボディに追加
    document.body.appendChild(script);

    // コンポーネントがアンマウントされたときにスクリプトを削除するクリーンアップ関数
    return () => {
      document.body.removeChild(script);
    };
  }, []);

  // Word文書にカスタム目次を挿入するメソッド
  const InsertTOCWord = async () => {
    if (wasmModule) {
      // 入力ファイル名と出力ファイル名を指定
      const inputFileName = 'Sample94.docx';
      const outputFileName = 'カスタム目次.docx';

      // フォントファイルを取得してVFSに追加
      await wasmModule.FetchFileToVFS('HarmonyOS_Sans_SC_Regular.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/`);

      // 入力ファイルを取得してVFSに追加
      await wasmModule.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/`);

      // Documentオブジェクトを作成
      const doc = wasmModule.Document.Create();

      // Word文書をロード
      doc.LoadFromFile({ fileName: inputFileName });

      // 新しいセクションを作成
      const section = doc.AddSection();
      // セクションに段落を追加
      const paragraph = section.AddParagraph();
      // セクションをカバーページの後に挿入
      doc.Sections.Insert(1, section);

      // カスタム目次を作成
      let toc = wasmModule.TableOfContent.Create(doc,`{\\o "1-2" \\n 1-1}`);

      // 目次を新しく作成した段落に追加
      paragraph.Items.Add(toc);

      // フィールドセパレーターとフィールド終了マークを挿入してTOCフィールドを終了
      paragraph.AppendFieldMark(wasmModule.FieldMarkType.FieldSeparator);
      paragraph.AppendFieldMark(wasmModule.FieldMarkType.FieldEnd);

      // この目次を文書の目次として設定
      doc.TOC = toc;

      // 目次を更新
      doc.UpdateTableOfContents();

      // VFSに文書を保存
      doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2019 });

      // VFSから文書を読み取る
      const wordArray = await wasmModule.FS.readFile(outputFileName);

      // Blobオブジェクトを生成し、Wordファイルのダウンロードをトリガー
      const blob = new Blob([wordArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `${outputFileName}`;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }
  };

  return (
      <div style={{ textAlign: 'center', height: '300px' }}>
        <h1>ReactでJavaScriptを使用してカスタム目次をWord文書に挿入する</h1>
        <button onClick={InsertTOCWord} disabled={!wasmModule}>
          挿入してダウンロード
        </button>
      </div>
  );
}

export default App;
```

結果
![2025-02-14_152556.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/b7adba94-e9d4-43c7-826f-d0ac4415d278.png)

本記事では、JavaScriptを使用してWord文書に目次を挿入する方法を示しました。