使用模版出word

java 使用模版导出docx.。

1。使用docx创建自己的的模版,每个表格定义自己的标签。

2.word自定义标签方法

3. 调用工具类WordExport。

package com.jeesite.common.utils.word;

import com.jeesite.common.codec.EncodeUtils;
import com.jeesite.common.lang.ObjectUtils;
import com.jeesite.common.web.http.HttpClientUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHeight;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTrPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STBorder;
import org.w3c.dom.Node;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 使用POI,进行Word相关的操作
 * @author  xuyu
 * <p>Modification History:</p>
 * <p>Date       Author      Description</p>
 * <p>------------------------------------------------------------------</p>
 * <p> </p>
 */
public class WordExport {

   /** 内部使用的文档对象 **/
   private XWPFDocument document;

   private BookMarks bookMarks = null;

   /**
    * 为文档设置模板
    * @param templatePath  模板文件名称
    */
   public void setTemplate(String templatePath) {
      try {
         this.document = new XWPFDocument(POIXMLDocument.openPackage(templatePath));

         bookMarks = new BookMarks(document);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }

   /**
    * 进行标签替换的例子,传入的Map中,key表示标签名称,value是替换的信息
    * @param indicator
    */
   public void replaceBookMark(Map<String, String> indicator) {
      //循环进行替换
      Iterator<String> bookMarkIter = bookMarks.getNameIterator();
      while (bookMarkIter.hasNext()) {
         String bookMarkName = bookMarkIter.next();

         //得到标签名称
         BookMark bookMark = bookMarks.getBookmark(bookMarkName);

         //进行替换
         if (indicator.get(bookMarkName) != null) {
            bookMark.insertTextAtBookMark(indicator.get(bookMarkName), BookMark.INSERT_BEFORE);
         }

      }

   }

   public void fillTableAtBookMark(String bookMarkName, List<Map<String, String>> content) {

      //rowNum来比较标签在表格的哪一行
      int rowNum = 0;

      //首先得到标签
      BookMark bookMark = bookMarks.getBookmark(bookMarkName);
      Map<String, String> columnMap = new HashMap<String, String>();
      Map<String, Node> styleNode = new HashMap<String, Node>();

      //标签是否处于表格内
      if (bookMark.isInTable()) {

         //获得标签对应的Table对象和Row对象
         XWPFTable table = bookMark.getContainerTable();
         XWPFTableRow row = bookMark.getContainerTableRow();
//       CTRow ctRow = row.getCtRow();
         List<XWPFTableCell> rowCell = row.getTableCells();
         for (int i = 0; i < rowCell.size(); i++) {
            columnMap.put(i + "", rowCell.get(i).getText().trim());
            //System.out.println(rowCell.get(i).getParagraphs().get(0).createRun().getFontSize());
            //System.out.println(rowCell.get(i).getParagraphs().get(0).getCTP());
            //System.out.println(rowCell.get(i).getParagraphs().get(0).getStyle());

            //获取该单元格段落的xml,得到根节点
            Node node1 = rowCell.get(i).getParagraphs().get(0).getCTP().getDomNode();

            //遍历根节点的所有子节点
            for (int x = 0; x < node1.getChildNodes().getLength(); x++) {
               if (node1.getChildNodes().item(x).getNodeName().equals(BookMark.RUN_NODE_NAME)) {
                  Node node2 = node1.getChildNodes().item(x);

                  //遍历所有节点为"w:r"的所有自己点,找到节点名为"w:rPr"的节点
                  for (int y = 0; y < node2.getChildNodes().getLength(); y++) {
                     if (node2.getChildNodes().item(y).getNodeName().endsWith(BookMark.STYLE_NODE_NAME)) {

                        //将节点为"w:rPr"的节点(字体格式)存到HashMap中
                        styleNode.put(i + "", node2.getChildNodes().item(y));
                     }
                  }
               } else {
                  continue;
               }
            }
         }

         //循环对比,找到该行所处的位置,删除改行        
         for (int i = 0; i < table.getNumberOfRows(); i++) {
            if (table.getRow(i).equals(row)) {
               rowNum = i;
               break;
            }
         }
         table.removeRow(rowNum);

         for (int i = 0; i < content.size(); i++) {
            //创建新的一行,单元格数是表的第一行的单元格数,
            //后面添加数据时,要判断单元格数是否一致
            XWPFTableRow tableRow = table.createRow();
            CTTrPr trPr = tableRow.getCtRow().addNewTrPr();
            CTHeight ht = trPr.addNewTrHeight();
            ht.setVal(BigInteger.valueOf(360));
         }

         //得到表格行数
         int rcount = table.getNumberOfRows();
         for (int i = rowNum; i < rcount; i++) {
            XWPFTableRow newRow = table.getRow(i);

            //判断newRow的单元格数是不是该书签所在行的单元格数
            if (newRow.getTableCells().size() != rowCell.size()) {

               //计算newRow和书签所在行单元格数差的绝对值
               //如果newRow的单元格数多于书签所在行的单元格数,不能通过此方法来处理,可以通过表格中文本的替换来完成
               //如果newRow的单元格数少于书签所在行的单元格数,要将少的单元格补上
               int sub = Math.abs(newRow.getTableCells().size() - rowCell.size());
               //将缺少的单元格补上
               for (int j = 0; j < sub; j++) {
                  newRow.addNewTableCell();
               }
            }

            List<XWPFTableCell> cells = newRow.getTableCells();

            for (int j = 0; j < cells.size(); j++) {
               XWPFParagraph para = cells.get(j).getParagraphs().get(0);
               XWPFRun run = para.createRun();
               if (content.get(i - rowNum).get(columnMap.get(j + "")) != null) {

                  //改变单元格的值,标题栏不用改变单元格的值 
                  run.setText(content.get(i - rowNum).get(columnMap.get(j + "")) + "");

                  //将单元格段落的字体格式设为原来单元格的字体格式
                  run.getCTR().getDomNode().insertBefore(styleNode.get(j + "").cloneNode(true), run.getCTR().getDomNode().getFirstChild());
               }

               para.setAlignment(ParagraphAlignment.CENTER);
            }
         }
      }
   }

   public void replaceText(Map<String, String> bookmarkMap, String bookMarkName) {

      //首先得到标签
      BookMark bookMark = bookMarks.getBookmark(bookMarkName);
      //获得书签标记的表格
      XWPFTable table = bookMark.getContainerTable();
      //获得所有的表
      //Iterator<XWPFTable> it = document.getTablesIterator();

      if (table != null) {
         //得到该表的所有行
         int rcount = table.getNumberOfRows();
         for (int i = 0; i < rcount; i++) {
            XWPFTableRow row = table.getRow(i);

            //获到改行的所有单元格
            List<XWPFTableCell> cells = row.getTableCells();
            for (XWPFTableCell c : cells) {
               for (Entry<String, String> e : bookmarkMap.entrySet()) {
                  if (c.getText().equals(e.getKey())) {

                     //删掉单元格内容
                     c.removeParagraph(0);

                     //给单元格赋值
                     c.setText(e.getValue());
                  }
               }
            }
         }
      }
   }

   public void saveAs(String fileName) {
      File newFile = new File(fileName);
      FileOutputStream fos = null;
      try {
         fos = new FileOutputStream(newFile);
         this.document.write(fos);
      } catch (FileNotFoundException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      } finally {
         try {
            if (fos != null){
               fos.flush();
            }
            if (fos != null){
               fos.close();
            }
         } catch (IOException e) {
            e.printStackTrace();
         }
      }
   }

// public static void main(String[] args) {
//    long startTime = System.currentTimeMillis();
//    WordExport changer = new WordExport();
//    String fileName = WordExport.class.getResource("Word模版.docx").getFile();
//    fileName = EncodeUtils.decodeUrl(fileName.substring(1));
//    System.out.println(fileName);
//
//    changer.setTemplate(fileName);
//    Map<String, String> content = new HashMap<String, String>();
//    content.put("Principles", "格式规范、标准统一、利于阅览");
//    content.put("Purpose", "规范会议操作、提高会议质量");
//    content.put("Scope", "公司会议、部门之间业务协调会议");
//
//    content.put("customerName", "**有限公司");
//    content.put("address", "机场路2号");
//    content.put("userNo", "3021170207");
//    content.put("tradeName", "水泥制造");
//    content.put("price1", "1.085");
//    content.put("price2", "0.906");
//    content.put("price3", "0.433");
//    content.put("numPrice", "0.675");
//
//    content.put("company_name", "**有限公司");
//    content.put("company_address", "机场路2号");
//    changer.replaceBookMark(content);
//
//    //替换表格标签
//    List<Map<String, String>> content2 = new ArrayList<Map<String, String>>();
//    Map<String, String> table1 = new HashMap<String, String>();
//
//    table1.put("MONTH", "*月份");
//    table1.put("SALE_DEP", "75分");
//    table1.put("TECH_CENTER", "80分");
//    table1.put("CUSTOMER_SERVICE", "85分");
//    table1.put("HUMAN_RESOURCES", "90分");
//    table1.put("FINANCIAL", "95分");
//    table1.put("WORKSHOP", "80分");
//    table1.put("TOTAL", "85分");
//
//    for (int i = 0; i < 3; i++) {
//       content2.add(table1);
//    }
//    changer.fillTableAtBookMark("Table", content2);
//    changer.fillTableAtBookMark("month", content2);
//
//    //表格中文本的替换
//    Map<String, String> table = new HashMap<String, String>();
//    table.put("CUSTOMER_NAME", "**有限公司");
//    table.put("ADDRESS", "机场路2号");
//    table.put("USER_NO", "3021170207");
//    table.put("tradeName", "水泥制造");
//    table.put("PRICE_1", "1.085");
//    table.put("PRICE_2", "0.906");
//    table.put("PRICE_3", "0.433");
//    table.put("NUM_PRICE", "0.675");
//    changer.replaceText(table, "Table2");
//
//    //保存替换后的WORD
//    changer.saveAs(fileName + "_out.docx");
//    System.out.println("time==" + (System.currentTimeMillis() - startTime));
//
// }

   /**
    * @param bookMarkName  模块书签
    * @param content     列表数据集
    * @param isTotal       是否有合计行
    */
   public void fillTableAtBookMarkObject(String bookMarkName, List<Map<String, Object>> content,boolean isTotal) {

      //rowNum来比较标签在表格的哪一行
      int rowNum = 0;
      //首先得到标签
      BookMark bookMark = bookMarks.getBookmark(bookMarkName);
      Map<String, String> columnMap = new HashMap<String, String>();
      Map<String, Node> styleNode = new HashMap<String, Node>();
      //标签是否处于表格内
      if (bookMark.isInTable())
      {
         //获得标签对应的Table对象和Row对象
         XWPFTable table = bookMark.getContainerTable();
         XWPFTableRow row = bookMark.getContainerTableRow();
//       CTRow ctRow = row.getCtRow();
         List<XWPFTableCell> rowCell = row.getTableCells();
         for (int i = 0; i < rowCell.size(); i++) {
            columnMap.put(i + "", rowCell.get(i).getText().trim());
            //获取该单元格段落的xml,得到根节点
            Node node1 = rowCell.get(i).getParagraphs().get(0).getCTP().getDomNode();
            //遍历根节点的所有子节点
            for (int x = 0; x < node1.getChildNodes().getLength(); x++) {
               if (node1.getChildNodes().item(x).getNodeName().equals(BookMark.RUN_NODE_NAME)) {
                  Node node2 = node1.getChildNodes().item(x);
                  //遍历所有节点为"w:r"的所有自己点,找到节点名为"w:rPr"的节点
                  for (int y = 0; y < node2.getChildNodes().getLength(); y++) {
                     if (node2.getChildNodes().item(y).getNodeName().endsWith(BookMark.STYLE_NODE_NAME)) {
                        //将节点为"w:rPr"的节点(字体格式)存到HashMap中
                        styleNode.put(i + "", node2.getChildNodes().item(y));
                     }
                  }
               } else {
                  continue;
               }
            }
         }
         //循环对比,找到该行所处的位置,删除改行
         for (int i = 0; i < table.getNumberOfRows(); i++) {
            if (table.getRow(i).equals(row)) {
               rowNum = i;
               break;
            }
         }
         table.removeRow(rowNum);
         for (int i = 0; i < content.size(); i++) {
            //创建新的一行,单元格数是表的第一行的单元格数,
            //后面添加数据时,要判断单元格数是否一致
            XWPFTableRow tableRow = table.createRow();
            CTTrPr trPr = tableRow.getCtRow().addNewTrPr();
            CTHeight ht = trPr.addNewTrHeight();
            ht.setVal(BigInteger.valueOf(360));
         }
         //得到表格行数
         int rcount = table.getNumberOfRows();
         for (int i = rowNum; i < rcount; i++) {
            XWPFTableRow newRow = table.getRow(i);
            //判断newRow的单元格数是不是该书签所在行的单元格数
            if (newRow.getTableCells().size() != rowCell.size())
            {
               //计算newRow和书签所在行单元格数差的绝对值
               //如果newRow的单元格数多于书签所在行的单元格数,不能通过此方法来处理,可以通过表格中文本的替换来完成
               //如果newRow的单元格数少于书签所在行的单元格数,要将少的单元格补上
               int sub = Math.abs(newRow.getTableCells().size() - rowCell.size());
               //将缺少的单元格补上
               for (int j = 0; j < sub; j++) {
                  newRow.addNewTableCell();
               }
            }
            List<XWPFTableCell> cells = newRow.getTableCells();
            for (int j = 0; j < cells.size(); j++) {
               XWPFParagraph para = cells.get(j).getParagraphs().get(0);
               XWPFRun run = para.createRun();
               XWPFDocument xwpfDocument = cells.get(j).getXWPFDocument();
               boolean nil = true;
               if (content.get(i - rowNum).get(columnMap.get(j + "")) != null) {
                  Object obj = content.get(i - rowNum).get(columnMap.get(j + ""));
                  if(obj instanceof String || obj instanceof Integer || obj instanceof Double) {
                     String text = ObjectUtils.toString(obj);
                     //给单元格赋值
                     run.setText(text);
                     //将单元格段落的字体格式设为原来单元格的字体格式
                     run.getCTR().getDomNode().insertBefore(styleNode.get(j + "").cloneNode(true), run.getCTR().getDomNode().getFirstChild());
                     xwpfDocument.removeBodyElement(xwpfDocument.getPosOfParagraph(para));
                     if(isTotal&&StringUtils.isNotEmpty(text)) {
                        nil = false;
                     }
                  }else if(obj instanceof Map){//图片流
                     try {
                        Map map = (HashMap)obj;
                        InputStream inputStream = new ByteArrayInputStream((byte[]) map.get("bytes"));
                        int width = (int)map.get("width");
                        int height = (int)map.get("height");
                        run.addPicture(inputStream,XWPFDocument.PICTURE_TYPE_PNG,"img.png,",Units.toEMU(width), Units.toEMU(height));
                        inputStream.close();
                        xwpfDocument.removeBodyElement(xwpfDocument.getPosOfParagraph(para));
                     } catch (Exception e1) {
                        e1.printStackTrace();
                     }
                  }
               }
               //如果为合计行,并且单元格为空
               if(isTotal&&nil) {
                  //不是第一个单元格并且是最后一个行数据
                  if (j != 0 && rcount - 1 == i) {
                     //去除左边框

                     cells.get(j).getCTTc().addNewTcPr().addNewTcBorders().addNewLeft().setVal(STBorder.NIL);
                  }
                  //不是最后一个单元格并且是最后一个行数据
                  if (j != cells.size() - 1 && rcount - 1 == i) {
                     //去除右边框
                     cells.get(j).getCTTc().addNewTcPr().addNewTcBorders().addNewRight().setVal(STBorder.NIL);
                  }
               }
               para.setAlignment(ParagraphAlignment.CENTER);
            }
         }
      }
   }
   public void replaceObject(Map<String, Object> bookmarkMap, String bookMarkName,int fontSize)
   {
      //首先得到标签
      BookMark bookMark = bookMarks.getBookmark(bookMarkName);
      Map<String, Node> styleNode = new HashMap();
      //获得书签标记的表格
      XWPFTable table = bookMark.getContainerTable();
      List<XWPFTableCell> rowCell = bookMark.getContainerTableRow().getTableCells();
      for (int i = 0; i < rowCell.size(); i++) {
         //获取该单元格段落的xml,得到根节点
         Node node1 = rowCell.get(i).getParagraphs().get(0).getCTP().getDomNode();

         //遍历根节点的所有子节点
         for (int x = 0; x < node1.getChildNodes().getLength(); x++) {
            if (node1.getChildNodes().item(x).getNodeName().equals(BookMark.RUN_NODE_NAME)) {
               Node node2 = node1.getChildNodes().item(x);

               //遍历所有节点为"w:r"的所有自己点,找到节点名为"w:rPr"的节点
               for (int y = 0; y < node2.getChildNodes().getLength(); y++) {
                  if (node2.getChildNodes().item(y).getNodeName().endsWith(BookMark.STYLE_NODE_NAME)) {
                     //将节点为"w:rPr"的节点(字体格式)存到HashMap中
                     styleNode.put(i + "", node2.getChildNodes().item(y));
                  }
               }
            } else {
               continue;
            }
         }
      }
      if (table != null) {
         //得到该表的所有行
         int rcount = table.getNumberOfRows();
         for (int i = 0; i < rcount; i++) {
            XWPFTableRow row = table.getRow(i);
            //获到改行的所有单元格
            List<XWPFTableCell> cells = row.getTableCells();
            int j = 0;
            for (XWPFTableCell c : cells) {
               for (Entry<String, Object> e : bookmarkMap.entrySet()) {
                  if (c.getText().equals(e.getKey())) {
                     //删掉单元格内容
                     c.removeParagraph(0);
                     Object obj = e.getValue();
                     XWPFParagraph para = c.addParagraph();
                     XWPFRun run = para.createRun();
                     XWPFDocument xwpfDocument = c.getXWPFDocument();
                     if(obj instanceof String || obj instanceof Integer || obj instanceof Double) {
                        //给单元格赋值
                        //c.setText(ObjectUtils.toString(obj));

                        run.setFontSize(fontSize);
                        run.setText(ObjectUtils.toString(obj));
                        //将单元格段落的字体格式设为原来单元格的字体格式
                        run.getCTR().getDomNode().insertBefore(styleNode.get(j + "").cloneNode(true), run.getCTR().getDomNode().getFirstChild());
                        xwpfDocument.removeBodyElement(xwpfDocument.getPosOfParagraph(para));
                     }else if(obj instanceof Map){
                        try {
                           Map map = (HashMap)obj;
                           InputStream inputStream = new ByteArrayInputStream((byte[]) map.get("bytes"));
                           int width = (int)map.get("width");
                           int height = (int)map.get("height");
                           run.addPicture(inputStream,XWPFDocument.PICTURE_TYPE_PNG,"img.png,",Units.toEMU(width), Units.toEMU(height));
                           inputStream.close();
                           xwpfDocument.removeBodyElement(xwpfDocument.getPosOfParagraph(para));
                        } catch (Exception e1) {
                           e1.printStackTrace();
                        }
                     }

                  }
               }
               j++;
            }
         }
      }
   }
   /**
    * @Description 向客户端写出文件
    * @author elvis luo
    * @date 16:31 2019/11/14
    * @return void
    */
   public void downWord(HttpServletResponse response, String fileName) {
      response.reset();
      response.setContentType("application/octet-stream; charset=utf-8");
      response.setHeader("Content-Disposition", "attachment; filename="+EncodeUtils.encodeUrl(fileName));
      response.setHeader("Access-Control-Allow-Origin", "*");
      try(ServletOutputStream out = response.getOutputStream()){
         this.document.write(out);
      }catch (Exception e){
         e.printStackTrace();
      }
   }
   /**
    * @Description 获取连接输入流
    * @author elvis luo
    * @date 17:35 2019/11/15
    * @return java.io.InputStream
    */

   public byte[] getBytes(String url) {
      if (StringUtils.isEmpty(url)){
         return  null;
      }
      HttpGet httpGet = new HttpGet(url);
      CloseableHttpClient httpclient;
      if ("https".equals(httpGet.getURI().getScheme())){
         httpclient = HttpClientUtils.createSSLInsecureClient();
      }else{
         httpclient = HttpClients.createDefault();
      }
      try(ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();CloseableHttpResponse _response = httpclient.execute(httpGet)){
         HttpEntity entity = _response.getEntity();
         entity.writeTo(byteArrayOutputStream);
         return byteArrayOutputStream.toByteArray();
      } catch (IOException e) {
         e.printStackTrace();
      }
      return null;
   }
}

4。工具类调用方法,使用jeesite提供的方法BookMarks

package com.jeesite.common.utils.word;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * 利用POI进行Word文件相关的操作,针对docx形式的封装
 * @author    
 * <p>Modification History:</p>
 * <p>Date       Author      Description</p>
 * <p>------------------------------------------------------------------</p>
 * <p> </p>
 */
public class BookMarks {

