POI を使い、WORD(docx)テンプレートを更新・出力する。


Spring Boot2 と Apache　POI を使い、 Word(docx)のテンプレートファイル内のテキストボックス内のテキスト、パラグラフ中のテキストを置換したファイルをダウンロードするサンプル。
localhost:8080/download/word へPOSTするとWordファイルのダウンロードが始まる。

[XlsMapper](http://mygreen.github.io/xlsmapper/sphinx/index.html) のような処理をやりたかったが、Wordファイル内のテンプレートキーとして入力された文字が、文字種類で分割されるなど容易に置換できそうになかったので、力技で。

参考：https://codeday.me/jp/qa/20190129/191063.html


```java:DownloadFilesController.java
	@Autowired
	WordBuilder wordBuilder;
	
	
    @PostMapping("/download/word")
    public ModelAndView  downloadWord(@RequestParam Map<String, String> requestMap) {
    	
    	ModelAndView mav = new ModelAndView(wordBuilder);

        return mav;
    	
    }
```


```java:WordBuilder.java
package jp.co.core.kenkei.view.sample.builder;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.xwpf.usermodel.IRunBody;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;

import lombok.extern.log4j.Log4j2;

@Log4j2
@Component
public class WordBuilder extends AbstractView {

	@Autowired
	private TemplateConfiguration configuration;

	private XWPFDocument createXWPFDocument() {

		File wordTemplateFile = new File(configuration.getWordTemplate());

		XWPFDocument workbook = null;
		try (

				InputStream is = new ByteArrayInputStream(

						Files.readAllBytes(wordTemplateFile.toPath()));

		) {

			workbook = new XWPFDocument(is);

		} catch (IOException | EncryptedDocumentException e) {
			log.error("create workbook error", e);

		}

		return workbook;
	}

	private void outputDocx(XWPFDocument doc) {

		for (XWPFParagraph p : doc.getParagraphs()) {
			List<XWPFRun> runs = p.getRuns();
			if (runs != null) {
				for (XWPFRun r : runs) {
					String text = r.getText(0);
					System.out.println("--- text=" + text);
					if (text != null && text.contains("あいさつ")) {
						text = text.replace("あいさつ", "リプレイス-HELLO");
						r.setText(text, 0);
					}
				}
			}
		}
		for (XWPFTable tbl : doc.getTables()) {
			for (XWPFTableRow row : tbl.getRows()) {
				for (XWPFTableCell cell : row.getTableCells()) {
					for (XWPFParagraph p : cell.getParagraphs()) {
						for (XWPFRun r : p.getRuns()) {
							String text = r.getText(0);
							System.out.println("TTT text=" + text);
							if (text.contains("needle")) {
								text = text.replace("needle", "haystack");
								r.setText(text);
							}
						}
					}
				}
			}
		}
	}

	private void replaceTextBox(XWPFDocument doc, String word1, String word2) throws XmlException {

		for (XWPFParagraph paragraph : doc.getParagraphs()) {
			XmlCursor cursor = paragraph.getCTP().newCursor();
			cursor.selectPath(
					"declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:txbxContent/w:p/w:r");

			List<XmlObject> ctrsintxtbx = new ArrayList<XmlObject>();

			while (cursor.hasNextSelection()) {
				cursor.toNextSelection();
				XmlObject obj = cursor.getObject();
				ctrsintxtbx.add(obj);
			}
			
			for (XmlObject obj : ctrsintxtbx) {
				
				CTR ctr = CTR.Factory.parse(obj.xmlText());
				
				// CTR ctr = CTR.Factory.parse(obj.newInputStream());
				XWPFRun bufferrun = new XWPFRun(ctr, (IRunBody) paragraph);
				
				String text = bufferrun.getText(0);
				System.out.println("::::: " + text);
				
				if (text != null && text.contains("文字壱") ) {
					text = text.replace(text,   text + word1 );
					bufferrun.setText(text, 0);
					obj.set(bufferrun.getCTR());
					continue;
				}
				
				if (text != null && text.contains("文字弐") ) {
					text = text.replace(text,   text + word2 );
					bufferrun.setText(text, 0);
					obj.set(bufferrun.getCTR());
					continue;
				}
				
				if(text == null) {
					continue;
				}
				
				text = text.replace(text,  "replaced:" + text );
				bufferrun.setText(text, 0);
				obj.set(bufferrun.getCTR());
			}
		}

//		  document.write(new FileOutputStream("WordReplaceTextInTextBoxNew.docx"));
//		  document.close();

	}

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		final String encodedFilename = URLEncoder.encode("Wordダウンロード：サンプル.docx", "UTF-8");
		response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);

		XWPFDocument document = createXWPFDocument();

		outputDocx(document);
		replaceTextBox(document, request.getParameter("word1"), request.getParameter("word2") );

		response.setContentType("application/msword");

		document.write(response.getOutputStream());
		if (document != null) {
			document.close();
		}

	}

}


```