001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/framework/xml/XMLTools.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    
037    package org.deegree.framework.xml;
038    
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.io.InputStreamReader;
042    import java.io.Reader;
043    import java.io.StringReader;
044    import java.net.URI;
045    import java.net.URISyntaxException;
046    import java.util.ArrayList;
047    import java.util.Iterator;
048    import java.util.List;
049    import java.util.Map;
050    
051    import javax.xml.parsers.DocumentBuilder;
052    import javax.xml.parsers.DocumentBuilderFactory;
053    import javax.xml.parsers.ParserConfigurationException;
054    
055    import org.deegree.datatypes.QualifiedName;
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.framework.util.StringTools;
059    import org.deegree.ogcbase.CommonNamespaces;
060    import org.jaxen.JaxenException;
061    import org.jaxen.XPath;
062    import org.jaxen.dom.DOMXPath;
063    import org.w3c.dom.Attr;
064    import org.w3c.dom.CDATASection;
065    import org.w3c.dom.Comment;
066    import org.w3c.dom.Document;
067    import org.w3c.dom.Element;
068    import org.w3c.dom.NamedNodeMap;
069    import org.w3c.dom.Node;
070    import org.w3c.dom.NodeList;
071    import org.w3c.dom.Text;
072    import org.xml.sax.InputSource;
073    import org.xml.sax.SAXException;
074    
075    /**
076     * XML Tools based on JAXP 1.1 for parsing documents and retrieving node values/node attributes. Furthermore this
077     * utility class provides node retrieval based on XPath expressions.
078     *
079     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
080     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
081     * @author last edited by: $Author: aschmitz $
082     *
083     * @version $Revision: 20895 $, $Date: 2009-11-19 15:59:59 +0100 (Do, 19. Nov 2009) $
084     */
085    public final class XMLTools {
086    
087        private static final ILogger LOG = LoggerFactory.getLogger( XMLTools.class );
088    
089        private XMLTools() {
090            // hidden constructor to prevent instantiation
091        }
092    
093        // ------------------------------------------------------------------------
094        // XPath based parsing methods
095        // ------------------------------------------------------------------------
096    
097        /**
098         * @param contextNode
099         * @param xPathQuery
100         * @param nsContext
101         * @return Node
102         * @throws XMLParsingException
103         */
104        public static Node getNode( Node contextNode, String xPathQuery, NamespaceContext nsContext )
105                                throws XMLParsingException {
106            Node node = null;
107            try {
108                XPath xpath = new DOMXPath( xPathQuery );
109                xpath.setNamespaceContext( nsContext );
110                node = (Node) xpath.selectSingleNode( contextNode );
111    
112                if ( xPathQuery.endsWith( "text()" ) ) {
113                    List<Node> nl = xpath.selectNodes( contextNode );
114                    int pos = xPathQuery.lastIndexOf( "/" );
115                    if ( pos > 0 ) {
116                        xPathQuery = xPathQuery.substring( 0, pos );
117                    } else {
118                        xPathQuery = ".";
119                    }
120                    xpath = new DOMXPath( xPathQuery );
121                    xpath.setNamespaceContext( nsContext );
122                    List<Node> nl_ = xpath.selectNodes( contextNode );
123                    List<String> tmp = new ArrayList<String>( nl_.size() );
124                    for ( int i = 0; i < nl_.size(); i++ ) {
125                        tmp.add( getStringValue( nl_.get( i ) ) );
126                    }
127    
128                    for ( int i = 0; i < nl.size(); i++ ) {
129                        try {
130                            nl.get( i ).getParentNode().removeChild( nl.get( i ) );
131                        } catch ( Exception e ) {
132                            // no exception thrown, why catch them?
133                        }
134                    }
135    
136                    Document doc = contextNode.getOwnerDocument();
137                    for ( int i = 0; i < tmp.size(); i++ ) {
138                        Text text = doc.createTextNode( tmp.get( i ) );
139                        nl_.get( i ).appendChild( text );
140                        node = text;
141                    }
142                }
143    
144            } catch ( JaxenException e ) {
145                throw new XMLParsingException( "Error evaluating XPath-expression '" + xPathQuery + "' from context node '"
146                                               + contextNode.getNodeName() + "': " + e.getMessage(), e );
147            }
148            return node;
149        }
150    
151        /**
152         * @param contextNode
153         * @param xpath
154         * @param nsContext
155         * @return the element
156         * @throws XMLParsingException
157         * @throws ClassCastException
158         *             if the node was not an element
159         */
160        public static Element getElement( Node contextNode, String xpath, NamespaceContext nsContext )
161                                throws XMLParsingException {
162            Node node = getNode( contextNode, xpath, nsContext );
163            return (Element) node;
164        }
165    
166        /**
167         * @param contextNode
168         * @param xPathQuery
169         * @param nsContext
170         * @param defaultValue
171         * @return the node's String value
172         * @throws XMLParsingException
173         */
174        public static String getNodeAsString( Node contextNode, String xPathQuery, NamespaceContext nsContext,
175                                              String defaultValue )
176                                throws XMLParsingException {
177    
178            String value = defaultValue;
179            Node node = getNode( contextNode, xPathQuery, nsContext );
180    
181            if ( node != null ) {
182                value = getStringValue( node );
183            }
184            return value;
185        }
186    
187        /**
188         * @param contextNode
189         * @param xPathQuery
190         * @param nsContext
191         * @param defaultValue
192         * @return the node's boolean value
193         * @throws XMLParsingException
194         */
195        public static boolean getNodeAsBoolean( Node contextNode, String xPathQuery, NamespaceContext nsContext,
196                                                boolean defaultValue )
197                                throws XMLParsingException {
198            boolean value = defaultValue;
199            Node node = getNode( contextNode, xPathQuery, nsContext );
200            if ( node != null ) {
201                String stringValue = getStringValue( node );
202    
203                if ( "true".equals( stringValue ) || "yes".equals( stringValue ) || "1".equals( stringValue ) ) {
204                    value = true;
205                } else if ( "false".equals( stringValue ) || "no".equals( stringValue ) || "0".equals( stringValue ) ) {
206                    value = false;
207                } else {
208                    throw new XMLParsingException( "XPath-expression '" + xPathQuery + " ' from context node '"
209                                                   + contextNode.getNodeName() + "' has an invalid value ('" + stringValue
210                                                   + "'). Valid values are: 'true', 'yes', '1' " + "'false', 'no' and '0'." );
211                }
212            }
213            return value;
214        }
215    
216        /**
217         * @param contextNode
218         * @param xPathQuery
219         * @param nsContext
220         * @param defaultValue
221         * @return the node's integer value
222         * @throws XMLParsingException
223         */
224        public static int getNodeAsInt( Node contextNode, String xPathQuery, NamespaceContext nsContext, int defaultValue )
225                                throws XMLParsingException {
226            int value = defaultValue;
227            Node node = getNode( contextNode, xPathQuery, nsContext );
228            if ( node != null ) {
229                String stringValue = getStringValue( node );
230                try {
231                    value = Integer.parseInt( stringValue );
232                } catch ( NumberFormatException e ) {
233                    throw new XMLParsingException( "Result '" + stringValue + "' of XPath-expression '" + xPathQuery
234                                                   + "' from context node '" + contextNode.getNodeName()
235                                                   + "' does not denote a valid integer value." );
236                }
237            }
238            return value;
239        }
240    
241        /**
242         * @param contextNode
243         * @param xPathQuery
244         * @param nsContext
245         * @param defaultValue
246         * @return the node's double value
247         * @throws XMLParsingException
248         */
249        public static double getNodeAsDouble( Node contextNode, String xPathQuery, NamespaceContext nsContext,
250                                              double defaultValue )
251                                throws XMLParsingException {
252            double value = defaultValue;
253            Node node = getNode( contextNode, xPathQuery, nsContext );
254            if ( node != null ) {
255                String stringValue = getStringValue( node );
256                try {
257                    value = Double.parseDouble( stringValue );
258                } catch ( NumberFormatException e ) {
259                    throw new XMLParsingException( "Result '" + stringValue + "' of XPath-expression '" + xPathQuery
260                                                   + "' from context node '" + contextNode.getNodeName()
261                                                   + "' does not denote a valid double value." );
262                }
263            }
264            return value;
265        }
266    
267        /**
268         * @param contextNode
269         * @param xPathQuery
270         * @param nsContext
271         * @param defaultValue
272         * @return the node as URI
273         * @throws XMLParsingException
274         */
275        public static URI getNodeAsURI( Node contextNode, String xPathQuery, NamespaceContext nsContext, URI defaultValue )
276                                throws XMLParsingException {
277            URI value = defaultValue;
278            Node node = getNode( contextNode, xPathQuery, nsContext );
279            if ( node != null ) {
280                String stringValue = getStringValue( node );
281                try {
282                    value = new URI( stringValue );
283                } catch ( URISyntaxException e ) {
284                    throw new XMLParsingException( "Result '" + stringValue + "' of XPath-expression '" + xPathQuery
285                                                   + "' from context node '" + contextNode.getNodeName()
286                                                   + "' does not denote a valid URI." );
287                }
288            }
289            return value;
290        }
291    
292        /**
293         * @param contextNode
294         * @param xPathQuery
295         * @param nsContext
296         * @param defaultValue
297         * @return the node as qualified name
298         * @throws XMLParsingException
299         */
300        public static QualifiedName getNodeAsQualifiedName( Node contextNode, String xPathQuery,
301                                                            NamespaceContext nsContext, QualifiedName defaultValue )
302                                throws XMLParsingException {
303    
304            QualifiedName value = defaultValue;
305            Node node = getNode( contextNode, xPathQuery, nsContext );
306    
307            if ( node != null ) {
308                value = getQualifiedNameValue( node );
309            }
310            return value;
311    
312        }
313    
314        /**
315         * returns a list of nodes matching the passed XPath
316         *
317         * @param contextNode
318         * @param xPathQuery
319         * @param nsContext
320         * @return a list of nodes matching the passed XPath
321         * @throws XMLParsingException
322         */
323        public static List<Node> getNodes( Node contextNode, String xPathQuery, NamespaceContext nsContext )
324                                throws XMLParsingException {
325            List<Node> nl = null;
326            try {
327                XPath xpath = new DOMXPath( xPathQuery );
328                xpath.setNamespaceContext( nsContext );
329                nl = xpath.selectNodes( contextNode );
330    
331                if ( xPathQuery.endsWith( "text()" ) ) {
332    
333                    int pos = xPathQuery.lastIndexOf( "/" );
334                    if ( pos > 0 ) {
335                        xPathQuery = xPathQuery.substring( 0, pos );
336                    } else {
337                        xPathQuery = ".";
338                    }
339                    xpath = new DOMXPath( xPathQuery );
340                    xpath.setNamespaceContext( nsContext );
341                    List<?> nl_ = xpath.selectNodes( contextNode );
342                    List<String> tmp = new ArrayList<String>( nl_.size() );
343                    for ( int i = 0; i < nl_.size(); i++ ) {
344                        tmp.add( getStringValue( (Node) nl_.get( i ) ) );
345                    }
346    
347                    for ( int i = 0; i < nl.size(); i++ ) {
348                        try {
349                            nl.get( i ).getParentNode().removeChild( nl.get( i ) );
350                        } catch ( Exception e ) {
351                            // ignored, but why? Nothing is actually thrown here?
352                        }
353                    }
354    
355                    nl.clear();
356                    Document doc = contextNode.getOwnerDocument();
357                    for ( int i = 0; i < tmp.size(); i++ ) {
358                        Text text = doc.createTextNode( tmp.get( i ) );
359                        ( (Node) nl_.get( i ) ).appendChild( text );
360                        nl.add( text );
361                    }
362                }
363            } catch ( JaxenException e ) {
364                throw new XMLParsingException( "Error evaluating XPath-expression '" + xPathQuery + "' from context node '"
365                                               + contextNode.getNodeName() + "': " + e.getMessage(), e );
366            }
367            return nl;
368        }
369    
370        /**
371         * @param contextNode
372         * @param xPathQuery
373         * @param nsContext
374         * @return the list of nodes as strings
375         * @throws XMLParsingException
376         */
377        public static String[] getNodesAsStrings( Node contextNode, String xPathQuery, NamespaceContext nsContext )
378                                throws XMLParsingException {
379            String[] values = null;
380            List<Node> nl = getNodes( contextNode, xPathQuery, nsContext );
381            if ( nl != null ) {
382                values = new String[nl.size()];
383                for ( int i = 0; i < nl.size(); i++ ) {
384                    values[i] = getStringValue( nl.get( i ) );
385                }
386            } else {
387                values = new String[0];
388            }
389            return values;
390        }
391    
392        /**
393         * @param contextNode
394         *            to get the strings from
395         * @param xPathQuery
396         *            finding the nodes
397         * @param nsContext
398         *            to find the namespaces from.
399         * @return the list of nodes as strings or an empty list, never <code>null</code>
400         * @throws XMLParsingException
401         */
402        public static List<String> getNodesAsStringList( Node contextNode, String xPathQuery, NamespaceContext nsContext )
403                                throws XMLParsingException {
404            List<String> result = new ArrayList<String>();
405            List<Node> nl = getNodes( contextNode, xPathQuery, nsContext );
406            if ( nl != null ) {
407                result = new ArrayList<String>( nl.size() );
408                for ( int i = 0; i < nl.size(); i++ ) {
409                    result.add( getStringValue( nl.get( i ) ) );
410                }
411            }
412            return result;
413        }
414    
415        /**
416         * @param contextNode
417         * @param xPathQuery
418         * @param nsContext
419         * @return the nodes as URIs
420         * @throws XMLParsingException
421         */
422        public static URI[] getNodesAsURIs( Node contextNode, String xPathQuery, NamespaceContext nsContext )
423                                throws XMLParsingException {
424            String[] values = getNodesAsStrings( contextNode, xPathQuery, nsContext );
425            URI[] uris = new URI[values.length];
426            for ( int i = 0; i < uris.length; i++ ) {
427                try {
428                    uris[i] = new URI( values[i] );
429                } catch ( URISyntaxException e ) {
430                    throw new XMLParsingException( "Result '" + values[i] + "' of XPath-expression '" + xPathQuery
431                                                   + "' from context node '" + contextNode.getNodeName()
432                                                   + "' does not denote a valid URI." );
433                }
434            }
435            return uris;
436        }
437    
438        /**
439         * @param contextNode
440         * @param xPathQuery
441         * @param nsContext
442         * @return the nodes as qualified names
443         * @throws XMLParsingException
444         */
445        public static QualifiedName[] getNodesAsQualifiedNames( Node contextNode, String xPathQuery,
446                                                                NamespaceContext nsContext )
447                                throws XMLParsingException {
448    
449            QualifiedName[] values = null;
450            List<Node> nl = getNodes( contextNode, xPathQuery, nsContext );
451            if ( nl != null ) {
452                values = new QualifiedName[nl.size()];
453                for ( int i = 0; i < nl.size(); i++ ) {
454                    values[i] = getQualifiedNameValue( nl.get( i ) );
455                }
456            } else {
457                values = new QualifiedName[0];
458            }
459            return values;
460    
461        }
462    
463        /**
464         * @param contextNode
465         * @param xPathQuery
466         * @param nsContext
467         * @return the node
468         * @throws XMLParsingException
469         */
470        public static Node getRequiredNode( Node contextNode, String xPathQuery, NamespaceContext nsContext )
471                                throws XMLParsingException {
472            Node node = getNode( contextNode, xPathQuery, nsContext );
473            if ( node == null ) {
474                throw new XMLParsingException( "XPath-expression '" + xPathQuery + "' from context node '"
475                                               + contextNode.getNodeName() + "' yields no result!" );
476            }
477            return node;
478        }
479    
480        /**
481         * @param contextNode
482         * @param xpath
483         * @param nsContext
484         * @return the element
485         * @throws XMLParsingException
486         * @throws ClassCastException
487         *             if the node was not an element
488         */
489        public static Element getRequiredElement( Node contextNode, String xpath, NamespaceContext nsContext )
490                                throws XMLParsingException {
491            Node node = getRequiredNode( contextNode, xpath, nsContext );
492            return (Element) node;
493        }
494    
495        /**
496         * @param contextNode
497         * @param xPathQuery
498         * @param nsContext
499         * @return the node as string
500         * @throws XMLParsingException
501         */
502        public static String getRequiredNodeAsString( Node contextNode, String xPathQuery, NamespaceContext nsContext )
503                                throws XMLParsingException {
504            Node node = getRequiredNode( contextNode, xPathQuery, nsContext );
505            return getStringValue( node );
506        }
507    
508        /**
509         * @param contextNode
510         *            the parent of the requested node
511         * @param xPathQuery
512         *            the node to get out of the dom
513         * @param nsContext
514         *            context of the node
515         * @param validValues
516         *            the values that are valid for the required node
517         * @return one of the String valid String values
518         * @throws XMLParsingException
519         *             if no Node was found or the text of the Node was not present in the given valid strings.
520         */
521        public static String getRequiredNodeAsString( Node contextNode, String xPathQuery, NamespaceContext nsContext,
522                                                      String[] validValues )
523                                throws XMLParsingException {
524            String value = getRequiredNodeAsString( contextNode, xPathQuery, nsContext );
525            boolean found = false;
526            for ( int i = 0; i < validValues.length; i++ ) {
527                if ( value.equals( validValues[i] ) ) {
528                    found = true;
529                    break;
530                }
531            }
532            if ( !found ) {
533                StringBuffer sb = new StringBuffer( "XPath-expression '" + xPathQuery + " ' from context node '"
534                                                    + contextNode.getNodeName()
535                                                    + "' has an invalid value. Valid values are: " );
536                for ( int i = 0; i < validValues.length; i++ ) {
537                    sb.append( "'" ).append( validValues[i] ).append( "'" );
538                    if ( i != validValues.length - 1 ) {
539                        sb.append( ", " );
540                    } else {
541                        sb.append( "." );
542                    }
543                }
544                throw new XMLParsingException( sb.toString() );
545            }
546            return value;
547        }
548    
549        /**
550         * Returns the parts of the targeted node value which are separated by the specified regex.
551         *
552         * @param contextNode
553         * @param xPathQuery
554         * @param nsContext
555         * @param regex
556         * @return the parts of the targeted node value which are separated by the specified regex.
557         * @throws XMLParsingException
558         */
559        public static String[] getRequiredNodeAsStrings( Node contextNode, String xPathQuery, NamespaceContext nsContext,
560                                                         String regex )
561                                throws XMLParsingException {
562            Node node = getRequiredNode( contextNode, xPathQuery, nsContext );
563            return StringTools.toArray( getStringValue( node ), regex, false );
564        }
565    
566        /**
567         * @param contextNode
568         * @param xPathQuery
569         * @param nsContext
570         * @return the node as boolean
571         * @throws XMLParsingException
572         */
573        public static boolean getRequiredNodeAsBoolean( Node contextNode, String xPathQuery, NamespaceContext nsContext )
574                                throws XMLParsingException {
575            boolean value = false;
576            Node node = getRequiredNode( contextNode, xPathQuery, nsContext );
577            String stringValue = getStringValue( node );
578            if ( "true".equals( stringValue ) || "yes".equals( stringValue ) ) {
579                value = true;
580            } else if ( "false".equals( stringValue ) || "no".equals( stringValue ) ) {
581                value = false;
582            } else {
583                throw new XMLParsingException( "XPath-expression '" + xPathQuery + " ' from context node '"
584                                               + contextNode.getNodeName() + "' has an invalid value ('" + stringValue
585                                               + "'). Valid values are: 'true', 'yes', 'false' and 'no'." );
586            }
587    
588            return value;
589        }
590    
591        /**
592         * @param contextNode
593         * @param xPathQuery
594         * @param nsContext
595         * @return the node as integer
596         * @throws XMLParsingException
597         */
598        public static int getRequiredNodeAsInt( Node contextNode, String xPathQuery, NamespaceContext nsContext )
599                                throws XMLParsingException {
600    
601            int value = 0;
602            String stringValue = getRequiredNodeAsString( contextNode, xPathQuery, nsContext );
603            try {
604                value = Integer.parseInt( stringValue );
605            } catch ( NumberFormatException e ) {
606                throw new XMLParsingException( "Result '" + stringValue + "' of XPath-expression '" + xPathQuery
607                                               + "' from context node '" + contextNode.getNodeName()
608                                               + "' does not denote a valid integer value." );
609            }
610            return value;
611        }
612    
613        /**
614         * @param contextNode
615         * @param xPathQuery
616         * @param nsContext
617         * @return the node as double
618         * @throws XMLParsingException
619         */
620        public static double getRequiredNodeAsDouble( Node contextNode, String xPathQuery, NamespaceContext nsContext )
621                                throws XMLParsingException {
622    
623            double value = 0;
624            String stringValue = getRequiredNodeAsString( contextNode, xPathQuery, nsContext );
625            try {
626                value = Double.parseDouble( stringValue );
627            } catch ( NumberFormatException e ) {
628                throw new XMLParsingException( "Result '" + stringValue + "' of XPath-expression '" + xPathQuery
629                                               + "' from context node '" + contextNode.getNodeName()
630                                               + "' does not denote a valid double value." );
631            }
632            return value;
633        }
634    
635        /**
636         * Returns the parts of the targeted node value which are separated by the specified regex. The string parts are
637         * converted to doubles.
638         *
639         * @param contextNode
640         * @param xPathQuery
641         * @param nsContext
642         * @param regex
643         * @return the parts of the targeted node value which are separated by the specified regex.
644         * @throws XMLParsingException
645         */
646        public static double[] getRequiredNodeAsDoubles( Node contextNode, String xPathQuery, NamespaceContext nsContext,
647                                                         String regex )
648                                throws XMLParsingException {
649            String[] parts = getRequiredNodeAsStrings( contextNode, xPathQuery, nsContext, regex );
650            double[] doubles = new double[parts.length];
651            for ( int i = 0; i < parts.length; i++ ) {
652                try {
653                    doubles[i] = Double.parseDouble( parts[i] );
654                } catch ( NumberFormatException e ) {
655                    throw new XMLParsingException( "Value '" + parts[i] + "' does not denote a valid double value." );
656                }
657            }
658            return doubles;
659        }
660    
661        /**
662         * @param contextNode
663         * @param xPathQuery
664         * @param nsContext
665         * @return the node as URI
666         * @throws XMLParsingException
667         */
668        public static URI getRequiredNodeAsURI( Node contextNode, String xPathQuery, NamespaceContext nsContext )
669                                throws XMLParsingException {
670    
671            URI uri = null;
672            String stringValue = getRequiredNodeAsString( contextNode, xPathQuery, nsContext );
673    
674            try {
675                uri = new URI( stringValue );
676            } catch ( URISyntaxException e ) {
677                throw new XMLParsingException( "Result '" + stringValue + "' of XPath-expression '" + xPathQuery
678                                               + "' from context node '" + contextNode.getNodeName()
679                                               + "' does not denote a valid URI." );
680            }
681            return uri;
682        }
683    
684        /**
685         * @param contextNode
686         * @param xPathQuery
687         * @param nsContext
688         * @return the node as qualified name
689         * @throws XMLParsingException
690         */
691        public static QualifiedName getRequiredNodeAsQualifiedName( Node contextNode, String xPathQuery,
692                                                                    NamespaceContext nsContext )
693                                throws XMLParsingException {
694            Node node = getRequiredNode( contextNode, xPathQuery, nsContext );
695            return getQualifiedNameValue( node );
696        }
697    
698        /**
699         * @param contextNode
700         * @param xPathQuery
701         * @param nsContext
702         * @return the nodes
703         * @throws XMLParsingException
704         */
705        public static List<Node> getRequiredNodes( Node contextNode, String xPathQuery, NamespaceContext nsContext )
706                                throws XMLParsingException {
707            List<Node> nl = getNodes( contextNode, xPathQuery, nsContext );
708            if ( nl.size() == 0 ) {
709                throw new XMLParsingException( "XPath-expression: '" + xPathQuery + "' from context node '"
710                                               + contextNode.getNodeName() + "' does not yield a result." );
711            }
712    
713            return nl;
714        }
715    
716        /**
717         * @param contextNode
718         * @param xpath
719         * @param nsContext
720         * @return a list of Elements
721         * @throws XMLParsingException
722         * @throws ClassCastException
723         *             if the resulting nodes of the xpath are not elements
724         */
725        public static List<Element> getRequiredElements( Node contextNode, String xpath, NamespaceContext nsContext )
726                                throws XMLParsingException {
727            List<Node> nodes = getRequiredNodes( contextNode, xpath, nsContext );
728    
729            ArrayList<Element> list = new ArrayList<Element>( nodes.size() );
730            for ( Node n : nodes ) {
731                list.add( (Element) n );
732            }
733    
734            return list;
735        }
736    
737        /**
738         * @param contextNode
739         * @param xpath
740         * @param nsContext
741         * @return a list of Elements
742         * @throws XMLParsingException
743         * @throws ClassCastException
744         *             if the resulting nodes of the xpath are not elements
745         */
746        public static List<Element> getElements( Node contextNode, String xpath, NamespaceContext nsContext )
747                                throws XMLParsingException {
748            List<Node> nodes = getNodes( contextNode, xpath, nsContext );
749    
750            ArrayList<Element> list = new ArrayList<Element>( nodes.size() );
751            for ( Node n : nodes ) {
752                list.add( (Element) n );
753            }
754    
755            return list;
756        }
757    
758        /**
759         * Returns the content of the nodes matching the XPathQuery as a String array. At least one node must match the
760         * query otherwise an exception will be thrown.
761         *
762         * @param contextNode
763         * @param xPathQuery
764         * @param nsContext
765         * @return the content of the nodes matching the XPathQuery as a String array.
766         * @throws XMLParsingException
767         */
768        public static String[] getRequiredNodesAsStrings( Node contextNode, String xPathQuery, NamespaceContext nsContext )
769                                throws XMLParsingException {
770    
771            List<Node> nl = getRequiredNodes( contextNode, xPathQuery, nsContext );
772    
773            String[] values = new String[nl.size()];
774            for ( int i = 0; i < nl.size(); i++ ) {
775                values[i] = getStringValue( nl.get( i ) );
776            }
777    
778            return values;
779        }
780    
781        /**
782         * @param contextNode
783         * @param xPathQuery
784         * @param nsContext
785         * @return the qualified names
786         * @throws XMLParsingException
787         */
788        public static QualifiedName[] getRequiredNodesAsQualifiedNames( Node contextNode, String xPathQuery,
789                                                                        NamespaceContext nsContext )
790                                throws XMLParsingException {
791    
792            List<Node> nl = getRequiredNodes( contextNode, xPathQuery, nsContext );
793    
794            QualifiedName[] values = new QualifiedName[nl.size()];
795            for ( int i = 0; i < nl.size(); i++ ) {
796                values[i] = getQualifiedNameValue( nl.get( i ) );
797            }
798    
799            return values;
800        }
801    
802        /**
803         * @param value
804         * @param validValues
805         * @throws XMLParsingException
806         */
807        public static void checkValue( String value, String[] validValues )
808                                throws XMLParsingException {
809            for ( int i = 0; i < validValues.length; i++ ) {
810                if ( validValues[i].equals( value ) ) {
811                    return;
812                }
813            }
814            StringBuffer sb = new StringBuffer( "Value '" ).append( value ).append( "' is invalid. Valid values are: " );
815            for ( int i = 0; i < validValues.length; i++ ) {
816                sb.append( "'" ).append( validValues[i] ).append( "'" );
817                if ( i != validValues.length - 1 ) {
818                    sb.append( ", " );
819                } else {
820                    sb.append( "." );
821                }
822            }
823            throw new XMLParsingException( sb.toString() );
824        }
825    
826        // ------------------------------------------------------------------------
827        // Node creation methods
828        // ------------------------------------------------------------------------
829    
830        /**
831         * Creates a new <code>Element</code> node from the given parameters and appends it to the also specified
832         * <code>Element</code>.
833         *
834         * @param element
835         *            <code>Element</code> that the new <code>Element</code> is appended to
836         * @param namespaceURI
837         *            use null for default namespace
838         * @param name
839         *            qualified name
840         * @return the appended <code>Element</code> node
841         */
842        public static Element appendElement( Element element, URI namespaceURI, String name ) {
843            return appendElement( element, namespaceURI, name, null );
844        }
845    
846        /**
847         * Appends a namespace binding for the specified element that binds the given prefix to the given namespace using a
848         * special attribute: xmlns:prefix=namespace
849         *
850         * @param element
851         * @param prefix
852         * @param namespace
853         */
854        public static void appendNSBinding( Element element, String prefix, URI namespace ) {
855            Attr attribute = element.getOwnerDocument().createAttributeNS( CommonNamespaces.XMLNS.toASCIIString(),
856                                                                           CommonNamespaces.XMLNS_PREFIX + ":" + prefix );
857            attribute.setNodeValue( namespace.toASCIIString() );
858            element.getAttributes().setNamedItemNS( attribute );
859        }
860    
861        /**
862         * Appends the default namespace binding for the specified element.
863         *
864         * @param element
865         * @param namespace
866         */
867        public static void appendNSDefaultBinding( Element element, URI namespace ) {
868            Attr attribute = element.getOwnerDocument().createAttributeNS( CommonNamespaces.XMLNS.toASCIIString(),
869                                                                           CommonNamespaces.XMLNS_PREFIX );
870            attribute.setNodeValue( namespace.toASCIIString() );
871            element.getAttributes().setNamedItemNS( attribute );
872        }
873    
874        /**
875         * Appends the given namespace bindings to the specified element.
876         * <p>
877         * NOTE: The prebound prefix "xml" is skipped.
878         *
879         * @param element
880         * @param nsContext
881         */
882        public static void appendNSBindings( Element element, NamespaceContext nsContext ) {
883            Map<String, URI> namespaceMap = nsContext.getNamespaceMap();
884            Iterator<String> prefixIter = namespaceMap.keySet().iterator();
885            while ( prefixIter.hasNext() ) {
886                String prefix = prefixIter.next();
887                if ( !CommonNamespaces.XMLNS_PREFIX.equals( prefix ) ) {
888                    URI namespace = namespaceMap.get( prefix );
889                    appendNSBinding( element, prefix, namespace );
890                }
891            }
892        }
893    
894        // ------------------------------------------------------------------------
895        // String value methods
896        // ------------------------------------------------------------------------
897    
898        /**
899         * Returns the text contained in the specified element.
900         *
901         * @param node
902         *            current element
903         * @return the textual contents of the element
904         */
905        public static String getStringValue( Node node ) {
906            NodeList children = node.getChildNodes();
907            StringBuffer sb = new StringBuffer( children.getLength() * 500 );
908            if ( node.getNodeValue() != null ) {
909                sb.append( node.getNodeValue().trim() );
910            }
911            if ( node.getNodeType() != Node.ATTRIBUTE_NODE ) {
912                for ( int i = 0; i < children.getLength(); i++ ) {
913                    if ( children.item( i ).getNodeType() == Node.TEXT_NODE
914                         || children.item( i ).getNodeType() == Node.CDATA_SECTION_NODE ) {
915                        sb.append( children.item( i ).getNodeValue() );
916                    }
917                }
918            }
919            return sb.toString();
920        }
921    
922        /**
923         * Returns the text contained in the specified child element of the given element.
924         *
925         * @param name
926         *            name of the child element
927         * @param namespace
928         *            namespace of the child element
929         * @param node
930         *            current element
931         * @param defaultValue
932         *            default value if element is missing
933         * @return the textual contents of the element or the given default value, if missing
934         */
935        public static String getStringValue( String name, URI namespace, Node node, String defaultValue ) {
936    
937            String value = defaultValue;
938            Element element = getChildElement( name, namespace, node );
939    
940            if ( element != null ) {
941                value = getStringValue( element );
942            }
943            if ( value == null || value.equals( "" ) ) {
944                value = defaultValue;
945            }
946    
947            return value;
948        }
949    
950        /**
951         * Returns the text contained in the specified child element of the given element.
952         *
953         * @param name
954         *            name of the child element
955         * @param namespace
956         *            namespace of the child element
957         * @param node
958         *            current element
959         * @return the textual contents of the element or null, if it is missing
960         * @throws XMLParsingException
961         *             if the specified child element is missing
962         */
963        public static String getRequiredStringValue( String name, URI namespace, Node node )
964                                throws XMLParsingException {
965            Element element = getRequiredChildElement( name, namespace, node );
966            return getStringValue( element );
967        }
968    
969        /**
970         * Returns the value of the specified node attribute.
971         *
972         * @param name
973         *            name of attribute
974         * @param namespaceURI
975         *            namespace of attribute
976         * @param node
977         *            current element
978         * @return the textual contents of the attribute
979         * @throws XMLParsingException
980         *             if specified attribute is missing
981         */
982        public static String getRequiredAttrValue( String name, URI namespaceURI, Node node )
983                                throws XMLParsingException {
984    
985            String namespace = namespaceURI == null ? null : namespaceURI.toString();
986    
987            String value = null;
988            NamedNodeMap atts = node.getAttributes();
989            if ( atts != null ) {
990                Attr attribute = null;
991                if ( namespace == null ) {
992                    attribute = (Attr) atts.getNamedItem( name );
993                } else {
994                    attribute = (Attr) atts.getNamedItemNS( namespace, name );
995                }
996    
997                if ( attribute != null ) {
998                    value = attribute.getValue();
999                }
1000            }
1001            if ( value == null ) {
1002                throw new XMLParsingException( "Required attribute " + name + '(' + namespaceURI + ") of element "
1003                                               + node.getNodeName() + " is missing." );
1004            }
1005            return value;
1006        }
1007    
1008        /**
1009         * Parses the value of the submitted <code>Node</code> as a <code>QualifiedName</code>.
1010         * <p>
1011         * To parse the text contents of an <code>Element</code> node, the actual text node must be given, not the
1012         * <code>Element</code> node itself.
1013         * </p>
1014         *
1015         * @param node
1016         * @return object representation of the element
1017         * @throws XMLParsingException
1018         */
1019        public static QualifiedName getQualifiedNameValue( Node node )
1020                                throws XMLParsingException {
1021    
1022            String name = node.getTextContent().trim();
1023            QualifiedName qName = null;
1024            if ( name.indexOf( ':' ) > -1 ) {
1025                String[] tmp = StringTools.toArray( name, ":", false );
1026                try {
1027                    qName = new QualifiedName( tmp[0], tmp[1], XMLTools.getNamespaceForPrefix( tmp[0], node ) );
1028                } catch ( URISyntaxException e ) {
1029                    throw new XMLParsingException( e.getMessage(), e );
1030                }
1031            } else {
1032                qName = new QualifiedName( name );
1033            }
1034            return qName;
1035        }
1036    
1037        /**
1038         * Returns the namespace URI that is bound to a given prefix at a certain node in the DOM tree.
1039         *
1040         * @param prefix
1041         * @param node
1042         * @return namespace URI that is bound to the given prefix, null otherwise
1043         * @throws URISyntaxException
1044         */
1045        public static URI getNamespaceForPrefix( String prefix, Node node )
1046                                throws URISyntaxException {
1047            if ( node == null ) {
1048                return null;
1049            }
1050            if ( node.getNodeType() == Node.ELEMENT_NODE ) {
1051                NamedNodeMap nnm = node.getAttributes();
1052                if ( nnm != null ) {
1053                    // LOG.logDebug( "(searching namespace for prefix (" + prefix
1054                    // + "), resulted in a namedNodeMap for the currentNode: " + node.getNodeName() );
1055                    for ( int i = 0; i < nnm.getLength(); i++ ) {
1056                        Attr a = (Attr) nnm.item( i );
1057                        // LOG.logDebug( "\t(searching namespace for prefix (" + prefix + "), resulted
1058                        // in an attribute: "
1059                        // + a.getName() );
1060    
1061                        if ( a.getName().startsWith( "xmlns:" ) && a.getName().endsWith( ':' + prefix ) ) {
1062                            return new URI( a.getValue() );
1063                        } else if ( prefix == null && a.getName().equals( "xmlns" ) ) {
1064                            return new URI( a.getValue() );
1065                        }
1066                    }
1067                }
1068            } else if ( node.getNodeType() == Node.ATTRIBUTE_NODE ) {
1069                return getNamespaceForPrefix( prefix, ( (Attr) node ).getOwnerElement() );
1070            }
1071            return getNamespaceForPrefix( prefix, node.getParentNode() );
1072        }
1073    
1074        // ------------------------------------------------------------------------
1075        // Old code - deprecated
1076        // ------------------------------------------------------------------------
1077    
1078        /**
1079         * Returns the specified child element of the given element. If there is more than one element with that name, the
1080         * first one is returned.
1081         *
1082         * @deprecated
1083         * @param name
1084         *            name of the child element
1085         * @param namespaceURI
1086         *            namespaceURI of the child element
1087         * @param node
1088         *            current element
1089         * @return the element or null, if it is missing
1090         * @throws XMLParsingException
1091         *             if the specified child element is missing
1092         * @throws XMLParsingException
1093         * @todo refactoring required
1094         */
1095        @Deprecated
1096        public static Element getRequiredChildElement( String name, URI namespaceURI, Node node )
1097                                throws XMLParsingException {
1098    
1099            String namespace = namespaceURI == null ? null : namespaceURI.toString();
1100    
1101            NodeList nl = node.getChildNodes();
1102            Element element = null;
1103            Element childElement = null;
1104    
1105            if ( ( nl != null ) && ( nl.getLength() > 0 ) ) {
1106                for ( int i = 0; i < nl.getLength(); i++ ) {
1107                    if ( nl.item( i ) instanceof Element ) {
1108                        element = (Element) nl.item( i );
1109                        String s = element.getNamespaceURI();
1110                        if ( ( s == null && namespace == null ) || ( namespace != null && namespace.equals( s ) ) ) {
1111                            if ( element.getLocalName().equals( name ) ) {
1112                                childElement = element;
1113                                break;
1114                            }
1115                        }
1116                    }
1117                }
1118            }
1119    
1120            if ( childElement == null ) {
1121                throw new XMLParsingException( "Required child-element " + name + '(' + namespaceURI + ") of element "
1122                                               + node.getNodeName() + " is missing." );
1123            }
1124    
1125            return childElement;
1126        }
1127    
1128        /**
1129         * Returns the specified child element of the given element. If there is more than one with that name, the first one
1130         * is returned.
1131         *
1132         * @deprecated
1133         * @param name
1134         *            name of the child element
1135         * @param namespaceURI
1136         *            namespace of the child element
1137         * @param node
1138         *            current element
1139         * @return the element or null, if it is missing
1140         * @TODO refactoring required
1141         */
1142        @Deprecated
1143        public static Element getChildElement( String name, URI namespaceURI, Node node ) {
1144    
1145            String namespace = namespaceURI == null ? null : namespaceURI.toString();
1146    
1147            NodeList nl = node.getChildNodes();
1148            Element element = null;
1149            Element childElement = null;
1150    
1151            if ( ( nl != null ) && ( nl.getLength() > 0 ) ) {
1152                for ( int i = 0; i < nl.getLength(); i++ ) {
1153                    if ( nl.item( i ) instanceof Element ) {
1154                        element = (Element) nl.item( i );
1155                        String s = element.getNamespaceURI();
1156                        if ( ( s == null && namespace == null ) || ( namespace != null && namespace.equals( s ) ) ) {
1157                            if ( element.getLocalName().equals( name ) ) {
1158                                childElement = element;
1159                                break;
1160                            }
1161                        }
1162                    }
1163                }
1164            }
1165            return childElement;
1166        }
1167    
1168        /**
1169         * Returns the specified child elements of the given element.
1170         *
1171         * @deprecated
1172         * @param name
1173         *            name of the child elements
1174         * @param namespaceURI
1175         *            namespaceURI of the child elements
1176         * @param node
1177         *            current element
1178         * @return list of matching child elements
1179         */
1180        @Deprecated
1181        public static ElementList getChildElements( String name, URI namespaceURI, Node node ) {
1182    
1183            String namespace = namespaceURI == null ? null : namespaceURI.toString();
1184    
1185            NodeList nl = node.getChildNodes();
1186            Element element = null;
1187            ElementList elementList = new ElementList();
1188    
1189            if ( ( nl != null ) && ( nl.getLength() > 0 ) ) {
1190                for ( int i = 0; i < nl.getLength(); i++ ) {
1191                    if ( nl.item( i ) instanceof Element ) {
1192                        element = (Element) nl.item( i );
1193    
1194                        String s = element.getNamespaceURI();
1195    
1196                        if ( ( s == null && namespace == null ) || ( namespace != null && namespace.equals( s ) ) ) {
1197                            if ( element.getLocalName().equals( name ) ) {
1198                                elementList.addElement( element );
1199                            }
1200                        }
1201                    }
1202                }
1203            }
1204            return elementList;
1205        }
1206    
1207        /**
1208         *
1209         * Create a new and empty DOM document.
1210         *
1211         * @return a new and empty DOM document.
1212         */
1213        public static Document create() {
1214            return getDocumentBuilder().newDocument();
1215        }
1216    
1217        /**
1218         * Create a new document builder with:
1219         * <UL>
1220         * <li>namespace awareness = true
1221         * <li>whitespace ignoring = false
1222         * <li>validating = false
1223         * <li>expandind entity references = false
1224         * </UL>
1225         *
1226         * @return new document builder
1227         */
1228        public static synchronized DocumentBuilder getDocumentBuilder() {
1229            DocumentBuilder builder = null;
1230            try {
1231                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1232                factory.setNamespaceAware( true );
1233                factory.setExpandEntityReferences( false );
1234                factory.setIgnoringElementContentWhitespace( false );
1235                factory.setValidating( false );
1236                try {
1237                    factory.setAttribute( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
1238                } catch ( IllegalArgumentException _ ) {
1239                    // ignore it, we just cannot set the feature
1240                }
1241                builder = factory.newDocumentBuilder();
1242            } catch ( Exception ex ) {
1243                LOG.logError( ex.getMessage(), ex );
1244            }
1245            return builder;
1246        }
1247    
1248        /**
1249         * Returns the specified attribute value of the given node.
1250         *
1251         * @param node
1252         *            current element
1253         * @param attrName
1254         *            local name of the attribute
1255         *
1256         * @return the value of the attribute or null, if it is missing
1257         * @deprecated use
1258         * @see #getAttrValue(Node, URI, String, String) instead
1259         */
1260        @Deprecated
1261        public static String getAttrValue( Node node, String attrName ) {
1262            NamedNodeMap atts = node.getAttributes();
1263            if ( atts == null ) {
1264                return null;
1265            }
1266            Attr a = (Attr) atts.getNamedItem( attrName );
1267            if ( a != null ) {
1268                return a.getValue();
1269            }
1270            return null;
1271        }
1272    
1273        /**
1274         * Returns the specified attribute value of the given node.
1275         *
1276         * @param node
1277         *            current element
1278         * @param namespaceURI
1279         *            namespace of the attribute
1280         * @param attrName
1281         *            local name of the attribute
1282         * @param defaultVal
1283         *            default value to be returned if attribute is nat available
1284         *
1285         * @return the value of the attribute or null, if it is missing
1286         */
1287        public static String getAttrValue( Node node, URI namespaceURI, String attrName, String defaultVal ) {
1288            if ( node == null ) {
1289                return null;
1290            }
1291            String namespace = namespaceURI == null ? null : namespaceURI.toString();
1292            NamedNodeMap atts = node.getAttributes();
1293            if ( atts == null ) {
1294                return defaultVal;
1295            }
1296            Attr a = null;
1297            if ( namespace == null ) {
1298                a = (Attr) atts.getNamedItem( attrName );
1299            } else {
1300                a = (Attr) atts.getNamedItemNS( namespace, attrName );
1301            }
1302            if ( a != null ) {
1303                return a.getValue();
1304            }
1305            return defaultVal;
1306        }
1307    
1308        /**
1309         * Parses an XML document and returns a DOM object. The underlying input stream is closed at the end.
1310         *
1311         * @param reader
1312         *            accessing the resource to parse
1313         * @return a DOM object, if en error occurs the response is <code>null</code>
1314         *
1315         * @throws IOException
1316         * @throws SAXException
1317         */
1318        public static Document parse( Reader reader )
1319                                throws IOException, SAXException {
1320            javax.xml.parsers.DocumentBuilder parser = null;
1321            Document doc = null;
1322            try {
1323                DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
1324                fac.setNamespaceAware( true );
1325                fac.setValidating( false );
1326                fac.setIgnoringElementContentWhitespace( false );
1327                fac.setValidating( false );
1328                try {
1329                    fac.setAttribute( "http://apache.org/xml/features/nonvalidating/load-external-dtd", false );
1330                } catch ( IllegalArgumentException _ ) {
1331                    // ignore, we just can't set the feature
1332                }
1333                parser = fac.newDocumentBuilder();
1334                doc = parser.parse( new InputSource( reader ) );
1335            } catch ( ParserConfigurationException ex ) {
1336                throw new IOException( "Unable to initialize DocumentBuilder: " + ex.getMessage() );
1337            } catch ( Exception e ) {
1338                throw new SAXException( e.getMessage() );
1339            } finally {
1340                reader.close();
1341            }
1342            return doc;
1343        }
1344    
1345        /**
1346         * Parses an XML document and returns a DOM object.
1347         *
1348         * @deprecated
1349         * @param is
1350         *            accessing the resource to parse
1351         * @return a DOM object
1352         * @throws IOException
1353         * @throws SAXException
1354         */
1355        @Deprecated
1356        public static Document parse( InputStream is )
1357                                throws IOException, SAXException {
1358            return parse( new InputStreamReader( is ) );
1359    
1360        }
1361    
1362        /**
1363         * Copies one node to another node.
1364         *
1365         * @param source
1366         * @param dest
1367         * @return the copied node
1368         */
1369        public static Node copyNode( Node source, Node dest ) {
1370            if ( source.getNodeType() == Node.TEXT_NODE ) {
1371                Text tn = dest.getOwnerDocument().createTextNode( getStringValue( source ) );
1372                return tn;
1373            }
1374            NamedNodeMap attr = source.getAttributes();
1375            if ( attr != null ) {
1376                for ( int i = 0; i < attr.getLength(); i++ ) {
1377                    ( (Element) dest ).setAttribute( attr.item( i ).getNodeName(), attr.item( i ).getNodeValue() );
1378                }
1379            }
1380            NodeList list = source.getChildNodes();
1381            for ( int i = 0; i < list.getLength(); i++ ) {
1382                if ( !( list.item( i ) instanceof Text ) ) {
1383                    if ( !( list.item( i ) instanceof Comment ) ) {
1384                        Element en = dest.getOwnerDocument().createElementNS( list.item( i ).getNamespaceURI(),
1385                                                                              list.item( i ).getNodeName() );
1386                        if ( list.item( i ).getNodeValue() != null ) {
1387                            en.setNodeValue( list.item( i ).getNodeValue() );
1388                        }
1389                        Node n = copyNode( list.item( i ), en );
1390                        dest.appendChild( n );
1391                    }
1392                } else if ( ( list.item( i ) instanceof CDATASection ) ) {
1393                    CDATASection cd = dest.getOwnerDocument().createCDATASection( list.item( i ).getNodeValue() );
1394                    dest.appendChild( cd );
1395                } else {
1396                    Text tn = dest.getOwnerDocument().createTextNode( list.item( i ).getNodeValue() );
1397                    dest.appendChild( tn );
1398                }
1399            }
1400            return dest;
1401        }
1402    
1403        /**
1404         * Appends a node to an element.
1405         * <p>
1406         * The node can be from the same document or a different one (it is automatically imported, if necessary).
1407         *
1408         * @param source
1409         * @param dest
1410         * @return the element that is appended to
1411         */
1412        public static Node insertNodeInto( Node source, Node dest ) {
1413            Node n = dest.getOwnerDocument().importNode( source, true );
1414            dest.appendChild( n );
1415            return dest;
1416        }
1417    
1418        /**
1419         * Returns the first child element of the submitted node.
1420         *
1421         * @param node
1422         * @return the first child element of the submitted node.
1423         */
1424        public static Element getFirstChildElement( Node node ) {
1425            NodeList nl = node.getChildNodes();
1426            Element element = null;
1427            if ( ( nl != null ) && ( nl.getLength() > 0 ) ) {
1428                for ( int i = 0; i < nl.getLength(); i++ ) {
1429                    if ( nl.item( i ) instanceof Element ) {
1430                        element = (Element) nl.item( i );
1431                        break;
1432                    }
1433                }
1434            }
1435            return element;
1436        }
1437    
1438        /**
1439         * @deprecated Returns the first child element of the submitted node that matches the given local name.
1440         *
1441         * @param node
1442         * @param name
1443         * @return the child element
1444         */
1445        @Deprecated
1446        public static Element getChildElement( Node node, String name ) {
1447            NodeList nl = node.getChildNodes();
1448            Element element = null;
1449            Element childElement = null;
1450            if ( ( nl != null ) && ( nl.getLength() > 0 ) ) {
1451                for ( int i = 0; i < nl.getLength(); i++ ) {
1452                    if ( nl.item( i ) instanceof Element ) {
1453                        element = (Element) nl.item( i );
1454    
1455                        if ( element.getNodeName().equals( name ) ) {
1456                            childElement = element;
1457    
1458                            break;
1459                        }
1460                    }
1461                }
1462            }
1463            return childElement;
1464        }
1465    
1466        /**
1467         * Returns all child elements of the given node.
1468         *
1469         * @param node
1470         * @return all child elements of the given node.
1471         */
1472        public static ElementList getChildElements( Node node ) {
1473            NodeList children = node.getChildNodes();
1474            ElementList list = new ElementList();
1475            for ( int i = 0; i < children.getLength(); i++ ) {
1476                if ( children.item( i ).getNodeType() == Node.ELEMENT_NODE ) {
1477                    list.elements.add( (Element) children.item( i ) );
1478                }
1479            }
1480            return list;
1481        }
1482    
1483        /**
1484         * sets the value of an existing node
1485         *
1486         * @param target
1487         * @param nodeValue
1488         */
1489        public static void setNodeValue( Element target, String nodeValue ) {
1490            NodeList nl = target.getChildNodes();
1491            for ( int i = 0; i < nl.getLength(); i++ ) {
1492                target.removeChild( nl.item( i ) );
1493            }
1494            Text text = target.getOwnerDocument().createTextNode( nodeValue );
1495            target.appendChild( text );
1496        }
1497    
1498        /**
1499         * Creates a new <code>Element</code> node from the given parameters and appends it to the also specified
1500         * <code>Element</code>. Adds a text node to the newly generated <code>Element</code> as well.
1501         *
1502         * @param element
1503         *            <code>Element</code> that the new <code>Element</code> is appended to
1504         * @param namespaceURI
1505         *            use null for default namespace
1506         * @param name
1507         *            qualified name
1508         * @param nodeValue
1509         *            value for a text node that is appended to the generated element
1510         * @return the appended <code>Element</code> node
1511         */
1512        public static Element appendElement( Element element, URI namespaceURI, String name, String nodeValue ) {
1513            String namespace = namespaceURI == null ? null : namespaceURI.toString();
1514            Element newElement = element.getOwnerDocument().createElementNS( namespace, name );
1515            if ( nodeValue != null && !nodeValue.equals( "" ) )
1516                newElement.appendChild( element.getOwnerDocument().createTextNode( nodeValue ) );
1517            element.appendChild( newElement );
1518            return newElement;
1519        }
1520    
1521        /**
1522         * @param xml
1523         * @return the formatted string, or the original XML string if something went wrong
1524         */
1525        public static String getAsPrettyString( String xml ) {
1526            try {
1527                Element e = getStringFragmentAsElement( xml );
1528                return new XMLFragment( e ).getAsPrettyString();
1529            } catch ( SAXException e ) {
1530                LOG.logError( "Unknown error", e );
1531            } catch ( IOException e ) {
1532                LOG.logError( "Unknown error", e );
1533            }
1534            return xml;
1535        }
1536    
1537        /**
1538         * Converts an XML fragment string into a DOM node. The fragment should NOT contain the XML prolog. Appends ALL
1539         * default namespace bindings for ease of use with broken fragments.
1540         *
1541         * @param fragment
1542         * @return a DOM element
1543         * @throws IOException
1544         * @throws SAXException
1545         */
1546        public static Element getStringFragmentAsElement( String fragment )
1547                                throws SAXException, IOException {
1548            StringBuffer xml = new StringBuffer( "<?xml version=\"1.0\"?>" );
1549    
1550            xml.append( "<bogus " );
1551            // append ALL namespaces
1552            Map<String, URI> map = CommonNamespaces.getNamespaceContext().getNamespaceMap();
1553            for ( String pre : map.keySet() ) {
1554                if ( pre.equals( "xmlns" ) ) {
1555                    continue;
1556                }
1557                xml.append( "xmlns:" ).append( pre ).append( "='" );
1558                xml.append( map.get( pre ).toString() ).append( "' " );
1559            }
1560            xml.append( ">" );
1561            xml.append( fragment );
1562            xml.append( "</bogus>" );
1563    
1564            StringReader in = new StringReader( xml.toString() );
1565    
1566            XMLFragment doc = new XMLFragment( in, "http://www.systemid.org" );
1567            return (Element) doc.getRootElement().getFirstChild();
1568        }
1569    
1570        /**
1571         * Works like {@link #getStringFragmentAsElement}, but does not throw exceptions and immediately imports the node
1572         * into another document.
1573         *
1574         * @param fragment
1575         * @param doc
1576         * @return an element in the new document
1577         */
1578        public static Element importStringFragment( String fragment, Document doc ) {
1579            try {
1580                Element e = getStringFragmentAsElement( fragment );
1581                return (Element) doc.importNode( e, true );
1582            } catch ( SAXException e ) {
1583                LOG.logError( "Could not convert String to XML.", e );
1584            } catch ( IOException e ) {
1585                LOG.logError( "Could not convert String to XML.", e );
1586            }
1587    
1588            return null;
1589        }
1590    
1591        /**
1592         * This method escapes Strings for XML by creating a DOM document, setting the text in an attribute, exporting it to
1593         * text and extracting the escaped string. That means that it's slow, and the method of property using DOM to create
1594         * the XML is a million times better. But if you're forced to use StringBuffers, here you have a little helper.
1595         *
1596         * @param str
1597         * @return the escaped string
1598         */
1599        public static String escape( String str ) {
1600            XMLFragment doc = new XMLFragment( new QualifiedName( "dummy" ) );
1601            doc.getRootElement().setAttribute( "dummy", str );
1602            String s = doc.getAsString().substring( 52 );
1603            return s.substring( 0, s.length() - 3 );
1604        }
1605    
1606    }