💡 完全な動作例は GitHub で入手できます:
office-metadata-pii-cleanup-nodejs

はじめに

アップロード エンドポイントはスタッフ メンバーから DOCX を受け取り、顧客チケットに保存します。テキスト自体は問題ありませんが、プロパティは問題です。ファイルには作成者、最後に保存した同僚、社内テンプレートの部門マネージャー、そして SharePoint から取得したため承認者の情報が含まれています。

メタデータ サニタイザーは、ファイルが保存される前にこれらのプロパティを削除し、作業結果を確認する小さなスクリプトです。このチュートリアルでは、Node.js と GroupDocs.Metadata を使用して、タグでプロパティを選択、名前で選択、選択的に削除できなくなったらすべて削除、そして残りを検証するという 4 つのステップで作成します。各ステップは数行で、完成したスクリプトは 100 行未満です。

メタデータ サニタイズが重要な理由

データは誰も選択せずに蓄積されます。Word は保存のたびに OS アカウントから AuthorLastSavedBy を書き込み、リビジョン カウンタを保持し、TotalEditingTimeLastPrinted を記録します。ドキュメント サーバーはチェックイン時にワークフロー パス、承認者 ID、コンテンツ タイプ URI を追加します。これらは文書を読んだり印刷したりしても表示されないため、校正時に検出されません。

手作業ではなく Node.js で行う利点は、スクリプトが数値を返すことです。各削除呼び出しは削除されたプロパティ数を報告し、そのカウントはログに書き込んだりテストでアサートしたり、文書が属するレコードに添付したりできます。

もう 1 つの理由は、バッチ ジョブが走っているときに顕在化します。手動でのクリーンアップはファイルごとに担当者の判断で行われるため、同じ種類の文書を 2 人がサニタイズすると結果が異なります。スクリプトはルールを 1 カ所に固定し、同じ 4 つのタグ、同じサブストリング リストを、キューに 3 ファイルでも 3 千ファイルでも同様に適用します。

前提条件

このパッケージは Java 経由の Node.js なので、マシンには Java ランタイムと Node が必要です。

インストール

npm install @groupdocs/groupdocs.metadata

サンプル プロジェクトはバージョン 26.7 を固定し、overrides エントリで nan^2.22.0 に設定しています。これにより現在の Node リリースでもネイティブ バインディングのビルドが可能になります。ライセンス ファイルが無い場合、ライブラリは評価モードで動作し、ここで示すすべての手順を実行するのに十分です。

ステップ 1 - 意味でプロパティを選択

フォーマットやパッケージによってプロパティ名は異なるため、最初のルールはタグでマッチさせます。ContainsTagSpecification はタグを受け取り、そのタグを持つすべてのプロパティにマッチします。.or() は複数の仕様を 1 つに統合します。

const T = groupdocs.Tags;
const spec = new groupdocs.ContainsTagSpecification(T.getPerson().getCreator())
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getEditor()))
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getManager()))
  .or(new groupdocs.ContainsTagSpecification(T.getCorporate().getCompany()));
const affected = metadata.removeProperties(spec);
metadata.save(outputPath);

重要ポイント:

  • 4 つのタグが ID グループをカバーします: 作成者、編集者、マネージャー、企業の会社フィールド。
  • Title、Subject、Keywords はそのまま残ります。これにより、これらをキーにしたレコード インデックスは機能し続けます。
  • removeProperties は真偽値ではなく、影響を受けた件数を返します。

全体を try/finally で囲み、finallymetadata.close() を呼び出してください。バインディングはその間ファイルを開いたままにし、finally が無いループはハンドル不足になります。

ステップ 2 - 名前でプロパティを選択

コメント スレッド、リビジョン カウンタ、サーバー フィールドにはタグが付いていません。これらには WithNameSpecification(needle, false) を使用し、名前にサブストリングが含まれるプロパティにマッチさせます。4 行のビルダーでサブストリングごとにチェーンします。

let spec = null;
for (const needle of needles) {
  const s = new groupdocs.WithNameSpecification(needle, false /* fullMatch */);
  spec = spec ? spec.or(s) : s;
}
return spec;

3 回のパスで異なるリストを使用します。まずコメントから:

const affected = metadata.removeProperties(
  nameContainsSpec(['Comment', 'Reviewer', 'Reviewed']));
metadata.save(outputPath);

次に編集タイムラインは、文書の作成過程を示す重要なグループです:

const affected = metadata.removeProperties(nameContainsSpec([
  'Revision', 'TrackedChange', 'LastPrinted', 'TotalEditingTime', 'EditTime',
]));
metadata.save(outputPath);