   /** 保存Word文件中定义的标签  **/
   private HashMap<String, BookMark> _bookmarks = null;

   /** 
    * 构造函数,用以分析文档,解析出所有的标签
    * @param document  Word OOXML document instance. 
    */
   public BookMarks(XWPFDocument document) {

      //初始化标签缓存
      this._bookmarks = new HashMap<String, BookMark>();

      // 首先解析文档普通段落中的标签 
      this.procParaList(document.getParagraphs());

      //利用繁琐的方法,从所有的表格中得到得到标签,处理比较原始和简单
      List<XWPFTable> tableList = document.getTables();

      for (XWPFTable table : tableList) {
         //得到表格的列信息
         List<XWPFTableRow> rowList = table.getRows();
         for (XWPFTableRow row : rowList) {
            //得到行中的列信息
            List<XWPFTableCell> cellList = row.getTableCells();
            for (XWPFTableCell cell : cellList) {
               //逐个解析标签信息
               //this.procParaList(cell.getParagraphs(), row);
               this.procParaList(cell);
            }
         }
      }
   }

   /**
    * 根据标签名称,获得标签的相关定义,如果不存在,则返回空
    * @param bookmarkName   标签名称
    * @return    返回封装好的对象
    */
   public BookMark getBookmark(String bookmarkName) {
      BookMark bookmark = null;
      if (this._bookmarks.containsKey(bookmarkName)) {
         bookmark = this._bookmarks.get(bookmarkName);
      }
      return bookmark;
   }

