One: Scene
Using Word templates to implement dynamic word generation
II: Basic Requirements
1: Replace the contents of the text
2: Replace the contents of the table (without dynamically generating the table)
3: The replaced content should be the same format as before the replacement
4: Easy to modify the template
5: The effect is as follows:
Template:
Results:
Three: POI analysis
How to use: directly read the Word file, replace the contents of the various parts inside
Pros: Use Word files directly as templates
Cons: The substitution logic itself cannot preserve formatting
Four: Why to choose the encapsulation poi
1: Because of the problem of time and learning costs (laziness), there is no study of docx XML rules, so it is decided to encapsulate the existing tools directly to implement the requirements.
2:freemarker itself only handles generic templates, and the underlying does not directly parse Word files.
The POI itself is a word file, so you can further encapsulate the API directly on the POI.
Five: Feasibility analysis
1.POI uses the Xwpfdocument object to parse the docx file, and the parsing process is completed by the read stream passing through the docx file at the time of construction, and the information is sorted and stored in different objects. and provide write (OutputStream stream) The Convert method converts these objects to XML and writes them to a file.
Objects in the 2.XWPFDocument are stored with information such as text and formatting, which can be obtained or modified.
3. Combining the above 1, 22 points, indicates that the POI has the possibility of modifying word text and preserving the format, which proves this point by writing a demo to modify the Xwpfrun text content in a Xwpfparagraph object in Xwpfdocument.
XWPFDocument docx = new XWPFDocument(InputStream);XWPFRun run = docx.getParagraphs().get(0).getRuns().get(0);run.setText("modify");docx.write(OutputStream);
VI: Principle of realization
1. Text content and table contents can be obtained by getparagraphs () and Gettables () two methods respectively
new XWPFDocument(InputStream);//文本内容List<XWPFParagraph> allXWPFParagraphs = docx.getParagraphs();//表格内容List<XWPFTable> xwpfTables = docx.getTables();
2. Check xwpftable found that the text content in the final table is still stored with the Xwpfparagraph object
//get each line <XWPFTableRow> Xwpftablerows = = Xwpftable.getrows (); list <XWPFTableCell> xwpftablecells = new arraylist<xwpftablecell> (); for (int i = 0 ; i < xwpftablerows.size (); i++) {//get all cells of a row Xwpftablecells.addall (Getcells (i)); } list <XWPFParagraph> xwpfparagraphs = new arraylist<xwpfparagraph> (); for (Xwpftablecell cell:xwpftablecells) {//get the text in each cell Xwpfparagraphs.addall (cell.getparagraphs ()); }
So it is likely that the last modification of the table and the text eventually comes together in the same place, which is to modify the text content of the xwpfparagraph. A simple test proves that the contents can be modified by xwpfparagraph, whether it is a table or a text .
3. Study the contents of Xwpfparagraph found that each Xwpfparagraph object is stored in a whole paragraph of content, and this whole paragraph of the content is divided into a plurality of parts stored in the Xwpfparagraph object of the collection list runs, Perhaps because the docx file is converted to XML when a large probability of the same format of the text will be separated, so each xwpfrun display is not continuous !! This is where the difficulty of implementing a Word template is.
4. Since there is no intention to delve further into the principle of docx-to-XML, we intend to encapsulate it on an existing discontinuous basis, combine it, simulate a "continuous" object, and finally replace the "continuous" object . My implementation is as follows
(1) to obtain the full text content, to determine whether to include ${key}
(2) The Xwpfrun object to which each character tag of the text content belongs
(3) If a match is obtained, the first character of the match is taken from the tag, the tag object corresponding to the character is obtained, all the replaced contents are marked as the object
(4) After the substitution is complete, iterate through all tags, re-group the characters to which the tag object belongs, and set the useless tag object text to NULL
Public class xwpfparagraphutils { PrivateXwpfparagraph paragraph;PrivateList<xwpfrun> Allxwpfruns;//All of the Run's string merged content PrivateStringBuffer context;//length corresponds to the Runchar set of the contextList<runchar> Runchars; Public xwpfparagraphutils(xwpfparagraph paragraph) { This. paragraph = paragraph; Initparameter (); }/** * Initialize each parameter * / Private void Initparameter() {context =NewStringBuffer (); Runchars =NewArraylist<xwpfparagraphutils.runchar> (); Allxwpfruns =NewArraylist<xwpfrun> (); Setxwpfrun (); }/** * Set Xwpfrun related parameters * @param Run * @throws Exception */ Private void Setxwpfrun() {allxwpfruns = Paragraph.getruns ();if(Allxwpfruns = =NULL|| Allxwpfruns.size () = =0){return; }Else{ for(Xwpfrun run:allxwpfruns) {intTestposition = Run.gettextposition (); String Text = Run.gettext (testposition);if(Text = =NULL|| Text.length () = =0){return; } This. context.append (text); for(inti =0; I < text.length (); i++) {Runchars.add (NewRunchar (Text.charat (i), run)); }}} System.out.println (Context.tostring ()); } PublicStringgetString(){returnContext.tostring (); } Public Boolean contains(String key) {returnContext.indexof (Key) >=0?true:false; }/** * Replace all matching values with the corresponding values * @param key (${key} in the matching template) * @param value after replacement * @return * * Public Boolean ReplaceAll(String key,string value) {BooleanReplacesuccess =false; Key ="${"+ key +"}"; while(Replace (key, value)) {replacesuccess =true; }returnreplacesuccess; }/** * Replace all matching values with corresponding values (${key} in key matching template) * @param param key-value collection to replace * @return */ Public Boolean ReplaceAll(map<string,string> param) {set<entry<string, string>> Entrys = Param.entryset ();BooleanReplacesuccess =false; for(entry<string, string> Entry:entrys) {String key = Entry.getkey ();BooleanCurrsuccessreplace = ReplaceAll (Key,entry.getvalue ()); replacesuccess = Replacesuccess?replacesuccess:currsuccessreplace; }returnreplacesuccess; }/** * Replace the value of the first match to the corresponding value * @param key * @param value * @return */ Private Boolean Replace(String key,string value) {if(Contains (key)) {/* * 1: Start and end subscript corresponding to key */ intStartIndex = Context.indexof (key);intEndIndex = Startindex+key.length ();/* * 2: Get the first matching Xwpfrun */Runchar Startrunchar = Runchars.get (StartIndex); Xwpfrun Startrun = Startrunchar.getrun ();/* * 3: Empty the matching key */Runchars.sublist (StartIndex, EndIndex). Clear ();/* * 4: Set value to Startrun */List<runchar> Addrunchar =NewArraylist<xwpfparagraphutils.runchar> (); for(inti =0; I < value.length (); i++) {Addrunchar.add (NewRunchar (Value.charat (i), startrun)); } runchars.addall (StartIndex, Addrunchar); Resetruncontext (Runchars);return true; }Else{return false; } }Private void Resetruncontext(list<runchar> newrunchars) {/** * Generate new Xwpfrun and context correspondence * *Hashmap<xwpfrun, stringbuffer> newruncontext =NewHashmap<xwpfrun, stringbuffer> ();//Reset ContextContext =NewStringBuffer (); for(Runchar runchar:newrunchars) {StringBuffer newruntext;if(Newruncontext.containskey (Runchar.getrun ())) {Newruntext = Newruncontext.get (Runchar.getrun ()); }Else{Newruntext =NewStringBuffer (); } context.append (Runchar.getvalue ()); Newruntext.append (Runchar.getvalue ()); Newruncontext.put (Runchar.getrun (), newruntext); }/** * traverse the old runcontext, replace the context * and reset the text of the run, if it does not match, the text is set to "" */ for(Xwpfrun run:allxwpfruns) {if(Newruncontext.containskey (Run)) {String Newcontext = Newruncontext.get (run). toString (); Xwpfrunutils.settext (Run,newcontext); }Else{Xwpfrunutils.settext (Run,""); } } }/** * Entity class: The correspondence between storage bytes and Xwpfrun objects * @author Jianqiu */Class runchar{/** * * bytes * * Private CharValue/** * corresponds to Xwpfrun * / PrivateXwpfrun run; Public Runchar(CharValue,xwpfrun run) { This. SetValue (value); This. Setrun (run); } Public Char GetValue() {returnValue } Public void SetValue(CharValue) { This. value = value; } PublicXwpfrunGetrun() {returnRun } Public void Setrun(Xwpfrun Run) { This. Run = run; } }}
5. Finally, just call the Xwpfdocument.write (OutputStream) method to get the Docx file based on the template.
Seven: summary
The complete code I have uploaded to GitHub, which contains instructions and test cases, can be used as a reference.
Https://github.com/DeveloperHuang/WordHandler
At present, it is simple to implement the replacement function, the subsequent function may not be available in the second half of the study.
Java: Encapsulating poi Simple template feature for Word's docx file