SharePoint 用パスは同じ呼び出しで Server, Workflow, Approver, ContentType, Template を追加します。サブストリング マッチングは意図的で、CommentsCountComment と同時に捕捉でき、フォーマットごとの正確な名前リストを維持する必要がありません。

ステップ 3 - 選択的に削除できなくなったらすべて削除

組織外に出すコピーの場合、1 回の呼び出しで 4 つのパスを置き換えます。

const affected = metadata.sanitize();
metadata.save(outputPath);

sanitize() はライブラリが検出したすべてのメタデータ パッケージ(カスタム OOXML パーツを含む)をクリアし、件数は通常、対象パスの合計を上回ります。Title と Subject も削除対象になるため、レビュー ループの中ではなく境界で実行すべきです。

ステップ 4 - 検証(サイレントミスは成功に見えるため)

スキャンは findProperties を通じて同じ仕様を再利用します。findProperties は書き込みなしで読み取ります。結果は Java コレクションなのでインデックスで走査します。

const props = metadata.findProperties(tagSpec.or(nameSpec));
for (let i = 0; i < props.getCount(); i++) {
  const p = props.get_Item(i);
  const val = p.getValue && p.getValue();
  const value = val ? String(val.getRawValue ? val.getRawValue() : val) : '';
  if (!value || value === '0' || value === '0.0') continue;
  leaks.push(`${p.getName()}=${value}`);
}

空文字とゼロのフィルタは正当です。0 にクリアされたリビジョン カウンタで走査が失敗した経験から追加しました。このフィルタがなければ、残存プロパティとして正しく報告されてしまいます。

完全な動作例

リポジトリは 6 つの関数を index.js に組み込み、ライセンスを適用し、resources/pii-sample.docx に対して各パスを実行し、すべての出力ファイルの存在をアサートし、漏れリストが空であることを最終的に確認します。アサートが失敗すると非ゼロコードで終了するため、CI のチェックとして機能し、単なるデモ以上の価値があります。

1 つ覚えておくべきポイントは、各パスが同じソース ファイルを読み取り、別々の出力に書き込む点です。これにより影響件数が独立し、コメント パスのログ行は ID ルール実行後の残りではなく、コメント ルールが見つけた件数を示します。

sanitize() の代わりにターゲット パスを実行すべきタイミングは?

文書がまだ使用中のときです。レビューア間で回るファイルは Title、Subject、Keywords を検索や分類に利用しますが、sanitize() はこれら 3 つと個人データをすべて削除します。コラボレーション中は ID とコメント パスだけを実行し、記述フィールドは残したままにし、最終的に組織外へ出すコピーで全削除を行います。

実務での活用例

アップロード ハンドラ

Express ルートで添付ファイルをサニタイズし、チケットに影響件数を記録、漏れリストが空でなければアップロードを拒否します。

夜間エクスポート ジョブ

ワーカーがエクスポート フォルダーを走査し、ID とサーバー パスを適用。文書に残存 PII がある場合は警告ではなくジョブを失敗させます。

公開前ゲート

ビルド ステップでドキュメント添付ファイルをサニタイズし、sanitize() を使用します。これらのファイルではメタデータを保持する必要がないためです。

ベスト プラクティスとヒント

  • 常に新しいパスに書き込み、元のファイルは紛争解決のために残しておく。
  • ループ内では特に finally ブロックでメタデータ オブジェクトをクローズする。
  • バッチ ジョブでは .or() で仕様をマージ。1 回のオープンと 1 回の保存で 4 回分の処理を済ませる方が効率的。
  • パスごとの影響件数(ゼロも含む)をログに残す。未対応フォーマットがある場合に可視化できる。

よくある問題のトラブルシューティング

汚染されていることが分かっている文書で件数が 0 になる
入力フォーマットが認識されているか確認してください。未読ファイルとクリーンなファイルは同じく 0 を返します。

漏れチェックが削除したばかりのプロパティを報告する
入力パスではなく、保存された出力パスを対象にしてください。スキャンは指定されたファイルを読み取ります。

Word でコメント バルーンがまだ表示される
コメントテキストは文書本体にあり、メタデータ パッケージには含まれません。GroupDocs.Metadata はコメント関連プロパティをクリアしますが、バルーン自体の削除には Aspose.Words などのコンテンツ 編集ライブラリが必要です。

結論

4 ステップ、6 関数、1 スクリプトで実行結果をレポート。タグ仕様はフォーマット横断的に ID グループを処理し、名前仕様はタグで分類できないファミリーをカバー、sanitize() が境界を処理し、漏れスキャンが全体をチェックに変換します。リポジトリをクローンし、実際のレビュー ラウンドを経た文書で実行し、件数を確認した上でパイプラインに必要なパスを選択してください。

追加リソース