   /** 
    * 得到所有的标签信息集合
    * 
    * @return 缓存的标签信息集合 
    */
   public Collection<BookMark> getBookmarkList() {
      return (this._bookmarks.values());
   }

   /** 
    * 返回文档中的标签名称迭代器
    * @return  由Map KEY 转换的迭代器
    */
   public Iterator<String> getNameIterator() {
      return (this._bookmarks.keySet().iterator());
   }

   private void procParaList(XWPFTableCell cell) {
      List<XWPFParagraph> paragraphList = cell.getParagraphs();

      for (XWPFParagraph paragraph : paragraphList) {
         //得到段落中的标签标记
         List<CTBookmark> bookmarkList = paragraph.getCTP().getBookmarkStartList();
         for (CTBookmark bookmark : bookmarkList) {
            this._bookmarks.put(bookmark.getName(), new BookMark(bookmark, paragraph, cell));
         }
      }
   }

   /**
    * 解析表格中的标签
    * @param paragraphList   传入的段落列表
    * @param tableRow   对应的表格行对象 
    */
   @SuppressWarnings("unused")
   private void procParaList(List<XWPFParagraph> paragraphList, XWPFTableRow tableRow) {

      NamedNodeMap attributes = null;
      Node colFirstNode = null;
      Node colLastNode = null;
      int firstColIndex = 0;
      int lastColIndex = 0;

      //循环判断,解析段落中的标签
      for (XWPFParagraph paragraph : paragraphList) {
         //得到段落中的标签标记
         List<CTBookmark> bookmarkList = paragraph.getCTP().getBookmarkStartList();

         for (CTBookmark bookmark : bookmarkList) {
            // With a bookmark in hand, test to see if the bookmarkStart tag 
            // has w:colFirst or w:colLast attributes. If it does, we are 
            // dealing with a bookmarked table cell. This will need to be 
            // handled differnetly - I think by an different concrete class 
            // that implements the Bookmark interface!! 
            attributes = bookmark.getDomNode().getAttributes();
            if (attributes != null) {

               // Get the colFirst and colLast attributes. If both - for 
               // now - are found, then we are dealing with a bookmarked 
               // cell. 
               colFirstNode = attributes.getNamedItem("w:colFirst");
               colLastNode = attributes.getNamedItem("w:colLast");
               if (colFirstNode != null && colLastNode != null) {

                  // Get the index of the cell (or cells later) from them. 
                  // First convefrt the String values both return to primitive 
                  // int value. TO DO, what happens if there is a 
                  // NumberFormatException. 
                  firstColIndex = Integer.parseInt(colFirstNode.getNodeValue());
                  lastColIndex = Integer.parseInt(colLastNode.getNodeValue());
                  // if the indices are equal, then we are dealing with a# 
                  // cell and can create the bookmark for it. 
                  if (firstColIndex == lastColIndex) {
                     this._bookmarks.put(bookmark.getName(), new BookMark(bookmark, paragraph, tableRow.getCell(firstColIndex)));
                  } else {
                     System.out.println("This bookmark " + bookmark.getName() + " identifies a number of cells in the "
                           + "table. That condition is not handled yet.");
                  }
               } else {
                  this._bookmarks.put(bookmark.getName(), new BookMark(bookmark, paragraph, tableRow.getCell(1)));
               }
            } else {
               this._bookmarks.put(bookmark.getName(), new BookMark(bookmark, paragraph, tableRow.getCell(1)));
            }
         }
      }
   }

