Word文書のキーワード置換とPDF変換の課題
Word文書内のキーワードを置換し、PDF形式に変換する処理において、主に以下の課題が存在します:
- Wordの内部構造ではテキストが複数のRunコンポーネントに分割されるため、キーワードが複数のRunに跨って存在する可能性がある
- 単純な文字列置換では元の書式情報が失われる
- ExcelからコピーされたテーブルのPDF変換時にコンテンツ消失の問題が発生する
解決アプローチ
段落全体のテキストを取得し、キーワードの存在を確認した後、キーワードの開始位置と終了位置を含むRunを特定して処理します。先頭と末尾のRunは部分的な置換を行い、中間のRunは空文字列に置換することで、書式を保持したキーワード置換を実現します。
実装コード
public class WordPdfConverter {
/**
* Word文書をPDFに変換し、指定されたパラメータを置換
*/
public static void convertWordToPdf(InputStream input, OutputStream output,
PdfOptions pdfOptions, Map<String, String> replacements) throws Exception {
XWPFDocument document = new XWPFDocument(input);
replaceKeywords(document, replacements);
PdfConverter.getInstance().convert(document, output, pdfOptions);
}
/**
* ドキュメント内のすべてのキーワードを置換
*/
private static void replaceKeywords(XWPFDocument doc, Map<String, String> replacementMap) {
// 段落内のキーワード置換
for (XWPFParagraph paragraph : doc.getParagraphs()) {
replaceInParagraph(paragraph, replacementMap);
}
// テーブルセル内のキーワード置換
for (XWPFTable table : doc.getTables()) {
for (XWPFTableRow row : table.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
for (XWPFParagraph paragraph : cell.getParagraphs()) {
replaceInParagraph(paragraph, replacementMap);
}
}
}
}
}
/**
* 個別の段落内でキーワード置換を実行
*/
private static void replaceInParagraph(XWPFParagraph para, Map<String, String> replacements) {
for (Map.Entry<String, String> entry : replacements.entrySet()) {
processKeywordReplacement(para, entry.getKey(), entry.getValue());
}
}
/**
* 特定のキーワードに対する置換処理
*/
private static void processKeywordReplacement(XWPFParagraph para, String target, String replacement) {
String paraText = para.getText();
int startIndex = paraText.indexOf(target);
while (startIndex >= 0) {
int endIndex = startIndex + target.length();
int currentPosition = 0;
boolean foundStart = false;
for (int i = 0; i < para.getRuns().size(); i++) {
XWPFRun run = para.getRuns().get(i);
String runContent = run.getText(run.getTextPosition());
if (runContent == null) continue;
if (!foundStart) {
if (currentPosition + runContent.length() > startIndex) {
// キーワード開始Runの処理
String prefix = runContent.substring(0, startIndex - currentPosition);
run.setText(prefix + replacement, 0);
foundStart = true;
}
currentPosition += runContent.length();
} else {
if (currentPosition >= endIndex) break;
if (currentPosition + runContent.length() <= endIndex) {
// 中間Runを空文字に置換
run.setText("", 0);
currentPosition += runContent.length();
} else {
// キーワード終了Runの処理
String suffix = runContent.substring(endIndex - currentPosition);
run.setText(suffix, 0);
break;
}
}
}
startIndex = paraText.indexOf(target, startIndex + replacement.length());
}
}
/**
* デバッグ用:Runの位置情報を出力
*/
private static void debugRunPositions(XWPFParagraph para) {
int pos = 0;
Map<Integer, XWPFRun> positionMap = new HashMap<>();
for (XWPFRun run : para.getRuns()) {
String text = run.getText(run.getTextPosition());
if (text != null) {
for (int i = 0; i < text.length(); i++) {
positionMap.put(pos + i, run);
}
pos += text.length();
}
}
System.out.println(positionMap);
}
}
使用例
public class Main {
public static void main(String[] args) throws Exception {
String inputPath = "/path/to/input.docx";
String outputPath = "/path/to/output.pdf";
InputStream source = new FileInputStream(inputPath);
OutputStream target = new FileOutputStream(outputPath);
Map<String, String> replacements = new HashMap<>();
replacements.put("置換対象1", "新しい値1");
replacements.put("置換対象2", "新しい値2");
PdfOptions options = PdfOptions.create();
WordPdfConverter.convertWordToPdf(source, target, options, replacements);
}
}
必要な依存関係
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
<version>1.0.6</version>
</dependency>
</dependencies>
注意事項
- ExcelからコピーされたテーブルはPDF変換時に内容が消失する可能性がある
- DOC形式のファイルは事前にDOCX形式に変換する必要がある
- Runの削除操作が正常に機能しない場合は空文字列への置換で対応