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