假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击
一,网上的API讲解
其实POI的生成Word文档的规则就是先把获取到的数据转成xml格式的数据,然后通过xpath解析表单式的应用取值,判断等等,然后在把取到的值放到word文档中,最后在输出来。
1.1,参考一
1、poi之word文档结构介绍之正文段落
一个文档包含多个段落,一个段落包含多个Runs,一个Runs包含多个Run,Run是文档的最小单元
获取所有段落:List<XWPFParagraph> paragraphs = word.getParagraphs();
获取一个段落中的所有Runs:List<XWPFRun> xwpfRuns = xwpfParagraph.getRuns();
获取一个Runs中的一个Run:XWPFRun run = xwpfRuns.get(index);
2、poi之word文档结构介绍之正文表格
一个文档包含多个表格,一个表格包含多行,一行包含多列(格),每一格的内容相当于一个完整的文档
获取所有表格:List<XWPFTable> xwpfTables = doc.getTables();
获取一个表格中的所有行:List<XWPFTableRow> xwpfTableRows = xwpfTable.getRows();
获取一行中的所有列:List<XWPFTableCell> xwpfTableCells = xwpfTableRow.getTableCells();
获取一格里的内容:List<XWPFParagraph> paragraphs = xwpfTableCell.getParagraphs();
之后和正文段落一样
注:
- 表格的一格相当于一个完整的docx文档,只是没有页眉和页脚。里面可以有表格,使用xwpfTableCell.getTables()获取,and so on
- 在poi文档中段落和表格是完全分开的,如果在两个段落中有一个表格,在poi中是没办法确定表格在段落中间的。(当然除非你本来知道了,这句是废话)。只有文档的格式固定,才能正确的得到文档的结构
3、poi之word文档结构介绍之页眉:
一个文档可以有多个页眉(不知道怎么会有多个页眉。。。),页眉里面可以包含段落和表格
获取文档的页眉:List<XWPFHeader> headerList = doc.getHeaderList();
获取页眉里的所有段落:List<XWPFParagraph> paras = header.getParagraphs();
获取页眉里的所有表格:List<XWPFTable> tables = header.getTables();
之后就一样了
4、poi之word文档结构介绍之页脚:
页脚和页眉基本类似,可以获取表示页数的角标
1.2,参考二
POI操作Word简介
POI读写Excel功能强大、操作简单。但是POI操作时,一般只用它读取word文档,POI只能能够创建简单的word文档,相对而言POI操作时的功能太少。
(2)POI创建Word文档的简单示例
XWPFDocument doc = new XWPFDocument();// 创建Word文件
XWPFParagraph p = doc.createParagraph();// 新建一个段落
p.setAlignment(ParagraphAlignment.CENTER);// 设置段落的对齐方式
p.setBorderBottom(Borders.DOUBLE);//设置下边框
p.setBorderTop(Borders.DOUBLE);//设置上边框
p.setBorderRight(Borders.DOUBLE);//设置右边框
p.setBorderLeft(Borders.DOUBLE);//设置左边框
XWPFRun r = p.createRun();//创建段落文本
r.setText("POI创建的Word段落文本");
r.setBold(true);//设置为粗体
r.setColor("FF0000");//设置颜色
p = doc.createParagraph();// 新建一个段落
r = p.createRun();
r.setText("POI读写Excel功能强大、操作简单。");
XWPFTable table= doc.createTable(3, 3);//创建一个表格
table.getRow(0).getCell(0).setText("表格1");
table.getRow(1).getCell(1).setText("表格2");
table.getRow(2).getCell(2).setText("表格3");
FileOutputStream out = newFileOutputStream("d:\\POI\\sample.doc");
doc.write(out);
out.close();
(3)POI读取Word文档里的文字
FileInputStream stream = newFileInputStream("d:\\POI\\sample.doc");
XWPFDocument doc = new XWPFDocument(stream);// 创建Word文件
for(XWPFParagraph p : doc.getParagraphs())//遍历段落
{
System.out.print(p.getParagraphText());
}
for(XWPFTable table : doc.getTables())//遍历表格
{
for(XWPFTableRow row : table.getRows())
{
for(XWPFTableCell cell : row.getTableCells())
{
System.out.print(cell.getText());
}
}
1.3,参考三,分段混乱
题:在操作POI替换world时发现getRuns将我们预设的${product}自动切换成了
${product, }]${product }成了两个部分
- 1
- 2
- 3
解决方法一。(未尝试) 强制把List中的内容合并成一个字符串,替换内容后,把段落中的XWPFRun全部remove掉,然后新建一个含有替换后内容的XPWFRun,并赋给当前段落。 解决方法二. 请用复制粘贴把你的${product}添加进world文档里面即可解决,不要手打 目前发现复制粘贴是没有问题的,感觉像是poi的一个bug不知道立贴为证。
注意:${这里尽量不要存中文,否在还出现上面情况}
二,项目应用
2.1,判断生成word的条件
1 private boolean getXpathRes(String json,String xpathRule){ 2 boolean isTrue = false; 3 try { 4 JSONObject obj = getGoodJson(json, json.replaceAll("\n", "").replaceAll("\"null\"", "\"\"").replaceAll(":null,", ":\"\",").replaceAll(" \"", "\"")); 5 XMLSerializer serializer = new XMLSerializer(); 6 String xml = serializer.write(obj,"UTF-8"); 7 log.info("测试用的,记得删除"+xml); 8 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 9 dbf.setValidating(false);10 DocumentBuilder db;11 db = dbf.newDocumentBuilder();12 StringReader stringReader = new StringReader(xml);13 InputSource inputSource = new InputSource(stringReader);14 Document doc;15 doc = db.parse(inputSource);16 XPathFactory factory = XPathFactory.newInstance();17 XPath xpath = factory.newXPath();18 isTrue = (Boolean) xpath.evaluate(xpathRule, doc,XPathConstants.BOOLEAN);19 } catch (Exception e) {20 log.info("合同解析生成XML报错:"+e.getMessage());21 }finally{22 return isTrue;23 }24 // return true;25 }
2.1.1,下面就是根据从数据库中取到值,判断规则,和json数据做对比的,就是json数据中有没有数据库中要的值。判断规则是xpath的规则运算符。
JSONObject obj = getGoodJson(json, json.replaceAll("\n", "").replaceAll("\"null\"", "\"\"").replaceAll(":null,", ":\"\",").replaceAll(" \"", "\""));
XMLSerializer serializer = new XMLSerializer(); String xml = serializer.write(obj,"UTF-8");--把json格式的数据以xml的格式输出首先得到:得到 DOM 解析器的工厂实例
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();然后从 DOM 工厂获得 DOM 解析器
dbf.setValidating(false);默认是false
DocumentBuilder db;db = dbf.newDocumentBuilder();当你有一组应用程序接口(API)只允许用Writer或Reader作为输入,但你又想使用String,这时可以用StringWriter或StringReader。
当读入文件时也一样。可以用StringReader代替Reader来哄骗API,而不必非得从某种形式的文件中读入。StringReader的构造器要求一个String参数。例如:xmlReader.parse(new InputSource(new StringReader(xmlStr)));StringReader stringReader = new StringReader(xml); --- 把符合xml的String转成document对象被java程序解读 StringReader stringReader = new StringReader(xml); InputSource inputSource = new InputSource(stringReader); Document doc; doc = db.parse(inputSource); --用xpath解析 --生成xpath对象 XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); 在 Java 中计算 XPath 表达式时,第二个参数指定需要的返回类型。有五种可能,都在javax.xml.xpath.XPathConstants 类中命名了常量:XPathConstants.NODESET
XPathConstants.BOOLEANXPathConstants.NUMBERXPathConstants.STRINGXPathConstants.NODE 获取节点 node.getTextContent() 获得节点的内容xpathRule:数据库中存储的//industrySubType!='20' and //industrySubType!='21' and //industrySubType!='22' and //industrySubType!='23' and //industrySubType!='26' and //industrySubType!='27' and //industrySubType!='28' and //industrySubType!='29' and //industrySubType!='30' and //industrySubType!='148' and //industrySubType!='31' and //industrySubType!='32' and //industrySubType!='37' and //industrySubType!='38' and //industrySubType!='39' and //industrySubType!='11' and //industrySubType!='12' and //industrySubType!='13' and //industrySubType!='14' and //industrySubType!='15' and //industrySubType!='16'//标示节点中的所有的xml节点
doc就是经过一系列处理,把json数据转化成document对象,并且能被xpath解读的对象:XPathConstants.BOOLEAN:是返回值,有这个数据就返回true,没有就是false
isTrue = (Boolean) xpath.evaluate(xpathRule, doc,XPathConstants.BOOLEAN);
这里需要见xpath的解析规则
2.2,获取模板之后,开始获取里面的参数,这个参数是在数据库中配置的。
1 private List
1 @SuppressWarnings("finally") 2 private String getXpathValue(String json,String xpathRule){ 3 String xpathValue = "/"; 4 try { 5 JSONObject obj = getGoodJson(json, json.replaceAll("\n", "").replaceAll("\"null\"", "\"\"").replaceAll(":null,", ":\"\",").replaceAll(" \"", "\"")); 6 XMLSerializer serializer = new XMLSerializer(); 7 String xml = serializer.write(obj,"UTF-8"); 8 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 9 dbf.setValidating(false);10 DocumentBuilder db;11 db = dbf.newDocumentBuilder();12 StringReader stringReader = new StringReader(xml);13 InputSource inputSource = new InputSource(stringReader);14 Document doc;15 doc = db.parse(inputSource);16 XPathFactory factory = XPathFactory.newInstance();17 XPath xpath = factory.newXPath();18 Node node = (Node)xpath.evaluate(xpathRule, doc,XPathConstants.NODE);19 if(node==null){20 return "/";21 }22 else{23 if(StringUtils.isEmpty(node.getTextContent())){24 return "/";25 }else{26 xpathValue = node.getTextContent();27 return node.getTextContent();28 }29 }30 } catch (Exception e) {31 log.info("合同解析生成XML报错,xpathRule==="+xpathRule);32 return "/";33 }finally{34 return xpathValue;35 }36 // return "/";37 }
划红线的是和一开始的是不一样的,这里是获取json转化过来的xml的文档的node节点的值的。
而
isTrue = (Boolean) xpath.evaluate(xpathRule, doc,XPathConstants.BOOLEAN); 是通过xpath判断
xpathRule有没有在经过封装的xml文件的document里面的。
orginParamList = econtractTemplateParamAdvancedService.queryEcontractTemplateParamAdvancedByMap(queryOriginParam);
--根据模板名称获取res里面装的是json转化成的map数据,根据数据库查出来的key值,去取map中的value值。并全部放在map中来。
根据数据库中参数的设置来看取值的逻辑取值逻辑1:
callmethod 空 methodparam 空则什么都不往map中放取值逻辑2:
callmethod 空 methodparam 有值:比如//merchantName它的取值逻辑主要也是用到了上面的xpath的取值逻辑。则去json转化的map中查找,找到数值后则放进map中来取值逻辑3:
callmethod 空 methodparam 有值:比如//isApiPayToBank=1 or //isApiPayToBill=1 or //isBatchApiPayToBank=1其实它和2的取值逻辑是一样的,只不过xpath的表单式不一样而已。取值逻辑4:
callmethod 有值,方法名比如setMerchantProperty methodparam 有值:比如参数address或者IpAddress_sin_99bill/IpAddress_sin_ban/IpAddress_bat_ban 可以放值多个参数这个一般是在json格式的数据中没有这个值,但是还要获取这个数据,通过反射找到setMerchantProperty 这个方法从数据库中其它表中来获取。address它是参数,需要根据它往反射类中的反射方法中传递的参数。而存在map中的key值则是word文档中的命名规则,比如input。value值则是从数据库中获取的。则会通过反射的方法来获取数值。可以看出来只要是input开头都是通过这个方法获取的。这个主要是为了给替换做准备的。反射的逻辑详见
取值逻辑5:
假如上面的4套规则还不能解决一些问题的话,则通过代码直接来设置map的key和value值,放到map中来,比如说当前的时间等等。 rsMap.put(paramAdvancde.getFieldName(), res.get(paramAdvancde.getFieldName()));最后通过得到的数据就是
1 [{payBankNum=/, cwmerchantContactEmail=222@qq.com, masterDay=11, masterContractNum=K18-2000-801-03, depositAccount=/, masterYear=2018, cwmerchantContactCz=/, masterMonth=2, billAccountShareAmount=/, merchantName=还有几天就要放假了呀, year=2018, telephoneormobilephone=18068334025, legalName=222, input28=名字, ddmerchantContactTelphone=18068334025, ddmerchantContactEmail=222@qq.com, bankName=/, cwmerchantContactName=名字, cwmerchantContactTelphone=18068334025, input3=广西壮族自治区贵港市, input5=3333, ddmerchantContactName=名字, input6=3333, input20=2018年 02月 11日, month=2, ddmerchantContactCz=/, merechantURL=/, input23=2019年 02月 10日, day=11, contract_num=K18-2000-801-03, templateName=dsmainfinancialword.docx, accessType=首选, bankAccount=/}, {perMinimum=, perMinimum_pri_bat_ban=, payToCompayKqAcccountRatio=/, payBankNum=/, input69=3333, perMinimum_pub_bat_99bill=, input19=/, payToCompayAcccount=□, input18=□, autoDiectPayOpenFee=/, perMaximum_pub_bat_99bill=/, masterDay=11, input13=□, masterYear=2018, masterMonth=2, holidayPayOpenFee=/, year=2018, holidayPayTechnologyFee=/, memberCode=10012401604, telephoneormobilephone=18068334025, accumulateQuota=/, groupPayCompayKqAccountRatio=/, perMinimum_pri_sin_ban=, perMaximum=/, apiZdPayOpenFee=/, KqPayXinShi=■, bankName=/, perMinimum_pri_bat_99bill=, cwmerchantContactTelphone=18068334025, perMaximum_pri_bat_99bill=/, ddmerchantContactName=名字, BankName_bat_ban_hidden=/, AccountNum_bat_ban=/, month=2, day=11, idContent=234324fds432@qq.com, contract_num=K18-2000-801-03, apiZdPayTechnologyFee=/, perMaximum_pub_bat_ban=/, cwmerchantContactEmail=222@qq.com, holidayPay=□, perMaximum_pri_sin_99bill=/, perMinimum_pub_sin_99bill=, masterContractNum=K18-2000-801-03, groupPayCompayBankAccountRatio=/, depositAccount=/, groupPayToPersonKqAccountRatio=/, cwmerchantContactCz=/, groupPayToPersonBankAccountRatio=/, merchantName=还有几天就要放假了呀, AccountName_bat_ban=/, isBatchApiPayToBank=□, perMaximum_pri_bat_ban=/, perMinimum_pri_sin_99bill=, perMaximum_pub_sin_ban=/, input28=□, ddmerchantContactEmail=222@qq.com, ddmerchantContactTelphone=18068334025, perMinimum_pub_sin_ban=, payToCompayBankAcccountRatio=/, payPersonBankAccountRatio=/, KqPayXinShiFeeRatio=0, payPersonAccountRatio=/, cwmerchantContactName=名字, input3=□, input7=□, input6=/, input9=□, perMaximum_pub_sin_99bill=/, autoDiectPay=□, input22=□, ddmerchantContactCz=/, input24=□, input26=□, perMinimum_pub_bat_ban=, bankAccount=/, templateName=dspayfinancialaddwordfir.docx, perMaximum_pri_sin_ban=/}, {telephoneormobilephone=18068334025, ddmerchantContactEmail=222@qq.com, ddmerchantContactTelphone=18068334025, bankName=/, payBankNum=/, cwmerchantContactEmail=222@qq.com, cwmerchantContactName=名字, cwmerchantContactTelphone=18068334025, masterDay=11, masterContractNum=K18-2000-801-03, ddmerchantContactName=名字, depositAccount=/, masterYear=2018, cwmerchantContactCz=/, masterMonth=2, month=2, merchantName=还有几天就要放假了呀, year=2018, ddmerchantContactCz=/, day=11, contract_num=K18-2000-801-03, templateName=quickSubjectSeal.docx, bankAccount=/}]
至于上面的KqPayXinShi=■则是按照上面的判断规则直接获取就行了
if(getXpathRes(json,xpath)){
rsMap.put(paramAdvancde.getFieldName(),"■"); }else{ rsMap.put(paramAdvancde.getFieldName(),"□"); }2.3,替换模板参数
1 private List> replaceParam(List > templatParamList,String contractNum,long contractType) 2 { 3 List > originParamList = new ArrayList >(); 4 if(!templatParamList.isEmpty()) 5 { 6 String templateName = ""; 7 Map resultMap = null; 8 Map queryOriginParam = new HashMap (); 9 List aTemplateParamList = new ArrayList ();10 for(Map originParam : templatParamList)11 { 12 templateName = (String) originParam.get("templateName");13 queryOriginParam.put("templateName", templateName);14 15 //获取每个模板需要替换的参数16 aTemplateParamList = econtractTemplateParamAdvancedService.queryEcontractTemplateParamAdvancedByMap(queryOriginParam);17 if(!aTemplateParamList.isEmpty())18 {19 resultMap = new HashMap ();20 for(EcontractTemplateParamAdvanced replaceParams : aTemplateParamList)21 {22 if(originParam.containsKey(replaceParams.getFieldName()))23 {24 //TODO 调用参数替换规则脚本 未完待续....25 resultMap.put(replaceParams.getAliasField(), originParam.get(replaceParams.getFieldName()));26 }27 }28 resultMap.put("templateName", templateName);29 resultMap.put("contract_num", contractNum);30 resultMap.put("contract_type", contractType);31 originParamList.add(resultMap);32 }33 }34 }35 log.info("替换模板参数成功..");36 return originParamList;37 }
1 if(originParam.containsKey(replaceParams.getFieldName()))2 3 originParam:4 {payBankNum=/, cwmerchantContactEmail=222@qq.com, masterDay=11, masterContractNum=K18-2000-801-03, depositAccount=/, masterYear=2018, cwmerchantContactCz=/, masterMonth=2, billAccountShareAmount=/, merchantName=还有几天就要放假了呀, year=2018, telephoneormobilephone=18068334025, legalName=222, input28=名字, ddmerchantContactTelphone=18068334025, ddmerchantContactEmail=222@qq.com, bankName=/, cwmerchantContactName=名字, cwmerchantContactTelphone=18068334025, input3=广西壮族自治区贵港市, input5=3333, ddmerchantContactName=名字, input6=3333, input20=2018年 02月 11日, month=2, ddmerchantContactCz=/, merechantURL=/, input23=2019年 02月 10日, day=11, contract_num=K18-2000-801-03, templateName=dsmainfinancialword.docx, accessType=首选, bankAccount=/}5 6 7 {$INPUT19$=/, $INPUT1$=K18-2000-801-03, $INPUT3$=广西壮族自治区贵港市, $INPUT7$=名字, $INPUT17$=/, $INPUT23$=2019年 02月 10日, $INPUT5$=3333, contract_type=0, $INPUT15$=/, $INPUT29$=222, $INPUT11$=名字, $INPUT13$=/, $INPUT27$=/, $INPUT9$=/, $INPUT18$=/, $INPUT2$=还有几天就要放假了呀, $INPUT4$=18068334025, $INPUT6$=3333, $INPUT8$=18068334025, $INPUT16$=/, $INPUT14$=222@qq.com, $INPUT20$=2018年 02月 11日, $INPUT10$=222@qq.com, $INPUT28$=名字, $INPUT26$=首选, contract_num=K18-2000-801-03, $INPUT12$=18068334025, templateName=dsmainfinancialword.docx}8 9 替换的原理就是把上面获取到的所有的值,根据数据库中的配置,全部提花成$input$开头的,因为word文档的每个参数就是这样设置的。
if(originParam.containsKey(replaceParams.getFieldName()))
originParam:
{payBankNum=/, cwmerchantContactEmail=222@qq.com, masterDay=11, masterContractNum=K18-2000-801-03, depositAccount=/, masterYear=2018, cwmerchantContactCz=/, masterMonth=2, billAccountShareAmount=/, merchantName=还有几天就要放假了呀, year=2018, telephoneormobilephone=18068334025, legalName=222, input28=名字, ddmerchantContactTelphone=18068334025, ddmerchantContactEmail=222@qq.com, bankName=/, cwmerchantContactName=名字, cwmerchantContactTelphone=18068334025, input3=广西壮族自治区贵港市, input5=3333, ddmerchantContactName=名字, input6=3333, input20=2018年 02月 11日, month=2, ddmerchantContactCz=/, merechantURL=/, input23=2019年 02月 10日, day=11, contract_num=K18-2000-801-03, templateName=dsmainfinancialword.docx, accessType=首选, bankAccount=/} {$INPUT19$=/, $INPUT1$=K18-2000-801-03, $INPUT3$=广西壮族自治区贵港市, $INPUT7$=名字, $INPUT17$=/, $INPUT23$=2019年 02月 10日, $INPUT5$=3333, contract_type=0, $INPUT15$=/, $INPUT29$=222, $INPUT11$=名字, $INPUT13$=/, $INPUT27$=/, $INPUT9$=/, $INPUT18$=/, $INPUT2$=还有几天就要放假了呀, $INPUT4$=18068334025, $INPUT6$=3333, $INPUT8$=18068334025, $INPUT16$=/, $INPUT14$=222@qq.com, $INPUT20$=2018年 02月 11日, $INPUT10$=222@qq.com, $INPUT28$=名字, $INPUT26$=首选, contract_num=K18-2000-801-03, $INPUT12$=18068334025, templateName=dsmainfinancialword.docx}替换的原理就是把上面获取到的所有的值,根据数据库中的配置,全部提花成$input$开头的,因为word文档的每个参数就是这样设置的。
2.4,替换word文档中的各个参数并合并word文档
fileName = dealWordHelperService.replaceWordText(finalOriginParamList,templeteFilePath,outFilePath);
templeteFilePath:模板存放的地方outFilePath:替换掉参数临时的路径
1 templateName = (String)templateParam.get("templateName");2 log.info("======开始替换模板参数=replaceWordText中==templateName==="+templateName+"=====");3 OPCPackage pack = POIXMLDocument.openPackage(templeteFile + File.separator + templateName);4 document = new XWPFDocument(pack);
OPCPackage pack = POIXMLDocument.openPackage(templeteFile + File.separator + templateName);
document = new XWPFDocument(pack); 获取document对象
2.4.1,替换页脚
templateParam:
1 {payBankNum=/, cwmerchantContactEmail=222@qq.com, masterDay=11, masterContractNum=K18-2000-801-03, depositAccount=/, masterYear=2018, cwmerchantContactCz=/, masterMonth=2, billAccountShareAmount=/, merchantName=还有几天就要放假了呀, year=2018, telephoneormobilephone=18068334025, legalName=222, input28=名字, ddmerchantContactTelphone=18068334025, ddmerchantContactEmail=222@qq.com, bankName=/, cwmerchantContactName=名字, cwmerchantContactTelphone=18068334025, input3=广西壮族自治区贵港市, input5=3333, ddmerchantContactName=名字, input6=3333, input20=2018年 02月 11日, month=2, ddmerchantContactCz=/, merechantURL=/, input23=2019年 02月 10日, day=11, contract_num=K18-2000-801-03, templateName=dsmainfinancialword.docx, accessType=首选, bankAccount=/}
OperatorWordUtil.replaceInFoot(document,templateParam);
1 public static void replaceInFoot(XWPFDocument doc, Mapparams) 2 { 3 List footerList = doc.getFooterList(); 4 if (footerList != null && footerList.size() > 0) 5 { 6 for (XWPFFooter xWPFFooter : footerList) 7 { 8 String text = xWPFFooter.getText(); 9 LOGGER.info("记得删除,遍历的text"+text);10 System.out.println("控制台记得删除,遍历的text"+text);11 List xWPFParagraphs = xWPFFooter.getParagraphs();12 if (xWPFParagraphs != null && xWPFParagraphs.size() > 0)13 {14 for (XWPFParagraph xWPFParagraph : xWPFParagraphs) 15 {16 replaceInPara(xWPFParagraph, params);17 }18 }19 }20 }21 }
1 public static void replaceInPara(XWPFParagraph paragraph, MapreplaceMap) 2 { 3 List runs = paragraph.getRuns(); 4 for (int i = 0; i < runs.size(); i++) 5 { 6 if (null == runs.get(i)) 7 { 8 continue; 9 }10 String oneparaString = runs.get(i).getText(runs.get(i).getTextPosition());11 if (StringUtils.isEmpty(oneparaString)) 12 {13 continue;14 }15 for (Map.Entry entry : replaceMap.entrySet())16 {17 if(null != entry.getValue())18 {19 oneparaString = oneparaString.replace(entry.getKey().trim(), (String.valueOf(entry.getValue())!=null? String.valueOf(entry.getValue()): ""));20 }21 else22 {23 oneparaString = oneparaString.replace(entry.getKey().trim(), "");24 }25 }26 if(oneparaString!=null && oneparaString.indexOf(breakFlag)>-1){ //需要换行27 LOGGER.info("----->需要手动换行的"+oneparaString);28 String[] breaks = oneparaString.split(breakFlag);29 int index = 0;30 for(String breakinfo:breaks){31 runs.get(i).setText(breakinfo, index);32 runs.get(i).addBreak();//换行33 index++;34 }35 }else{ //不用换行,根据模板正常显示,原来的逻辑36 runs.get(i).setText(oneparaString, 0);37 }38 }39 }
2.4.2,替换字段里的参数
1 public static void replaceInPara(XWPFDocument doc, Mapparams) 2 { 3 Iterator iterator = doc.getParagraphsIterator(); 4 XWPFParagraph para; 5 while (iterator.hasNext()) 6 { 7 para = iterator.next(); 8 replaceInPara(para, params); 9 }10 }
replaceInPara还是上面的方法
2.4.3,替换表格里面的变量
1 public static void replaceInTable(XWPFDocument doc, Mapparams) 2 { 3 4 Iterator iterator = doc.getTablesIterator(); 5 XWPFTable table; 6 List rows; 7 List cells; 8 List paras; 9 while (iterator.hasNext())10 {11 table = iterator.next();12 rows = table.getRows();13 for (XWPFTableRow row : rows)14 {15 cells = row.getTableCells();16 for (XWPFTableCell cell : cells)17 {18 paras = cell.getParagraphs();19 for (XWPFParagraph para : paras)20 {21 replaceInPara(para, params);22 }23 }24 }25 }26 }
replaceInPara还是上面的方法
一段XWPFParagraph一段的去读
读到每一段之后他会在吧这一段进行按照word文档当初设置的格式,假如是黏贴复制的话不会换行String oneparaString = runs.get(i).getText(runs.get(i).getTextPosition()),假如是手写的的话就会换行,所以要想把自己的变量单独的取出来必须手动的输入这个变量才会遍历的时候单独的把这个字段取出来。一个段落就是一个XWPFParagraph,注意这个段落就是我们传统意义上的段落比如
一行就是String oneparaString = runs.get(i).getText(runs.get(i).getTextPosition()),但是行就不是我们传统意义上的行了,请见下图关于oneparaString的解释。它就是一行行的读的。眼睛看上去应该是一行,其实是三行,因为在写好的基础上,我们手动的添加了一个$INPUT6$,一定注意是手动,而不是黏贴复制的。
假如是表格的话,就会在每个单元格算一个段落,word文档中有时候表格是设置的,我们眼睛有时候看不到的,比如每个单元格就是一个cell,其实就是一个段落。
paras = cell.getParagraphs(); cell
在在单元格的基础上进行一行行的读,读的规则和上面的oneparaString 规则是一样的。
2.5,输出(生成临时的word文档,记得上传服务器之后在删除,否则会积累好多的垃圾数据的)
//生成临时word文件
fileName = newTemWord(outFilePath, templateName, document);fileNames.append(fileName).append(",");
1 private String newTemWord(String outFilePath, String templateName, 2 XWPFDocument document) throws FileNotFoundException, IOException 3 { 4 String outTempFilePath = wordFile + File.separator + outFilePath; 5 FileOutputStream outStream = null; 6 String fileName = System.currentTimeMillis() + templateName; 7 String filePath = outTempFilePath+ File.separator + fileName; 8 outStream = new FileOutputStream(filePath); 9 document.write(outStream);10 outStream.close();11 return fileName;12 }
2.6,把多个word文档合并成一个word文档
finalWordName = getFinalWordDoc(fileNames.toString(),outFilePath);
log.info("合同生成成功1!合同名称:"+finalWordName);
1 private String getFinalWordDoc(String fileNames,String outFilePath)2 {3 String outTempFilePath = wordFile + File.separator + outFilePath;4 String finalWordName = OperatorWordUtil.mergeWordDocx(outTempFilePath, fileNames);5 return finalWordName;6 }
1 public static String mergeWordDocx(String outTempFilePath,String fileNames) 2 { 3 String finalWordName = ""; 4 if(fileNames.length() > 0 && fileNames != null) 5 { 6 FileInputStream InStream = null; 7 String filePath = ""; 8 ListInputStreams = new ArrayList (); 9 String[] fileNameArray = fileNames.toString().split(",");10 InputStream InputStream = null;11 OutputStream outputStream = null;12 try 13 {14 for(String fileName : fileNameArray)15 {16 filePath = outTempFilePath + File.separator + fileName;17 InStream = new FileInputStream(filePath);18 InputStreams.add(InStream);19 } 20 21 //合并word22 InputStream = mergeDocx(InputStreams,outTempFilePath);23 finalWordName = System.currentTimeMillis()+".docx";24 outputStream = new FileOutputStream(outTempFilePath+File.separator+finalWordName);25 int bytesWritten = 0;26 int byteCount = 0;27 byte[] bytes = new byte[100000000];28 while ((byteCount = InputStream.read(bytes)) != -1)29 {30 outputStream.write(bytes, bytesWritten, byteCount);31 bytesWritten += byteCount;32 }33 }34 catch (Exception e)35 {36 e.printStackTrace();37 }38 finally {39 try {40 if (null != outputStream) {41 outputStream.close();42 }43 44 if (null != InputStream) {45 InputStream.close();46 }47 48 if (null != InStream) {49 InStream.close();50 }51 } catch (Exception e2) {52 LOGGER.error("OperatorWordUtil close stream failed!", e2);53 }54 }55 }56 return finalWordName;57 }
1 public static InputStream mergeDocx(final Liststreams,String outTempFilePath) throws Docx4JException, IOException 2 { 3 WordprocessingMLPackage target = null; 4 File tmpdir = new File(outTempFilePath); 5 final File generated = File.createTempFile("generated", ".docx",tmpdir); 6 int chunkId = 0; 7 Iterator it = streams.iterator(); 8 while (it.hasNext()) 9 {10 InputStream is = it.next();11 if (is != null) 12 {13 if (target == null) 14 {15 // Copy first (master) document16 OutputStream os = new FileOutputStream(generated);17 os.write(IOUtils.toByteArray(is));18 os.close();19 20 target = WordprocessingMLPackage.load(generated);21 } 22 else 23 {24 // Attach the others (Alternative input parts)25 insertDocx(target.getMainDocumentPart(), IOUtils.toByteArray(is), chunkId++);26 }27 }28 }29 30 if (target != null)31 {32 target.save(generated);33 return new FileInputStream(generated);34 } 35 else 36 {37 return null;38 }39 }
1 private static void insertDocx(MainDocumentPart main, byte[] bytes, int chunkId) 2 { 3 try 4 { 5 AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(new PartName("/part" + chunkId + ".docx")); 6 afiPart.setContentType(new ContentType(ContentTypes.WORDPROCESSINGML_DOCUMENT)); 7 afiPart.setBinaryData(bytes); 8 Relationship altChunkRel = main.addTargetPart(afiPart); 9 CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();10 chunk.setId(altChunkRel.getId());11 main.addObject(chunk);12 }13 catch (Exception e)14 {15 e.printStackTrace();16 }17 }
1 public final static String WORDPROCESSINGML_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击