All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.docx4j.utils.XPathAwareCloner Maven / Gradle / Ivy

Go to download

docx4j is a library which helps you to work with the Office Open XML file format as used in docx documents, pptx presentations, and xlsx spreadsheets.

There is a newer version: 11.5.0
Show newest version
package org.docx4j.utils;

import java.util.List;

import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.jaxb.JaxbValidationEventHandler;
import org.docx4j.jaxb.XPathBinderAssociationIsPartialException;
import org.docx4j.wml.P;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;

/**
 * Some users wish to be able to use an XPath on the result
 * of cloning a JAXB object.  This variant on XmlUtils.deepCopy 
 * allows that.
 * 
 * Note that if you use this object's deepCopy method more 
 * than once, the results returned by getJAXBNodesViaXPath
 * will only be on your last deepCopy.
 */
public class XPathAwareCloner {
	
	private static Logger log = LoggerFactory.getLogger(XPathAwareCloner.class);	
			
	/** Clone this JAXB object, using default JAXBContext. */ 
	public Object deepCopy(Object o) {		
		return deepCopy(o, Context.jc);		
	}	
	
	Object jaxbElement;
	
	/** Clone this JAXB object
	 * @param value
	 * @param jc
	 * @return
	 */
	public Object deepCopy(Object o, JAXBContext jc) {
		
		if (o==null) {
			throw new IllegalArgumentException("Can't clone a null argument");
		}
		
		try {
			// To be XPath aware, we need a binder.
			// But to unmarshall using a binder, we need to unmarshal a node.
			// So, our marshall should be to a W3C document
			org.w3c.dom.Document doc = XmlUtils.marshaltoW3CDomDocument(o, jc);
			
			// OK, unmarshall to binder
			binder = jc.createBinder();
			JaxbValidationEventHandler eventHandler = new JaxbValidationEventHandler();
			eventHandler.setContinue(false);
			binder.setEventHandler(eventHandler);
			jaxbElement =  binder.unmarshal( doc);
			
			//log.debug("Clone: " + XmlUtils.marshaltoString(jaxbElement, true, true));
			
			return jaxbElement;
		} catch (JAXBException ex) {
			throw new IllegalArgumentException(ex);
		}
	}
	

	private Binder binder;
	
	
	/**
	 * Enables synchronization between XML infoset nodes and JAXB objects 
	 * representing same XML document.
	 * 
	 * An instance of this class maintains the association between XML nodes
	 * of an infoset preserving view and a JAXB representation of an XML document. 
	 * Navigation between the two views is provided by the methods 
	 * getXMLNode(Object) and getJAXBNode(Object) .
	 * 
	 * In theory, modifications can be made to either the infoset preserving view or 
	 * the JAXB representation of the document while the other view remains
	 * unmodified. The binder ought to be able to synchronize the changes made in
	 * the modified view back into the other view using the appropriate
	 * Binder update methods, #updateXML(Object, Object) or #updateJAXB(Object).
	 * 
	 * But JAXB doesn't currently work as advertised .. access to this
	 * object is offered for advanced users on an experimental basis only.
	 */
	public Binder getBinder() {
		
		return binder;
	}
	
	/**
	 * Fetch JAXB Nodes matching an XPath (for example "//w:p").
	 * 
	 * If you have modified your JAXB objects (eg added or changed a 
	 * w:p paragraph), you need to update the association. The problem
	 * is that this can only be done ONCE, owing to a bug in JAXB:
	 * see https://jaxb.dev.java.net/issues/show_bug.cgi?id=459
	 * 
	 * So this is left for you to choose to do via the refreshXmlFirst parameter.   
	 * 
	 * @param xpathExpr
	 * @param refreshXmlFirst
	 * @return
	 * @throws JAXBException
	 * @throws XPathBinderAssociationIsPartialException 
	 */	
	public List getJAXBNodesViaXPath(String xpathExpr, boolean refreshXmlFirst) 
			throws JAXBException, XPathBinderAssociationIsPartialException {
		
		return XmlUtils.getJAXBNodesViaXPath(binder, jaxbElement, xpathExpr, refreshXmlFirst);
	}	
	
	/**
	 * @param args
	 * @throws JAXBException 
	 * @throws XPathBinderAssociationIsPartialException 
	 */
	public static void main(String[] args) throws JAXBException, XPathBinderAssociationIsPartialException {
		
	    String pString = ""
	    	      +""
	    	        +"Here is some text."
	    	      +""
	    	      +""
	    	        +""
	    	          +""
	    	        +""
	    	        +"An italic run."
	    	      +""
	    	      +""
	    	        +""
	    	          +""
	    	        +""
	    	        +""  +""
	    	      +""
	    	      +""
	    	        +"More stuff."
	    	      +""
	    	      +""
	    	        +""  +""
	    	      +""
	    	      +""
	    	        +""
	    	          +""
	    	        +""
	    	        +"More stuff."
	    	      +""
	    	      +""
	    	        +"" +""
	    	      +""
	    	      +""
	    	        +"The run we are seeking."
	    	      +""
	    	      +""
	    	        +""
	    	          +""
	    	        +""
	    	        +"More stuff."
	    	      +""
	    	      +""
	    	        +"" +""
	    	      +""
	    	      +""
	    	        +"More stuff."
	    	      +""
	    	    +"";
	    
	    P pIn = (P)XmlUtils.unmarshalString(pString);
	    
	    XPathAwareCloner cloner = new XPathAwareCloner();
	    P clonedP = (P)cloner.deepCopy(pIn);
	    
	    List results = cloner.getJAXBNodesViaXPath("//w:r[contains( w:t, 'seeking')]", false);
	    //List results = cloner.getJAXBNodesViaXPath("//w:r", false);
	    
	    int i=1;
	    for (Object result: results) {
	    	System.out.println("\n\r" + i + ": " + XmlUtils.marshaltoString(result, true, true));
	    	i++;
	    }
		

	}

}