Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  package org.docx4j.model.fields;
  
  import java.util.List;
  
 
This class puts fields into a "canonical" representation (see FieldRef for description). It does this in 2 steps: - step 1: use XSLT to convert simple fields into complex ones - step 2: put all the instructions into a single run Currently the canonicalisation is done at the paragraph level, so it is not suitable for fields (such as TOC) which extend across paragraphs. TOC will need to be regenerated (using Word) if touched by canonicalisation.

Author(s):
jharrop
 
 public class FieldsPreprocessor {
 	
 	private static Logger log = LoggerFactory.getLogger(FieldsPreprocessor.class);		
 
     private final static QName _RInstrText_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main"
     		"instrText");
     private final static QName _PHyperlink_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main"
     		"hyperlink");
     
 	
 	static Templates xslt;			
 	static {
 		try {
 			Source xsltSource = new StreamSource(
 						org.docx4j.utils.ResourceUtils.getResource(
 								"org/docx4j/model/fields/FieldsSimpleToComplex.xslt"));
 			 = XmlUtils.getTransformerTemplate(xsltSource);
 		} catch (IOException e) {
 		}
 		
 	}
 	
 	private FieldsPreprocessor(List<FieldReffieldRefs) {
 		this. = fieldRefs;
 	}

Convert any w:fldSimple in this part to complex field.

 
 	public static void complexifyFields(JaxbXmlPart partthrows Docx4JException {
 		
 				part.getJaxbElement() ); 	
 		
 //		XPathsPart xPathsPart = null;
 				
 		try {
 			// Use constructor which takes Unmarshaller, rather than JAXBContext,
 			// so we can set JaxbValidationEventHandler
 								
 			org.docx4j.XmlUtils.transform(docnullresult);
 			
 			part.setJaxbElement(result);
 		} catch (Exception e) {
 			throw new Docx4JException("Problems transforming fields"e);			
 		}
	}
	public static P canonicalise(P pList<FieldReffieldRefs) {
		/*
		 * Result is something like:
		 * 
		        <w:p>
		            <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>
		        </w:p>		  
		 
		 * Note that the content between begin and separate could be more complex
		 * including nested fields.
		 **/
		FieldsPreprocessor fp = new FieldsPreprocessor(fieldRefs);
		return fp.canonicaliseInstance(p);
	}
	private P canonicaliseInstance(P p) {
		P newP = Context.getWmlObjectFactory().createP();
		newP.setPPr(p.getPPr());
//		fieldRPr = null;
		// log.debug(XmlUtils.marshaltoString(newP, true));
		return newP;
	}

