一、设置数据

  1. final List<Object> list = new ArrayList<Object>() {
  2. {
  3. add(new TextRenderData("ver 0.0.3"));
  4. add(new PictureRenderData(100, 120, "src/test/resources/logo.png"));
  5. add(new TextRenderData("9d55b8", "Deeply in love with the things you love, just deepoove."));
  6. add(new TextRenderData("ver 0.0.4"));
  7. add(new PictureRenderData(100, 120, "src/test/resources/logo.png"));
  8. add(Numberings.ofDecimalParentheses()
  9. .addItem("Deeply in love with the things you love, just deepoove.")
  10. .addItem("Deeply in love with the things you love, just deepoove.")
  11. .addItem("Deeply in love with the things you love, just deepoove.").create());
  12. add(Tables.of(new String[][] {
  13. new String[] { "00", "01" },
  14. new String[] { "10", "11" },
  15. }).create());
  16. }
  17. };

源码探究

1.序号对象生成

代码如下:

  1. add(Numberings.ofDecimalParentheses()
  2. .addItem("Deeply in love with the things you love, just deepoove.")
  3. .addItem("Deeply in love with the things you love, just deepoove.")
  4. .addItem("Deeply in love with the things you love, just deepoove.").create());

进入Numbering类,此处调用了一个方法addItem

  1. public NumberingBuilder addItem(String text) {
  2. this.addItem(Texts.of(text).create());
  3. return this;
  4. }

此处的Texts.of(text).create()一系列源码如下:
首先通过of获取一个TextBuilder实例对象:

  1. public static TextBuilder of(String text) {
  2. return new TextBuilder(text);
  3. }

再调用该类的create()方法
3.TextRenderData.PNG
返回一个TextRenderData对象:

  1. @Override
  2. public TextRenderData create() {
  3. TextRenderData data = null;
  4. if (null != url) {
  5. data = new HyperlinkTextRenderData(text, url);
  6. } else if (null != bookmark) {
  7. data = new BookmarkTextRenderData(text, bookmark);
  8. } else {
  9. data = new TextRenderData(text);
  10. }
  11. data.setStyle(style);
  12. return data;
  13. }

在当前方法中,使用到了一个类属性:url

  1. private String url;

在当前类中,url内容通过一个方法进行设置:

  1. public TextBuilder link(String url) {
  2. this.url = url;
  3. // default blue color and underline
  4. if (null == this.style) {
  5. this.style = Style.builder().buildColor("0000FF").buildUnderlinePatterns(UnderlinePatterns.SINGLE)
  6. .build();
  7. }
  8. return this;
  9. }

不过因为刚才创建TextBuilder过程中并没有设置url,因此此处的url为空,因此程序选择进入:

  1. else {
  2. data = new TextRenderData(text);
  3. }

创建了一个TextRenderData对象,且同样也没有设置style,因此此处设置的style为空:

  1. data.setStyle(style); // style == null

继续回到方法调用的第一层

  1. public NumberingBuilder addItem(String text) {
  2. this.addItem(Texts.of(text).create());
  3. return this;
  4. }

第一次点击没有到底,再次进入 this.addItem(Texts.of(text).create());

  1. public NumberingBuilder addItem(TextRenderData item) {
  2. data.getItems().add(new NumberingItemRenderData(0, Paragraphs.of(item).create()));
  3. return this;
  4. }

首先来看Paragraphs.of(item).create():
首先来看Paragraphs.of(item),此处的item为TextRenderData类型的实例对象:

  1. public static ParagraphBuilder of(PictureRenderData picture) {
  2. return of().addPicture(picture);
  3. }
  1. public static ParagraphBuilder of(TextRenderData text) {
  2. return of().addText(text);
  3. }
  1. @Override
  2. public ParagraphRenderData create() {
  3. ParagraphRenderData data = new ParagraphRenderData();
  4. data.setContents(contents);
  5. data.setParagraphStyle(paragraphStyle);
  6. return data;
  7. }

将之前的text内容绑定到Paragraphs的contents属性中,返回一个ParagraphRenderData实例对象,因为此处paragraphStyle没有设置,因此为null
接下来再来看new NumberingItemRenderData(0, Paragraphs.of(item).create())

  1. public NumberingItemRenderData(int level, ParagraphRenderData item) {
  2. this.level = level;
  3. this.item = item;
  4. }

