基于 Dmitry Stolbov 的回答以及它遇到的问题和限制以及我在下面的类中提供的其余响应,它实现了在段落和表格中搜索的方法 generateDocument。
在这里,我解决了回复中发现的几个问题,例如:
- .setText(x, 0) 替换而不是添加
- 包含“\t”的段落存在问题。当我们用这个字符运行 run.getText(int position) 时,我们会得到 null,所以我们不能在它上面使用 .contains()。
- 当要替换的 keyTag 拆分为多个运行时,合并运行在一起
这很好用,但我需要一些关于如何解决我遇到的问题的见解。有时文件中要替换的值大于要替换的标签,这最终会搞砸对齐。例如:
模板:
输出文件:
发生的事情是 {#branch#} 和 {#insurCompanyCorporateName#} 被更大的字符串替换,在 {#branch#} 标记之后有几个“\t”元素,再加上 { #insurCompanyCorporateName#} 值也大于标签,将内容向前推使其拆分到下一行。
我想知道是否有人对我在运行时如何理解我要替换的值是否使文档分割线或弄乱页面中其他元素的位置有一些见解。在这种情况下,我希望我的程序能够理解他应该在分支之后删除一些“\t”。或者可能将 {#insurCompanyCorporateName#} 拆分为新行,但使新行开始低于原始标签或其他内容。
想法?
班级:
package com.idoine.struts2.action.shared;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
/**
* Created by migue on 11/11/2020.
*/
public class DocumentGeneratorAction {
public static ByteArrayInputStream generateDocument(String templatePath, JSONObject fields){
/** used as reference: https://stackoverflow.com/a/49765239/5936443 [at 11/11/2020]
This method is responsible for generating a document as a ByteArrayInputStream, using an exisiting word template at templatePath
It replaces any keyTags in the document by the corresponding value in the JSONObject fields
it assumes the keyTags come preceeded by the separator "{#" and proceeded by "#}", in the following form: {#keyTag#}
*/
try {
XWPFDocument doc = new XWPFDocument(OPCPackage.open(templatePath));
// search in paragraphs
for(XWPFParagraph p : doc.getParagraphs()){
replaceFieldsParagraph(p, fields);
}
// search in tables
for(XWPFTable t : doc.getTables()){
replaceFieldsTable(t, fields);
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
doc.write(out);
ByteArrayInputStream inputStream = new ByteArrayInputStream(out.toByteArray());
return inputStream;
} catch (IOException e) {
e.printStackTrace();
} catch (InvalidFormatException e) {
e.printStackTrace();
}
return null;
}
public static void replaceFieldsParagraph(XWPFParagraph paragraph, JSONObject fields){
/** this method is responsible for replacing any ocurrences in the paragraph of any of the keyTags
* present in the JSONObject fields by the corresponding value */
String text = paragraph.getText(); //all the text from each run concatenated
String findStr;
if( !text.contains("{#")) //paragraph doesn't have keys to replace
return;
// for each field to replace, search it in the curr paragraph
for( String key : fields.keySet()){
findStr = "{#" + key + "#}";
// if paragraph doesn't have current key, we skip to next key
if( text.contains(findStr)) {
mergeRunsWithSplittedKeyTags(paragraph);
for (XWPFRun run : paragraph.getRuns()) {
// check if current run has current key
checkAndReplaceFieldRun(run, findStr, String.valueOf(fields.get(key)));
}
}
}
}
public static void replaceFieldsTable(XWPFTable table, JSONObject fields){
/** this method is responsible for replacing any ocurrences in the table of any of the keyTags
* present in the JSONObject fields by the corresponding value */
if( table.getNumberOfRows() > 0){
for(XWPFTableRow row : table.getRows()){ // iterate over rows
for( XWPFTableCell cell : row.getTableCells()){ // iterate over columns
if( cell.getParagraphs() != null && cell.getParagraphs().size()>0){
for(XWPFParagraph paragraph : cell.getParagraphs()){ // get cell paragraphs
replaceFieldsParagraph(paragraph, fields); // replacing existing keyTags in paragraph
}
}
}
}
}
}
public static void checkAndReplaceFieldRun(XWPFRun run, String findStr, String value){
String runText = run.getText(0);
if( runText!= null && runText.contains(findStr)){
runText = runText.replace(findStr, value);
run.setText(runText, 0);
}
}
public static void mergeRunsWithSplittedKeyTags(XWPFParagraph paragraph){
/**
A run is a part of the paragraph that has the same formatting.
Word separates the text in paragraphs by different runs in a almost 'random' way,
sometimes the tag we are looking for is splitted across multiple runs.
This method merges the runs that have a keyTag or part of one,
so that the keyTag starting with "{#" and ending with "#}" is in the same run
*/
String runText;
XWPFRun run, nextRun;
List<XWPFRun> runs = paragraph.getRuns();
for( int i=0 ; i<runs.size(); i++){
run = runs.get(i);
runText = run.getText(0);
if( runText != null &&
(runText.contains("{#") || // current run has the complete separator "{#"
(runText.contains("{") && (runs.get(i + 1).getText(0)!=null && runs.get(i + 1).getText(0).substring(0, 1).equals("#"))))){ //current run has the first char, next run has the second char
while( !openTagMatchesCloseTag(runText) ){
nextRun = runs.get(i + 1);
runText = runText + nextRun.getText(0);
paragraph.removeRun(i + 1);
}
run.setText(runText, 0); // if we don't set with arg pos=0 it doesn't replace the contents, it adds to them and repeats chars
}
}
}
public static boolean openTagMatchesCloseTag(String runText){
/** This method validates if we have a complete run.
* Either by having no keyTags present, or by having a complete keyTag.
* If we have parts of a keyTag, but not the complete one, returns false.*/
int incompleteOpenTagCount = runText.split("\\{", -1).length - 1; // "{"
int completeOpenTagCount = runText.split("\\{#", -1).length - 1; // "{#"
int completeCloseTagCount = runText.split("#}", -1).length - 1; // "#}"
if(completeOpenTagCount>0){ // we already have open and close tags, compare the counts
return completeOpenTagCount == completeCloseTagCount;
} else {
if( incompleteOpenTagCount>0 ){ // we only have a "{" not the whole "{#"
return false;
}
}
//doesn't have neither "{" nor "{#", so there's no need to close tags
return true;
}
}