一步一步摸索实现

开发需求:在法院文书的【经办法院】上方插入对应法院名称的蒙文头图片

在固定位置插入文字

public static void createPicInDoc(XWPFParagraph para,String value){
        try {
            XWPFDocument document = para.getDocument();
            XmlCursor xmlCursor = para.getCTP().newCursor();
            XWPFParagraph newPara = document.insertNewParagraph(xmlCursor);
            //如newPara为空,就按表格式搜索
            if(ObjectUtils.isNull(newPara)){
                List<XWPFTable> tables = document.getTables();
                for (XWPFTable tab :tables) {
                    XWPFTableRow row = tab.getRow(0);
                    XWPFTableCell cell = row.getCell(0);
                    List<XWPFParagraph> paragraphs = cell.getParagraphs();
                    XWPFParagraph xwpfParagraph = paragraphs.get(2);
                    XmlCursor xmlCursor1 = xwpfParagraph.getCTP().newCursor();
                    newPara = cell.insertNewParagraph(xmlCursor1);
                    XWPFRun run = newPara.createRun();
                    run.setText("hjx_caozuo_ceshi");
                    run.setFontSize(20);
                    run.setFontFamily("宋体");
                    run.setBold(true);
                }
            }
            byte[] bytes = Base64.decodeBase64(value);
            String blipId = document.addPictureData(bytes, 5);
            newPara.setAlignment(para.getAlignment());
            createPicture(blipId,0, 540, 77, newPara);
        } catch (Exception e) {
            logger.error("向word中添加图片异常!!!",e);
        }
    }

image.png

以上说明光标位置正确了,但是行和列是写死的,后面再看怎么改。

但是改成插入图片还是有问题,打成zip包发现图片已经存入doc里了,但是xml里没有<pic:pic这个标签节点,说明插入图片xml还有问题

在指定位置插入游标

先改了找经办法院位置的问题,单提了一个方法出来,但是循环嵌套比较多,不知道sonar能不能通过

/**
 * 从document中找【经办法院】所在位置,并插入新段落游标,返回新段落游标
 * @param document
 * @return XWPFParagraph
 */
public static XWPFParagraph findJBFYpicFromTable (XWPFDocument document) {
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for(XWPFTableCell cell : row.getTableCells()) {
                    if(cell.getText().contains("【经办法院】")){
                        for (XWPFParagraph para:cell.getParagraphs()) {
                            if(para.getText().contains("【经办法院】")){
                                XmlCursor xmlCursor = para.getCTP().newCursor();
                                return cell.insertNewParagraph(xmlCursor);
                            }
                        }
                    }
                }
            }
        }
        return null;
    }

在游标处插入图片

public static void createPicInDoc(XWPFParagraph para,String value){
        try {
            XWPFDocument document = para.getDocument();
            XmlCursor xmlCursor = para.getCTP().newCursor();
            XWPFParagraph newPara = document.insertNewParagraph(xmlCursor);
            //如newPara为空,就按表格式搜索
            if(ObjectUtils.isNull(newPara)){
                newPara = findJBFYpicFromTable(document);
            }
            byte[] bytes = Base64.decodeBase64(value);
            String blipId = document.addPictureData(bytes, 5);
            newPara.setAlignment(para.getAlignment());
            createPicture(blipId,0, 540, 77, newPara);
        } catch (Exception e) {
            logger.error("向word中添加图片异常!!!",e);
        }
    }

在createPicture之后使用流输出一份word到桌面,此时发现图片已经插入,只是没有替换其他标签,因为后面本应该还有其他操作,只是在这里输出了文件,这样看来,图片已经插入,可能是后面替换标签时又被替换了

过程中插入成功,最终生成文件插入失败,原因排查

跟代码找到,是在这里被remove了(cell.removeParagraph(j))

private static void batchProcessTableNewLine(XWPFTableCell cell){
		List<XWPFParagraph> paragraphs = cell.getParagraphs();
		int orgSize = paragraphs.size();
		String cellText = null;
		try{
			cellText =cell.getText();
		}catch(Exception e){
			logger.error("cell获取text失败", e);
		}
		if(StringUtils.isNotBlank(cellText)){
			for(int j = 0; j < orgSize; j++){
				XWPFParagraph paragraph = paragraphs.get(j);
				processNewLine(cell, paragraph);
			}
			for(int j = orgSize-1; j >= 0; j--){
				cell.removeParagraph(j);
			}
		}
	}

再往后排查,是表格式的,每个单元格中的paragraph会把新生成的paragraph完全复制一份放到原来的cell格中,然后将原来的paragraphremove掉,但是复制的时候并没复制到图片内容。

private static void setNewParagraphInfos(XWPFParagraph newParagraph, XWPFParagraph baseParagraph){
		newParagraph.setIndentationFirstLine(baseParagraph.getIndentationFirstLine());
		newParagraph.setSpacingBefore(baseParagraph.getSpacingBefore()==NumberUtil.NUM_MINUS_1?0:baseParagraph.getSpacingBefore());
		newParagraph.setSpacingAfter(baseParagraph.getSpacingAfter()==NumberUtil.NUM_MINUS_1?0:baseParagraph.getSpacingAfter());
		newParagraph.setAlignment(baseParagraph.getAlignment());
		boolean baseSpaceLineStyle = baseParagraph.getCTP().getPPr().getSpacing() != null &&
				baseParagraph.getCTP().getPPr().getSpacing().getLineRule() != null &&
				baseParagraph.getCTP().getPPr().getSpacing().getLine() != null;
		if(baseSpaceLineStyle){
			newParagraph.getCTP().getPPr().getSpacing().setLineRule(baseParagraph.getCTP().getPPr().getSpacing().getLineRule());
			newParagraph.getCTP().getPPr().getSpacing().setLine(baseParagraph.getCTP().getPPr().getSpacing().getLine());
		}
        newParagraph.getCTP().getPPr().setInd(baseParagraph.getCTP().getPPr().getInd());
        newParagraph.getCTP().getPPr().setNumPr(baseParagraph.getCTP().getPPr().getNumPr());
	}
	private static void setNewRunInfos(XWPFRun newRun, XWPFRun baseRun, String newRunTxt){
		RunStyleUtil.copyRunStyle(baseRun,newRun);
		newRun.setText(newRunTxt);
		newRun.setSubscript(baseRun.getSubscript());
	}