给构造函数传入两个参数,分别为序号级别(貌似和序号生成层级有关)和ParagraphRenderData实例对象
NumberingItemRenderData类含有如下属性:

  1. public class NumberingItemRenderData implements Serializable {
  2. public static final int LEVEL_NORMAL = -1;
  3. private static final long serialVersionUID = 1L;
  4. // improved: Support DocumentRenderData
  5. private ParagraphRenderData item;
  6. private int level;

后面两个我们已经通过刚才的构造参数绑定上了,回到开头:

  1. public NumberingBuilder addItem(TextRenderData item) {
  2. data.getItems().add(new NumberingItemRenderData(0, Paragraphs.of(item).create()));
  3. return this;
  4. }

此处的data.getItems()返回一个NumberingItemRenderData类型的List集合:

  1. public List<NumberingItemRenderData> getItems() {
  2. return items;
  3. }

因此,此处将一个配置好的NumberingItemRenderData对象放入NumberingItemRenderData类型为泛型的List集合中,调用List结合的add方法
最后返回this,即返回一个NumberingBuilder实例对象

回到调用的第一层:

  1. public NumberingBuilder addItem(String text) {
  2. this.addItem(Texts.of(text).create());
  3. return this;
  4. }

所以这个方法的最终意义为,将内容和层级添加进下面这个列表当中去:

  1. private List<NumberingItemRenderData> items = new ArrayList<>();

2.序号类型选择

回到最开始的地方:

  1. add(Numberings.ofDecimalParentheses()
  2. .addItem("Deeply in love with the things you love, just deepoove.")
  3. .addItem("Deeply in love with the things you love, just deepoove.")
  4. .addItem("Deeply in love with the things you love, just deepoove.").create());

前面在.addItem()方法中将要使用序号的内容添加进了集合当中。接下来还需好考虑的问题就是:怎么样去选择序号的类型?
在此处可以看到调用了一个方法

  1. Numberings.ofDecimalParentheses()

发现此方法好像对应着一个序号类型:

  1. public static NumberingBuilder ofDecimalParentheses() {
  2. return of(NumberingFormat.DECIMAL_PARENTHESES);
  3. }

并且通过调用of()方法,也给当前这个Numberings实例对象完成了格式绑定的任务

  1. public static NumberingBuilder of(NumberingFormat format) {
  2. return new NumberingBuilder(format);
  3. }
  1. private NumberingBuilder(NumberingFormat format) {
  2. data = new NumberingRenderData(format, new ArrayList<>());
  3. }
  1. private NumberingRenderData data;

序号的类型来自NumberingFormatl类,其中有设置以下几个常量

  1. public class NumberingFormat implements Serializable {
  2. private static final long serialVersionUID = 1L;
  3. public static final NumberingFormat BULLET = new NumberingFormat(NumFormat.BULLET, "●");
  4. public static final NumberingFormat DECIMAL;
  5. public static final NumberingFormat DECIMAL_PARENTHESES;
  6. public static final NumberingFormat LOWER_LETTER;
  7. public static final NumberingFormat LOWER_ROMAN;
  8. public static final NumberingFormat UPPER_LETTER;
  9. public static final NumberingFormat UPPER_ROMAN;
  10. /**
  11. * 1. 2. 3.
  12. */
  13. public static final Builder DECIMAL_BUILDER;
  14. /**
  15. * 1) 2) 3)
  16. */
  17. public static final Builder DECIMAL_PARENTHESES_BUILDER;
  18. /**
  19. * a. b. c.
  20. */
  21. public static final Builder LOWER_LETTER_BUILDER;
  22. /**
  23. * i ⅱ ⅲ
  24. */
  25. public static final Builder LOWER_ROMAN_BUILDER;
  26. /**
  27. * A. B. C.
  28. */
  29. public static final Builder UPPER_LETTER_BUILDER;
  30. /**
  31. * Ⅰ Ⅱ Ⅲ
  32. */
  33. public static final Builder UPPER_ROMAN_BUILDER;

以及以下两个变量

  1. private int numFmt;
  2. private String lvlText;

其中常量中的值由一个静态代码块进行设置:

  1. static {
  2. DECIMAL_BUILDER = builder("%{0}.").withNumFmt(NumFormat.DECIMAL);
  3. DECIMAL_PARENTHESES_BUILDER = builder("%{0})").withNumFmt(NumFormat.DECIMAL);
  4. LOWER_LETTER_BUILDER = builder("%{0}.").withNumFmt(NumFormat.LOWER_LETTER);
  5. LOWER_ROMAN_BUILDER = builder("%{0}.").withNumFmt(NumFormat.LOWER_ROMAN);
  6. UPPER_LETTER_BUILDER = builder("%{0}.").withNumFmt(NumFormat.UPPER_LETTER);
  7. UPPER_ROMAN_BUILDER = builder("%{0}.").withNumFmt(NumFormat.UPPER_ROMAN);
  8. DECIMAL = DECIMAL_BUILDER.build(0);
  9. DECIMAL_PARENTHESES = DECIMAL_PARENTHESES_BUILDER.build(0);
  10. LOWER_LETTER = LOWER_LETTER_BUILDER.build(0);
  11. LOWER_ROMAN = LOWER_ROMAN_BUILDER.build(0);
  12. UPPER_LETTER = UPPER_LETTER_BUILDER.build(0);
  13. UPPER_ROMAN = UPPER_ROMAN_BUILDER.build(0);
  14. }

可以看到此处使用了当前类中的一个方法

  1. public static Builder builder(String lvlTemplate) {
  2. return new Builder(lvlTemplate);
  3. }

跳入一个内部类中的构造方法,给当前内部类Builder属性lvlTemplate(模式)就行设置:

  1. private Builder(String lvlTemplate) {
  2. this.lvlTemplate = lvlTemplate;
  3. }

设置成功后返回一个Builder对象,接下来再调用当前内部类中的withNumFmt方法:

  1. public Builder withNumFmt(NumFormat numFmt) {
  2. this.numFmt = numFmt;
  3. return this;
  4. }

此处给当前内部类的numFmt属性设置了值,同样返回一个Builder实例对象:

  1. public Builder withNumFmt(NumFormat numFmt) {
  2. this.numFmt = numFmt;
  3. return this;
  4. }

序号类型的具体设置规则

  1. /**
  2. * 1. 2. 3.
  3. */
  4. public static final Builder DECIMAL_BUILDER; // "%{0}." DECIMAL
  5. /**
  6. * 1) 2) 3)
  7. */
  8. public static final Builder DECIMAL_PARENTHESES_BUILDER; // %{0}) DECIMAL
  9. /**
  10. * a. b. c.
  11. */
  12. public static final Builder LOWER_LETTER_BUILDER; // %{0}. 同理可得 a) b) c) 样式为%{0}) LOWER_LETTER
  13. /**
  14. * i ⅱ ⅲ
  15. */
  16. public static final Builder LOWER_ROMAN_BUILDER; // %{0}. LOWER_ROMAN
  17. /**
  18. * A. B. C.
  19. */
  20. public static final Builder UPPER_LETTER_BUILDER; // %{0}. UPPER_LETTER
  21. /**
  22. * Ⅰ Ⅱ Ⅲ
  23. */
  24. public static final Builder UPPER_ROMAN_BUILDER; // %{0}. UPPER_ROMAN

二、将数据放入一个Map集合中

  1. Map<String, Object> datas = new HashMap<String, Object>();
  2. datas.put("website", list);

三、创建一个配置类

  1. Configure config = Configure.builder().bind("website", new ListRenderPolicy() {
  2. }).build();

配置配置类

绑定插件,命名为website

  1. public ConfigureBuilder bind(String tagName, RenderPolicy policy) {
  2. config.customPolicy(tagName, policy);
  3. return this;
  4. }

将插件放入CUSTOM_POLICYS集合中

  1. public void customPolicy(String tagName, RenderPolicy policy) {
  2. CUSTOM_POLICYS.put(tagName, policy);
  3. }

四、根据模板文件和配置类创建一个模板文档对象

  1. XWPFTemplate template = XWPFTemplate.compile("src/test/resources/template/render_list.docx", config);

1.模板文件

1.模板文件.PNG

2.构建一个模板对象

将配置文件和配置类都加载进配置模板中

  1. public static XWPFTemplate compile(String absolutePath, Configure config) {
  2. return compile(new File(absolutePath), config);
  3. }
  1. public static XWPFTemplate compile(File templateFile, Configure config) {
  2. try {
  3. return compile(new FileInputStream(templateFile), config);
  4. } catch (FileNotFoundException e) {
  5. throw new ResolverException("Cannot find the file [" + templateFile.getPath() + "]", e);
  6. }
  7. }
  1. public static XWPFTemplate compile(InputStream inputStream, Configure config) {
  2. try {
  3. XWPFTemplate template = new XWPFTemplate();
  4. template.config = config;
  5. template.doc = new NiceXWPFDocument(inputStream);
  6. template.resolver = new TemplateResolver(template.config);
  7. template.renderer = new DefaultRender();
  8. template.eleTemplates = template.resolver.resolveDocument(template.doc);
  9. return template;
  10. } catch (OLE2NotOfficeXmlFileException e) {
  11. logger.error("Poi-tl currently only supports .docx format");
  12. throw new ResolverException("Compile template failed", e);
  13. } catch (IOException e) {
  14. throw new ResolverException("Compile template failed", e);
  15. }
  16. }

在生成一个NiceXWPFDocument对象时,调用了父类XWPFDocument类中的构造方法

  1. public NiceXWPFDocument(InputStream in) throws IOException {
  2. this(in, false);
  3. }

不过在执行顺序上,加载模板文档流进该对象时,首先应该执行静态属性

  1. private static Logger logger = LoggerFactory.getLogger(NiceXWPFDocument.class);

再执行NiceXWPFDocument类中static静态代码块

  1. static {
  2. try {
  3. // 使用反射获取xwpfrelation的构造方法
  4. Constructor<XWPFRelation> constructor = ReflectionUtils.findConstructor(XWPFRelation.class, String.class,
  5. String.class, String.class, POIXMLRelation.NoArgConstructor.class,
  6. POIXMLRelation.PackagePartConstructor.class);
  7. COMMENTS = constructor.newInstance(
  8. new Object[] { "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
  9. "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
  10. "/word/comments.xml", new POIXMLRelation.NoArgConstructor() {
  11. @Override
  12. public POIXMLDocumentPart init() {
  13. return new XWPFComments();
  14. }
  15. }, new POIXMLRelation.PackagePartConstructor() {
  16. @Override
  17. public POIXMLDocumentPart init(PackagePart part) throws IOException, XmlException {
  18. return new XWPFComments(part);
  19. }
  20. } });
  21. } catch (Exception e) {
  22. logger.warn("init comments releation error: {}", e.getMessage());
  23. }
  24. }

在此静态代码块中,可以看见,首先是利用反射的方法调用XWPFRelation类创建了一个构造方法的对象

  1. Constructor<XWPFRelation> constructor = ReflectionUtils.findConstructor(XWPFRelation.class, String.class,
  2. String.class, String.class, POIXMLRelation.NoArgConstructor.class,
  3. POIXMLRelation.PackagePartConstructor.class);

然后再通过这个对象生成一个XWPFRelation对象

  1. COMMENTS = constructor.newInstance(
  2. new Object[] { "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
  3. "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
  4. "/word/comments.xml", new POIXMLRelation.NoArgConstructor() {
  5. @Override
  6. public POIXMLDocumentPart init() {
  7. return new XWPFComments();
  8. }
  9. }, new POIXMLRelation.PackagePartConstructor() {
  10. @Override
  11. public POIXMLDocumentPart init(PackagePart part) throws IOException, XmlException {
  12. return new XWPFComments(part);
  13. }
  14. } });

此时,NiceXWPFDocument中的属性

  1. protected static XWPFRelation COMMENTS;

是有内容的
1.NiceXWPFDocument 静态代码块为COMMENT属性填充内容.PNG
接下来继续往下执行

  1. template.resolver = new TemplateResolver(template.config);
  2. template.renderer = new DefaultRender();
  3. template.eleTemplates = template.resolver.resolveDocument(template.doc);
  4. return template;
  5. } catch (OLE2NotOfficeXmlFileException e) {
  6. logger.error("Poi-tl currently only supports .docx format");
  7. throw new ResolverException("Compile template failed", e);
  8. } catch (IOException e) {
  9. throw new ResolverException("Compile template failed", e);
  10. }
  11. }

