一、设置数据
final List<Object> list = new ArrayList<Object>() {{add(new TextRenderData("ver 0.0.3"));add(new PictureRenderData(100, 120, "src/test/resources/logo.png"));add(new TextRenderData("9d55b8", "Deeply in love with the things you love, just deepoove."));add(new TextRenderData("ver 0.0.4"));add(new PictureRenderData(100, 120, "src/test/resources/logo.png"));add(Numberings.ofDecimalParentheses().addItem("Deeply in love with the things you love, just deepoove.").addItem("Deeply in love with the things you love, just deepoove.").addItem("Deeply in love with the things you love, just deepoove.").create());add(Tables.of(new String[][] {new String[] { "00", "01" },new String[] { "10", "11" },}).create());}};
源码探究
1.序号对象生成
代码如下:
add(Numberings.ofDecimalParentheses().addItem("Deeply in love with the things you love, just deepoove.").addItem("Deeply in love with the things you love, just deepoove.").addItem("Deeply in love with the things you love, just deepoove.").create());
进入Numbering类,此处调用了一个方法addItem:
public NumberingBuilder addItem(String text) {this.addItem(Texts.of(text).create());return this;}
此处的Texts.of(text).create()一系列源码如下:
首先通过of获取一个TextBuilder实例对象:
public static TextBuilder of(String text) {return new TextBuilder(text);}
再调用该类的create()方法
返回一个TextRenderData对象:
@Overridepublic TextRenderData create() {TextRenderData data = null;if (null != url) {data = new HyperlinkTextRenderData(text, url);} else if (null != bookmark) {data = new BookmarkTextRenderData(text, bookmark);} else {data = new TextRenderData(text);}data.setStyle(style);return data;}
在当前方法中,使用到了一个类属性:url
private String url;
在当前类中,url内容通过一个方法进行设置:
public TextBuilder link(String url) {this.url = url;// default blue color and underlineif (null == this.style) {this.style = Style.builder().buildColor("0000FF").buildUnderlinePatterns(UnderlinePatterns.SINGLE).build();}return this;}
不过因为刚才创建TextBuilder过程中并没有设置url,因此此处的url为空,因此程序选择进入:
else {data = new TextRenderData(text);}
创建了一个TextRenderData对象,且同样也没有设置style,因此此处设置的style为空:
data.setStyle(style); // style == null
继续回到方法调用的第一层
public NumberingBuilder addItem(String text) {this.addItem(Texts.of(text).create());return this;}
第一次点击没有到底,再次进入 this.addItem(Texts.of(text).create());
public NumberingBuilder addItem(TextRenderData item) {data.getItems().add(new NumberingItemRenderData(0, Paragraphs.of(item).create()));return this;}
首先来看Paragraphs.of(item).create():
首先来看Paragraphs.of(item),此处的item为TextRenderData类型的实例对象:
public static ParagraphBuilder of(PictureRenderData picture) {return of().addPicture(picture);}
public static ParagraphBuilder of(TextRenderData text) {return of().addText(text);}
@Overridepublic ParagraphRenderData create() {ParagraphRenderData data = new ParagraphRenderData();data.setContents(contents);data.setParagraphStyle(paragraphStyle);return data;}
将之前的text内容绑定到Paragraphs的contents属性中,返回一个ParagraphRenderData实例对象,因为此处paragraphStyle没有设置,因此为null
接下来再来看new NumberingItemRenderData(0, Paragraphs.of(item).create()):
public NumberingItemRenderData(int level, ParagraphRenderData item) {this.level = level;this.item = item;}
给构造函数传入两个参数,分别为序号级别(貌似和序号生成层级有关)和ParagraphRenderData实例对象
NumberingItemRenderData类含有如下属性:
public class NumberingItemRenderData implements Serializable {public static final int LEVEL_NORMAL = -1;private static final long serialVersionUID = 1L;// improved: Support DocumentRenderDataprivate ParagraphRenderData item;private int level;
后面两个我们已经通过刚才的构造参数绑定上了,回到开头:
public NumberingBuilder addItem(TextRenderData item) {data.getItems().add(new NumberingItemRenderData(0, Paragraphs.of(item).create()));return this;}
此处的data.getItems()返回一个NumberingItemRenderData类型的List集合:
public List<NumberingItemRenderData> getItems() {return items;}
因此,此处将一个配置好的NumberingItemRenderData对象放入NumberingItemRenderData类型为泛型的List集合中,调用List结合的add方法
最后返回this,即返回一个NumberingBuilder实例对象
回到调用的第一层:
public NumberingBuilder addItem(String text) {this.addItem(Texts.of(text).create());return this;}
所以这个方法的最终意义为,将内容和层级添加进下面这个列表当中去:
private List<NumberingItemRenderData> items = new ArrayList<>();
2.序号类型选择
回到最开始的地方:
add(Numberings.ofDecimalParentheses().addItem("Deeply in love with the things you love, just deepoove.").addItem("Deeply in love with the things you love, just deepoove.").addItem("Deeply in love with the things you love, just deepoove.").create());
前面在.addItem()方法中将要使用序号的内容添加进了集合当中。接下来还需好考虑的问题就是:怎么样去选择序号的类型?
在此处可以看到调用了一个方法
Numberings.ofDecimalParentheses()
发现此方法好像对应着一个序号类型:
public static NumberingBuilder ofDecimalParentheses() {return of(NumberingFormat.DECIMAL_PARENTHESES);}
并且通过调用of()方法,也给当前这个Numberings实例对象完成了格式绑定的任务
public static NumberingBuilder of(NumberingFormat format) {return new NumberingBuilder(format);}
private NumberingBuilder(NumberingFormat format) {data = new NumberingRenderData(format, new ArrayList<>());}
private NumberingRenderData data;
序号的类型来自NumberingFormatl类,其中有设置以下几个常量
public class NumberingFormat implements Serializable {private static final long serialVersionUID = 1L;public static final NumberingFormat BULLET = new NumberingFormat(NumFormat.BULLET, "●");public static final NumberingFormat DECIMAL;public static final NumberingFormat DECIMAL_PARENTHESES;public static final NumberingFormat LOWER_LETTER;public static final NumberingFormat LOWER_ROMAN;public static final NumberingFormat UPPER_LETTER;public static final NumberingFormat UPPER_ROMAN;/*** 1. 2. 3.*/public static final Builder DECIMAL_BUILDER;/*** 1) 2) 3)*/public static final Builder DECIMAL_PARENTHESES_BUILDER;/*** a. b. c.*/public static final Builder LOWER_LETTER_BUILDER;/*** i ⅱ ⅲ*/public static final Builder LOWER_ROMAN_BUILDER;/*** A. B. C.*/public static final Builder UPPER_LETTER_BUILDER;/*** Ⅰ Ⅱ Ⅲ*/public static final Builder UPPER_ROMAN_BUILDER;
以及以下两个变量
private int numFmt;private String lvlText;
其中常量中的值由一个静态代码块进行设置:
static {DECIMAL_BUILDER = builder("%{0}.").withNumFmt(NumFormat.DECIMAL);DECIMAL_PARENTHESES_BUILDER = builder("%{0})").withNumFmt(NumFormat.DECIMAL);LOWER_LETTER_BUILDER = builder("%{0}.").withNumFmt(NumFormat.LOWER_LETTER);LOWER_ROMAN_BUILDER = builder("%{0}.").withNumFmt(NumFormat.LOWER_ROMAN);UPPER_LETTER_BUILDER = builder("%{0}.").withNumFmt(NumFormat.UPPER_LETTER);UPPER_ROMAN_BUILDER = builder("%{0}.").withNumFmt(NumFormat.UPPER_ROMAN);DECIMAL = DECIMAL_BUILDER.build(0);DECIMAL_PARENTHESES = DECIMAL_PARENTHESES_BUILDER.build(0);LOWER_LETTER = LOWER_LETTER_BUILDER.build(0);LOWER_ROMAN = LOWER_ROMAN_BUILDER.build(0);UPPER_LETTER = UPPER_LETTER_BUILDER.build(0);UPPER_ROMAN = UPPER_ROMAN_BUILDER.build(0);}
可以看到此处使用了当前类中的一个方法
public static Builder builder(String lvlTemplate) {return new Builder(lvlTemplate);}
跳入一个内部类中的构造方法,给当前内部类Builder属性lvlTemplate(模式)就行设置:
private Builder(String lvlTemplate) {this.lvlTemplate = lvlTemplate;}
设置成功后返回一个Builder对象,接下来再调用当前内部类中的withNumFmt方法:
public Builder withNumFmt(NumFormat numFmt) {this.numFmt = numFmt;return this;}
此处给当前内部类的numFmt属性设置了值,同样返回一个Builder实例对象:
public Builder withNumFmt(NumFormat numFmt) {this.numFmt = numFmt;return this;}
序号类型的具体设置规则
/*** 1. 2. 3.*/public static final Builder DECIMAL_BUILDER; // "%{0}." DECIMAL/*** 1) 2) 3)*/public static final Builder DECIMAL_PARENTHESES_BUILDER; // %{0}) DECIMAL/*** a. b. c.*/public static final Builder LOWER_LETTER_BUILDER; // %{0}. 同理可得 a) b) c) 样式为%{0}) LOWER_LETTER/*** i ⅱ ⅲ*/public static final Builder LOWER_ROMAN_BUILDER; // %{0}. LOWER_ROMAN/*** A. B. C.*/public static final Builder UPPER_LETTER_BUILDER; // %{0}. UPPER_LETTER/*** Ⅰ Ⅱ Ⅲ*/public static final Builder UPPER_ROMAN_BUILDER; // %{0}. UPPER_ROMAN
二、将数据放入一个Map集合中
Map<String, Object> datas = new HashMap<String, Object>();datas.put("website", list);
三、创建一个配置类
Configure config = Configure.builder().bind("website", new ListRenderPolicy() {}).build();
配置配置类
绑定插件,命名为website
public ConfigureBuilder bind(String tagName, RenderPolicy policy) {config.customPolicy(tagName, policy);return this;}
将插件放入CUSTOM_POLICYS集合中
public void customPolicy(String tagName, RenderPolicy policy) {CUSTOM_POLICYS.put(tagName, policy);}
四、根据模板文件和配置类创建一个模板文档对象
XWPFTemplate template = XWPFTemplate.compile("src/test/resources/template/render_list.docx", config);
1.模板文件

2.构建一个模板对象
将配置文件和配置类都加载进配置模板中
public static XWPFTemplate compile(String absolutePath, Configure config) {return compile(new File(absolutePath), config);}
public static XWPFTemplate compile(File templateFile, Configure config) {try {return compile(new FileInputStream(templateFile), config);} catch (FileNotFoundException e) {throw new ResolverException("Cannot find the file [" + templateFile.getPath() + "]", e);}}
public static XWPFTemplate compile(InputStream inputStream, Configure config) {try {XWPFTemplate template = new XWPFTemplate();template.config = config;template.doc = new NiceXWPFDocument(inputStream);template.resolver = new TemplateResolver(template.config);template.renderer = new DefaultRender();template.eleTemplates = template.resolver.resolveDocument(template.doc);return template;} catch (OLE2NotOfficeXmlFileException e) {logger.error("Poi-tl currently only supports .docx format");throw new ResolverException("Compile template failed", e);} catch (IOException e) {throw new ResolverException("Compile template failed", e);}}
在生成一个NiceXWPFDocument对象时,调用了父类XWPFDocument类中的构造方法
public NiceXWPFDocument(InputStream in) throws IOException {this(in, false);}
不过在执行顺序上,加载模板文档流进该对象时,首先应该执行静态属性
private static Logger logger = LoggerFactory.getLogger(NiceXWPFDocument.class);
再执行NiceXWPFDocument类中static静态代码块
static {try {// 使用反射获取xwpfrelation的构造方法Constructor<XWPFRelation> constructor = ReflectionUtils.findConstructor(XWPFRelation.class, String.class,String.class, String.class, POIXMLRelation.NoArgConstructor.class,POIXMLRelation.PackagePartConstructor.class);COMMENTS = constructor.newInstance(new Object[] { "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml","http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments","/word/comments.xml", new POIXMLRelation.NoArgConstructor() {@Overridepublic POIXMLDocumentPart init() {return new XWPFComments();}}, new POIXMLRelation.PackagePartConstructor() {@Overridepublic POIXMLDocumentPart init(PackagePart part) throws IOException, XmlException {return new XWPFComments(part);}} });} catch (Exception e) {logger.warn("init comments releation error: {}", e.getMessage());}}
在此静态代码块中,可以看见,首先是利用反射的方法调用XWPFRelation类创建了一个构造方法的对象
Constructor<XWPFRelation> constructor = ReflectionUtils.findConstructor(XWPFRelation.class, String.class,String.class, String.class, POIXMLRelation.NoArgConstructor.class,POIXMLRelation.PackagePartConstructor.class);
然后再通过这个对象生成一个XWPFRelation对象
COMMENTS = constructor.newInstance(new Object[] { "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml","http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments","/word/comments.xml", new POIXMLRelation.NoArgConstructor() {@Overridepublic POIXMLDocumentPart init() {return new XWPFComments();}}, new POIXMLRelation.PackagePartConstructor() {@Overridepublic POIXMLDocumentPart init(PackagePart part) throws IOException, XmlException {return new XWPFComments(part);}} });
此时,NiceXWPFDocument中的属性
protected static XWPFRelation COMMENTS;
是有内容的
接下来继续往下执行
template.resolver = new TemplateResolver(template.config);template.renderer = new DefaultRender();template.eleTemplates = template.resolver.resolveDocument(template.doc);return template;} catch (OLE2NotOfficeXmlFileException e) {logger.error("Poi-tl currently only supports .docx format");throw new ResolverException("Compile template failed", e);} catch (IOException e) {throw new ResolverException("Compile template failed", e);}}
此时变量的情况是这样的
现在进入TemplateResolver类,执行顺序如下
private static Logger logger = LoggerFactory.getLogger(TemplateResolver.class);
public TemplateResolver(Configure config) {this(config, config.getElementTemplateFactory());}
此时已经创建了一个TemplateResolver实例对象,并且我们的插件已经载入
再次向下执行,创建了一个空的DefaultRender实例对象,用来待会加载数据
template.renderer = new DefaultRender();
将刚才创建好的NiceXWPFDocument实例对象加载进模板解析器中
template.eleTemplates = template.resolver.resolveDocument(template.doc);
进入resolveDocument方法
@Overridepublic List<MetaTemplate> resolveDocument(XWPFDocument doc) {List<MetaTemplate> metaTemplates = new ArrayList<>();if (null == doc) return metaTemplates;logger.info("Resolve the document start...");// doc.getBodyElements() 是poi提供的APImetaTemplates.addAll(resolveBodyElements(doc.getBodyElements()));metaTemplates.addAll(resolveBodys(doc.getHeaderList()));metaTemplates.addAll(resolveBodys(doc.getFooterList()));metaTemplates.addAll(resolveBodys(doc.getFootnotes()));metaTemplates.addAll(resolveBodys(doc.getEndnotes()));if (doc instanceof NiceXWPFDocument) {metaTemplates.addAll(resolveBodys(((NiceXWPFDocument) doc).getAllComments()));}logger.info("Resolve the document end, resolve and create {} MetaTemplates.", metaTemplates.size());return metaTemplates;}
此处的.getBodyElements是使用的父类XWPFDocument类中的方法,可以看到在这里,将NiceXWPFDocument实例对象中的一些模块提取出来并存放到了一个泛型为MetaTemplate的List集合List中
public List<MetaTemplate> resolveBodyElements(List<IBodyElement> bodyElements) {List<MetaTemplate> metaTemplates = new ArrayList<>();if (null == bodyElements) return metaTemplates;// current iterable templates state// 关于Deque 双端队列 https://blog.csdn.net/devnn/article/details/82716447// 双端队列 实现了在队列头和队列尾的高效插入和移除// 双端队列适合用于工作密取(Work Stealing)模式// 在工作密取设计中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者// 双端队列末尾秘密的获取工作。// 因此密取工作模式比传统的生产者-消费者模式具有更高的可伸缩性,这是因为工作者线程不会在单个共享的任务队列上发生竞争Deque<BlockTemplate> stack = new LinkedList<BlockTemplate>();// 遍历元素列表// 此处只能对段落和表格进行识别for (IBodyElement element : bodyElements) {if (element == null) continue;// 获取段落类型if (element.getElementType() == BodyElementType.PARAGRAPH) {XWPFParagraph paragraph = (XWPFParagraph) element;new RunningRunParagraph(paragraph, templatePattern).refactorRun();resolveXWPFRuns(paragraph.getRuns(), metaTemplates, stack);// 获取表格类型} else if (element.getElementType() == BodyElementType.TABLE) {XWPFTable table = (XWPFTable) element;List<XWPFTableRow> rows = table.getRows();if (null == rows) continue;for (XWPFTableRow row : rows) {List<XWPFTableCell> cells = row.getTableCells();if (null == cells) continue;cells.forEach(cell -> {addNewMeta(metaTemplates, stack, resolveBodyElements(cell.getBodyElements()));});}}}checkStack(stack);return metaTemplates;}
在判断当前元素为段落时,创建一个RunningRunParagraph对象,并将XWPFParagraph类型的对象和Pattern类型的对象作为参数传递到构造方法中
public RunningRunParagraph(XWPFParagraph paragraph, Pattern pattern) {this.paragraph = new XWPFParagraphWrapper(paragraph);// 获取当前段落中所有r标签this.runs = paragraph.getRuns();// 如果r为空,那么就说明这是一个空段落if (null == runs || runs.isEmpty()) return;// 判断当前段落文本内容是否符合指定正则规则Matcher matcher = pattern.matcher(paragraph.getParagraphText());if (matcher.find()) {refactorParagraph();}buildRunEdge(pattern);}
此处的pattern是直接用的的AbstractRsolver中的属性
public abstract class AbstractResolver implements Resolver {protected final Configure config;protected Pattern templatePattern;protected Pattern gramerPattern;
在构造方法中被调用赋值
public AbstractResolver(Configure config) {this.config = config;patternCreated();}
void patternCreated() {String sign = getGramarRegex(config);String prefix = RegexUtils.escapeExprSpecialWord(config.getGramerPrefix());String suffix = RegexUtils.escapeExprSpecialWord(config.getGramerSuffix());templatePattern = Pattern.compile(MessageFormat.format(FORMAT_TEMPLATE, prefix, sign, config.getGrammerRegex(), suffix));gramerPattern = Pattern.compile(MessageFormat.format(FORMAT_GRAMER, prefix, suffix));}
而其中常量FORMAT_TEMPLATE内容如下
private static final String FORMAT_TEMPLATE = "{0}{1}{2}{3}";
其为一个由prefix sign config.getGrammerRegex()和suffix组成的模式而生成的正则表达式规则,而这些值是通过下面的代码获得的
String sign = getGramarRegex(config);String prefix = RegexUtils.escapeExprSpecialWord(config.getGramerPrefix());String suffix = RegexUtils.escapeExprSpecialWord(config.getGramerSuffix());
回到前面,对RunningRunParagraph实例对象调用了refactorRun()方法
public List<XWPFRun> refactorRun() {if (pairs.isEmpty()) return null;List<XWPFRun> templateRuns = new ArrayList<XWPFRun>();int size = pairs.size();Pair<RunEdge, RunEdge> runEdgePair;for (int n = size - 1; n >= 0; n--) {runEdgePair = pairs.get(n);RunEdge startEdge = runEdgePair.getLeft();RunEdge endEdge = runEdgePair.getRight();int startRunPos = startEdge.getRunPos();int endRunPos = endEdge.getRunPos();int startOffset = startEdge.getRunEdge();int endOffset = endEdge.getRunEdge();String startText = runs.get(startRunPos).text();String endText = runs.get(endRunPos).text();if (endOffset + 1 >= endText.length()) {// delete the redundant end Run directlyif (startRunPos != endRunPos) paragraph.removeRun(endRunPos);} else {// split end run, set extra in a runString extra = endText.substring(endOffset + 1, endText.length());if (startRunPos == endRunPos) {// create run and set extra contentXWPFRun extraRun = paragraph.insertNewRun(endRunPos + 1);StyleUtils.styleRun(extraRun, runs.get(endRunPos));buildExtra(extra, extraRun);} else {// Set the extra content to the redundant end runXWPFRun extraRun = runs.get(endRunPos);buildExtra(extra, extraRun);}}// remove extra runfor (int m = endRunPos - 1; m > startRunPos; m--) {paragraph.removeRun(m);}if (startOffset <= 0) {// set the start Run directlyXWPFRun templateRun = runs.get(startRunPos);templateRun.setText(startEdge.getTag(), 0);templateRuns.add(runs.get(startRunPos));} else {// split start run, set extra in a runString extra = startText.substring(0, startOffset);XWPFRun extraRun = runs.get(startRunPos);buildExtra(extra, extraRun);XWPFRun templateRun = paragraph.insertNewRun(startRunPos + 1);StyleUtils.styleRun(templateRun, extraRun);templateRun.setText(startEdge.getTag(), 0);templateRuns.add(runs.get(startRunPos + 1));}}return templateRuns;}
在测试中发现pairs属性为空,因此上面这个方法并没有执行,因此暂时不清楚此方法到底有什么用
再往下走,进入resolveXWPFRuns方法,传入参数为:段落中所有r内容、空集合、空双端队列
private void resolveXWPFRuns(List<XWPFRun> runs, final List<MetaTemplate> metaTemplates,final Deque<BlockTemplate> stack) {for (XWPFRun run : runs) {String text = null;if (StringUtils.isBlank(text = run.getText(0))) {// textboxList<MetaTemplate> visitBodyElements = resolveTextbox(run);if (!visitBodyElements.isEmpty()) {addNewMeta(metaTemplates, stack, visitBodyElements);continue;}// pictureList<PictureTemplate> pictureTemplates = resolveXWPFPictures(run.getEmbeddedPictures());if (!pictureTemplates.isEmpty()) {addNewMeta(metaTemplates, stack, pictureTemplates);continue;}// w:pict v:imagedataPictImageTemplate pictImageTemplate = resolvePictImage(run);if (null != pictImageTemplate) {addNewMeta(metaTemplates, stack, pictImageTemplate);continue;}// chartChartTemplate chartTemplate = resolveXWPFChart(run);if (null != chartTemplate) {addNewMeta(metaTemplates, stack, chartTemplate);continue;}continue;}RunTemplate runTemplate = (RunTemplate) parseTemplateFactory(text, run, run);if (null == runTemplate) continue;char charValue = runTemplate.getSign().charValue();if (charValue == config.getIterable().getLeft()) {IterableTemplate freshIterableTemplate = new IterableTemplate(runTemplate);stack.push(freshIterableTemplate);} else if (charValue == config.getIterable().getRight()) {if (stack.isEmpty()) throw new ResolverException("Mismatched start/end tags: No start mark found for end mark " + runTemplate);BlockTemplate latestIterableTemplate = stack.pop();if (StringUtils.isNotEmpty(runTemplate.getTagName())&& !latestIterableTemplate.getStartMark().getTagName().equals(runTemplate.getTagName())) {throw new ResolverException("Mismatched start/end tags: start mark "+ latestIterableTemplate.getStartMark() + " does not match to end mark " + runTemplate);}latestIterableTemplate.setEndMark(runTemplate);if (latestIterableTemplate instanceof IterableTemplate) {latestIterableTemplate = ((IterableTemplate) latestIterableTemplate).buildIfInline();}addNewMeta(metaTemplates, stack, latestIterableTemplate);} else {addNewMeta(metaTemplates, stack, runTemplate);}}}
经过测试发现,resolveDocument这个方法是用来解析模板中的标签的,例如此处模板中内容如下
经过resolveDocument方法解析后,此时变量情况如下
所以回到最开始,XWPFTemplate中的eleTemplates属性是用来存放标签内容的
template.eleTemplates = template.resolver.resolveDocument(template.doc);
回到表层,此处获得了一个配置了文档内容和标签内容以及一些设置的XWPFTemplate文档对象
XWPFTemplate template = XWPFTemplate.compile("src/test/resources/template/render_list.docx", config);
五、加载数据进模板,并输出
template.render(datas).writeToFile("out_render_list.docx");
1.加载数据
public XWPFTemplate render(Object model) {this.renderer.render(this, model);return this;}
传入参数为模板对象(XWPFTemplate类型)以及待载入的数据对象(Object类型 此处为一个Map集合)
@Overridepublic void render(XWPFTemplate template, Object root) {// 分别检查模板对象和数据对象是否为空Objects.requireNonNull(template, "Template must not be null.");Objects.requireNonNull(root, "Data root must not be null");LOGGER.info("Render template start...");// 通过调用XWPFTemplate属性Configure类中提供的工厂类 构建RenderDataCompute 获取一个绑定了数据对象的RenderDataCompute实例对象RenderDataCompute renderDataCompute = template.getConfig().getRenderDataComputeFactory().newCompute(root);StopWatch watch = new StopWatch();try {watch.start();renderTemplate(template, renderDataCompute);renderInclude(template, renderDataCompute);} catch (Exception e) {if (e instanceof RenderException) throw (RenderException) e;throw new RenderException("Cannot render docx template, please check the Exception", e);} finally {watch.stop();}LOGGER.info("Successfully Render template in {} millis", TimeUnit.NANOSECONDS.toMillis(watch.getNanoTime()));}
在try{}catch(){}块中有这样两个调用语句
renderTemplate(template, renderDataCompute);renderInclude(template, renderDataCompute);
第一条语句:renderTemplate()
private void renderTemplate(XWPFTemplate template, RenderDataCompute renderDataCompute) {// lognew LogProcessor().process(template.getElementTemplates());// renderDocumentProcessor documentRender = new DocumentProcessor(template, template.getResolver(), renderDataCompute);documentRender.process(template.getElementTemplates());}
在此方法中,首先是对日志进行了处理,暂时没有深入挖掘,所以不知道实现细节是怎么样的;
第二个是对数据进行一个绑定操作,传入另外一个参数为刚才从模板方法中获得的RenderDataCompute实例对象
这里又创建了一个DocumentProcessor实例方法,在调用构造方法时候分别传入了单个参数:XWPFTemplate类型的模板方法、Resolver类型的模板标签解析器,以及RenderDataCompute类型的数据模型
public DocumentProcessor(XWPFTemplate template, final Resolver resolver,final RenderDataCompute renderDataCompute) {elementProcessor = new ElementProcessor(template, resolver, renderDataCompute);iterableProcessor = new IterableProcessor(template, resolver, renderDataCompute);inlineIterableProcessor = new InlineIterableProcessor(template, resolver, renderDataCompute);}
执行上述几步操作,分别执行以下几个构造方法
public ElementProcessor(XWPFTemplate template, Resolver resolver, RenderDataCompute renderDataCompute) {super(template, resolver, renderDataCompute);}
public IterableProcessor(XWPFTemplate template, Resolver resolver, RenderDataCompute renderDataCompute) {super(template, resolver, renderDataCompute);}
public InlineIterableProcessor(XWPFTemplate template, Resolver resolver, RenderDataCompute renderDataCompute) {super(template, resolver, renderDataCompute);}
而上述对象的创建都赋值给了当前的属性
public class DocumentProcessor implements Visitor {private ElementProcessor elementProcessor;private IterableProcessor iterableProcessor;private InlineIterableProcessor inlineIterableProcessor;
回到上一层
DocumentProcessor documentRender = new DocumentProcessor(template, template.getResolver(), renderDataCompute);documentRender.process(template.getElementTemplates());
将模中标签信息都传入process方法中
public void process(List<MetaTemplate> templates) {// process in order( or sort first)templates.forEach(template -> template.accept(this));Set<XWPFTextboxContent> textboxs = obtainTextboxes(templates);textboxs.forEach(content -> {content.getXmlObject().set(content.getCTTxbxContent());});}
从传入参数来看,process方法应该是要将数据和标签进行结合的方法
将这里的foreach循环改为增强for循环
for(MetaTemplate template : templates){template.accept(this);}
debug进入.accept方法
@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}

@Overridepublic void visit(RunTemplate runTemplate) {runTemplate.accept(elementProcessor);}

void visit(ElementTemplate eleTemplate) {RenderPolicy policy = eleTemplate.findPolicy(template.getConfig());Objects.requireNonNull(policy, "Cannot find render policy: [" + eleTemplate.getTagName() + "]");if (policy instanceof DocxRenderPolicy) return;logger.info("Start render Template {}, Sign:{}, policy:{}", eleTemplate, eleTemplate.getSign(),ClassUtils.getShortClassName(policy.getClass()));policy.render(eleTemplate, renderDataCompute.compute(eleTemplate.getTagName()), template);}
2.输出文件
public void writeToFile(String path) throws IOException {this.writeAndClose(new FileOutputStream(path));}
public void writeAndClose(OutputStream out) throws IOException {try {this.write(out);out.flush();} finally {PoitlIOUtils.closeQuietlyMulti(this.doc, out);}}
文件输出过程:
public void write(OutputStream out) throws IOException {this.doc.write(out);}
其中doc为XWPFTemplate类属性NiceXWPFDocument类型的doc对象
public class XWPFTemplate implements Closeable {...private NiceXWPFDocument doc;...
因为NiceXWPFDocument继承自XWPFDocument类,因此写出文件时可以直接使用其write方法
public class NiceXWPFDocument extends XWPFDocument {
意外发现序号的生成办法
public BigInteger addNewMultiLevelNumberingId(NumberingFormat... numFmts) {XWPFNumbering numbering = this.getNumbering();if (null == numbering) {numbering = this.createNumbering();}XWPFNumberingWrapper numberingWrapper = new XWPFNumberingWrapper(numbering);// 对应着numberings.xml文件 可以通过此对象对已存在的序号进行修改,也可以增加新的序号// <w:abstractNum w:abstractNumId="1">CTAbstractNum cTAbstractNum = CTAbstractNum.Factory.newInstance();// if we have an existing document, we must determine the next// free number first.cTAbstractNum.setAbstractNumId(numberingWrapper.getNextAbstractNumID());// CTMultiLevelType addNewMultiLevelType = cTAbstractNum.addNewMultiLevelType();// addNewMultiLevelType.setVal(STMultiLevelType.HYBRID_MULTILEVEL);for (int i = 0; i < numFmts.length; i++) {NumberingFormat numFmt = numFmts[i];// <w:lvl w:ilvl="0">CTLvl cTLvl = cTAbstractNum.addNewLvl();CTPPrBase ppr = cTLvl.isSetPPr() ? cTLvl.getPPr() : cTLvl.addNewPPr();CTInd ind = ppr.isSetInd() ? ppr.getInd() : ppr.addNewInd();ind.setLeft(BigInteger.valueOf(UnitUtils.cm2Twips(0.74f) * i));Enum fmt = STNumberFormat.Enum.forInt(numFmt.getNumFmt());String val = numFmt.getLvlText();// <w:numFmt w:val="decimal"/>cTLvl.addNewNumFmt().setVal(fmt);// <w:lvlText w:val="%1)"/>cTLvl.addNewLvlText().setVal(val);// <w:start w:val="1"/>cTLvl.addNewStart().setVal(BigInteger.valueOf(1));cTLvl.setIlvl(BigInteger.valueOf(i));if (fmt == STNumberFormat.BULLET) {cTLvl.addNewLvlJc().setVal(STJc.LEFT);CTRPr addNewRPr = cTLvl.addNewRPr();CTFonts ctFonts = addNewRPr.addNewRFonts();ctFonts.setAscii("Wingdings");ctFonts.setHAnsi("Wingdings");ctFonts.setHint(STHint.DEFAULT);}}XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum);BigInteger abstractNumID = numbering.addAbstractNum(abstractNum);return numbering.addNum(abstractNumID);}
3.NiceXWPFDocument和XWPFDocument
XWPFDocument是POI中为读取.docx文档定义的一个文档类,而NiceXWPFDocument是Poi-tl为处理数据和模板类所定义的一个文档类,其继承自XWPFDocument类。
- 类属性比较
XWPFDoucment
protected List<XWPFFooter> footers = new ArrayList();protected List<XWPFHeader> headers = new ArrayList();protected List<XWPFComment> comments = new ArrayList();protected List<XWPFHyperlink> hyperlinks = new ArrayList();protected List<XWPFParagraph> paragraphs = new ArrayList();protected List<XWPFTable> tables = new ArrayList();protected List<XWPFSDT> contentControls = new ArrayList();protected List<IBodyElement> bodyElements = new ArrayList();protected List<XWPFPictureData> pictures = new ArrayList();protected Map<Long, List<XWPFPictureData>> packagePictures = new HashMap();protected XWPFEndnotes endnotes;protected XWPFNumbering numbering;protected XWPFStyles styles;protected XWPFFootnotes footnotes;private CTDocument1 ctDocument;private XWPFSettings settings;protected final List<XWPFChart> charts = new ArrayList();private IdentifierManager drawingIdManager = new IdentifierManager(0L, 4294967295L);private FootnoteEndnoteIdManager footnoteIdManager = new FootnoteEndnoteIdManager(this);private XWPFHeaderFooterPolicy headerFooterPolicy;
NiceXWPFDocument
protected XWPFComments comments;protected List<XWPFTable> allTables = new ArrayList<XWPFTable>();protected List<XWPFPicture> allPictures = new ArrayList<XWPFPicture>();protected IdenifierManagerWrapper idenifierManagerWrapper;protected boolean adjustDoc = false;protected Map<XWPFChart, PackagePart> chartMappingPart = new HashMap<>();protected static XWPFRelation COMMENTS;
六、预览效果


