org.docx4j.utils.XPathAwareCloner Maven / Gradle / Ivy
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