此时变量的情况是这样的
2.执行complier 位模板属性填充内容.PNG
现在进入TemplateResolver类,执行顺序如下

  1. private static Logger logger = LoggerFactory.getLogger(TemplateResolver.class);
  1. public TemplateResolver(Configure config) {
  2. this(config, config.getElementTemplateFactory());
  3. }

此时已经创建了一个TemplateResolver实例对象,并且我们的插件已经载入
3.模板解析器加载配置.PNG
再次向下执行,创建了一个空的DefaultRender实例对象,用来待会加载数据

  1. template.renderer = new DefaultRender();

将刚才创建好的NiceXWPFDocument实例对象加载进模板解析器中

  1. template.eleTemplates = template.resolver.resolveDocument(template.doc);

进入resolveDocument方法

  1. @Override
  2. public List<MetaTemplate> resolveDocument(XWPFDocument doc) {
  3. List<MetaTemplate> metaTemplates = new ArrayList<>();
  4. if (null == doc) return metaTemplates;
  5. logger.info("Resolve the document start...");
  6. // doc.getBodyElements() 是poi提供的API
  7. metaTemplates.addAll(resolveBodyElements(doc.getBodyElements()));
  8. metaTemplates.addAll(resolveBodys(doc.getHeaderList()));
  9. metaTemplates.addAll(resolveBodys(doc.getFooterList()));
  10. metaTemplates.addAll(resolveBodys(doc.getFootnotes()));
  11. metaTemplates.addAll(resolveBodys(doc.getEndnotes()));
  12. if (doc instanceof NiceXWPFDocument) {
  13. metaTemplates.addAll(resolveBodys(((NiceXWPFDocument) doc).getAllComments()));
  14. }
  15. logger.info("Resolve the document end, resolve and create {} MetaTemplates.", metaTemplates.size());
  16. return metaTemplates;
  17. }