   /**
    * 解析普通段落中的标签
    * @param paragraphList  传入的段落
    */
   private void procParaList(List<XWPFParagraph> paragraphList) {
      for (XWPFParagraph paragraph : paragraphList) {
         List<CTBookmark> bookmarkList = paragraph.getCTP().getBookmarkStartList();
         //循环加入标签
         for (CTBookmark bookmark : bookmarkList) {
            this._bookmarks.put(bookmark.getName(), new BookMark(bookmark, paragraph));
         }
      }
   }
}

这个是对bookMark的进步封装。

package com.jeesite.common.utils.word;

import java.util.List;
import java.util.Stack;

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.XmlException;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Word 文件中标签的封装类,保存了其定义和内部的操作
 * <p>Modification History:</p>
 * <p>Date       Author      Description</p>
 * <p>------------------------------------------------------------------</p>
 * <p> </p>
 */
public class BookMark {

   //以下为定义的常量

   /** 替换标签时,设于标签的后面   **/
   public static final int INSERT_AFTER = 0;

   /** 替换标签时,设于标签的前面   **/
   public static final int INSERT_BEFORE = 1;

   /** 替换标签时,将内容替换书签   **/
   public static final int REPLACE = 2;

   /** docx中定义的部分常量引用  **/
   public static final String RUN_NODE_NAME = "w:r";
   public static final String TEXT_NODE_NAME = "w:t";
   public static final String BOOKMARK_START_TAG = "bookmarkStart";
   public static final String BOOKMARK_END_TAG = "bookmarkEnd";
   public static final String BOOKMARK_ID_ATTR_NAME = "w:id";
   public static final String STYLE_NODE_NAME = "w:rPr";

   /** 内部的标签定义类  **/
   private CTBookmark _ctBookmark = null;

   /** 标签所处的段落  **/
   private XWPFParagraph _para = null;

   /** 标签所在的表cell对象  **/
   private XWPFTableCell _tableCell = null;

   /** 标签名称 **/
   private String _bookmarkName = null;

   /** 该标签是否处于表格内  **/
   private boolean _isCell = false;

   /**
    * 构造函数
    * @param ctBookmark
    * @param para
    */
   public BookMark(CTBookmark ctBookmark, XWPFParagraph para) {
      this._ctBookmark = ctBookmark;
      this._para = para;
      this._bookmarkName = ctBookmark.getName();
      this._tableCell = null;
      this._isCell = false;
   }

   /**
    * 构造函数,用于表格中的标签
    * @param ctBookmark
    * @param para
    * @param tableCell
    */
   public BookMark(CTBookmark ctBookmark, XWPFParagraph para, XWPFTableCell tableCell) {
      this(ctBookmark, para);
      this._tableCell = tableCell;
      this._isCell = true;
   }

   public boolean isInTable() {
      return this._isCell;
   }

   public XWPFTable getContainerTable() {
      return this._tableCell.getTableRow().getTable();
   }

   public XWPFTableRow getContainerTableRow() {
      return this._tableCell.getTableRow();
   }

