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