此处的.getBodyElements是使用的父类XWPFDocument类中的方法,可以看到在这里,将NiceXWPFDocument实例对象中的一些模块提取出来并存放到了一个泛型为MetaTemplate的List集合List中

  1. public List<MetaTemplate> resolveBodyElements(List<IBodyElement> bodyElements) {
  2. List<MetaTemplate> metaTemplates = new ArrayList<>();
  3. if (null == bodyElements) return metaTemplates;
  4. // current iterable templates state
  5. // 关于Deque 双端队列 https://blog.csdn.net/devnn/article/details/82716447
  6. // 双端队列 实现了在队列头和队列尾的高效插入和移除
  7. // 双端队列适合用于工作密取(Work Stealing)模式
  8. // 在工作密取设计中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者
  9. // 双端队列末尾秘密的获取工作。
  10. // 因此密取工作模式比传统的生产者-消费者模式具有更高的可伸缩性,这是因为工作者线程不会在单个共享的任务队列上发生竞争
  11. Deque<BlockTemplate> stack = new LinkedList<BlockTemplate>();
  12. // 遍历元素列表
  13. // 此处只能对段落和表格进行识别
  14. for (IBodyElement element : bodyElements) {
  15. if (element == null) continue;
  16. // 获取段落类型
  17. if (element.getElementType() == BodyElementType.PARAGRAPH) {
  18. XWPFParagraph paragraph = (XWPFParagraph) element;
  19. new RunningRunParagraph(paragraph, templatePattern).refactorRun();
  20. resolveXWPFRuns(paragraph.getRuns(), metaTemplates, stack);
  21. // 获取表格类型
  22. } else if (element.getElementType() == BodyElementType.TABLE) {
  23. XWPFTable table = (XWPFTable) element;
  24. List<XWPFTableRow> rows = table.getRows();
  25. if (null == rows) continue;
  26. for (XWPFTableRow row : rows) {
  27. List<XWPFTableCell> cells = row.getTableCells();
  28. if (null == cells) continue;
  29. cells.forEach(cell -> {
  30. addNewMeta(metaTemplates, stack, resolveBodyElements(cell.getBodyElements()));
  31. });
  32. }
  33. }
  34. }
  35. checkStack(stack);
  36. return metaTemplates;
  37. }

