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<Node> 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<Node> 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<Object> 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 = "<w:p xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
+"<w:r>"
+"<w:t xml:space=\"preserve\">Here is some text.</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:rPr>"
+"<w:i/>"
+"</w:rPr>"
+"<w:t>An italic run.</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:rPr>"
+"<w:i/>"
+"</w:rPr>"
+"<w:t xml:space=\"preserve\">" +"</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:t>More stuff.</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:t xml:space=\"preserve\">" +"</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:rPr>"
+"<w:b/>"
+"</w:rPr>"
+"<w:t>More stuff.</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:t xml:space=\"preserve\">" +"</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:t xml:space=\"preserve\">The run we are seeking.</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:rPr>"
+"<w:b/>"
+"</w:rPr>"
+"<w:t>More stuff.</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:t xml:space=\"preserve\">" +"</w:t>"
+"</w:r>"
+"<w:r>"
+"<w:t>More stuff.</w:t>"
+"</w:r>"
+"</w:p>";
P pIn = (P)XmlUtils.unmarshalString(pString);
XPathAwareCloner cloner = new XPathAwareCloner();
P clonedP = (P)cloner.deepCopy(pIn);
List<Object> results = cloner.getJAXBNodesViaXPath("//w:r[contains( w:t, 'seeking')]", false);
//List<Object> 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++;
}
}
}