   public String getBookmarkName() {
      return this._bookmarkName;
   }

   /** 
    * Insert text into the Word document in the location indicated by this 
    * bookmark. 
    * 
    * @param bookmarkValue An instance of the String class that encapsulates 
    * the text to insert into the document. 
    * @param where A primitive int whose value indicates where the text ought 
    * to be inserted. There are three options controlled by constants; insert 
    * the text immediately in front of the bookmark (Bookmark.INSERT_BEFORE), 
    * insert text immediately after the bookmark (Bookmark.INSERT_AFTER) and 
    * replace any and all text that appears between the bookmark's square 
    * brackets (Bookmark.REPLACE). 
    */
   public void insertTextAtBookMark(String bookmarkValue, int where) {

      //根据标签的类型,进行不同的操作
      if (this._isCell) {
         this.handleBookmarkedCells(bookmarkValue, where);
      } else {

         //普通标签,直接创建一个元素
         XWPFRun run = this._para.createRun();
         run.setText(bookmarkValue);
         switch (where) {
         case BookMark.INSERT_AFTER:
            this.insertAfterBookmark(run);
            break;
         case BookMark.INSERT_BEFORE:
            this.insertBeforeBookmark(run);
            break;
         case BookMark.REPLACE:
            this.replaceBookmark(run);
            break;
         }
      }
   }

   /** 
    * Inserts some text into a Word document in a position that is immediately 
    * after a named bookmark. 
    * 
    * Bookmarks can take two forms, they can either simply mark a location 
    * within a document or they can do this but contain some text. The 
    * difference is obvious from looking at some XML markup. The simple 
    * placeholder bookmark will look like this; 
    * 
    * <pre>
    * 
    * <w:bookmarkStart w:name="AllAlone" w:id="0"/><w:bookmarkEnd w:id="0"/>
    * 
    * </pre>
    * 
    * Simply a pair of tags where one tag has the name bookmarkStart, the other 
    * the name bookmarkEnd and both share matching id attributes. In this case, 
    * the text will simply be inserted into the document at a point immediately 
    * after the bookmarkEnd tag. No styling will be applied to the text, it 
    * will simply inherit the documents defaults. 
    * 
    * The more complex case looks like this; 
    * 
    * <pre>
    * 
    * <w:bookmarkStart w:name="InStyledText" w:id="3"/>
    *   <w:r w:rsidRPr="00DA438C">
    *     <w:rPr>
    *       <w:rFonts w:hAnsi="Engravers MT" w:ascii="Engravers MT" w:cs="Arimo"/>
    *       <w:color w:val="FF0000"/>
    *     </w:rPr>
    *     <w:t>text</w:t>
    *   </w:r>
    * <w:bookmarkEnd w:id="3"/>
    * 
    * </pre>
    * 
    * Here, the user has selected the word 'text' and chosen to insert a 
    * bookmark into the document at that point. So, the bookmark tags 'contain' 
    * a character run that is styled. Inserting any text after this bookmark, 
    * it is important to ensure that the styling is preserved and copied over 
    * to the newly inserted text. 
    * 
    * The approach taken to dealing with both cases is similar but slightly 
    * different. In both cases, the code simply steps along the document nodes 
    * until it finds the bookmarkEnd tag whose ID matches that of the 
    * bookmarkStart tag. Then, it will look to see if there is one further node 
    * following the bookmarkEnd tag. If there is, it will insert the text into 
    * the paragraph immediately in front of this node. If, on the other hand, 
    * there are no more nodes following the bookmarkEnd tag, then the new run 
    * will simply be positioned at the end of the paragraph. 
    * 
    * Styles are dealt with by 'looking' for a 'w:rPr' element whilst iterating 
    * through the nodes. If one is found, its details will be captured and 
    * applied to the run before the run is inserted into the paragraph. If 
    * there are multiple runs between the bookmarkStart and bookmarkEnd tags 
    * and these have different styles applied to them, then the style applied 
    * to the last run before the bookmarkEnd tag - if any - will be cloned and 
    * applied to the newly inserted text. 
    * 
    * @param run An instance of the XWPFRun class that encapsulates the text 
    * that is to be inserted into the document following the bookmark. 
    */
   private void insertAfterBookmark(XWPFRun run) {
      Node nextNode = null;
      Node insertBeforeNode = null;
      Node styleNode = null;
      int bookmarkStartID = 0;
      int bookmarkEndID = -1;

      // Capture the id of the bookmarkStart tag. The code will step through 
      // the document nodes 'contained' within the start and end tags that have 
      // matching id numbers. 
      bookmarkStartID = this._ctBookmark.getId().intValue();

      // Get the node for the bookmark start tag and then enter a loop that 
      // will step from one node to the next until the bookmarkEnd tag with 
      // a matching id is fouind. 
      nextNode = this._ctBookmark.getDomNode();
      while (bookmarkStartID != bookmarkEndID) {

         // Get the next node along and check to see if it is a bookmarkEnd 
         // tag. If it is, get its id so that the containing while loop can 
         // be terminated once the correct end tag is found. Note that the 
         // id will be obtained as a String and must be converted into an 
         // integer. This has been coded to fail safely so that if an error 
         // is encuntered converting the id to an int value, the while loop 
         // will still terminate. 
         nextNode = nextNode.getNextSibling();
         if (nextNode.getNodeName().contains(BookMark.BOOKMARK_END_TAG)) {
            try {
               bookmarkEndID = Integer.parseInt(nextNode.getAttributes().getNamedItem(BookMark.BOOKMARK_ID_ATTR_NAME).getNodeValue());
            } catch (NumberFormatException nfe) {
               bookmarkEndID = bookmarkStartID;
            }
         } // If we are not dealing with a bookmarkEnd node, are we dealing 
            // with a run node that MAY contains styling information. If so, 
            // then get that style information from the run. 
         else {
            if (nextNode.getNodeName().equals(BookMark.RUN_NODE_NAME)) {
               styleNode = this.getStyleNode(nextNode);
            }
         }
      }

      // After the while loop completes, it should have located the correct 
      // bookmarkEnd tag but we cannot perform an insert after only an insert 
      // before operation and must, therefore, get the next node. 
      insertBeforeNode = nextNode.getNextSibling();

      // Style the newly inserted text. Note that the code copies or clones 
      // the style it found in another run, failure to do this would remove the 
      // style from one node and apply it to another. 
      if (styleNode != null) {
         run.getCTR().getDomNode().insertBefore(styleNode.cloneNode(true), run.getCTR().getDomNode().getFirstChild());
      }

      // Finally, check to see if there was a node after the bookmarkEnd 
      // tag. If there was, then this code will insert the run in front of 
      // that tag. If there was no node following the bookmarkEnd tag then the 
      // run will be inserted at the end of the paragarph and this was taken 
      // care of at the point of creation. 
      if (insertBeforeNode != null) {
         this._para.getCTP().getDomNode().insertBefore(run.getCTR().getDomNode(), insertBeforeNode);
      }
   }

