1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.support.xml;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStreamWriter;
18 import java.io.StringReader;
19 import java.io.StringWriter;
20 import java.io.Writer;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.StringTokenizer;
30
31 import javax.xml.namespace.QName;
32 import javax.xml.parsers.DocumentBuilder;
33 import javax.xml.parsers.DocumentBuilderFactory;
34 import javax.xml.parsers.ParserConfigurationException;
35 import javax.xml.transform.TransformerException;
36
37 import org.apache.log4j.Logger;
38 import org.apache.xmlbeans.XmlCursor;
39 import org.apache.xmlbeans.XmlException;
40 import org.apache.xmlbeans.XmlObject;
41 import org.apache.xmlbeans.XmlOptions;
42 import org.w3c.dom.Attr;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.DocumentFragment;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NamedNodeMap;
47 import org.w3c.dom.Node;
48 import org.w3c.dom.NodeList;
49 import org.w3c.dom.Text;
50 import org.w3c.dom.traversal.DocumentTraversal;
51 import org.w3c.dom.traversal.NodeFilter;
52 import org.w3c.dom.traversal.TreeWalker;
53 import org.xml.sax.InputSource;
54 import org.xml.sax.SAXException;
55
56 import com.eviware.soapui.impl.wsdl.WsdlInterface;
57 import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
58 import com.eviware.soapui.support.types.StringToStringMap;
59 import com.sun.org.apache.xpath.internal.XPathAPI;
60
61 /***
62 * General XML-related utilities
63 */
64
65 public final class XmlUtils
66 {
67 private static DocumentBuilder documentBuilder;
68 private final static Logger log = Logger.getLogger( XmlUtils.class );
69
70 static public Document parse( InputStream in )
71 {
72 try
73 {
74 return ensureDocumentBuilder().parse( in );
75 }
76 catch( Exception e )
77 {
78 log.error( "Error parsing InputStream; " + e.getMessage(), e );
79 }
80
81 return null;
82 }
83
84 static public Document parse( String fileName ) throws IOException
85 {
86 try
87 {
88 return ensureDocumentBuilder().parse( fileName );
89 }
90 catch( SAXException e )
91 {
92 log.error( "Error parsing fileName [" + fileName + "]; " + e.getMessage(), e );
93 }
94
95 return null;
96 }
97
98 public static String entitize( String xml )
99 {
100 return xml.replaceAll( "&", "&" ).replaceAll( "<", "<" ).replaceAll( ">", ">" ).
101 replaceAll( "\"", """ ).replaceAll( "'", "'" );
102 }
103
104 static public Document parse( InputSource inputSource ) throws IOException
105 {
106 try
107 {
108 return ensureDocumentBuilder().parse( inputSource );
109 }
110 catch( SAXException e )
111 {
112 throw new IOException( e.toString() );
113 }
114 }
115
116 private static DocumentBuilder ensureDocumentBuilder()
117 {
118 if( documentBuilder == null )
119 {
120 try
121 {
122 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
123 dbf.setNamespaceAware( true );
124 documentBuilder = dbf.newDocumentBuilder();
125 }
126 catch( ParserConfigurationException e )
127 {
128 log.error( "Error creating DocumentBuilder; " + e.getMessage() );
129 }
130 }
131
132 return documentBuilder;
133 }
134
135 public static void serializePretty( Document document )
136 {
137 try
138 {
139 serializePretty( document, new OutputStreamWriter( System.out ) );
140 }
141 catch( IOException e )
142 {
143 log.error( "Failed to seraialize: " + e );
144 }
145 }
146
147 public static void serializePretty( Document dom, Writer writer )
148 throws IOException
149 {
150 try
151 {
152 XmlObject xmlObject = XmlObject.Factory.parse(dom.getDocumentElement());
153 serializePretty(xmlObject, writer);
154 }
155 catch (XmlException e)
156 {
157 throw new IOException( e.toString() );
158 }
159 }
160
161 public static void serializePretty( XmlObject xmlObject, Writer writer ) throws IOException
162 {
163 XmlOptions options = new XmlOptions();
164 options.setSavePrettyPrint();
165 options.setSavePrettyPrintIndent( 3 );
166 options.setSaveNoXmlDecl();
167 options.setSaveAggressiveNamespaces();
168 xmlObject.save(writer, options);
169 }
170
171 public static void serialize( Document dom, Writer writer )
172 throws IOException
173 {
174 serialize( dom.getDocumentElement(), writer );
175 }
176
177 public static void serialize( Element elm, Writer writer )
178 throws IOException
179 {
180 try
181 {
182 XmlObject xmlObject = XmlObject.Factory.parse(elm);
183 xmlObject.save( writer );
184 }
185 catch (XmlException e)
186 {
187 throw new IOException( e.toString() );
188 }
189 }
190
191 static public void setElementText( Element elm, String text )
192 {
193 Node node = elm.getFirstChild();
194 if( node == null )
195 {
196 if( text != null)
197 elm.appendChild( elm.getOwnerDocument().createTextNode( text ) );
198 }
199 else if( node.getNodeType() == Node.TEXT_NODE )
200 {
201 if( text == null )
202 node.getParentNode().removeChild( node );
203 else
204 node.setNodeValue( text );
205 }
206 else if( text != null )
207 {
208 Text textNode = node.getOwnerDocument().createTextNode( text );
209 elm.insertBefore( textNode, elm.getFirstChild() );
210 }
211 }
212
213 public static String getChildElementText( Element elm, String name )
214 {
215 Element child = getFirstChildElement( elm, name );
216 return child == null ? null : getElementText( child );
217 }
218
219 public static Element getFirstChildElement( Element elm )
220 {
221 return getFirstChildElement( elm, null );
222 }
223
224 public static Element getFirstChildElement( Element elm, String name )
225 {
226 NodeList nl = elm.getChildNodes();
227 for( int c = 0; c < nl.getLength(); c++ )
228 {
229 Node node = nl.item( c );
230 if( node.getNodeType() == Node.ELEMENT_NODE && (name == null || node.getNodeName().equals( name )) )
231 return (Element) node;
232 }
233
234 return null;
235 }
236
237 public static Element getFirstChildElementNS( Element elm, String tns, String localName )
238 {
239 if( tns == null && localName == null )
240 return getFirstChildElement( elm );
241
242 if( tns == null )
243 return getFirstChildElement( elm, localName );
244
245 NodeList nl = elm.getChildNodes();
246 for( int c = 0; c < nl.getLength(); c++ )
247 {
248 Node node = nl.item( c );
249 if( node.getNodeType() != Node.ELEMENT_NODE ) continue;
250
251 if( localName == null && tns.equals( node.getNamespaceURI() ))
252 return ( Element ) node;
253
254 if( localName != null && tns.equals( node.getNamespaceURI() ) && localName.equals( node.getLocalName() ))
255 return ( Element ) node;
256 }
257
258 return null;
259 }
260
261 static public String getElementText( Element elm )
262 {
263 Node node = elm.getFirstChild();
264 if( node != null && node.getNodeType() == Node.TEXT_NODE )
265 return node.getNodeValue();
266
267 return null;
268 }
269
270 static public String getFragmentText( DocumentFragment elm )
271 {
272 Node node = elm.getFirstChild();
273 if( node != null && node.getNodeType() == Node.TEXT_NODE )
274 return node.getNodeValue();
275
276 return null;
277 }
278
279 public static String getChildElementText( Element elm, String name, String defaultValue )
280 {
281 String result = getChildElementText( elm, name );
282 return result == null ? defaultValue : result;
283 }
284
285 static public String getNodeValue( Node node )
286 {
287 if( node.getNodeType() == Node.ELEMENT_NODE )
288 return getElementText( (Element) node );
289 else if( node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE )
290 return getFragmentText( (DocumentFragment) node );
291 else
292 return node.getNodeValue();
293 }
294
295 public static Node createNodeFromPath( Element modelElement, String path )
296 {
297 Document document = modelElement.getOwnerDocument();
298 StringTokenizer st = new StringTokenizer( path, "/" );
299 while( st.hasMoreTokens() )
300 {
301 String t = st.nextToken();
302
303 if( st.hasMoreTokens() )
304 {
305 if( t.equals( ".." ) )
306 {
307 modelElement = (Element) modelElement.getParentNode();
308 }
309 else
310 {
311 Element elm = getFirstChildElement( modelElement, t );
312 if( elm == null )
313 modelElement = (Element) modelElement.insertBefore(
314 document.createElement( t ),
315 getFirstChildElement( modelElement, t ) );
316 else
317 modelElement = elm;
318 }
319 }
320 else
321 {
322 modelElement = (Element) modelElement.insertBefore(
323 document.createElement( t ),
324 getFirstChildElement( modelElement, t ) );
325 }
326 }
327
328 return modelElement;
329 }
330
331 public static Element addChildElement( Element element, String name, String text )
332 {
333 Document document = element.getOwnerDocument();
334 Element result = (Element) element.appendChild( document.createElement( name ) );
335 if( text != null )
336 result.appendChild( document.createTextNode( text ) );
337
338 return result;
339 }
340
341 public static void setChildElementText( Element element, String name, String text )
342 {
343 Element elm = getFirstChildElement( element, name );
344 if( elm == null )
345 {
346 elm = element.getOwnerDocument().createElement( name );
347 element.appendChild( elm );
348 }
349
350 setElementText( elm, text );
351 }
352
353 public static Document parseXml( String xmlString ) throws IOException
354 {
355 return parse( new InputSource( new StringReader( xmlString )));
356 }
357
358 public static void dumpParserErrors(XmlObject xmlObject)
359 {
360 List errors = new ArrayList();
361 xmlObject.validate(new XmlOptions().setErrorListener(errors));
362 for (Iterator i = errors.iterator(); i.hasNext();)
363 {
364 System.out.println(i.next());
365 }
366 }
367
368 public static String transferValues(String source, String dest)
369 {
370 try
371 {
372 Document sourceDom = parseXml(source);
373 Document destDom = parseXml(dest);
374
375 TreeWalker walker = ((DocumentTraversal)sourceDom).createTreeWalker( sourceDom.getDocumentElement(),
376 NodeFilter.SHOW_ELEMENT, null, true );
377
378 Element elm = (Element) walker.nextNode();
379 while( elm != null )
380 {
381 String path = getElementPath( elm );
382 Node nsNode = getNSNode( elm );
383 Element elm2 = (Element) XPathAPI.selectSingleNode( destDom.getDocumentElement(), path, nsNode );
384 if( elm2 != null )
385 {
386
387 NamedNodeMap attributes = elm.getAttributes();
388 for( int c = 0; c < attributes.getLength(); c++ )
389 {
390 Attr attr = (Attr) attributes.item( c );
391 elm2.setAttribute( attr.getNodeName(), attr.getNodeValue() );
392 }
393
394
395 setElementText( elm2, getElementText( elm ));
396 }
397
398 elm = (Element) walker.nextNode();
399 }
400
401 StringWriter writer = new StringWriter();
402 serialize( destDom, writer );
403 return writer.toString();
404 }
405 catch (TransformerException e)
406 {
407 e.printStackTrace();
408 }
409 catch (IOException e)
410 {
411 e.printStackTrace();
412 }
413
414 return dest;
415 }
416
417 private static Node getNSNode(Node elm)
418 {
419 Element result = elm.getOwnerDocument().createElement("nsnode");
420
421 while( elm != null )
422 {
423 NamedNodeMap attributes = elm.getAttributes();
424 if( attributes != null )
425 {
426 for( int c = 0; c < attributes.getLength(); c++ )
427 {
428 Node attr = attributes.item( c );
429 if( attr.getNodeName().startsWith( "xmlns:" ) || attr.getNodeName().equals( "xmlns") )
430 {
431 result.setAttribute( attr.getNodeName(), attr.getNodeValue() );
432 }
433 }
434 }
435
436 elm = elm.getParentNode();
437 }
438
439 return result;
440 }
441
442 /***
443 * Returns absolute xpath for specified element, ignores namespaces
444 *
445 * @param elm the element to create for
446 * @return the elements path in its containing document
447 */
448
449 public static String getElementPath(Element element)
450 {
451 Node elm = element;
452
453 String result = elm.getNodeName() + "[" + getElementIndex( elm ) + "]";
454 while( elm.getParentNode() != null && elm.getParentNode().getNodeType() != Node.DOCUMENT_NODE )
455 {
456 elm = elm.getParentNode();
457 result = elm.getNodeName() + "[" + getElementIndex( elm ) + "]/" + result;
458 }
459
460 return "/" + result;
461 }
462
463 /***
464 * Gets the index of the specified element amongst elements with the same name
465 *
466 * @param element the element to get for
467 * @return the index of the element, will be >= 1
468 */
469
470 public static int getElementIndex(Node element)
471 {
472 int result = 1;
473
474 Node elm = element.getPreviousSibling();
475 while( elm != null )
476 {
477 if( elm.getNodeType() == Node.ELEMENT_NODE && elm.getNodeName().equals( element.getNodeName() ))
478 result++;
479 elm = elm.getPreviousSibling();
480 }
481
482 return result;
483 }
484
485 public static String declareXPathNamespaces( String xmlString ) throws XmlException
486 {
487 return declareXPathNamespaces( XmlObject.Factory.parse(xmlString));
488 }
489
490 public static synchronized String prettyPrintXml( String xml )
491 {
492 try
493 {
494 if( xml == null )
495 return null;
496
497 StringWriter writer = new StringWriter();
498 XmlUtils.serializePretty( XmlObject.Factory.parse( xml ), writer );
499 return writer.toString();
500 }
501 catch( Exception e )
502 {
503 log.warn( "Failed to prettyPrint xml: " + e );
504 return xml;
505 }
506 }
507
508 public static synchronized String prettyPrintXml( XmlObject xml )
509 {
510 try
511 {
512 if( xml == null )
513 return null;
514
515 StringWriter writer = new StringWriter();
516 XmlUtils.serializePretty( xml, writer );
517 return writer.toString();
518 }
519 catch( Exception e )
520 {
521 log.warn( "Failed to prettyPrint xml: " + e );
522 return xml.xmlText();
523 }
524 }
525
526 public static String declareXPathNamespaces(WsdlInterface iface)
527 {
528 StringBuffer buf = new StringBuffer();
529 buf.append( "declare namespace soap='" );
530 buf.append( iface.getSoapVersion().getEnvelopeNamespace() );
531 buf.append( "';\n");
532
533 try
534 {
535 Collection<String> namespaces = iface.getWsdlContext().getDefinedNamespaces();
536 int c = 1;
537 for (Iterator<String> i = namespaces.iterator(); i.hasNext();)
538 {
539 buf.append("declare namespace ns");
540 buf.append(c++);
541 buf.append("='");
542 buf.append(i.next());
543 buf.append("';\n");
544 }
545 }
546 catch (Exception e)
547 {
548 e.printStackTrace();
549 }
550
551 return buf.toString();
552 }
553
554 public static String createXPath(Node node )
555 {
556 return createXPath( node, false, false, null );
557 }
558
559 public static String createXPath(Node node, boolean anonymous, boolean selectText, XPathModifier modifier )
560 {
561 StringToStringMap nsMap = new StringToStringMap();
562 int nsCnt = 1;
563 List<String> pathComponents = new ArrayList<String>();
564
565 String namespaceURI = node.getNamespaceURI();
566 if( node.getNodeType() == Node.ATTRIBUTE_NODE )
567 {
568 if( namespaceURI.length() > 0 )
569 {
570 String prefix = node.getPrefix();
571 if( prefix == null || prefix.length() == 0 )
572 prefix = "ns" + nsCnt++;
573
574 nsMap.put( namespaceURI, prefix );
575 pathComponents.add( "@" + prefix + ":" + node.getLocalName() );
576 }
577 else
578 {
579 pathComponents.add( "@" + node.getLocalName() );
580 }
581 node = ((Attr)node).getOwnerElement();
582 }
583
584 if( node.getNodeType() == Node.ELEMENT_NODE )
585 {
586 int index = anonymous ? 0 : findNodeIndex( node );
587
588 String pc = null;
589
590 namespaceURI = node.getNamespaceURI();
591 if( namespaceURI.length() > 0 )
592 {
593 String prefix = node.getPrefix();
594 if( prefix == null || prefix.length() == 0 )
595 prefix = "ns" + nsCnt++;
596
597 nsMap.put( namespaceURI, prefix );
598 pc = prefix + ":" + node.getLocalName();
599 }
600 else
601 {
602 pc = node.getLocalName();
603 }
604
605 String elementText = XmlUtils.getElementText( (Element) node );
606
607
608 if( selectText && pathComponents.isEmpty() && elementText != null && elementText.trim().length() > 0 )
609 pathComponents.add( "text()" );
610
611 pathComponents.add( pc + ((index == 0 ) ? "" : "[" + index + "]" ));
612 }
613 else
614 return null;
615
616 node = node.getParentNode();
617 namespaceURI = node.getNamespaceURI();
618 while( node != null && node.getNodeType() == Node.ELEMENT_NODE &&
619 !node.getNodeName().equals( "Body" ) &&
620 !namespaceURI.equals( SoapVersion.Soap11.getEnvelopeNamespace() ) &&
621 !namespaceURI.equals( SoapVersion.Soap12.getEnvelopeNamespace() ))
622 {
623 int index = anonymous ? 0 : findNodeIndex( node );
624
625 String ns = nsMap.get( namespaceURI );
626 String pc = null;
627
628 if( ns == null && namespaceURI.length() > 0 )
629 {
630 String prefix = node.getPrefix();
631 if( prefix == null || prefix.length() == 0 )
632 prefix = "ns" + nsCnt++;
633
634 nsMap.put( namespaceURI, prefix );
635 ns = nsMap.get( namespaceURI );
636
637 pc = prefix + ":" + node.getLocalName();
638 }
639 else if( ns != null )
640 {
641 pc = ns + ":" + node.getLocalName();
642 }
643 else
644 {
645 pc = node.getLocalName();
646 }
647
648 pathComponents.add( pc + ((index == 0 ) ? "" : "[" + index + "]" ));
649 node = node.getParentNode();
650 namespaceURI = node.getNamespaceURI();
651 }
652
653 StringBuffer xpath = new StringBuffer();
654
655 for( Iterator<String> i = nsMap.keySet().iterator(); i.hasNext(); )
656 {
657 String ns = i.next();
658 xpath.append( "declare namespace " + nsMap.get( ns ) + "='" + ns + "';\n");
659 }
660
661 if( modifier != null )
662 modifier.beforeSelector( xpath );
663
664 xpath.append( "/" );
665
666 for( int c = pathComponents.size()-1; c >= 0; c-- )
667 {
668 xpath.append( "/" ).append( pathComponents.get( c ));
669 }
670
671 if( modifier != null )
672 modifier.afterSelector( xpath );
673
674 return xpath.toString();
675 }
676
677 private static int findNodeIndex(Node node)
678 {
679 String nm = node.getLocalName();
680 String ns = node.getNamespaceURI();
681
682 Node parentNode = node.getParentNode();
683 if( parentNode.getNodeType() != Node.ELEMENT_NODE )
684 return 1;
685
686 NodeList nl = ((Element)parentNode).getElementsByTagNameNS( ns, nm );
687
688 if( nl.getLength() == 1 )
689 return 0;
690
691 for( int c = 0; c < nl.getLength(); c++ )
692 if( nl.item( c ) == node )
693 return c+1;
694
695 throw new RuntimeException( "Child node not found in parent!?" );
696 }
697
698 public static boolean setNodeValue( Node domNode, String string )
699 {
700 short nodeType = domNode.getNodeType();
701 if( nodeType == Node.ELEMENT_NODE )
702 {
703 setElementText( ( Element ) domNode, string );
704 return true;
705 }
706 else if( nodeType == Node.ATTRIBUTE_NODE || nodeType == Node.TEXT_NODE )
707 {
708 domNode.setNodeValue( string );
709 return true;
710 }
711
712 return false;
713 }
714
715 public static String declareXPathNamespaces( XmlObject xmlObject )
716 {
717 Map<QName,String> map = new HashMap<QName,String>();
718 XmlCursor cursor = xmlObject.newCursor();
719
720 while( cursor.hasNextToken() )
721 {
722 if( cursor.toNextToken().isNamespace() )
723 map.put( cursor.getName(), cursor.getTextValue() );
724 }
725
726 Iterator<QName> i = map.keySet().iterator();
727 int nsCnt = 0;
728
729 StringBuffer buf = new StringBuffer();
730 Set<String> prefixes = new HashSet<String>();
731 Set<String> usedPrefixes = new HashSet<String>();
732
733 while( i.hasNext() )
734 {
735 QName name = i.next();
736 String prefix = name.getLocalPart();
737 if( prefix.length() == 0 ) prefix = "ns" + Integer.toString( ++nsCnt );
738 else if( prefix.equals( "xsd") || prefix.equals( "xsi")) continue;
739
740 if( prefixes.contains( prefix ))
741 {
742 int c = 1;
743 while( usedPrefixes.contains( prefix + c )) c++;
744
745 prefix = prefix + Integer.toString( c );
746 }
747 else prefixes.add( prefix );
748
749 buf.append( "declare namespace " );
750 buf.append( prefix );
751 buf.append( "='" );
752 buf.append( map.get( name ));
753 buf.append( "';\n");
754
755 usedPrefixes.add( prefix );
756 }
757
758 return buf.toString();
759 }
760
761 public static String setXPathContent( String emptyResponse, String string, String actor )
762 {
763 try
764 {
765 XmlObject xmlObject = XmlObject.Factory.parse( emptyResponse );
766 XmlObject[] path = xmlObject.selectPath( string );
767 for( XmlObject xml : path )
768 {
769 setNodeValue( xml.getDomNode(), actor );
770 }
771
772 return xmlObject.toString();
773 }
774 catch( XmlException e )
775 {
776 e.printStackTrace();
777 }
778
779 return null;
780 }
781
782 public static QName getQName( Node node )
783 {
784 if( node.getNamespaceURI() == null )
785 return new QName( node.getNodeName());
786 else
787 return new QName( node.getNamespaceURI(), node.getLocalName() );
788 }
789
790 public static String removeXPathNamespaceDeclarations( String xpath )
791 {
792 while( xpath.startsWith( "declare namespace" ))
793 {
794 int ix = xpath.indexOf( ';' );
795 if( ix == -1 )
796 break;
797
798 xpath = xpath.substring( ix+1 ).trim();
799 }
800 return xpath;
801 }
802
803 public static String stripWhitespaces( String content )
804 {
805 try
806 {
807 XmlObject xml = XmlObject.Factory.parse( content, new XmlOptions().setLoadStripWhitespace() );
808 content = xml.xmlText();
809 }
810 catch( XmlException e )
811 {
812 e.printStackTrace();
813 }
814
815
816 return content;
817 }
818
819 public static NodeList getChildElements( Element elm )
820 {
821 List<Element> list = new ArrayList<Element>();
822
823 NodeList nl = elm.getChildNodes();
824 for( int c = 0; c < nl.getLength(); c++ )
825 {
826 if( nl.item( c ).getNodeType() == Node.ELEMENT_NODE )
827 list.add( ( Element ) nl.item( c ) );
828 }
829
830 return new ElementNodeList( list );
831 }
832
833 private final static class ElementNodeList implements NodeList
834 {
835 private final List<Element> list;
836
837 public ElementNodeList( List<Element> list )
838 {
839 this.list = list;
840 }
841
842 public int getLength()
843 {
844 return list.size();
845 }
846
847 public Node item( int index )
848 {
849 return list.get( index );
850 }}
851
852 public static boolean seemsToBeXml( String requestContent )
853 {
854 try
855 {
856 return requestContent != null && XmlObject.Factory.parse( requestContent ) != null;
857 }
858 catch( XmlException e )
859 {
860 return false;
861 }
862 }
863 }
864