一、设置数据
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对象:
@Override
public 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 underline
if (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);
}
@Override
public 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 DocumentRenderData
private 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() {
@Override
public POIXMLDocumentPart init() {
return new XWPFComments();
}
}, new POIXMLRelation.PackagePartConstructor() {
@Override
public 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() {
@Override
public POIXMLDocumentPart init() {
return new XWPFComments();
}
}, new POIXMLRelation.PackagePartConstructor() {
@Override
public 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方法
@Override
public List<MetaTemplate> resolveDocument(XWPFDocument doc) {
List<MetaTemplate> metaTemplates = new ArrayList<>();
if (null == doc) return metaTemplates;
logger.info("Resolve the document start...");
// doc.getBodyElements() 是poi提供的API
metaTemplates.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 directly
if (startRunPos != endRunPos) paragraph.removeRun(endRunPos);
} else {
// split end run, set extra in a run
String extra = endText.substring(endOffset + 1, endText.length());
if (startRunPos == endRunPos) {
// create run and set extra content
XWPFRun extraRun = paragraph.insertNewRun(endRunPos + 1);
StyleUtils.styleRun(extraRun, runs.get(endRunPos));
buildExtra(extra, extraRun);
} else {
// Set the extra content to the redundant end run
XWPFRun extraRun = runs.get(endRunPos);
buildExtra(extra, extraRun);
}
}
// remove extra run
for (int m = endRunPos - 1; m > startRunPos; m--) {
paragraph.removeRun(m);
}
if (startOffset <= 0) {
// set the start Run directly
XWPFRun templateRun = runs.get(startRunPos);
templateRun.setText(startEdge.getTag(), 0);
templateRuns.add(runs.get(startRunPos));
} else {
// split start run, set extra in a run
String 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))) {
// textbox
List<MetaTemplate> visitBodyElements = resolveTextbox(run);
if (!visitBodyElements.isEmpty()) {
addNewMeta(metaTemplates, stack, visitBodyElements);
continue;
}
// picture
List<PictureTemplate> pictureTemplates = resolveXWPFPictures(run.getEmbeddedPictures());
if (!pictureTemplates.isEmpty()) {
addNewMeta(metaTemplates, stack, pictureTemplates);
continue;
}
// w:pict v:imagedata
PictImageTemplate pictImageTemplate = resolvePictImage(run);
if (null != pictImageTemplate) {
addNewMeta(metaTemplates, stack, pictImageTemplate);
continue;
}
// chart
ChartTemplate 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集合)
@Override
public 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) {
// log
new LogProcessor().process(template.getElementTemplates());
// render
DocumentProcessor 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方法
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public 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;