现在的问题就是如何把这部分set进去,两个部分,一个 <w:pPr>,一个 <w:r><w:drawing>。

 <w:pPr>
    <w:jc w:val="center"/>
  </w:pPr>
  <w:r>
    <w:drawing>
      <wp:inline distT="0" distB="0" distL="0" distR="0">
        <wp:extent cx="5143500" cy="733425"/>
        <wp:docPr id="1" name="xx"/>
        <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
          <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
            <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
              <pic:nvPicPr>
                <pic:cNvPr id="0" name="Generated"/>
                <pic:cNvPicPr/>
              </pic:nvPicPr>
              <pic:blipFill>
                <a:blip r:embed="rId17"/>
                <a:stretch>
                  <a:fillRect/>
                </a:stretch>
              </pic:blipFill>
              <pic:spPr>
                <a:xfrm>
                  <a:off x="0" y="0"/>
                  <a:ext cx="5143500" cy="733425"/>
                </a:xfrm>
                <a:prstGeom prst="rect">
                  <a:avLst/>
                </a:prstGeom>
              </pic:spPr>
            </pic:pic>
          </a:graphicData>
        </a:graphic>
      </wp:inline>
    </w:drawing>
  </w:r>

新增了

newParagraph.getCTP().setPPr(baseParagraph.getCTP().getPPr());
CTR ctr = newParagraph.getCTP().addNewR();
ctr.set(baseParagraph.getCTP().getRArray(0));

现在图片不会被丢失了,但是出了新问题,表格中的文本都重复了

image.png

这部分还是加个判断吧,条件为<w:r>下有<w:drawing>节点,即这个段落有图片时这样做。

if(!Objects.isNull(baseParagraph.getCTP().getRArray(0).getDrawingArray(0))){
			CTR ctr = newParagraph.getCTP().addNewR();
			ctr.set(baseParagraph.getCTP().getRArray(0));
		}

这样写运行中直接被catch了,后面发现应该是baseParagraph.getCTP().getRArray(0)这里就有可能已经为空了,再点的话就空指针了。

暂时改成这样,暂时解决,后面看怎么改

try{
	if(baseParagraph.getCTP().getRArray(0).getDrawingArray(0)!=null){
		CTR ctr = newParagraph.getCTP().addNewR();
		ctr.set(baseParagraph.getCTP().getRArray(0));
	}
}catch(Exception e){

}

image.png

  Optional.ofNullable(baseParagraph.getCTP())
				.map(p -> p.getRArray(0))
				.map(a -> a.getDrawingArray(0)).ifPresent(m -> {
			CTR ctr = newParagraph.getCTP().addNewR();
			ctr.set(baseParagraph.getCTP().getRArray(0));
		});

最终实现代码

public static void createPicInDoc(XWPFParagraph para,String value){
        try {
            XWPFDocument document = para.getDocument();
            XmlCursor xmlCursor = para.getCTP().newCursor();
            XWPFParagraph newPara = document.insertNewParagraph(xmlCursor);
            //如newPara为空,就按表格式搜索
            if(ObjectUtils.isNull(newPara)){
                newPara = findJBFYpicFromTable(document);
            }
            byte[] bytes = Base64.decodeBase64(value);
            String blipId = document.addPictureData(bytes, 5);
            newPara.setAlignment(para.getAlignment());
            createPicture(blipId,0, 540, 77, newPara);
        } catch (Exception e) {
            logger.error("向word中添加图片异常!!!",e);
        }
    }
 /**
     * 从document中找【经办法院】所在位置,并插入新段落游标,返回新段落游标
     * @param document
     * @return XWPFParagraph
     */
    public static XWPFParagraph findJBFYpicFromTable (XWPFDocument document) {
        for (XWPFTable table : document.getTables()) {
            for (XWPFTableRow row : table.getRows()) {
                for(XWPFTableCell cell : row.getTableCells()) {
                    if(cell.getText().contains("【经办法院】")){
                        for (XWPFParagraph para:cell.getParagraphs()) {
                            if(para.getText().contains("【经办法院】")){
                                XmlCursor xmlCursor = para.getCTP().newCursor();
                                return cell.insertNewParagraph(xmlCursor);
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
  //当段落里存在图片节点,就复制一份到新段落中
		List<CTR> rList = baseParagraph.getCTP().getRList();
		for (CTR c: rList) {
			if(c.getDrawingList().size()>0){
				CTR ctr = newParagraph.getCTP().addNewR();
				ctr.set(c);
			}
		}
  XWPFTableCell cell = document.getTables().get(0).getRows().get(0).getTableCells().get(0);

                XWPFTableCell cell = Optional.of(document.getTables())
                        .map(a -> a.get(0))
                        .map(b -> b.getRows())
                        .map(c -> c.get(0))
                        .map(d -> d.getTableCells())
                        .map(e -> e.get(0));