在判断当前元素为段落时,创建一个RunningRunParagraph对象,并将XWPFParagraph类型的对象和Pattern类型的对象作为参数传递到构造方法中

  1. public RunningRunParagraph(XWPFParagraph paragraph, Pattern pattern) {
  2. this.paragraph = new XWPFParagraphWrapper(paragraph);
  3. // 获取当前段落中所有r标签
  4. this.runs = paragraph.getRuns();
  5. // 如果r为空,那么就说明这是一个空段落
  6. if (null == runs || runs.isEmpty()) return;
  7. // 判断当前段落文本内容是否符合指定正则规则
  8. Matcher matcher = pattern.matcher(paragraph.getParagraphText());
  9. if (matcher.find()) {
  10. refactorParagraph();
  11. }
  12. buildRunEdge(pattern);
  13. }

此处的pattern是直接用的的AbstractRsolver中的属性

  1. public abstract class AbstractResolver implements Resolver {
  2. protected final Configure config;
  3. protected Pattern templatePattern;
  4. protected Pattern gramerPattern;

在构造方法中被调用赋值

  1. public AbstractResolver(Configure config) {
  2. this.config = config;
  3. patternCreated();
  4. }
  1. void patternCreated() {
  2. String sign = getGramarRegex(config);
  3. String prefix = RegexUtils.escapeExprSpecialWord(config.getGramerPrefix());
  4. String suffix = RegexUtils.escapeExprSpecialWord(config.getGramerSuffix());
  5. templatePattern = Pattern
  6. .compile(MessageFormat.format(FORMAT_TEMPLATE, prefix, sign, config.getGrammerRegex(), suffix));
  7. gramerPattern = Pattern.compile(MessageFormat.format(FORMAT_GRAMER, prefix, suffix));
  8. }

而其中常量FORMAT_TEMPLATE内容如下

  1. private static final String FORMAT_TEMPLATE = "{0}{1}{2}{3}";

其为一个由prefix sign config.getGrammerRegex()和suffix组成的模式而生成的正则表达式规则,而这些值是通过下面的代码获得的

  1. String sign = getGramarRegex(config);
  2. String prefix = RegexUtils.escapeExprSpecialWord(config.getGramerPrefix());
  3. String suffix = RegexUtils.escapeExprSpecialWord(config.getGramerSuffix());

回到前面,对RunningRunParagraph实例对象调用了refactorRun()方法

  1. public List<XWPFRun> refactorRun() {
  2. if (pairs.isEmpty()) return null;
  3. List<XWPFRun> templateRuns = new ArrayList<XWPFRun>();
  4. int size = pairs.size();
  5. Pair<RunEdge, RunEdge> runEdgePair;
  6. for (int n = size - 1; n >= 0; n--) {
  7. runEdgePair = pairs.get(n);
  8. RunEdge startEdge = runEdgePair.getLeft();
  9. RunEdge endEdge = runEdgePair.getRight();
  10. int startRunPos = startEdge.getRunPos();
  11. int endRunPos = endEdge.getRunPos();
  12. int startOffset = startEdge.getRunEdge();
  13. int endOffset = endEdge.getRunEdge();
  14. String startText = runs.get(startRunPos).text();
  15. String endText = runs.get(endRunPos).text();
  16. if (endOffset + 1 >= endText.length()) {
  17. // delete the redundant end Run directly
  18. if (startRunPos != endRunPos) paragraph.removeRun(endRunPos);
  19. } else {
  20. // split end run, set extra in a run
  21. String extra = endText.substring(endOffset + 1, endText.length());
  22. if (startRunPos == endRunPos) {
  23. // create run and set extra content
  24. XWPFRun extraRun = paragraph.insertNewRun(endRunPos + 1);
  25. StyleUtils.styleRun(extraRun, runs.get(endRunPos));
  26. buildExtra(extra, extraRun);
  27. } else {
  28. // Set the extra content to the redundant end run
  29. XWPFRun extraRun = runs.get(endRunPos);
  30. buildExtra(extra, extraRun);
  31. }
  32. }
  33. // remove extra run
  34. for (int m = endRunPos - 1; m > startRunPos; m--) {
  35. paragraph.removeRun(m);
  36. }
  37. if (startOffset <= 0) {
  38. // set the start Run directly
  39. XWPFRun templateRun = runs.get(startRunPos);
  40. templateRun.setText(startEdge.getTag(), 0);
  41. templateRuns.add(runs.get(startRunPos));
  42. } else {
  43. // split start run, set extra in a run
  44. String extra = startText.substring(0, startOffset);
  45. XWPFRun extraRun = runs.get(startRunPos);
  46. buildExtra(extra, extraRun);
  47. XWPFRun templateRun = paragraph.insertNewRun(startRunPos + 1);
  48. StyleUtils.styleRun(templateRun, extraRun);
  49. templateRun.setText(startEdge.getTag(), 0);
  50. templateRuns.add(runs.get(startRunPos + 1));
  51. }
  52. }
  53. return templateRuns;
  54. }

在测试中发现pairs属性为空,因此上面这个方法并没有执行,因此暂时不清楚此方法到底有什么用
再往下走,进入resolveXWPFRuns方法,传入参数为:段落中所有r内容、空集合、空双端队列

  1. private void resolveXWPFRuns(List<XWPFRun> runs, final List<MetaTemplate> metaTemplates,
  2. final Deque<BlockTemplate> stack) {
  3. for (XWPFRun run : runs) {
  4. String text = null;
  5. if (StringUtils.isBlank(text = run.getText(0))) {
  6. // textbox
  7. List<MetaTemplate> visitBodyElements = resolveTextbox(run);
  8. if (!visitBodyElements.isEmpty()) {
  9. addNewMeta(metaTemplates, stack, visitBodyElements);
  10. continue;
  11. }
  12. // picture
  13. List<PictureTemplate> pictureTemplates = resolveXWPFPictures(run.getEmbeddedPictures());
  14. if (!pictureTemplates.isEmpty()) {
  15. addNewMeta(metaTemplates, stack, pictureTemplates);
  16. continue;
  17. }
  18. // w:pict v:imagedata
  19. PictImageTemplate pictImageTemplate = resolvePictImage(run);
  20. if (null != pictImageTemplate) {
  21. addNewMeta(metaTemplates, stack, pictImageTemplate);
  22. continue;
  23. }
  24. // chart
  25. ChartTemplate chartTemplate = resolveXWPFChart(run);
  26. if (null != chartTemplate) {
  27. addNewMeta(metaTemplates, stack, chartTemplate);
  28. continue;
  29. }
  30. continue;
  31. }
  32. RunTemplate runTemplate = (RunTemplate) parseTemplateFactory(text, run, run);
  33. if (null == runTemplate) continue;
  34. char charValue = runTemplate.getSign().charValue();
  35. if (charValue == config.getIterable().getLeft()) {
  36. IterableTemplate freshIterableTemplate = new IterableTemplate(runTemplate);
  37. stack.push(freshIterableTemplate);
  38. } else if (charValue == config.getIterable().getRight()) {
  39. if (stack.isEmpty()) throw new ResolverException(
  40. "Mismatched start/end tags: No start mark found for end mark " + runTemplate);
  41. BlockTemplate latestIterableTemplate = stack.pop();
  42. if (StringUtils.isNotEmpty(runTemplate.getTagName())
  43. && !latestIterableTemplate.getStartMark().getTagName().equals(runTemplate.getTagName())) {
  44. throw new ResolverException("Mismatched start/end tags: start mark "
  45. + latestIterableTemplate.getStartMark() + " does not match to end mark " + runTemplate);
  46. }
  47. latestIterableTemplate.setEndMark(runTemplate);
  48. if (latestIterableTemplate instanceof IterableTemplate) {
  49. latestIterableTemplate = ((IterableTemplate) latestIterableTemplate).buildIfInline();
  50. }
  51. addNewMeta(metaTemplates, stack, latestIterableTemplate);
  52. } else {
  53. addNewMeta(metaTemplates, stack, runTemplate);
  54. }
  55. }
  56. }

经过测试发现,resolveDocument这个方法是用来解析模板中的标签的,例如此处模板中内容如下
4.模板标签.PNG
经过resolveDocument方法解析后,此时变量情况如下
5.模板解析.PNG
所以回到最开始,XWPFTemplate中的eleTemplates属性是用来存放标签内容的

  1. template.eleTemplates = template.resolver.resolveDocument(template.doc);

回到表层,此处获得了一个配置了文档内容和标签内容以及一些设置的XWPFTemplate文档对象

  1. XWPFTemplate template = XWPFTemplate.compile("src/test/resources/template/render_list.docx", config);

五、加载数据进模板,并输出

  1. template.render(datas).writeToFile("out_render_list.docx");

1.加载数据

  1. public XWPFTemplate render(Object model) {
  2. this.renderer.render(this, model);
  3. return this;
  4. }

传入参数为模板对象(XWPFTemplate类型)以及待载入的数据对象(Object类型 此处为一个Map集合)

  1. @Override
  2. public void render(XWPFTemplate template, Object root) {
  3. // 分别检查模板对象和数据对象是否为空
  4. Objects.requireNonNull(template, "Template must not be null.");
  5. Objects.requireNonNull(root, "Data root must not be null");
  6. LOGGER.info("Render template start...");
  7. // 通过调用XWPFTemplate属性Configure类中提供的工厂类 构建RenderDataCompute 获取一个绑定了数据对象的RenderDataCompute实例对象
  8. RenderDataCompute renderDataCompute = template.getConfig().getRenderDataComputeFactory().newCompute(root);
  9. StopWatch watch = new StopWatch();
  10. try {
  11. watch.start();
  12. renderTemplate(template, renderDataCompute);
  13. renderInclude(template, renderDataCompute);
  14. } catch (Exception e) {
  15. if (e instanceof RenderException) throw (RenderException) e;
  16. throw new RenderException("Cannot render docx template, please check the Exception", e);
  17. } finally {
  18. watch.stop();
  19. }
  20. LOGGER.info("Successfully Render template in {} millis", TimeUnit.NANOSECONDS.toMillis(watch.getNanoTime()));
  21. }

在try{}catch(){}块中有这样两个调用语句

  1. renderTemplate(template, renderDataCompute);
  2. renderInclude(template, renderDataCompute);

第一条语句:renderTemplate()

  1. private void renderTemplate(XWPFTemplate template, RenderDataCompute renderDataCompute) {
  2. // log
  3. new LogProcessor().process(template.getElementTemplates());
  4. // render
  5. DocumentProcessor documentRender = new DocumentProcessor(template, template.getResolver(), renderDataCompute);
  6. documentRender.process(template.getElementTemplates());
  7. }

在此方法中,首先是对日志进行了处理,暂时没有深入挖掘,所以不知道实现细节是怎么样的;
第二个是对数据进行一个绑定操作,传入另外一个参数为刚才从模板方法中获得的RenderDataCompute实例对象
这里又创建了一个DocumentProcessor实例方法,在调用构造方法时候分别传入了单个参数:XWPFTemplate类型的模板方法、Resolver类型的模板标签解析器,以及RenderDataCompute类型的数据模型

  1. public DocumentProcessor(XWPFTemplate template, final Resolver resolver,
  2. final RenderDataCompute renderDataCompute) {
  3. elementProcessor = new ElementProcessor(template, resolver, renderDataCompute);
  4. iterableProcessor = new IterableProcessor(template, resolver, renderDataCompute);
  5. inlineIterableProcessor = new InlineIterableProcessor(template, resolver, renderDataCompute);
  6. }

执行上述几步操作,分别执行以下几个构造方法

  1. public ElementProcessor(XWPFTemplate template, Resolver resolver, RenderDataCompute renderDataCompute) {
  2. super(template, resolver, renderDataCompute);
  3. }
  1. public IterableProcessor(XWPFTemplate template, Resolver resolver, RenderDataCompute renderDataCompute) {
  2. super(template, resolver, renderDataCompute);
  3. }
  1. public InlineIterableProcessor(XWPFTemplate template, Resolver resolver, RenderDataCompute renderDataCompute) {
  2. super(template, resolver, renderDataCompute);
  3. }

而上述对象的创建都赋值给了当前的属性

  1. public class DocumentProcessor implements Visitor {
  2. private ElementProcessor elementProcessor;
  3. private IterableProcessor iterableProcessor;
  4. private InlineIterableProcessor inlineIterableProcessor;

回到上一层

  1. DocumentProcessor documentRender = new DocumentProcessor(template, template.getResolver(), renderDataCompute);
  2. documentRender.process(template.getElementTemplates());

将模中标签信息都传入process方法中

  1. public void process(List<MetaTemplate> templates) {
  2. // process in order( or sort first)
  3. templates.forEach(template -> template.accept(this));
  4. Set<XWPFTextboxContent> textboxs = obtainTextboxes(templates);
  5. textboxs.forEach(content -> {
  6. content.getXmlObject().set(content.getCTTxbxContent());
  7. });
  8. }

从传入参数来看,process方法应该是要将数据和标签进行结合的方法
6.输出process.PNG
将这里的foreach循环改为增强for循环

  1. for(MetaTemplate template : templates){
  2. template.accept(this);
  3. }

debug进入.accept方法

  1. @Override
  2. public void accept(Visitor visitor) {
  3. visitor.visit(this);
  4. }

7.accept中的Vistor.PNG

  1. @Override
  2. public void visit(RunTemplate runTemplate) {
  3. runTemplate.accept(elementProcessor);
  4. }

8.visit.PNG

  1. void visit(ElementTemplate eleTemplate) {
  2. RenderPolicy policy = eleTemplate.findPolicy(template.getConfig());
  3. Objects.requireNonNull(policy, "Cannot find render policy: [" + eleTemplate.getTagName() + "]");
  4. if (policy instanceof DocxRenderPolicy) return;
  5. logger.info("Start render Template {}, Sign:{}, policy:{}", eleTemplate, eleTemplate.getSign(),
  6. ClassUtils.getShortClassName(policy.getClass()));
  7. policy.render(eleTemplate, renderDataCompute.compute(eleTemplate.getTagName()), template);
  8. }

2.输出文件

  1. public void writeToFile(String path) throws IOException {
  2. this.writeAndClose(new FileOutputStream(path));
  3. }
  1. public void writeAndClose(OutputStream out) throws IOException {
  2. try {
  3. this.write(out);
  4. out.flush();
  5. } finally {
  6. PoitlIOUtils.closeQuietlyMulti(this.doc, out);
  7. }
  8. }

文件输出过程:

  1. public void write(OutputStream out) throws IOException {
  2. this.doc.write(out);
  3. }

其中doc为XWPFTemplate类属性NiceXWPFDocument类型的doc对象

  1. public class XWPFTemplate implements Closeable {
  2. ...
  3. private NiceXWPFDocument doc;
  4. ...

因为NiceXWPFDocument继承自XWPFDocument类,因此写出文件时可以直接使用其write方法

  1. public class NiceXWPFDocument extends XWPFDocument {

意外发现序号的生成办法

  1. public BigInteger addNewMultiLevelNumberingId(NumberingFormat... numFmts) {
  2. XWPFNumbering numbering = this.getNumbering();
  3. if (null == numbering) {
  4. numbering = this.createNumbering();
  5. }
  6. XWPFNumberingWrapper numberingWrapper = new XWPFNumberingWrapper(numbering);
  7. // 对应着numberings.xml文件 可以通过此对象对已存在的序号进行修改,也可以增加新的序号
  8. // <w:abstractNum w:abstractNumId="1">
  9. CTAbstractNum cTAbstractNum = CTAbstractNum.Factory.newInstance();
  10. // if we have an existing document, we must determine the next
  11. // free number first.
  12. cTAbstractNum.setAbstractNumId(numberingWrapper.getNextAbstractNumID());
  13. // CTMultiLevelType addNewMultiLevelType = cTAbstractNum.addNewMultiLevelType();
  14. // addNewMultiLevelType.setVal(STMultiLevelType.HYBRID_MULTILEVEL);
  15. for (int i = 0; i < numFmts.length; i++) {
  16. NumberingFormat numFmt = numFmts[i];
  17. // <w:lvl w:ilvl="0">
  18. CTLvl cTLvl = cTAbstractNum.addNewLvl();
  19. CTPPrBase ppr = cTLvl.isSetPPr() ? cTLvl.getPPr() : cTLvl.addNewPPr();
  20. CTInd ind = ppr.isSetInd() ? ppr.getInd() : ppr.addNewInd();
  21. ind.setLeft(BigInteger.valueOf(UnitUtils.cm2Twips(0.74f) * i));
  22. Enum fmt = STNumberFormat.Enum.forInt(numFmt.getNumFmt());
  23. String val = numFmt.getLvlText();
  24. // <w:numFmt w:val="decimal"/>
  25. cTLvl.addNewNumFmt().setVal(fmt);
  26. // <w:lvlText w:val="%1)"/>
  27. cTLvl.addNewLvlText().setVal(val);
  28. // <w:start w:val="1"/>
  29. cTLvl.addNewStart().setVal(BigInteger.valueOf(1));
  30. cTLvl.setIlvl(BigInteger.valueOf(i));
  31. if (fmt == STNumberFormat.BULLET) {
  32. cTLvl.addNewLvlJc().setVal(STJc.LEFT);
  33. CTRPr addNewRPr = cTLvl.addNewRPr();
  34. CTFonts ctFonts = addNewRPr.addNewRFonts();
  35. ctFonts.setAscii("Wingdings");
  36. ctFonts.setHAnsi("Wingdings");
  37. ctFonts.setHint(STHint.DEFAULT);
  38. }
  39. }
  40. XWPFAbstractNum abstractNum = new XWPFAbstractNum(cTAbstractNum);
  41. BigInteger abstractNumID = numbering.addAbstractNum(abstractNum);
  42. return numbering.addNum(abstractNumID);
  43. }

3.NiceXWPFDocument和XWPFDocument

XWPFDocument是POI中为读取.docx文档定义的一个文档类,而NiceXWPFDocument是Poi-tl为处理数据和模板类所定义的一个文档类,其继承自XWPFDocument类。

- 类属性比较

XWPFDoucment

  1. protected List<XWPFFooter> footers = new ArrayList();
  2. protected List<XWPFHeader> headers = new ArrayList();
  3. protected List<XWPFComment> comments = new ArrayList();
  4. protected List<XWPFHyperlink> hyperlinks = new ArrayList();
  5. protected List<XWPFParagraph> paragraphs = new ArrayList();
  6. protected List<XWPFTable> tables = new ArrayList();
  7. protected List<XWPFSDT> contentControls = new ArrayList();
  8. protected List<IBodyElement> bodyElements = new ArrayList();
  9. protected List<XWPFPictureData> pictures = new ArrayList();
  10. protected Map<Long, List<XWPFPictureData>> packagePictures = new HashMap();
  11. protected XWPFEndnotes endnotes;
  12. protected XWPFNumbering numbering;
  13. protected XWPFStyles styles;
  14. protected XWPFFootnotes footnotes;
  15. private CTDocument1 ctDocument;
  16. private XWPFSettings settings;
  17. protected final List<XWPFChart> charts = new ArrayList();
  18. private IdentifierManager drawingIdManager = new IdentifierManager(0L, 4294967295L);
  19. private FootnoteEndnoteIdManager footnoteIdManager = new FootnoteEndnoteIdManager(this);
  20. private XWPFHeaderFooterPolicy headerFooterPolicy;

NiceXWPFDocument

  1. protected XWPFComments comments;
  2. protected List<XWPFTable> allTables = new ArrayList<XWPFTable>();
  3. protected List<XWPFPicture> allPictures = new ArrayList<XWPFPicture>();
  4. protected IdenifierManagerWrapper idenifierManagerWrapper;
  5. protected boolean adjustDoc = false;
  6. protected Map<XWPFChart, PackagePart> chartMappingPart = new HashMap<>();
  7. protected static XWPFRelation COMMENTS;

六、预览效果

2.输出效果.PNG
2.1输出兄啊过.PNG