A list of FieldRef objects representing outermost fields only.
	private FieldRef currentField=null;
	private R newR;
	private void handleContent(List<ObjectobjectsContentAccessor attachmentPoint) {
		// handles case where the run(s) containing the field are inside a P, or inside a P.Hyperlink 
		// (eg a PAGEREF in a table of contents).
		for (Object o : objects ) {
			// Handling for hyperlink (can occur in field result, and might contain another
			// nested field).  Since at present the field processing here is for
			// MERGEFIELD and DOCPROPERTY fields, this is currently just handled by else below.
			//	if ( o instanceof P.Hyperlink
			//			|| ((o instanceof JAXBElement
			//					&& ((JAXBElement)o).getName().equals(_PHyperlink_QNAME)) )	) {
			//	
			if ( o instanceof R ) {
				R existingRun = (R)o;
				handleRun(existingRunattachmentPoint);
else if (o instanceof ProofErr) {
				// Ignore
				// What happens if we ignore eg grammarStart, but its matching
				// grammarEnd is outside and retained?
				// Well, a stray spellStart doesn't matter to Word 2010, so
				// assume others would be ok as well.
else {
				// its not something we're interested in
				.debug("Retaining" + XmlUtils.unwrap(o).getClass().getName());
				attachmentPoint.getContent().add(o);
				// prepare new run
			}
//			if (newR.getContent().size() > 0 && !attachmentPoint.getContent().contains(newR)) {
//				attachmentPoint.getContent().add(newR);
//			}
		}
	}
	private boolean fieldIsTopLevel() {
		return .size()==1;
	}
	private boolean inParentResult() {
		FieldRef thisField = .pop();
		try {
			FieldRef parentField = .pop();
			boolean inResult = parentField.haveSeenSeparate();
			// restore stack
			.push(parentField);
			.push(thisField);
			return inResult;
catch (NoSuchElementException e) {
			// No parent
			// restore stack
			.push(thisField);
			return false;
		}
	}
	private boolean preserveResult(FieldRef fieldRef) {
		if (fieldRef.isLock()) return true;
		if (fieldRef.getFldName().equals("MERGEFIELD")
				|| fieldRef.getFldName().equals("DOCPROPERTY")) {
			return false;
		}
		return true;
	}
	private boolean preserveParentResult() {
		FieldRef thisField = .pop();
		FieldRef parentField = .pop();
		boolean preserveParentResult = preserveResult(parentField);
		// restore stack
		.push(parentField);
		.push(thisField);
		return preserveParentResult;
	}
	private void handleRun(R existingRunContentAccessor newAttachPoint) {
		// note that the newR object persists between invocations of this method,
		// so you have to be careful to actually add it to the docx 
		// before re-creating it
		.debug("\nInput run: \n " + XmlUtils.marshaltoString(existingRuntruetrue));
		for (Object o2 : existingRun.getContent() ) {
			.setRPr(existingRun.getRPr());
				.debug("\n\n begin.. ");
				// Setup a FieldRef object 
				 = new FieldRef((FldChar)XmlUtils.unwrap(o2));							
				.setParent(newAttachPoint);							
				.setBeginRun(); // may as well do this
				if (inParentResult()) {
else {
						.debug(".. but in result, so don't add to run");
					}
else {
					if ( fieldIsTopLevel() ) { 
						.setBeginRun(); // IMPORTANT, so we can delete it when we perform mail merge
else {
					}
				}
else if (isCharType(o2.)) {
				if (inParentResult()) {
else {
						.debug(".. but in result, so don't add to run");
					}
else {
					if (!newAttachPoint.getContent().contains()) {
						newAttachPoint.getContent().add();
						.debug("-- attaching -->" + XmlUtils.marshaltoString(truetrue));
					}
					if ( fieldIsTopLevel() ) {
						// Top level field separator
						// Create result slot
					}
				}
else if (isCharType(o2.)) {
				.debug("\n\n .. end ");
				if (inParentResult()) {
						if (.getFldName().equals("FORMTEXT")) {
							/*
							 * Workaround for a bug in Word 2010.
							 * 
							 * If you have multiple FORMTEXT in a single run,
							 * for example:
							 * 
							 *      <w:fldChar w:fldCharType="begin">
							          <w:ffData>
							            <w:name w:val="Text12"/>
							            <w:enabled/>
							            <w:calcOnExit w:val="false"/>
							            <w:textInput/>
							          </w:ffData>
							        </w:fldChar>
							        <w:instrText xml:space="preserve"> FORMTEXT </w:instrText>
							        <w:fldChar w:fldCharType="separate"/>
							        <w:t> </w:t>
							        <w:fldChar w:fldCharType="end"/>
							        <w:fldChar w:fldCharType="begin">
							          <w:ffData>
							            <w:name w:val="Text12"/>
							            <w:enabled/>
							            <w:calcOnExit w:val="false"/>
							            <w:textInput/>
							          </w:ffData>
							        </w:fldChar>
							        <w:instrText xml:space="preserve"> FORMTEXT </w:instrText>
							        <w:fldChar w:fldCharType="separate"/>
							        <w:t> </w:t>
							        <w:fldChar w:fldCharType="end"/>						
							 *
							 * Word 2010 does not display all the w:t elements (ie spaces appear to
							 * be missing).
							 * 
							 * Adding w:t/@xml:space="preserve" doesn't help.
							 * 
							 * So the workaround here is to start a new run after each END tag.
							 */
							if (!newAttachPoint.getContent().contains()) {
								newAttachPoint.getContent().add();
								.debug("-- attaching -->" + XmlUtils.marshaltoString(truetrue));
							}
						}						
else {
						.debug(".. but in result, so don't add to run");
					}
else {  // still in END processing
					if ( fieldIsTopLevel() ) {
							// Word 2010 can produce a docx where:
							//  <w:r>
							//    <w:fldChar w:fldCharType="separate"/>
							//  </w:r>
							// is missing (valid per spec).
							// For top level fields only, we add this
							.debug(".. ADDING SEP ..  ");
	//						R separateR = Context.getWmlObjectFactory().createR();							
							FldChar fldChar = Context.getWmlObjectFactory().createFldChar();
							.getContent().add(fldChar);
							if (!newAttachPoint.getContent().contains()) {
								newAttachPoint.getContent().add();
								.debug("-- attaching -->" + XmlUtils.marshaltoString(truetrue));
							}
						}					
						// set up results slot - only for top-level fields
						 = .getResultsSlot(); // MERGEFORMAT processing below may have set this already
						if (==null) {
						}
						if (!newAttachPoint.getContent().contains()) { // test, since this is also done immediately before each loop ends
							newAttachPoint.getContent().add();
							.debug("-- attaching -->" + XmlUtils.marshaltoString(truetrue));
						}
						// create a run specifically for end char
						newAttachPoint.getContent().add();
						//for whatever follows the field
else {
					}
				}
else if (==null) {
					// run content before or after the field
					// - preserve this content
					if (!newAttachPoint.getContent().contains()) {
						newAttachPoint.getContent().add();
						.debug("-- attaching -->" + XmlUtils.marshaltoString(truetrue));
					}
else if ( !.haveSeenSeparate() ) {
				// Handles problems with empty w:instrText elements within complex field "begin" section
				Object o = XmlUtils.unwrap(o2);
				if (o instanceof Text && ((Texto).getValue().trim().isEmpty()) {
					.debug("Empty w:instrText found. Ignore it!");
					continue;
				}
//				log.debug("Processing " +((JAXBElement<Text>)o2).getValue().getValue() );
				if (inParentResult()) {
else {
						.debug(".. but in result, so don't add to run");
					}
else {				
				}
else if (preserveResult()) {
				// ie locked, or not MERGEFIELD, or DOCPROPERTY
				.debug("preserveResult-> adding");
					.setResultsSlot();  // no harm in doing this - same as in SEPARATE processing?
else if (.getResultsSlot()!=) {
					.warn("Multiple runs in results slot?");
				}
else {
				// result content .. can ignore unless it has \* MERGEFORMAT
				// if \* MERGEFORMAT, attach the rPr of first run in the result
				if (o2 instanceof R
					R resultR = Context.getWmlObjectFactory().createR();
					resultR.setRPr(((R)o2).getRPr()); // could be null, but that's ok
					.debug("MERGEFORMAT Set rPr");
				}
				// TODO: a TOC field usually has a PAGEREF wrapped in a hyperlink in its
				// result part.  We should either keep the entire result, or empty it.
				// only do this if the field has no nested field; we need a way to look ahead
				// to see whether a nested field is coming up)
				// we only want a single run between SEPARATOR and END,
				// and we added that in the SEPARATE stuff above
				.debug("IGNORING " + XmlUtils.marshaltoString(o2truetrue));
			// Doesn't solve the problem of Word failing to display some spaces.
//			if ( o2 instanceof Text
//					|| ((o2 instanceof JAXBElement
//							&& ((JAXBElement)o2).getName().equals(_RT_QNAME)) )	) {
//				Text t = (Text)XmlUtils.unwrap(o2);
//				t.setSpace("preserve");
//			}
			if (.getContent().size() > 0 && !newAttachPoint.getContent().contains()) {
				newAttachPoint.getContent().add();
			}
// end for (Object o2 : existingRun.getContent() )
	}
	    private final static QName _RT_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main""t");
//	public static boolean containsCharType(Object o, STFldCharType charType) {
//		
//		if (o instanceof R) {
//			for (Object o2 : ((R)o).getContent() ) {
//				
//				if (isCharType(o2, charType)) {
//						return true;
//				}				
//			}
//		} 
//		return false;
//	}	
	public static boolean isCharType(Object o2STFldCharType charType) {
		o2 = XmlUtils.unwrap(o2);
		if (o2 instanceof org.docx4j.wml.FldChar) {
			FldChar fldChar = (FldChar)o2;
			if (fldChar.getFldCharType().equals(charType) ) {
				return true;
else {
			}
		}
		return false;
	}	
New to GrepCode? Check out our FAQ X