   /** 
    * Inserts some text into a Word document immediately in front of the 
    * location of a bookmark. 
    * 
    * This case is slightly more straightforward than inserting after the 
    * bookmark. For example, it is possible only to insert a new node in front 
    * of an existing node. When inserting after the bookmark, then end node had 
    * to be located whereas, in this case, the node is already known, it is the 
    * CTBookmark itself. The only information that must be discovered is 
    * whether there is a run immediately in front of the boookmarkStart tag and 
    * whether that run is styled. If there is and if it is, then this style 
    * must be cloned and applied the text which will be inserted into the 
    * paragraph. 
    * 
    * @param run An instance of the XWPFRun class that encapsulates the text 
    * that is to be inserted into the document following the bookmark. 
    */
   private void insertBeforeBookmark(XWPFRun run) {
      Node insertBeforeNode = null;
      Node childNode = null;
      Node styleNode = null;

      // Get the dom node from the bookmarkStart tag and look for another 
      // node immediately preceding it. 
      insertBeforeNode = this._ctBookmark.getDomNode();
      childNode = insertBeforeNode.getPreviousSibling();

      // If a node is found, try to get the styling from it. 
      if (childNode != null) {
         styleNode = this.getStyleNode(childNode);

         // If that previous node was styled, then apply this style to the 
         // text which will be inserted. 
         if (styleNode != null) {
            run.getCTR().getDomNode().insertBefore(styleNode.cloneNode(true), run.getCTR().getDomNode().getFirstChild());
         }
      }

      // Insert the text into the paragraph immediately in front of the 
      // bookmarkStart tag. 
      this._para.getCTP().getDomNode().insertBefore(run.getCTR().getDomNode(), insertBeforeNode);
   }

   /** 
    * Replace the text - if any - contained between the bookmarkStart and it's 
    * matching bookmarkEnd tag with the text specified. The technique used will 
    * resemble that employed when inserting text after the bookmark. In short, 
    * the code will iterate along the nodes until it encounters a matching 
    * bookmarkEnd tag. Each node encountered will be deleted unless it is the 
    * final node before the bookmarkEnd tag is encountered and it is a 
    * character run. If this is the case, then it can simply be updated to 
    * contain the text the users wishes to see inserted into the document. If 
    * the last node is not a character run, then it will be deleted, a new run 
    * will be created and inserted into the paragraph between the bookmarkStart 
    * and bookmarkEnd tags. 
    * 
    * @param run An instance of the XWPFRun class that encapsulates the text 
    * that is to be inserted into the document following the bookmark. 
    */
   private void replaceBookmark(XWPFRun run) {
      Node nextNode = null;
      Node styleNode = null;
      Node lastRunNode = null;
//    Node toDelete = null;
//    NodeList childNodes = null;
      Stack<Node> nodeStack = null;
//    boolean textNodeFound = false;
//    boolean foundNested = true;
      int bookmarkStartID = 0;
      int bookmarkEndID = -1;
//    int numChildNodes = 0;

      nodeStack = new Stack<Node>();
      bookmarkStartID = this._ctBookmark.getId().intValue();
      nextNode = this._ctBookmark.getDomNode();
      nodeStack.push(nextNode);

      // Loop through the nodes looking for a matching bookmarkEnd tag 
      while (bookmarkStartID != bookmarkEndID) {
         nextNode = nextNode.getNextSibling();
         nodeStack.push(nextNode);

         // If an end tag is found, does it match the start tag? If so, end 
         // the while loop. 
         if (nextNode.getNodeName().contains(BookMark.BOOKMARK_END_TAG)) {
            try {
               bookmarkEndID = Integer.parseInt(nextNode.getAttributes().getNamedItem(BookMark.BOOKMARK_ID_ATTR_NAME).getNodeValue());
            } catch (NumberFormatException nfe) {
               bookmarkEndID = bookmarkStartID;
            }
         }
         //else { 
         // Place a reference to the node on the nodeStack 
         //    nodeStack.push(nextNode); 
         //} 
      }

      // If the stack of nodes found between the bookmark tags is not empty 
      // then they have to be removed. 
      if (!nodeStack.isEmpty()) {

         // Check the node at the top of the stack. If it is a run, get it's 
         // style - if any - and apply to the run that will be replacing it. 
         //lastRunNode = nodeStack.pop(); 
         lastRunNode = nodeStack.peek();

         if ((lastRunNode.getNodeName().equals(BookMark.RUN_NODE_NAME))) {
            styleNode = this.getStyleNode(lastRunNode);
            if (styleNode != null) {
               run.getCTR().getDomNode().insertBefore(styleNode.cloneNode(true), run.getCTR().getDomNode().getFirstChild());
            }
         }

         // Delete any and all node that were found in between the start and 
         // end tags. This is slightly safer that trying to delete the nodes 
         // as they are found while stepping through them in the loop above. 

         // If we are peeking, then this line can be commented out.             
         //this._para.getCTP().getDomNode().removeChild(lastRunNode); 
         this.deleteChildNodes(nodeStack);
      }

      // Place the text into position, between the bookmark tags. 
      this._para.getCTP().getDomNode().insertBefore(run.getCTR().getDomNode(), nextNode);
   }

   /** 
    * When replacing the bookmark's text, it is necessary to delete any nodes 
    * that are found between matching start and end tags. Complications occur 
    * here because it is possible to have bookmarks nested within bookmarks to 
    * almost any level and it is important to not remove any inner or nested 
    * bookmarks when replacing the contents of an outer or containing 
    * bookmark. This code successfully handles the simplest occurrence - where 
    * one bookmark completely contains another - but not more complex cases 
    * where one bookmark overlaps another in the markup. That is still to do. 
    * 
    * @param nodeStack An instance of the Stack class that encapsulates 
    * references to any and all nodes found between the opening and closing 
    * tags of a bookmark. 
    */
   private void deleteChildNodes(Stack<Node> nodeStack) {
      Node toDelete = null;
      int bookmarkStartID = 0;
      int bookmarkEndID = 0;
      boolean inNestedBookmark = false;

      // The first element in the list will be a bookmarkStart tag and that 
      // must not be deleted. 
      for (int i = 1; i < nodeStack.size(); i++) {

         // Get an element. If it is another bookmarkStart tag then 
         // again, we do not want to delete it, it's matching end tag 
         // or any nodes that fall inbetween. 
         toDelete = nodeStack.elementAt(i);
         if (toDelete.getNodeName().contains(BookMark.BOOKMARK_START_TAG)) {
            bookmarkStartID = Integer.parseInt(toDelete.getAttributes().getNamedItem(BookMark.BOOKMARK_ID_ATTR_NAME).getNodeValue());
            inNestedBookmark = true;
         } else if (toDelete.getNodeName().contains(BookMark.BOOKMARK_END_TAG)) {
            bookmarkEndID = Integer.parseInt(toDelete.getAttributes().getNamedItem(BookMark.BOOKMARK_ID_ATTR_NAME).getNodeValue());
            if (bookmarkEndID == bookmarkStartID) {
               inNestedBookmark = false;
            }
         } else {
            if (!inNestedBookmark) {
               this._para.getCTP().getDomNode().removeChild(toDelete);
            }
         }
      }
   }

