source: trunk/docx4j/src/main/java/org/docx4j/XmlUtils.java @ 1758

Revision 1758, 39.0 KB checked in by jharrop, 3 months ago (diff)

For Oracle's Java 1.7, System.setProperty("javax.xml.parsers.SAXParserFactory",

"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");

Line 
1/*
2 *  Copyright 2007-2008, Plutext Pty Ltd.
3 *   
4 *  This file is part of docx4j.
5
6    docx4j is licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8
9    You may obtain a copy of the License at
10
11        http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18
19 */
20
21
22package org.docx4j;
23
24import java.io.ByteArrayInputStream;
25import java.io.ByteArrayOutputStream;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.StringReader;
29import java.io.StringWriter;
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35
36import javax.xml.bind.Binder;
37import javax.xml.bind.JAXBContext;
38import javax.xml.bind.JAXBElement;
39import javax.xml.bind.JAXBException;
40import javax.xml.bind.Marshaller;
41import javax.xml.bind.Unmarshaller;
42import javax.xml.bind.util.JAXBResult;
43import javax.xml.namespace.NamespaceContext;
44import javax.xml.namespace.QName;
45import javax.xml.parsers.DocumentBuilder;
46import javax.xml.parsers.DocumentBuilderFactory;
47import javax.xml.parsers.ParserConfigurationException;
48import javax.xml.transform.ErrorListener;
49import javax.xml.transform.Result;
50import javax.xml.transform.Templates;
51import javax.xml.transform.Transformer;
52import javax.xml.transform.TransformerConfigurationException;
53import javax.xml.transform.TransformerException;
54import javax.xml.transform.dom.DOMSource;
55import javax.xml.transform.stream.StreamResult;
56import javax.xml.transform.stream.StreamSource;
57import javax.xml.xpath.XPath;
58import javax.xml.xpath.XPathConstants;
59import javax.xml.xpath.XPathExpressionException;
60import javax.xml.xpath.XPathFactory;
61
62import org.apache.log4j.Logger;
63import org.apache.xalan.trace.PrintTraceListener;
64import org.apache.xalan.trace.TraceManager;
65import org.apache.xalan.transformer.TransformerImpl;
66import org.docx4j.jaxb.Context;
67import org.docx4j.jaxb.JaxbValidationEventHandler;
68import org.docx4j.jaxb.NamespacePrefixMapperUtils;
69import org.docx4j.jaxb.NamespacePrefixMappings;
70import org.docx4j.openpackaging.exceptions.Docx4JException;
71import org.w3c.dom.Attr;
72import org.w3c.dom.Document;
73import org.w3c.dom.NamedNodeMap;
74import org.w3c.dom.Node;
75import org.w3c.dom.NodeList;
76import org.xml.sax.InputSource;
77import org.xml.sax.SAXException;
78
79public class XmlUtils {
80       
81        private static Logger log = Logger.getLogger(XmlUtils.class);   
82               
83        // See http://www.edankert.com/jaxpimplementations.html for
84        // a helpful list.
85       
86        public static String TRANSFORMER_FACTORY_ORIGINAL;
87       
88        public static String TRANSFORMER_FACTORY_PROCESSOR_XALAN = "org.apache.xalan.processor.TransformerFactoryImpl";
89        // TRANSFORMER_FACTORY_PROCESSOR_SUN .. JDK/JRE does not include anything like com.sun.org.apache.xalan.TransformerFactoryImpl
90       
91//      public static String TRANSFORMER_FACTORY_SAXON = "net.sf.saxon.TransformerFactoryImpl";
92
93        // *.xsltc.trax.TransformerImpl don't
94        // work with our extension functions in their current form.
95        //public static String TRANSFORMER_FACTORY_XSLTC_XALAN = "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";
96        //public static String TRANSFORMER_FACTORY_XSLTC_SUN = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
97       
98        public static javax.xml.transform.TransformerFactory tfactory; 
99       
100        static {
101               
102                javax.xml.transform.TransformerFactory tmpfactory = javax.xml.transform.TransformerFactory.newInstance();                       
103                TRANSFORMER_FACTORY_ORIGINAL = tmpfactory.getClass().getName();
104                tmpfactory = null;
105                log.debug("Set TRANSFORMER_FACTORY_ORIGINAL to " + TRANSFORMER_FACTORY_ORIGINAL);
106                               
107                setTFactory();
108               
109        // Crimson fails to parse the HTML XSLT, so use Xerces ..
110                // .. this one is available in Java 6. 
111                System.out.println(System.getProperty("java.vendor"));
112                System.out.println(System.getProperty("java.version"));
113                if ((System.getProperty("java.version").startsWith("1.6")
114                                && System.getProperty("java.vendor").startsWith("Sun"))
115                                || (System.getProperty("java.version").startsWith("1.7")
116                                                && System.getProperty("java.vendor").startsWith("Oracle"))) {
117               
118                        System.setProperty("javax.xml.parsers.SAXParserFactory", 
119                        "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
120        //              System.setProperty("javax.xml.parsers.SAXParserFactory",
121        //                              "org.apache.xerces.jaxp.SAXParserFactoryImpl");
122                       
123                        // Not needed
124        //      System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
125        //                              "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
126               
127                } else {
128                        log.warn("Using default SAXParserFactory: " + System.getProperty("javax.xml.parsers.SAXParserFactory" ));
129                }
130               
131        }
132       
133        private static void setTFactory() {
134               
135                // see further docs/JAXP_TransformerFactory_XSLT_notes.txt
136               
137                try {
138                        System.setProperty("javax.xml.transform.TransformerFactory",
139                                        TRANSFORMER_FACTORY_PROCESSOR_XALAN);
140//                                      TRANSFORMER_FACTORY_SAXON);
141                       
142                        tfactory = javax.xml.transform.TransformerFactory
143                                        .newInstance();
144                        // We've got our factory now, so set it back again!
145                        System.setProperty("javax.xml.transform.TransformerFactory",
146                                        TRANSFORMER_FACTORY_ORIGINAL);
147                } catch (javax.xml.transform.TransformerFactoryConfigurationError e) {
148                       
149                        log.error(e);
150                       
151                        // Provider org.apache.xalan.processor.TransformerFactoryImpl not found
152                        System.out.println("Warning: Xalan jar missing from classpath; xslt not supported");
153                       
154                        // but try anyway
155                        System.setProperty("javax.xml.transform.TransformerFactory",
156                                        TRANSFORMER_FACTORY_ORIGINAL);
157                       
158                        tfactory = javax.xml.transform.TransformerFactory
159                        .newInstance();
160                }
161               
162                LoggingErrorListener errorListener = new LoggingErrorListener(false);
163                tfactory.setErrorListener(errorListener);
164               
165        }
166
167        /**
168         * If an object is wrapped in a JAXBElement, return the object.
169         * Warning: be careful with this. If you are copying objects
170         * into your document (rather than just reading them), you'll
171         * probably want the object to remain wrapped (JAXB usually wraps them
172         * for a reason; without the wrapper, you'll (probably?) need an
173         * @XmlRootElement annotation in order to be able to marshall ie save your
174         * document).
175         *
176         * @param o
177         * @return
178         */
179        public static Object unwrap(Object o) {
180               
181                if (o==null) return null;
182               
183                if (o instanceof javax.xml.bind.JAXBElement) {
184                        log.debug("Unwrapped " + ((JAXBElement)o).getDeclaredType().getName() );
185                        log.debug("name: " + ((JAXBElement)o).getName() );
186                        return ((JAXBElement)o).getValue();
187                } else {
188                        return o;
189                }
190               
191                /*
192                 * Interestingly, DocumentSettingsPart (settings.xml) gets unwrapped,
193                 * and CTSettings does not have @XmlRootElement, but this does
194                 * not cause a problem when it is marshalled.
195                 */
196        }
197
198//      public static Object unwrap(Object o, Class<?> x) {
199//             
200//              if (o instanceof x) {
201//                     
202//              }
203//              else if (o instanceof javax.xml.bind.JAXBElement) {
204//                      log.debug("Unwrapped " + ((JAXBElement)o).getDeclaredType().getName() );
205//                      return ((JAXBElement)o).getValue();
206//              } else {
207//                      return o;
208//              }
209//      }
210
211       
212        public static String JAXBElementDebug(javax.xml.bind.JAXBElement o)  {
213                               
214                String prefix = null;
215                if (o.getName().getNamespaceURI()!=null) {
216                        try {
217                                prefix = NamespacePrefixMapperUtils.getPreferredPrefix(o.getName().getNamespaceURI(), null, false);
218                        } catch (JAXBException e) {
219                                e.printStackTrace();
220                        }
221                }
222                if (prefix!=null) {
223                        return  prefix + ':' + o.getName().getLocalPart() 
224                                + " is a javax.xml.bind.JAXBElement; it has declared type " 
225                                + o.getDeclaredType().getName(); 
226                } else {
227                        return  o.getName() + " is a javax.xml.bind.JAXBElement; it has declared type " 
228                                + o.getDeclaredType().getName();                       
229                }
230               
231        }
232
233       
234
235        /** Unmarshal an InputStream as an object in the package org.docx4j.jaxb.document.
236         *  Note: you should ensure you include a namespace declaration for w: and
237         *  any other namespace in the xml string.
238         *  Also, the object you are attempting to unmarshall to might need to
239         *  have an @XmlRootElement annotation for things to work.  */ 
240        public static Object unmarshal(InputStream is) throws JAXBException {   
241                return unmarshal(is, Context.jc);
242        }
243
244        public static Object unmarshal(InputStream is, JAXBContext jc) throws JAXBException {
245                Object o = null;
246                Unmarshaller u = jc.createUnmarshaller();                                               
247                u.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
248                o = u.unmarshal( is );
249                return o;
250        }
251       
252       
253        /** Unmarshal a String as an object in the package org.docx4j.jaxb.document.
254         *  Note: you should ensure you include a namespace declaration for w: and
255         *  any other namespace in the xml string.
256         *  Also, the object you are attempting to unmarshall to might need to
257         *  have an @XmlRootElement annotation for things to work.  */ 
258        public static Object unmarshalString(String str) throws JAXBException {         
259                return unmarshalString(str, Context.jc);
260        }
261       
262        public static Object unmarshalString(String str, JAXBContext jc, Class declaredType) throws JAXBException {             
263                Unmarshaller u = jc.createUnmarshaller();                                               
264                u.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
265                Object o = u.unmarshal( new javax.xml.transform.stream.StreamSource(new java.io.StringReader(str)),
266                                declaredType);
267                if (o instanceof JAXBElement) {
268                        return ((JAXBElement)o).getValue();
269                } else {
270                        return o;
271                }
272        }
273       
274
275        public static Object unmarshalString(String str, JAXBContext jc) throws JAXBException {
276                log.debug("Unmarshalling '" + str + "'");                       
277                Unmarshaller u = jc.createUnmarshaller();                                               
278                u.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
279                return u.unmarshal( new javax.xml.transform.stream.StreamSource(
280                                new java.io.StringReader(str)) );
281        }
282
283        public static Object unmarshal(Node n) throws JAXBException {
284                       
285                Unmarshaller u = Context.jc.createUnmarshaller();                                       
286                u.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
287
288                return u.unmarshal( n );
289        }
290
291        public static Object unmarshal(Node n, JAXBContext jc, Class declaredType) throws JAXBException {
292               
293                // THIS DOESN"T WORK PROPERLY WITH 1.6.0.03 JAXB RI
294                // (at least with CTTextParagraphProperties in
295                //  a Xalan org.apache.xml.dtm.ref.DTMNodeProxy)
296                // but converting the Node to a String and
297                // unmarshalling that is fine!
298               
299                Unmarshaller u = jc.createUnmarshaller();                                       
300                u.setEventHandler(new org.docx4j.jaxb.JaxbValidationEventHandler());
301                Object o = u.unmarshal(n,
302                                declaredType);
303                if ( o instanceof javax.xml.bind.JAXBElement) {
304                        return ((JAXBElement)o).getValue();
305                } else {
306                        return o;
307                }
308        }
309       
310    /**
311         * Give a string of wml containing ${key1}, ${key2}, return a suitable
312         * object.
313         *
314         * @param wmlTemplateString
315         * @param mappings
316         * @return
317         */
318        public static Object unmarshallFromTemplate(String wmlTemplateString, 
319                        java.util.HashMap<String, String> mappings) throws JAXBException {
320            return unmarshallFromTemplate(wmlTemplateString, mappings, Context.jc);
321         }
322       
323        public static Object unmarshallFromTemplate(String wmlTemplateString, 
324                        java.util.HashMap<String, String> mappings, JAXBContext jc) throws JAXBException {
325            String wmlString = replace(wmlTemplateString, 0, new StringBuilder(), mappings).toString();
326            log.debug("Results of substitution: " + wmlString);
327            return unmarshalString(wmlString, jc);
328         }
329       
330        public static Object unmarshallFromTemplate(String wmlTemplateString, 
331                        java.util.HashMap<String, String> mappings, JAXBContext jc, Class<?> declaredType) throws JAXBException {
332              String wmlString = replace(wmlTemplateString, 0, new StringBuilder(), mappings).toString();
333              return unmarshalString(wmlString, jc, declaredType);
334           }
335       
336       
337         private static StringBuilder replace(String s, int offset, StringBuilder b, java.util.HashMap<String, String> mappings) {
338            int startKey = s.indexOf("${", offset);
339            if (startKey == -1)
340               return b.append(s.substring(offset));
341            else {
342               b.append(s.substring(offset, startKey));
343               int keyEnd = s.indexOf('}', startKey);
344               String key = s.substring(startKey + 2, keyEnd);
345               String val = mappings.get(key);
346               if (val==null) {
347                   log.warn("Invalid key '" + key + "' or key not mapped to a value");
348                   b.append(key );
349               } else {
350                   b.append(val  );
351               }
352               return replace(s, keyEnd + 1, b, mappings);
353            }
354         }
355
356        /** Marshal to a String */ 
357        public static String marshaltoString(Object o, boolean suppressDeclaration ) {
358
359                JAXBContext jc = Context.jc;
360                return marshaltoString(o, suppressDeclaration, false, jc );
361        }
362
363        /** Marshal to a String */ 
364        public static String marshaltoString(Object o, boolean suppressDeclaration, JAXBContext jc ) {
365
366                return marshaltoString(o, suppressDeclaration, false, jc );
367
368        }
369       
370        /** Marshal to a String */ 
371        public static String marshaltoString(Object o, boolean suppressDeclaration, boolean prettyprint ) {
372
373                JAXBContext jc = Context.jc;
374                return marshaltoString(o, suppressDeclaration, prettyprint, jc );
375        }
376       
377        /** Marshal to a String */ 
378        public static String marshaltoString(Object o, boolean suppressDeclaration, boolean prettyprint, 
379                        JAXBContext jc ) {
380                               
381                /* http://weblogs.java.net/blog/kohsuke/archive/2005/10/101_ways_to_mar.html
382                 *
383                 * If you are writing to a file, a socket, or memory, then you should use
384                 * the version that takes OutputStream. Unless you change the target
385                 * encoding to something else (default is UTF-8), there's a special
386                 * marshaller codepath for OutputStream, which makes it run really fast.
387                 * You also don't have to use BufferedOutputStream, since the JAXB RI
388                 * does the adequate buffering.
389                 *
390                 * You can also write to Writer, but in this case you'll be responsible
391                 * for encoding characters, so in general you need to be careful. If
392                 * you want to marshal XML into an encoding other than UTF-8, it's best
393                 *  to use the JAXB_ENCODING property and then write to OutputStream,
394                 *  as it escapes characters to things like &#x1824; correctly.
395                 */
396               
397                if(o==null) {
398                        return null;                   
399                }
400               
401                try {                   
402                        Marshaller m=jc.createMarshaller();
403                        NamespacePrefixMapperUtils.setProperty(m, 
404                                        NamespacePrefixMapperUtils.getPrefixMapper());                 
405                       
406                        if (prettyprint) {
407                                m.setProperty("jaxb.formatted.output", true);
408                        }
409                       
410                        /* Fix for:
411                         *              <t tstamp='1198193417585' snum='1' op='update'>
412                         * ~~~~~~~>     <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
413                         *                              <ns1:sdt xmlns:ns1="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
414                         *                                      <ns1:sdtPr><ns1:id ns1:val="1814108031"/><ns1:tag ns1:val="1"/></ns1:sdtPr><ns1:sdtContent><ns1:p><ns1:r><ns1:t>Lookin</ns1:t></ns1:r><ns1:r><ns1:t> pretty</ns1:t></ns1:r></ns1:p></ns1:sdtContent></ns1:sdt></t>
415                         */
416                       
417                        /* http://weblogs.java.net/blog/kohsuke/archive/2005/10/101_ways_to_mar.html
418                         *
419                         * JAXB_FRAGMENT prevents the marshaller from producing an XML declaration,
420                         * so the above works just fine. The downside of this approach is that if
421                         * the ancestor elements declare the namespaces, JAXB won't be able to take
422                         * advantage of them.
423                         */
424                       
425                        if (suppressDeclaration) {
426                                m.setProperty(Marshaller.JAXB_FRAGMENT,true);
427                        }                       
428                       
429                        StringWriter sWriter = new StringWriter();
430                        m.marshal(o, sWriter);
431                        return sWriter.toString();
432                       
433/*          Alternative implementation
434 
435                        java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream();                       
436                        marshaller.marshal(o, out);                     
437                    byte[] bytes = out.toByteArray();
438                    return new String(bytes);
439 */     
440                       
441                } catch (JAXBException e) {
442                    throw new RuntimeException(e);
443                }
444        }
445
446        /** Marshal to a String, for object
447         *  missing an @XmlRootElement annotation.  */
448        public static String marshaltoString(Object o, boolean suppressDeclaration, boolean prettyprint,
449                        JAXBContext jc,
450                        String uri, String local, Class declaredType) {
451                // TODO - refactor this.
452                try {
453
454                        Marshaller m = jc.createMarshaller();
455                        NamespacePrefixMapperUtils.setProperty(m, 
456                                        NamespacePrefixMapperUtils.getPrefixMapper());                 
457                       
458                        if (prettyprint) {
459                                m.setProperty("jaxb.formatted.output", true);
460                        }
461                       
462                        if (suppressDeclaration) {
463                                m.setProperty(Marshaller.JAXB_FRAGMENT,true);
464                        }                       
465                       
466                        StringWriter sWriter = new StringWriter();
467
468                        m.marshal( 
469                                        new JAXBElement(new QName(uri,local), declaredType, o ),
470                                        sWriter);
471
472                        return sWriter.toString();
473                       
474                } catch (JAXBException e) {
475                    throw new RuntimeException(e);
476                } 
477        }
478       
479       
480        public static java.io.InputStream marshaltoInputStream(Object o, boolean suppressDeclaration, JAXBContext jc ) {
481               
482                /* http://weblogs.java.net/blog/kohsuke/archive/2005/10/101_ways_to_mar.html
483                 *
484                 * If you are writing to a file, a socket, or memory, then you should use
485                 * the version that takes OutputStream. Unless you change the target
486                 * encoding to something else (default is UTF-8), there's a special
487                 * marshaller codepath for OutputStream, which makes it run really fast.
488                 * You also don't have to use BufferedOutputStream, since the JAXB RI
489                 * does the adequate buffering.
490                 *
491                 */
492               
493                try {                   
494                        Marshaller m=jc.createMarshaller();
495                        NamespacePrefixMapperUtils.setProperty(m, 
496                                        NamespacePrefixMapperUtils.getPrefixMapper());                 
497                                               
498                        if (suppressDeclaration) {
499                                m.setProperty(Marshaller.JAXB_FRAGMENT,true);
500                        }
501                       
502                        ByteArrayOutputStream os = new ByteArrayOutputStream();
503                        m.marshal(o, os);                       
504
505                        // Now copy from the outputstream to inputstream
506                        // See http://ostermiller.org/convert_java_outputstream_inputstream.html
507                       
508                        return new java.io.ByteArrayInputStream(os.toByteArray());
509                       
510                       
511                } catch (JAXBException e) {
512                        throw new RuntimeException(e);
513                }
514        }
515
516       
517       
518        /** Marshal to a W3C document */ 
519        public static org.w3c.dom.Document marshaltoW3CDomDocument(Object o) {
520
521                return marshaltoW3CDomDocument(o, Context.jc);
522        }
523
524        /** Marshal to a W3C document */
525        public static org.w3c.dom.Document marshaltoW3CDomDocument(Object o, JAXBContext jc) {
526                // TODO - refactor this.
527                try {
528
529                        Marshaller marshaller = jc.createMarshaller();
530
531                        javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory
532                                        .newInstance();
533                        dbf.setNamespaceAware(true);
534                        org.w3c.dom.Document doc = dbf.newDocumentBuilder().newDocument();
535
536                        NamespacePrefixMapperUtils.setProperty(marshaller, 
537                                        NamespacePrefixMapperUtils.getPrefixMapper());                 
538
539                        marshaller.marshal(o, doc);
540
541                        return doc;
542                } catch (JAXBException e) {
543                    throw new RuntimeException(e);
544                } catch (ParserConfigurationException e) {
545                    throw new RuntimeException(e);
546                }
547        }
548
549        /** Marshal to a W3C document, for object
550         *  missing an @XmlRootElement annotation.  */
551        public static org.w3c.dom.Document marshaltoW3CDomDocument(Object o, JAXBContext jc,
552                        String uri, String local, Class declaredType) {
553                // TODO - refactor this.
554                try {
555
556                        Marshaller marshaller = jc.createMarshaller();
557
558                        javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory
559                                        .newInstance();
560                        dbf.setNamespaceAware(true);
561                        org.w3c.dom.Document doc = dbf.newDocumentBuilder().newDocument();
562
563                        NamespacePrefixMapperUtils.setProperty(marshaller, 
564                                        NamespacePrefixMapperUtils.getPrefixMapper());                 
565
566                        // See http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html
567                        marshaller.marshal( 
568                                        new JAXBElement(new QName(uri,local), declaredType, o ),
569                                        doc);
570
571                        return doc;
572                } catch (JAXBException e) {
573                    throw new RuntimeException(e);
574                } catch (ParserConfigurationException e) {
575                    throw new RuntimeException(e);
576                }
577        }
578       
579       
580        /** Clone this JAXB object, using default JAXBContext. */ 
581        public static <T> T deepCopy(T value) {         
582                return deepCopy(value, Context.jc);             
583        }
584       
585       
586        /** Clone this JAXB object
587         * @param value
588         * @param jc
589         * @return
590         */
591        public static <T> T deepCopy(T value, JAXBContext jc) {
592               
593                if (value==null) {
594                        throw new IllegalArgumentException("Can't clone a null argument");
595                }
596               
597                try {
598                        JAXBElement<?> elem;
599                        Class<?> valueClass;
600                        if (value instanceof JAXBElement<?>) {
601                                log.debug("deep copy of JAXBElement..");
602                                elem = (JAXBElement<?>) value;
603                                valueClass = elem.getDeclaredType();
604                        } else {
605                                log.debug("deep copy of " + value.getClass().getName() );
606                                @SuppressWarnings("unchecked")
607                                Class<T> classT = (Class<T>) value.getClass();
608                                elem = new JAXBElement<T>(new QName("temp"), classT, value);
609                                valueClass = classT;
610                        }
611
612                        Marshaller mar = jc.createMarshaller();
613                        ByteArrayOutputStream bout = new ByteArrayOutputStream(256);
614                        mar.marshal(elem, bout);
615
616                        Unmarshaller unmar = jc.createUnmarshaller();
617                        elem = unmar.unmarshal(new StreamSource(new ByteArrayInputStream(
618                                        bout.toByteArray())), valueClass);
619
620                        T res;
621                        if (value instanceof JAXBElement<?>) {
622                                @SuppressWarnings("unchecked")
623                                T resT = (T) elem;
624                                res = resT;
625                        } else {
626                                @SuppressWarnings("unchecked")
627                                T resT = (T) elem.getValue();
628                                res = resT;
629                        }
630//                      log.info("deep copy success!");
631                        return res;
632                } catch (JAXBException ex) {
633                        throw new IllegalArgumentException(ex);
634                }
635        }
636       
637       
638    public static String w3CDomNodeToString(Node n) {
639         
640                // Why doesn't Java have a nice neat way of getting
641                // the XML as a String?? 
642                       
643                StringWriter sw = new StringWriter();
644                try {
645                                Transformer serializer = tfactory.newTransformer();
646                                serializer.setOutputProperty(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
647                                //serializer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
648                                serializer.transform( new DOMSource(n) , new StreamResult(sw) );                               
649                                return sw.toString();
650                                //log.debug("serialised:" + n);
651                        } catch (Exception e) {
652                                // Unexpected!
653                                e.printStackTrace();
654                                return null;
655                        } 
656    }
657
658        /** Use DocumentBuilderFactory to create and return a new w3c dom Document. */ 
659        public static org.w3c.dom.Document neww3cDomDocument() {
660               
661                try {
662                        javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
663                        dbf.setNamespaceAware(true);
664                        return dbf.newDocumentBuilder().newDocument();
665                } catch (ParserConfigurationException e) {
666                    throw new RuntimeException(e);
667                }               
668               
669        }
670       
671          /**
672           * @param docBuilder
673           *          the parser
674           * @param parent
675           *          node to add fragment to
676           * @param fragment
677           *          a well formed XML fragment
678         * @throws ParserConfigurationException
679           */
680        public static void appendXmlFragment(Document document, Node parent, String fragment)
681                        throws IOException, SAXException, ParserConfigurationException {
682
683                DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance()
684                                .newDocumentBuilder();
685
686                Node fragmentNode = docBuilder.parse(
687                                new InputSource(new StringReader(fragment)))
688                                .getDocumentElement();
689               
690                fragmentNode = document.importNode(fragmentNode, true);
691               
692                parent.appendChild(fragmentNode);
693        }
694       
695        /**
696         * Prepare a JAXB transformation result for some given context.
697         * @param context The JAXB context.
698         * @return The result data structure created.
699         * @throws Docx4JException In case of configuration errors.
700         */
701        public static JAXBResult prepareJAXBResult(final JAXBContext context)
702                        throws Docx4JException {
703
704                final JAXBResult result;
705                try {
706                        final Unmarshaller unmarshaller = context.createUnmarshaller();
707                        unmarshaller.setEventHandler(new JaxbValidationEventHandler());
708                        result = new JAXBResult(unmarshaller);
709
710                } catch (JAXBException e) {
711                        throw new Docx4JException("Error preparing empty JAXB result", e);
712                }
713                return result;
714        }
715   
716    public static void transform(org.w3c.dom.Document doc,
717                javax.xml.transform.Templates template, 
718                          Map<String, Object> transformParameters, 
719                          javax.xml.transform.Result result) throws Docx4JException {
720
721        if (doc == null ) {
722                Throwable t = new Throwable();
723                throw new Docx4JException( "Null DOM Doc", t);
724        }
725       
726                javax.xml.transform.dom.DOMSource domSource = new javax.xml.transform.dom.DOMSource(doc);
727               
728        transform(domSource,
729                        template, 
730                         transformParameters, 
731                         result);
732    }
733   
734
735    public static Templates getTransformerTemplate(
736                          javax.xml.transform.Source xsltSource) throws TransformerConfigurationException {
737           
738        return tfactory.newTemplates(xsltSource);
739    }   
740
741    /**
742     *
743     * Transform an input document using XSLT
744     *
745     * @param doc
746     * @param xslt
747     * @param transformParameters
748     * @param result
749     * @throws Docx4JException In case serious transformation errors occur
750     */
751    public static void transform(javax.xml.transform.Source source,
752                                          javax.xml.transform.Templates template, 
753                                          Map<String, Object> transformParameters, 
754                                          javax.xml.transform.Result result) throws Docx4JException {
755       
756        if (source == null ) {
757                Throwable t = new Throwable();
758                throw new Docx4JException( "Null Source doc", t);
759        }
760               
761                // Use the template to create a transformer
762                // A Transformer may not be used in multiple threads running concurrently.
763                // Different Transformers may be used concurrently by different threads.
764                // A Transformer may be used multiple times. Parameters and output properties
765                // are preserved across transformations.               
766                javax.xml.transform.Transformer xformer;
767    try {
768      xformer = template.newTransformer();
769    } catch (TransformerConfigurationException e) {
770      throw new Docx4JException("The Transformer is ill-configured", e);
771    }
772                if (!xformer.getClass().getName().equals(
773                                "org.apache.xalan.transformer.TransformerImpl")) {
774                        log
775                                        .error("Detected "
776                                                        + xformer.getClass().getName()
777                                                        + ", but require org.apache.xalan.transformer.TransformerImpl. "
778                                                        + "Ensure Xalan 2.7.0 is on your classpath!");
779                }
780                LoggingErrorListener errorListener = new LoggingErrorListener(false);
781                xformer.setErrorListener(errorListener);
782
783                if (transformParameters != null) {
784                        Iterator parameterIterator = transformParameters.entrySet()
785                                        .iterator();
786                        while (parameterIterator.hasNext()) {
787                                Map.Entry pairs = (Map.Entry) parameterIterator.next();
788
789                                if (pairs.getKey() == null) {
790                                        log.info("Skipped null key");
791                                        // pairs = (Map.Entry)parameterIterator.next();
792                                        continue;
793                                }
794
795                                if (pairs.getValue() == null) {
796                                        log.warn("parameter '" + pairs.getKey() + "' was null.");
797                                } else {
798                                        xformer.setParameter((String) pairs.getKey(), pairs
799                                                        .getValue());
800                                }
801                        }
802                }
803
804        /* SUPER DEBUGGING
805            // http://xml.apache.org/xalan-j/usagepatterns.html#debugging
806            // debugging
807            // Set up a PrintTraceListener object to print to a file.
808            java.io.FileWriter fw = new java.io.FileWriter("/tmp/xslt-events" + xsltCount++ + ".log");
809            java.io.PrintWriter pw = new java.io.PrintWriter(fw);
810            PrintTraceListener ptl = new PrintTraceListener(pw);
811
812            // Print information as each node is 'executed' in the stylesheet.
813            ptl.m_traceElements = true;
814            // Print information after each result-tree generation event.
815            ptl.m_traceGeneration = true;
816            // Print information after each selection event.
817            ptl.m_traceSelection = true;
818            // Print information whenever a template is invoked.
819            ptl.m_traceTemplates = true;
820            // Print information whenever an extension is called.
821            ptl.m_traceExtension = true;
822            TransformerImpl transformerImpl = (TransformerImpl)xformer;
823
824              // Register the TraceListener with the TraceManager associated
825              // with the TransformerImpl.
826              TraceManager trMgr = transformerImpl.getTraceManager();
827              trMgr.addTraceListener(ptl);
828
829*/
830                // DEBUGGING
831                // use the identity transform if you want to send wordDocument;
832                // otherwise you'll get the XHTML
833                // javax.xml.transform.Transformer xformer = tfactory.newTransformer();
834        try {
835            xformer.transform(source, result);
836        } catch (TransformerException e) {
837          throw new Docx4JException("Cannot perform the transformation", e);
838        } finally {
839            //pw.flush();
840        }
841       
842    }
843   
844        /**
845         * Fetch JAXB Nodes matching an XPath (for example "//w:p").
846         *
847         * If you have modified your JAXB objects (eg added or changed a
848         * w:p paragraph), you need to update the association. The problem
849         * is that this can only be done ONCE, owing to a bug in JAXB:
850         * see https://jaxb.dev.java.net/issues/show_bug.cgi?id=459
851         *
852         * So this is left for you to choose to do via the refreshXmlFirst parameter.   
853         *
854         * @param binder
855         * @param jaxbElement
856         * @param refreshXmlFirst
857         * @param xpathExpr
858         * @return
859         * @throws JAXBException
860         */
861        public static List<Object> getJAXBNodesViaXPath(Binder<Node> binder, 
862                        Object jaxbElement, String xpathExpr, boolean refreshXmlFirst) 
863                        throws JAXBException {
864               
865                Node node;
866                if (refreshXmlFirst) 
867                        node = binder.updateXML(jaxbElement);
868                node = binder.getXMLNode(jaxbElement);
869               
870                //log.debug("XPath will execute against: " + XmlUtils.w3CDomNodeToString(node));
871               
872        List<Object> resultList = new ArrayList<Object>();
873        for( Node n : xpath(node, xpathExpr) ) {
874                Object o = binder.getJAXBNode(n);
875                if (o==null) {
876                        log.warn("no object association for xpath result!");
877                } else {
878                        if (o instanceof javax.xml.bind.JAXBElement) {
879                                log.debug("added " + JAXBElementDebug((JAXBElement)o) );
880                        } else {
881                                log.debug("added " + o.getClass().getName() );                         
882                        }
883                        resultList.add(o);                     
884                }
885        }
886        return resultList;
887    }
888       
889    public static List<Node> xpath(Node node, String xpathExpression) {
890        XPathFactory xpf = XPathFactory.newInstance();
891        XPath xpath = xpf.newXPath();
892
893        NamespaceContext nsContext = new NamespacePrefixMappings();
894       
895        return xpath(node, xpathExpression, nsContext);
896       
897    }   
898
899    public static List<Node> xpath(Node node, String xpathExpression, NamespaceContext nsContext) {
900       
901//      log.info("Using XPathFactory: " + XPathFactory.DEFAULT_PROPERTY_NAME + ": "
902//                      + System.getProperty(XPathFactory.DEFAULT_PROPERTY_NAME));   
903//        System.setProperty(XPathFactory.DEFAULT_PROPERTY_NAME,
904//                      "org.apache.xpath.jaxp.XPathFactoryImpl");
905       
906        // create XPath
907        XPathFactory xpf = XPathFactory.newInstance();
908        XPath xpath = xpf.newXPath();
909
910                xpath.setNamespaceContext(nsContext);
911       
912        try {
913            List<Node> result = new ArrayList<Node>();
914            NodeList nl = (NodeList) xpath.evaluate(xpathExpression, node, XPathConstants.NODESET);
915            log.debug("evaluate returned " + nl.getLength() );
916            for( int i=0; i<nl.getLength(); i++ ) {
917                result.add(nl.item(i));
918            }
919            return result;
920        } catch (XPathExpressionException e) {
921            log.error(e);
922            throw new RuntimeException(e);
923        }
924    }   
925
926     
927     static class LoggingErrorListener implements ErrorListener {
928         
929         // See http://www.cafeconleche.org/slides/sd2003west/xmlandjava/346.html
930         
931         boolean strict;
932         
933          public LoggingErrorListener(boolean strict) {
934          }
935         
936          public void warning(TransformerException exception) {
937           
938            log.warn(exception.getMessage(), exception);
939           
940            // Don't throw an exception and stop the processor
941            // just for a warning; but do log the problem
942          }
943         
944          public void error(TransformerException exception)
945           throws TransformerException {
946           
947            log.error(exception.getMessage(), exception);
948           
949            // XSLT is not as draconian as XML. There are numerous errors
950            // which the processor may but does not have to recover from;
951            // e.g. multiple templates that match a node with the same
952            // priority. If I do not want to allow that,  I'd throw this
953            // exception here.
954            if (strict) {
955                throw exception;
956            }
957           
958          }
959         
960          public void fatalError(TransformerException exception)
961           throws TransformerException {
962           
963            log.error(exception.getMessage(), exception);
964
965            // This is an error which the processor cannot recover from;
966            // e.g. a malformed stylesheet or input document
967            // so I must throw this exception here.
968            throw exception;
969           
970          }
971             
972        }
973
974        public static void treeCopy( NodeList sourceNodes, Node destParent ) {         
975        for (int i=0; i<sourceNodes.getLength(); i++) {
976                treeCopy((Node)sourceNodes.item(i), destParent);
977        }               
978        }
979     
980     
981        /**
982         * Copy a node from one DOM document to another.  Used
983         * to avoid relying on an underlying implementation which might
984         * not support importNode
985         * (eg Xalan's org.apache.xml.dtm.ref.DTMNodeProxy).
986         *
987         * WARNING: doesn't fully support namespaces!
988         *
989         * @param sourceNode
990         * @param destParent
991         */
992        public static void treeCopy( Node sourceNode, Node destParent ) {
993               
994                // http://osdir.com/ml/text.xml.xerces-j.devel/2004-04/msg00066.html
995                // suggests the problem has been fixed?
996               
997                // source node maybe org.apache.xml.dtm.ref.DTMNodeProxy
998                // (if its xslt output we are copying)
999                // or com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl
1000                // (if its marshalled JAXB)
1001               
1002        log.debug("node type" + sourceNode.getNodeType());
1003       
1004        switch (sourceNode.getNodeType() ) {
1005
1006                case Node.DOCUMENT_NODE: // type 9
1007       
1008//                      log.debug("DOCUMENT:" + w3CDomNodeToString(sourceNode) );
1009//                      if (sourceNode.getChildNodes().getLength()==0) {
1010//                              log.debug("..no children!");
1011//                      }
1012                       
1013                // recurse on each child
1014                NodeList nodes = sourceNode.getChildNodes();
1015                if (nodes != null) {
1016                    for (int i=0; i<nodes.getLength(); i++) {
1017                        log.debug("child " + i + "of DOCUMENT_NODE");
1018                        //treeCopy((DTMNodeProxy)nodes.item(i), destParent);
1019                        treeCopy((Node)nodes.item(i), destParent);
1020                    }
1021                }
1022                break;
1023            case Node.ELEMENT_NODE:
1024               
1025                // Copy of the node itself
1026                        log.debug("copying: " + sourceNode.getNodeName() );
1027                        Node newChild;
1028                        if ( destParent instanceof Document ) {
1029                                newChild = ((Document)destParent).createElementNS(
1030                                        sourceNode.getNamespaceURI(), sourceNode.getLocalName() );
1031                        } else {
1032                                newChild = destParent.getOwnerDocument().createElementNS(
1033                                        sourceNode.getNamespaceURI(), sourceNode.getLocalName() );                                     
1034                        }
1035                        destParent.appendChild(newChild);
1036                       
1037                        // .. its attributes
1038                NamedNodeMap atts = sourceNode.getAttributes();
1039                for (int i = 0 ; i < atts.getLength() ; i++ ) {
1040                       
1041                        Attr attr = (Attr)atts.item(i);
1042                       
1043//                      log.debug("attr.getNodeName(): " + attr.getNodeName());
1044//                      log.debug("attr.getNamespaceURI(): " + attr.getNamespaceURI());
1045//                      log.debug("attr.getLocalName(): " + attr.getLocalName());
1046//                      log.debug("attr.getPrefix(): " + attr.getPrefix());
1047                       
1048                        if ( attr.getNodeName().startsWith("xmlns:")) {
1049                                /* A document created from a dom4j document using dom4j 1.6.1's io.domWriter
1050                                        does this ?!
1051                                        attr.getNodeName(): xmlns:w
1052                                        attr.getNamespaceURI(): null
1053                                        attr.getLocalName(): null
1054                                        attr.getPrefix(): null
1055                                       
1056                                        unless i'm doing something wrong, this is another reason to
1057                                        remove use of dom4j from docx4j
1058                                */ 
1059                                ; 
1060                                // this is a namespace declaration. not our problem
1061                        } else if (attr.getNamespaceURI()==null) {
1062                                //log.debug("attr.getLocalName(): " + attr.getLocalName() + "=" + attr.getValue());
1063                                ((org.w3c.dom.Element)newChild).setAttribute(
1064                                                attr.getLocalName(), attr.getValue() );
1065                        } else if ( attr.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/")) {
1066                                ; // this is a namespace declaration. not our problem
1067                        } else  {
1068                                ((org.w3c.dom.Element)newChild).setAttributeNS(attr.getNamespaceURI(), 
1069                                                attr.getLocalName(), attr.getValue() );                                         
1070                        }
1071                }
1072
1073                // recurse on each child
1074                NodeList children = sourceNode.getChildNodes();
1075                if (children != null) {
1076                    for (int i=0; i<children.getLength(); i++) {
1077                        //treeCopy( (DTMNodeProxy)children.item(i), newChild);
1078                        treeCopy( (Node)children.item(i), newChild);
1079                    }
1080                }
1081
1082                break;
1083
1084            case Node.TEXT_NODE:
1085               
1086                // Where destParent is com.sun.org.apache.xerces.internal.dom.DocumentImpl,
1087                // destParent.getOwnerDocument() returns null.
1088                // #document ; com.sun.org.apache.xerces.internal.dom.DocumentImpl
1089               
1090                //System.out.println(destParent.getNodeName() + " ; " + destParent.getClass().getName() );
1091                if (destParent.getOwnerDocument()==null
1092                                && destParent.getNodeName().equals("#document")) {
1093                        Node textNode = ((Document)destParent).createTextNode(sourceNode.getNodeValue());   
1094                        destParent.appendChild(textNode);
1095                } else {
1096                        Node textNode = destParent.getOwnerDocument().createTextNode(sourceNode.getNodeValue());   
1097                        destParent.appendChild(textNode);
1098                }
1099                break;
1100
1101//                case Node.CDATA_SECTION_NODE:
1102//                    writer.write("<![CDATA[" +
1103//                                 node.getNodeValue() + "]]>");
1104//                    break;
1105//
1106//                case Node.COMMENT_NODE:
1107//                    writer.write(indentLevel + "<!-- " +
1108//                                 node.getNodeValue() + " -->");
1109//                    writer.write(lineSeparator);
1110//                    break;
1111//
1112//                case Node.PROCESSING_INSTRUCTION_NODE:
1113//                    writer.write("<?" + node.getNodeName() +
1114//                                 " " + node.getNodeValue() +
1115//                                 "?>");
1116//                    writer.write(lineSeparator);
1117//                    break;
1118//
1119//                case Node.ENTITY_REFERENCE_NODE:
1120//                    writer.write("&" + node.getNodeName() + ";");
1121//                    break;
1122//
1123//                case Node.DOCUMENT_TYPE_NODE:
1124//                    DocumentType docType = (DocumentType)node;
1125//                    writer.write("<!DOCTYPE " + docType.getName());
1126//                    if (docType.getPublicId() != null)  {
1127//                        System.out.print(" PUBLIC \"" +
1128//                            docType.getPublicId() + "\" ");
1129//                    } else {
1130//                        writer.write(" SYSTEM ");
1131//                    }
1132//                    writer.write("\"" + docType.getSystemId() + "\">");
1133//                    writer.write(lineSeparator);
1134//                    break;
1135        }
1136    }
1137       
1138}
Note: See TracBrowser for help on using the repository browser.