Apache POIでExcelファイルのオートシェイプ内のテキストを置換する

# Apache POIでExcelファイルのオートシェイプ内のテキストを置換する

## 前提

- 試行錯誤して動いた内容をまとめています
- Excelファイルは.xlsx形式を対象としています
- この記事では簡単な例として1文字列を置換してみます

## 制限

- 1文字列の中で文字ごとに別々の書式設定がされている場合は対応できません
- グループ化されたオートシェイプには対応できません（あらかじめグループ化を解除しておけばOK）

## コード

Githubにもコードを置いてます。

https://github.com/sadapon2008/autoshape-text-replace

```java:src/main/java/sadapon2008/Application.java
package sadapon2008;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFShape;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFSimpleShape;
import org.apache.poi.xssf.usermodel.XSSFTextParagraph;
import org.apache.poi.xssf.usermodel.XSSFTextRun;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody;

public class Application {

	public static void main(String[] args) {
		// コマンドライン引数のチェックと取得
		if (args.length < 4) {
			System.exit(1);
		}
		// 置換対象の文字列
		String textTarget = args[0];
		// 置換後の文字列
		String textReplacement = args[1];
		// 置換対象の.xlsxファイル
		String filenameSrc = args[2];
		// 置換後に作成される.xlsxファイル
		String filenameDest = args[3];
		
		try {
			// 出力先ファイルをコピーしてから書き換える
			Files.copy(Paths.get(filenameSrc), Paths.get(filenameDest), StandardCopyOption.REPLACE_EXISTING);
			OPCPackage pkg = OPCPackage.open(new FileInputStream(filenameDest));
			XSSFWorkbook workBook = new XSSFWorkbook(pkg);
			// シートごとに処理する
			int n = workBook.getNumberOfSheets();
			for (int i = 0; i < n; i++) {
				XSSFSheet sheet = workBook.getSheetAt(i);
				XSSFDrawing drawing = sheet.createDrawingPatriarch();
				// オートシェイプごとに処理する
				for (XSSFShape shape : drawing.getShapes()) {
					if (!(shape instanceof XSSFSimpleShape)) {
						// グループ化したオートシェイプには非対応
						continue;
					}
					// グループ化してないオートシェイプに対して処理する
					XSSFSimpleShape simpleShape = (XSSFSimpleShape)shape;
					CTTextBody textBody = simpleShape.getCTShape().getTxBody();
					if (null == textBody) {
						continue;
					}
					for (XSSFTextParagraph textParagraph : simpleShape.getTextParagraphs()) {
						for (XSSFTextRun textRun : textParagraph.getTextRuns()) {
							// 書式設定がされている単位のテキストを置換していく
							// そのため文字単位で書式設定が違う場合には非対応
							textRun.setText(textRun.getText().replace(textTarget, textReplacement));
						}
					}
				}
			}
			FileOutputStream fileOut = new FileOutputStream(filenameDest);
			workBook.write(fileOut);
			fileOut.close();
			workBook.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
```

## ビルド＆実行例

gradleでビルドする場合は以下のようなbuild.gradleを使います。

```build.gradle
apply plugin: 'java'
apply plugin:'application'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'

mainClassName = 'sadapon2008.Application'

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.apache.poi', name: 'poi', version: '3.15'
    compile group: 'org.apache.poi', name: 'poi-ooxml', version: '3.15'

    testCompile 'junit:junit:4.12'
}

run {
    if (project.hasProperty("appArgs")) {
        args Eval.me(appArgs)
    }
}
```

そしてgradlewで以下のように実行します。

```
./gradlew run -PappArgs="['置換前','置換後','input.xlsx','output.xlsx']"
```