   /** 
    * Recover styling information - if any - from another document node. Note 
    * that it is only possible to accomplish this if the node is a run (w:r) 
    * and this could be tested for in the code that calls this method. However, 
    * a check is made in the calling code as to whether a style has been found 
    * and only if a style is found is it applied. This method always returns 
    * null if it does not find a style making that checking process easier. 
    * 
    * @param parentNode An instance of the Node class that encapsulates a 
    * reference to a document node. 
    * @return An instance of the Node class that encapsulates the styling 
    * information applied to a character run. Note that if no styling 
    * information is found in the run OR if the node passed as an argument to 
    * the parentNode parameter is NOT a run, then a null value will be 
    * returned. 
    */
   private Node getStyleNode(Node parentNode) {
      Node childNode = null;
      Node styleNode = null;
      if (parentNode != null) {

         // If the node represents a run and it has child nodes then 
         // it can be processed further. Note, whilst testing the code, it 
         // was observed that although it is possible to get a list of a nodes 
         // children, even when a node did have children, trying to obtain this 
         // list would often return a null value. This is the reason why the 
         // technique of stepping from one node to the next is used here. 
         if (parentNode.getNodeName().equalsIgnoreCase(BookMark.RUN_NODE_NAME) && parentNode.hasChildNodes()) {

            // Get the first node and catch it's reference for return if 
            // the first child node is a style node (w:rPr). 
            childNode = parentNode.getFirstChild();
            if (childNode != null && childNode.getNodeName().equals("w:rPr")) {
               styleNode = childNode;
            } else {
               // If the first node was not a style node and there are other 
               // child nodes remaining to be checked, then step through 
               // the remaining child nodes until either a style node is 
               // found or until all child nodes have been processed. 
               while (childNode != null && (childNode = childNode.getNextSibling()) != null) {
                  if (childNode.getNodeName().equals(BookMark.STYLE_NODE_NAME)) {
                     styleNode = childNode;
                     // Note setting to null here if a style node is 
                     // found in order order to terminate any further 
                     // checking 
                     childNode = null;
                  }
               }
            }
         }
      }
      return (styleNode);
   }

   /** 
    * Get the text - if any - encapsulated by this bookmark. The creator of a 
    * Word document can chose to select one or more items of text and then 
    * insert a bookmark at that location. The highlighted text will appear 
    * between the square brackets that denote the location of a bookmark in the 
    * document's text and they will be returned by a call to this method. 
    * 
    * @return An instance of the String class encapsulating any text that 
    * appeared between the opening and closing square bracket associated with 
    * this bookmark. 
    * @throws XmlException Thrown if a problem is encountered parsing the XML 
    * markup recovered from the document in order to construct a CTText 
    * instance which may required to obtain the bookmarks text. 
    */
   public String getBookmarkText() throws XmlException {
      StringBuilder builder = null;
      // Are we dealing with a bookmarked table cell? If so, the entire 
      // contents of the cell - if anything - must be recovered and returned. 
      if (this._tableCell != null) {
         builder = new StringBuilder(this._tableCell.getText());
      } else {
         builder = this.getTextFromBookmark();
      }
      return (builder == null ? null : builder.toString());
   }

   /** 
    * There are two types of bookmarks. One is a simple placeholder whilst the 
    * second is still a placeholder but it 'contains' some text. In the second 
    * instance, the creator of the document has selected some text and then 
    * chosen to insert a bookmark there and the difference if obvious when 
    * looking at the XML markup. 
    * 
    * The simple case; 
    * 
    * <pre>
    * 
    * <w:bookmarkStart w:name="AllAlone" w:id="0"/><w:bookmarkEnd w:id="0"/>
    * 
    * </pre>
    * 
    * The more complex case; 
    * 
    * <pre>
    * 
    * <w:bookmarkStart w:name="InStyledText" w:id="3"/>
    *   <w:r w:rsidRPr="00DA438C">
    *     <w:rPr>
    *       <w:rFonts w:hAnsi="Engravers MT" w:ascii="Engravers MT" w:cs="Arimo"/>
    *       <w:color w:val="FF0000"/>
    *     </w:rPr>
    *     <w:t>text</w:t>
    *   </w:r>
    * <w:bookmarkEnd w:id="3"/>
    * 
    * </pre>
    * 
    * This method assumes that the user wishes to recover the content from any 
    * character run that appears in the markup between a matching pair of 
    * bookmarkStart and bookmarkEnd tags; thus, using the example above again, 
    * this method would return the String 'text' to the user. It is possible 
    * however for a bookmark to contain more than one run and for a bookmark to 
    * contain other bookmarks. In both of these cases, this code will return 
    * the text contained within any and all runs that appear in the XML markup 
    * between matching bookmarkStart and bookmarkEnd tags. The term 'matching 
    * bookmarkStart and bookmarkEndtags' here means tags whose id attributes 
    * have matching value. 
    * 
    * @return An instance of the StringBuilder class encapsulating the text 
    * recovered from any character run elements found between the bookmark's 
    * start and end tags. If no text is found then a null value will be 
    * returned. 
    * @throws XmlException Thrown if a problem is encountered parsing the XML 
    * markup recovered from the document in order to construct a CTText 
    * instance which may be required to obtain the bookmarks text. 
    */
   private StringBuilder getTextFromBookmark() throws XmlException {
      int startBookmarkID = 0;
      int endBookmarkID = -1;
      Node nextNode = null;
//    Node childNode = null;
//    CTText text = null;
      StringBuilder builder = null;
//    String rawXML = null;

      // Get the ID of the bookmark from it's start tag, the DOM node from the 
      // bookmark (to make looping easier) and initialise the StringBuilder. 
      startBookmarkID = this._ctBookmark.getId().intValue();
      nextNode = this._ctBookmark.getDomNode();
      builder = new StringBuilder();

      // Loop through the nodes held between the bookmark's start and end 
      // tags. 
      while (startBookmarkID != endBookmarkID) {

         // Get the next node and, if it is a bookmarkEnd tag, get it's ID 
         // as matching ids will terminate the while loop.. 
         nextNode = nextNode.getNextSibling();
         if (nextNode.getNodeName().contains(BookMark.BOOKMARK_END_TAG)) {

            // Get the ID attribute from the node. It is a String that must 
            // be converted into an int. An exception could be thrown and so 
            // the catch clause will ensure the loop ends neatly even if the 
            // value might be incorrect. Must inform the user. 
            try {
               endBookmarkID = Integer.parseInt(nextNode.getAttributes().getNamedItem(BookMark.BOOKMARK_ID_ATTR_NAME).getNodeValue());
            } catch (NumberFormatException nfe) {
               endBookmarkID = startBookmarkID;
            }
         } else {
            // This is not a bookmarkEnd node and can processed it for any 
            // text it may contain. Note the check for both type - it must 
            // be a run - and contain children. Interestingly, it seems as 
            // though the node may contain children and yet the call to 
            // nextNode.getChildNodes() will still return an empty list, 
            // hence the need to step through the child nodes. 
            if (nextNode.getNodeName().equals(BookMark.RUN_NODE_NAME) && nextNode.hasChildNodes()) {
               // Get the text from the child nodes. 
               builder.append(this.getTextFromChildNodes(nextNode));
            }
         }
      }
      return (builder);
   }

