| 1 | package org.docx4j.utils; |
|---|
| 2 | |
|---|
| 3 | import java.util.List; |
|---|
| 4 | |
|---|
| 5 | import javax.xml.bind.Binder; |
|---|
| 6 | import javax.xml.bind.JAXBContext; |
|---|
| 7 | import javax.xml.bind.JAXBElement; |
|---|
| 8 | import javax.xml.bind.JAXBException; |
|---|
| 9 | import javax.xml.namespace.QName; |
|---|
| 10 | |
|---|
| 11 | import org.apache.log4j.Logger; |
|---|
| 12 | import org.docx4j.XmlUtils; |
|---|
| 13 | import org.docx4j.jaxb.Context; |
|---|
| 14 | import org.docx4j.jaxb.JaxbValidationEventHandler; |
|---|
| 15 | import org.docx4j.wml.P; |
|---|
| 16 | import org.w3c.dom.Node; |
|---|
| 17 | |
|---|
| 18 | /** |
|---|
| 19 | * Some users wish to be able to use an XPath on the result |
|---|
| 20 | * of cloning a JAXB object. This variant on XmlUtils.deepCopy |
|---|
| 21 | * allows that. |
|---|
| 22 | * |
|---|
| 23 | * Note that if you use this object's deepCopy method more |
|---|
| 24 | * than once, the results returned by getJAXBNodesViaXPath |
|---|
| 25 | * will only be on your last deepCopy. |
|---|
| 26 | */ |
|---|
| 27 | public class XPathAwareCloner { |
|---|
| 28 | |
|---|
| 29 | private static Logger log = Logger.getLogger(XPathAwareCloner.class); |
|---|
| 30 | |
|---|
| 31 | /** Clone this JAXB object, using default JAXBContext. */ |
|---|
| 32 | public Object deepCopy(Object o) { |
|---|
| 33 | return deepCopy(o, Context.jc); |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | Object jaxbElement; |
|---|
| 37 | |
|---|
| 38 | /** Clone this JAXB object |
|---|
| 39 | * @param value |
|---|
| 40 | * @param jc |
|---|
| 41 | * @return |
|---|
| 42 | */ |
|---|
| 43 | public Object deepCopy(Object o, JAXBContext jc) { |
|---|
| 44 | |
|---|
| 45 | if (o==null) { |
|---|
| 46 | throw new IllegalArgumentException("Can't clone a null argument"); |
|---|
| 47 | } |
|---|
| 48 | |
|---|
| 49 | try { |
|---|
| 50 | // To be XPath aware, we need a binder. |
|---|
| 51 | // But to unmarshall using a binder, we need to unmarshal a node. |
|---|
| 52 | // So, our marshall should be to a W3C document |
|---|
| 53 | org.w3c.dom.Document doc = XmlUtils.marshaltoW3CDomDocument(o, jc); |
|---|
| 54 | |
|---|
| 55 | // OK, unmarshall to binder |
|---|
| 56 | binder = jc.createBinder(); |
|---|
| 57 | JaxbValidationEventHandler eventHandler = new JaxbValidationEventHandler(); |
|---|
| 58 | eventHandler.setContinue(false); |
|---|
| 59 | binder.setEventHandler(eventHandler); |
|---|
| 60 | jaxbElement = binder.unmarshal( doc); |
|---|
| 61 | |
|---|
| 62 | //log.debug("Clone: " + XmlUtils.marshaltoString(jaxbElement, true, true)); |
|---|
| 63 | |
|---|
| 64 | return jaxbElement; |
|---|
| 65 | } catch (JAXBException ex) { |
|---|
| 66 | throw new IllegalArgumentException(ex); |
|---|
| 67 | } |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | |
|---|
| 71 | private Binder<Node> binder; |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | /** |
|---|
| 75 | * Enables synchronization between XML infoset nodes and JAXB objects |
|---|
| 76 | * representing same XML document. |
|---|
| 77 | * |
|---|
| 78 | * An instance of this class maintains the association between XML nodes |
|---|
| 79 | * of an infoset preserving view and a JAXB representation of an XML document. |
|---|
| 80 | * Navigation between the two views is provided by the methods |
|---|
| 81 | * getXMLNode(Object) and getJAXBNode(Object) . |
|---|
| 82 | * |
|---|
| 83 | * In theory, modifications can be made to either the infoset preserving view or |
|---|
| 84 | * the JAXB representation of the document while the other view remains |
|---|
| 85 | * unmodified. The binder ought to be able to synchronize the changes made in |
|---|
| 86 | * the modified view back into the other view using the appropriate |
|---|
| 87 | * Binder update methods, #updateXML(Object, Object) or #updateJAXB(Object). |
|---|
| 88 | * |
|---|
| 89 | * But JAXB doesn't currently work as advertised .. access to this |
|---|
| 90 | * object is offered for advanced users on an experimental basis only. |
|---|
| 91 | */ |
|---|
| 92 | public Binder<Node> getBinder() { |
|---|
| 93 | |
|---|
| 94 | return binder; |
|---|
| 95 | } |
|---|
| 96 | |
|---|
| 97 | /** |
|---|
| 98 | * Fetch JAXB Nodes matching an XPath (for example "//w:p"). |
|---|
| 99 | * |
|---|
| 100 | * If you have modified your JAXB objects (eg added or changed a |
|---|
| 101 | * w:p paragraph), you need to update the association. The problem |
|---|
| 102 | * is that this can only be done ONCE, owing to a bug in JAXB: |
|---|
| 103 | * see https://jaxb.dev.java.net/issues/show_bug.cgi?id=459 |
|---|
| 104 | * |
|---|
| 105 | * So this is left for you to choose to do via the refreshXmlFirst parameter. |
|---|
| 106 | * |
|---|
| 107 | * @param xpathExpr |
|---|
| 108 | * @param refreshXmlFirst |
|---|
| 109 | * @return |
|---|
| 110 | * @throws JAXBException |
|---|
| 111 | */ |
|---|
| 112 | public List<Object> getJAXBNodesViaXPath(String xpathExpr, boolean refreshXmlFirst) |
|---|
| 113 | throws JAXBException { |
|---|
| 114 | |
|---|
| 115 | return XmlUtils.getJAXBNodesViaXPath(binder, jaxbElement, xpathExpr, refreshXmlFirst); |
|---|
| 116 | } |
|---|
| 117 | |
|---|
| 118 | /** |
|---|
| 119 | * @param args |
|---|
| 120 | * @throws JAXBException |
|---|
| 121 | */ |
|---|
| 122 | public static void main(String[] args) throws JAXBException { |
|---|
| 123 | |
|---|
| 124 | String pString = "<w:p xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">" |
|---|
| 125 | +"<w:r>" |
|---|
| 126 | +"<w:t xml:space=\"preserve\">Here is some text.</w:t>" |
|---|
| 127 | +"</w:r>" |
|---|
| 128 | +"<w:r>" |
|---|
| 129 | +"<w:rPr>" |
|---|
| 130 | +"<w:i/>" |
|---|
| 131 | +"</w:rPr>" |
|---|
| 132 | +"<w:t>An italic run.</w:t>" |
|---|
| 133 | +"</w:r>" |
|---|
| 134 | +"<w:r>" |
|---|
| 135 | +"<w:rPr>" |
|---|
| 136 | +"<w:i/>" |
|---|
| 137 | +"</w:rPr>" |
|---|
| 138 | +"<w:t xml:space=\"preserve\">" +"</w:t>" |
|---|
| 139 | +"</w:r>" |
|---|
| 140 | +"<w:r>" |
|---|
| 141 | +"<w:t>More stuff.</w:t>" |
|---|
| 142 | +"</w:r>" |
|---|
| 143 | +"<w:r>" |
|---|
| 144 | +"<w:t xml:space=\"preserve\">" +"</w:t>" |
|---|
| 145 | +"</w:r>" |
|---|
| 146 | +"<w:r>" |
|---|
| 147 | +"<w:rPr>" |
|---|
| 148 | +"<w:b/>" |
|---|
| 149 | +"</w:rPr>" |
|---|
| 150 | +"<w:t>More stuff.</w:t>" |
|---|
| 151 | +"</w:r>" |
|---|
| 152 | +"<w:r>" |
|---|
| 153 | +"<w:t xml:space=\"preserve\">" +"</w:t>" |
|---|
| 154 | +"</w:r>" |
|---|
| 155 | +"<w:r>" |
|---|
| 156 | +"<w:t xml:space=\"preserve\">The run we are seeking.</w:t>" |
|---|
| 157 | +"</w:r>" |
|---|
| 158 | +"<w:r>" |
|---|
| 159 | +"<w:rPr>" |
|---|
| 160 | +"<w:b/>" |
|---|
| 161 | +"</w:rPr>" |
|---|
| 162 | +"<w:t>More stuff.</w:t>" |
|---|
| 163 | +"</w:r>" |
|---|
| 164 | +"<w:r>" |
|---|
| 165 | +"<w:t xml:space=\"preserve\">" +"</w:t>" |
|---|
| 166 | +"</w:r>" |
|---|
| 167 | +"<w:r>" |
|---|
| 168 | +"<w:t>More stuff.</w:t>" |
|---|
| 169 | +"</w:r>" |
|---|
| 170 | +"</w:p>"; |
|---|
| 171 | |
|---|
| 172 | P pIn = (P)XmlUtils.unmarshalString(pString); |
|---|
| 173 | |
|---|
| 174 | XPathAwareCloner cloner = new XPathAwareCloner(); |
|---|
| 175 | P clonedP = (P)cloner.deepCopy(pIn); |
|---|
| 176 | |
|---|
| 177 | List<Object> results = cloner.getJAXBNodesViaXPath("//w:r[contains( w:t, 'seeking')]", false); |
|---|
| 178 | //List<Object> results = cloner.getJAXBNodesViaXPath("//w:r", false); |
|---|
| 179 | |
|---|
| 180 | int i=1; |
|---|
| 181 | for (Object result: results) { |
|---|
| 182 | System.out.println("\n\r" + i + ": " + XmlUtils.marshaltoString(result, true, true)); |
|---|
| 183 | i++; |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | |
|---|
| 187 | } |
|---|
| 188 | |
|---|
| 189 | } |
|---|