package org.docx4j.model.fields; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.docx4j.XmlUtils; import org.docx4j.jaxb.Context; import org.docx4j.wml.CTFFData; import org.docx4j.wml.ContentAccessor; import org.docx4j.wml.FldChar; import org.docx4j.wml.R; import org.docx4j.wml.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The objective of this class is to represent a complex field * (containing nested fields, if any; nested fields are * represented by FieldRef object of their own). * * TODO, consider whether to make this abstract, with * differing concrete implementations for top level and nested fields. * * Background. There are simple fields: * <w:fldSimple w:instr=" DATE "> <w:r> <w:rPr> <w:noProof/> </w:rPr> <w:instrText>4/12/2011</w:instrText> <!-- ???? --> </w:r> </w:fldSimple> * * and there are complex fields: * <w:p> <w:r> <w:fldChar w:fldCharType="begin"/> </w:r> <w:r> <w:instrText xml:space="preserve"> REF hi \h </w:instrText> </w:r> <w:r> <w:fldChar w:fldCharType="separate"/> </w:r> <w:r> <w:t>Hello</w:t> </w:r> </w:p> * A simple field can also take the complex form: * <w:r> <w:fldChar w:fldCharType="begin"/> <w:instrText xml:space="preserve">DATE </w:instrText> <w:fldChar w:fldCharType="separate"/> </w:r> <w:r> <w:t>4/12/2011</w:t> </w:r> <w:r> <w:fldChar w:fldCharType="end"/> </w:r> * A complex field can contain nested fields, in either * its instruction part, or result part. * * we need to represent nest fields in the instructions * part only (since nested fields in the results part * get re-generated). * * An example of a nested field in the instructions part: * <w:p> <w:r> <w:fldChar w:fldCharType="begin"/> </w:r> <w:r> <w:instrText xml:space="preserve"> IF </w:instrText> </w:r> <w:fldSimple w:instr=" DATE "> <w:r> <w:rPr> <w:noProof/> </w:rPr> <w:instrText>4/12/2011</w:instrText> </w:r> </w:fldSimple> <w:r> <w:instrText xml:space="preserve">="4/12/2011" "it is 4/12" "not 4/12" </w:instrText> </w:r> <w:r> <w:fldChar w:fldCharType="separate"/> </w:r> <w:r> <w:rPr> <w:noProof/> </w:rPr> <w:t>today</w:t> </w:r> <w:r> <w:fldChar w:fldCharType="end"/> </w:r> </w:p> * An example of nested fields in the results part: * * <w:p w:rsidR="00BA0732" w:rsidRDefault="00BA0732"> <w:pPr> <w:pStyle w:val="TOC1"/> <w:tabs> <w:tab w:val="right" w:leader="dot" w:pos="9016"/> </w:tabs> <w:rPr> <w:noProof/> </w:rPr> </w:pPr> <w:r> <w:fldChar w:fldCharType="begin"/> </w:r> <w:r> <w:instrText xml:space="preserve"> TOC \o "1-3" \h \z \ u</w:instrText> </w:r> <w:r> <w:fldChar w:fldCharType="separate"/> </w:r> <w:hyperlink w:anchor="_Toc310757867" w:history="1"> <w:r w:rsidRPr="0061726E"> <w:rPr> <w:rStyle w:val="Hyperlink"/> <w:noProof/> </w:rPr> <w:t>one</w:t> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:tab/> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:fldChar w:fldCharType="begin"/> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:instrText xml:space="preserve"> PAGEREF _Toc310757867 \h </w:instrText> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:fldChar w:fldCharType="separate"/> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:t>1</w:t> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:fldChar w:fldCharType="end"/> </w:r> </w:hyperlink> </w:p> <w:p w:rsidR="00BA0732" w:rsidRDefault="00BA0732"> <w:pPr> <w:pStyle w:val="TOC2"/> <w:tabs> <w:tab w:val="right" w:leader="dot" w:pos="9016"/> </w:tabs> <w:rPr> <w:noProof/> </w:rPr> </w:pPr> <w:hyperlink w:anchor="_Toc310757868" w:history="1"> <w:r w:rsidRPr="0061726E"> <w:rPr> <w:rStyle w:val="Hyperlink"/> <w:noProof/> </w:rPr> <w:t>oneone</w:t> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:tab/> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:fldChar w:fldCharType="begin"/> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:instrText xml:space="preserve"> PAGEREF _Toc310757868 \h </w:instrText> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:fldChar w:fldCharType="separate"/> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:t>1</w:t> </w:r> <w:r> <w:rPr> <w:noProof/> <w:webHidden/> </w:rPr> <w:fldChar w:fldCharType="end"/> </w:r> </w:hyperlink> </w:p> <w:p w:rsidR="00BA0732" w:rsidRDefault="00BA0732" w:rsidP="00BA0732"> <w:r> <w:fldChar w:fldCharType="end"/> </w:r> </w:p> * * In general, you can "canonicalise" the field representation * to be * (i) instructions, contained within a single run * (ii) results, immediately following, though not nec just as following siblings * (iii) the final <w:fldChar w:fldCharType="end"/> * * Since the purpose of our field support is to update the field * results, we can delete (ii) and (iii) before adding them in again. * * The document is preprocessed to put it into this form. * * @author jharrop * */ public class FieldRef { private static Logger log = LoggerFactory.getLogger(FieldRef.class); public FieldRef(FldChar fldCharBegin) { this.fldCharBegin = fldCharBegin; } private FldChar fldCharBegin; protected String fldName = null; /** * The name of the (outer most) field, for example DATE, MERGEFIELD. * * Assume for now that this is contained in instructions.get(0). * * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/file_2.html">field syntax</a> * @return */ public String getFldName() { Object o = XmlUtils.unwrap(instructions.get(0)); if (o instanceof Text) { return FormattingSwitchHelper.getFldSimpleName( ((Text)o).getValue() ); } else { log.error("TODO: extract field name from " + o.getClass().getName() ); if (o instanceof FieldRef) { // contains a nested field?! FieldRef nested = (FieldRef)o; log.error("Nested field " + nested.getFldName() ); } else { if(log.isErrorEnabled()) { log.error(XmlUtils.marshaltoString(instructions.get(0), true, true)); } } return null; } } private ContentAccessor parent; public ContentAccessor getParent() { return parent; } public void setParent(ContentAccessor parent) { this.parent = parent; } /** * The run * * <w:r> <w:fldChar w:fldCharType="begin"/> <w:instrText xml:space="preserve"> ... </w:instrText> <w:fldChar w:fldCharType="separate"/> </w:r> */ private R beginRun; public R getBeginRun() { return beginRun; } public void setBeginRun(R beginRun) { this.beginRun = beginRun; } private boolean seenSeparate=false; public boolean haveSeenSeparate() { return seenSeparate; } public void setSeenSeparate(boolean seenSeparate) { this.seenSeparate = seenSeparate; } private void processFldBegin() { formFieldProperties = fldCharBegin.getFfData(); customFieldData = fldCharBegin.getFldData(); dirty = fldCharBegin.isDirty(); lock = fldCharBegin.isFldLock(); } private Boolean mergeFormat; /** * @return whether \* MERGEFORMAT is set */ public Boolean isMergeFormat() { if (mergeFormat==null) { //Work it out. Assume for now that this is contained in instructions.get(0). mergeFormat = Boolean.FALSE; Object o = XmlUtils.unwrap(instructions.get(0)); if (o instanceof Text) { String instr = ((Text)o).getValue(); if (instr.contains("MERGEFORMAT")) { mergeFormat = Boolean.TRUE; } } else { if(log.isErrorEnabled()) { log.error("TODO: extract field name from " + o.getClass().getName()); log.error(XmlUtils.marshaltoString(instructions.get(0), true, true)); } } } return mergeFormat; } private boolean dirty; /** * Specifies that this field has been flagged by an application to indicate that its current results * are invalid (stale) due to other modifications made to the document, and these contents should be * updated before they are displayed. * @return whether stale * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/fldChar.html">the spec</a> */ public boolean isDirty() { return dirty; } /** * @param whether stale * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/fldChar.html">the spec</a> */ public void setDirty(boolean dirty) { this.dirty = dirty; fldCharBegin.setDirty(dirty); // Note that this doesn't set dirty on any nested fields. TODO: Consider whether it should. } private boolean lock; /** * @return the lock * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/fldChar.html">the spec</a> */ public boolean isLock() { return lock; } /** * Specifies that the parent complex field shall not have its field result recalculated, even if * an application attempts to recalculate the results of all fields in the document or a * recalculation is explicitly requested. * * @param lock the lock to set * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/fldChar.html">the spec</a> */ public void setLock(boolean lock) { this.lock = lock; fldCharBegin.setFldLock(lock); } private Text customFieldData; /** * application-specific data associated with this field. * * @return the customFieldData * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/fldData_1.html">the spec</a> */ public Text getCustomFieldData() { return customFieldData; } /** * @param customFieldData the customFieldData to set * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/fldData_1.html">the spec</a> */ public void setCustomFieldData(Text customFieldData) { this.customFieldData = customFieldData; fldCharBegin.setFldData(customFieldData); } private CTFFData formFieldProperties; /** * Properties specific to FORMCHECKBOX, FORMDROPDOWN, FORMTEXT * * @return the formFieldProperties * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/ffData.html">the spec</a> */ public CTFFData getFormFieldProperties() { return formFieldProperties; } /** * @param formFieldProperties the formFieldProperties to set * @see <a href="http://webapp.docx4java.org/OnlineDemo/ecma376/WordML/ffData.html">the spec</a> */ public void setFormFieldProperties(CTFFData formFieldProperties) { this.formFieldProperties = formFieldProperties; fldCharBegin.setFfData(formFieldProperties); } /** * The run * <w:r> <w:fldChar w:fldCharType="end"/> </w:r> Store a reference to it so we can delete it. */ private R endRun; public R getEndRun() { return endRun; } public void setEndRun(R endRun) { this.endRun = endRun; } /** * A list of the content between the outermost w:fldChar begin and separate elements; * in the simplest case, this will be a single w:instrText object; * in a more general case it will be a mixture of w:instrText and FieldRef objects * (and possibly other things such as w:br). */ private List<Object> instructions = new ArrayList<Object>(); /** * @return the instructions */ public List<Object> getInstructions() { return instructions; } // private JAXBElement<Text> instrText; // public String getInstr() { // return instrText.getValue().getValue(); // } // public void setInstrText(JAXBElement<Text> instrTextIn) { // if (this.instrText==null){ // this.instrText = instrTextIn; // } else { // // Merge // String text = this.getInstr() + instrTextIn.getValue().getValue(); // this.instrText.getValue().setValue(text); // } // } private R resultsSlot; public R getResultsSlot() { return resultsSlot; } public void setResultsSlot(R resultsSlot) { this.resultsSlot = resultsSlot; } public void setResult(String val) { resultsSlot.getContent().clear(); StringTokenizer st = new StringTokenizer(val, "\n\r\f"); // tokenize on the newline character, the carriage-return character, and the form-feed character // our docfrag may contain several runs boolean firsttoken = true; while (st.hasMoreTokens()) { String line = (String) st.nextToken(); if (firsttoken) { firsttoken = false; } else { resultsSlot.getContent().add(Context.getWmlObjectFactory().createBr()); } org.docx4j.wml.Text text = Context.getWmlObjectFactory().createText(); resultsSlot.getContent().add(text); if (line.startsWith(" ") || line.endsWith(" ") ) { // TODO: tab character? text.setSpace("preserve"); } text.setValue(line); } } }