   /** 
    * Iterates through all and any children of the Node whose reference will be 
    * passed as an argument to the node parameter, and recover the contents of 
    * any text nodes. Testing revealed that a node can be called a text node 
    * and yet report it's type as being something different, an element node 
    * for example. Calling the getNodeValue() method on a text node will return 
    * the text the node encapsulates but doing the same on an element node will 
    * not. In fact, the call will simply return a null value. As a result, this 
    * method will test the nodes name to catch all text nodes - those whose 
    * name is to 'w:t' and then it's type. If the type is reported to be a text 
    * node, it is a trivial task to get at it's contents. However, if the type 
    * is not reported as a text type, then it is necessary to parse the raw XML 
    * markup for the node to recover it's value. 
    * 
    * @param node An instance of the Node class that encapsulates a reference 
    * to a node recovered from the document being processed. It should be 
    * passed a reference to a character run - 'w:r' - node. 
    * @return An instance of the String class that encapsulates the text 
    * recovered from the nodes children, if they are text nodes. 
    * @throws XmlException Thrown if a problem is encountered parsing the XML 
    * markup recovered from the document in order to construct the CTText 
    * instance which may be required to obtain the bookmarks text. 
    */
   private String getTextFromChildNodes(Node node) throws XmlException {
      NodeList childNodes = null;
      Node childNode = null;
      CTText text = null;
      StringBuilder builder = new StringBuilder();
      int numChildNodes = 0;

      // Get a list of chid nodes from the node passed to the method and 
      // find out how many children there are in the list. 
      childNodes = node.getChildNodes();
      numChildNodes = childNodes.getLength();

      // Iterate through the children one at a time - it is possible for a 
      // run to ciontain zero, one or more text nodes - and recover the text 
      // from an text type child nodes. 
      for (int i = 0; i < numChildNodes; i++) {

         // Get a node and check it's name. If this is 'w:t' then process as 
         // text type node. 
         childNode = childNodes.item(i);

         if (childNode.getNodeName().equals(BookMark.TEXT_NODE_NAME)) {

            // If the node reports it's type as txet, then simply call the 
            // getNodeValue() method to get at it's text. 
            if (childNode.getNodeType() == Node.TEXT_NODE) {
               builder.append(childNode.getNodeValue());
            } else {
               // Correct the type by parsing the node's XML markup and 
               // creating a CTText object. Call the getStringValue() 
               // method on that to get the text. 
               text = CTText.Factory.parse(childNode);
               builder.append(text.getStringValue());
            }
         }
      }
      return (builder.toString());
   }

   private void handleBookmarkedCells(String bookmarkValue, int where) {
      List<XWPFParagraph> paraList = null;
//    List<XWPFRun> runs = null;
      XWPFParagraph para = null;
//    XWPFRun readRun = null;
      // Get a list if paragraphs from the table cell and remove any and all. 
      paraList = this._tableCell.getParagraphs();
      for (int i = 0; i < paraList.size(); i++) {
         this._tableCell.removeParagraph(i);
      }
      para = this._tableCell.addParagraph();
      para.createRun().setText(bookmarkValue);
   }
}

5.使用方法。

7.我使用的例子。 

public void createDocxByTemplate(HttpServletRequest request,HttpServletResponse response){
   String distributorCode =  request.getParameter("distributorCode");
   DistrOrder distrOrder = distrOrderService.get(distributorCode);
   if (distrOrder == null){
      return;
   }
   WordExport changer = new WordExport();
   String fileName = DistrOrderController.class.getResource("订单协议Template.docx").getFile();
   fileName = EncodeUtils.decodeUrl(fileName.substring(1));
   System.out.println(fileName);
   changer.setTemplate(fileName);
   Map<String, Object> content = new HashMap<String, Object>();
   content.put("customerName", distrOrder.getCustomerName());
   content.put("createTime",DateUtil.formatDate(distrOrder.getCreateTime(),"yyyy年MM月dd日"));
   content.put("documentCode", distrOrder.getDocumentCode());

   content.put("mobilePhone", distrOrder.getMobilePhone());
   content.put("distribution", distrOrder.getDistribution());
   content.put("detailedAddress",distrOrder.getProvince() + distrOrder.getCity() + distrOrder.getRegion()+ distrOrder.getDetailedAddress());
   //changer.fillTableAtBookMark("table", content2);
   changer.replaceObject(content,"table1",9);
   //商品明细表格
   Long total = 0l;
   Double totalAmount = 0.0;
   List<Map<String, Object>> content2 = new ArrayList<Map<String, Object>>();
   //Map<String, String> table2 = new HashMap<String, String>();
   List<DistrOrderDetail> details = distrOrder.getDistrOrderDetailList();
   if (!ListUtils.isEmpty(details)){

      List<String> skus = details.stream().map(DistrOrderDetail::getSku).collect(Collectors.toList());
      List<Map<String, String>> mapList = apiGoodsService.getSizesInfoBySkus(skus);
      //根据SKU查询K3物料资料表中的其他属性页签的字段“属性值(输入)
      List<MaterialProperty> materials = distrOrderService.getInputPropertyValue(skus);
      //根据SKU查询产品系统表中基础信息模块SKU的物料名称

      List<K3GoodsInfo> goodsNames = k3GoodsInfoService.getGoodsNameBySkus(skus);
      for (Integer i = 0; i < details.size(); i++) {
         Map<String, Object> table2 = new HashMap<String, Object>();
         DistrOrderDetail detail = details.get(i);
         String sku =detail.getSku();
         Integer no = i+1;
         table2.put("no",no.toString());
         table2.put("sku",sku);
         String goodsName = "";
         Optional<K3GoodsInfo> optionalName = goodsNames.stream().filter(e-> e.getMaterialNo().equals(sku)).findFirst();
         if (optionalName.isPresent()){
            goodsName = optionalName.get().getMaterialName();
         }
         table2.put("goodsName",goodsName);
         Map map = MapUtils.newHashMap();
         map.put("width",50);
         map.put("height",60);
         map.put("bytes",changer.getBytes(detail.getSkuUrl()));
         table2.put("skuurl",map);
         //设置尺寸
         Optional<Map<String,String>> optional = mapList.stream().filter(e-> e.containsKey(sku)).findFirst();
         String skuSize ;
         if(optional.isPresent()){
            skuSize = optional.get().get(detail.getSku());
         }else{
            skuSize = "暂无尺寸信息";
         }
         table2.put("size",skuSize);
         //设置材质
         String material = "";
         if (!ListUtils.isEmpty(materials)){
            Optional<MaterialProperty> optional1 = materials.stream().filter(f -> sku.equals(f.getMaterialNo())).findFirst();
            if (optional1.isPresent()){
               material = optional1.get().getInputValue();
            }
         }
         table2.put("material",material);
         table2.put("price",detail.getPrice().toString());
         total += detail.getQuantity();
         totalAmount +=  detail.getAmount();
         table2.put("quantity",detail.getQuantity().toString());
         table2.put("amount",detail.getAmount().toString());
         content2.add(table2);
      }
   }
   //模拟合计项
   Map<String, Object> map = MapUtils.newHashMap();
   map.put("no","");
   map.put("sku","");
   map.put("goodsName","");
   map.put("skuurl","");
   map.put("size","");
   map.put("material","");
   map.put("price","合计:");
   map.put("quantity",total.toString());
   map.put("amount",totalAmount.toString());
   content2.add(map);
   changer.fillTableAtBookMarkObject("table2", content2,true);
   //下表格
   Map<String, Object> content1 = new HashMap<String, Object>();
   content1.put("totalMoney",distrOrder.getTotalPrice().toString());
   changer.replaceObject( content1,"table3",9);
   changer.downWord(response,"分销订单协议" + ".docx");

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值