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
498 StringWriter writer = new StringWriter();
499 XmlUtils.serializePretty( XmlObject.Factory.parse( xml ), writer );
500 return writer.toString();
501 }
502 catch( Exception e )
503 {
504 log.warn( "Failed to prettyPrint xml: " + e );
505 return xml;
506 }
507 }
508
509 public static String declareXPathNamespaces(WsdlInterface iface)
510 {
511 StringBuffer buf = new StringBuffer();
512 buf.append( "declare namespace soap='" );
513 buf.append( iface.getSoapVersion().getEnvelopeNamespace() );
514 buf.append( "';\n");
515
516 try
517 {
518 Collection<String> namespaces = iface.getWsdlContext().getDefinedNamespaces();
519 int c = 1;
520 for (Iterator<String> i = namespaces.iterator(); i.hasNext();)
521 {
522 buf.append("declare namespace ns");
523 buf.append(c++);
524 buf.append("='");
525 buf.append(i.next());
526 buf.append("';\n");
527 }
528 }
529 catch (Exception e)
530 {
531 e.printStackTrace();
532 }
533
534 return buf.toString();
535 }
536
537 public static String createXPath(Node node )
538 {
539 return createXPath( node, false, false, null );
540 }
541
542 public static String createXPath(Node node, boolean anonymous, boolean selectText, XPathModifier modifier )
543 {
544 StringToStringMap nsMap = new StringToStringMap();
545 int nsCnt = 1;
546 List<String> pathComponents = new ArrayList<String>();
547
548 String namespaceURI = node.getNamespaceURI();
549 if( node.getNodeType() == Node.ATTRIBUTE_NODE )
550 {
551 if( namespaceURI.length() > 0 )
552 {
553 String prefix = node.getPrefix();
554 if( prefix == null || prefix.length() == 0 )
555 prefix = "ns" + nsCnt++;
556
557 nsMap.put( namespaceURI, prefix );
558 pathComponents.add( "@" + prefix + ":" + node.getLocalName() );
559 }
560 else
561 {
562 pathComponents.add( "@" + node.getLocalName() );
563 }
564 node = ((Attr)node).getOwnerElement();
565 }
566
567 if( node.getNodeType() == Node.ELEMENT_NODE )
568 {
569 int index = anonymous ? 0 : findNodeIndex( node );
570
571 String pc = null;
572
573 namespaceURI = node.getNamespaceURI();
574 if( namespaceURI.length() > 0 )
575 {
576 String prefix = node.getPrefix();
577 if( prefix == null || prefix.length() == 0 )
578 prefix = "ns" + nsCnt++;
579
580 nsMap.put( namespaceURI, prefix );
581 pc = prefix + ":" + node.getLocalName();
582 }
583 else
584 {
585 pc = node.getLocalName();
586 }
587
588 String elementText = XmlUtils.getElementText( (Element) node );
589
590
591 if( selectText && pathComponents.isEmpty() && elementText != null && elementText.trim().length() > 0 )
592 pathComponents.add( "text()" );
593
594 pathComponents.add( pc + ((index == 0 ) ? "" : "[" + index + "]" ));
595 }
596 else
597 return null;
598
599 node = node.getParentNode();
600 namespaceURI = node.getNamespaceURI();
601 while( node != null && node.getNodeType() == Node.ELEMENT_NODE &&
602 !node.getNodeName().equals( "Body" ) &&
603 !namespaceURI.equals( SoapVersion.Soap11.getEnvelopeNamespace() ) &&
604 !namespaceURI.equals( SoapVersion.Soap12.getEnvelopeNamespace() ))
605 {
606 int index = anonymous ? 0 : findNodeIndex( node );
607
608 String ns = nsMap.get( namespaceURI );
609 String pc = null;
610
611 if( ns == null && namespaceURI.length() > 0 )
612 {
613 String prefix = node.getPrefix();
614 if( prefix == null || prefix.length() == 0 )
615 prefix = "ns" + nsCnt++;
616
617 nsMap.put( namespaceURI, prefix );
618 ns = nsMap.get( namespaceURI );
619
620 pc = prefix + ":" + node.getLocalName();
621 }
622 else if( ns != null )
623 {
624 pc = ns + ":" + node.getLocalName();
625 }
626 else
627 {
628 pc = node.getLocalName();
629 }
630
631 pathComponents.add( pc + ((index == 0 ) ? "" : "[" + index + "]" ));
632 node = node.getParentNode();
633 namespaceURI = node.getNamespaceURI();
634 }
635
636 StringBuffer xpath = new StringBuffer();
637
638 for( Iterator<String> i = nsMap.keySet().iterator(); i.hasNext(); )
639 {
640 String ns = i.next();
641 xpath.append( "declare namespace " + nsMap.get( ns ) + "='" + ns + "';\n");
642 }
643
644 if( modifier != null )
645 modifier.beforeSelector( xpath );
646
647 xpath.append( "/" );
648
649 for( int c = pathComponents.size()-1; c >= 0; c-- )
650 {
651 xpath.append( "/" ).append( pathComponents.get( c ));
652 }
653
654 if( modifier != null )
655 modifier.afterSelector( xpath );
656
657 return xpath.toString();
658 }
659
660 private static int findNodeIndex(Node node)
661 {
662 String nm = node.getLocalName();
663 String ns = node.getNamespaceURI();
664
665 Node parentNode = node.getParentNode();
666 if( parentNode.getNodeType() != Node.ELEMENT_NODE )
667 return 1;
668
669 NodeList nl = ((Element)parentNode).getElementsByTagNameNS( ns, nm );
670
671 if( nl.getLength() == 1 )
672 return 0;
673
674 for( int c = 0; c < nl.getLength(); c++ )
675 if( nl.item( c ) == node )
676 return c+1;
677
678 throw new RuntimeException( "Child node not found in parent!?" );
679 }
680
681 public static boolean setNodeValue( Node domNode, String string )
682 {
683 short nodeType = domNode.getNodeType();
684 if( nodeType == Node.ELEMENT_NODE )
685 {
686 setElementText( ( Element ) domNode, string );
687 return true;
688 }
689 else if( nodeType == Node.ATTRIBUTE_NODE || nodeType == Node.TEXT_NODE )
690 {
691 domNode.setNodeValue( string );
692 return true;
693 }
694
695 return false;
696 }
697
698 public static String declareXPathNamespaces( XmlObject xmlObject )
699 {
700 Map<QName,String> map = new HashMap<QName,String>();
701 XmlCursor cursor = xmlObject.newCursor();
702
703 while( cursor.hasNextToken() )
704 {
705 if( cursor.toNextToken().isNamespace() )
706 map.put( cursor.getName(), cursor.getTextValue() );
707 }
708
709 Iterator<QName> i = map.keySet().iterator();
710 int nsCnt = 0;
711
712 StringBuffer buf = new StringBuffer();
713 Set<String> prefixes = new HashSet<String>();
714 Set<String> usedPrefixes = new HashSet<String>();
715
716 while( i.hasNext() )
717 {
718 QName name = i.next();
719 String prefix = name.getLocalPart();
720 if( prefix.length() == 0 ) prefix = "ns" + Integer.toString( ++nsCnt );
721 else if( prefix.equals( "xsd") || prefix.equals( "xsi")) continue;
722
723 if( prefixes.contains( prefix ))
724 {
725 int c = 1;
726 while( usedPrefixes.contains( prefix + c )) c++;
727
728 prefix = prefix + Integer.toString( c );
729 }
730 else prefixes.add( prefix );
731
732 buf.append( "declare namespace " );
733 buf.append( prefix );
734 buf.append( "='" );
735 buf.append( map.get( name ));
736 buf.append( "';\n");
737
738 usedPrefixes.add( prefix );
739 }
740
741 return buf.toString();
742 }
743
744 public static String setXPathContent( String emptyResponse, String string, String actor )
745 {
746 try
747 {
748 XmlObject xmlObject = XmlObject.Factory.parse( emptyResponse );
749 XmlObject[] path = xmlObject.selectPath( string );
750 for( XmlObject xml : path )
751 {
752 setNodeValue( xml.getDomNode(), actor );
753 }
754
755 return xmlObject.toString();
756 }
757 catch( XmlException e )
758 {
759 e.printStackTrace();
760 }
761
762 return null;
763 }
764
765 public static QName getQName( Node node )
766 {
767 if( node.getNamespaceURI() == null )
768 return new QName( node.getNodeName());
769 else
770 return new QName( node.getNamespaceURI(), node.getLocalName() );
771 }
772
773 public static String removeXPathNamespaceDeclarations( String xpath )
774 {
775 while( xpath.startsWith( "declare namespace" ))
776 {
777 int ix = xpath.indexOf( ';' );
778 if( ix == -1 )
779 break;
780
781 xpath = xpath.substring( ix+1 ).trim();
782 }
783 return xpath;
784 }
785
786 public static String stripWhitespaces( String content )
787 {
788 try
789 {
790 XmlObject xml = XmlObject.Factory.parse( content, new XmlOptions().setLoadStripWhitespace() );
791 content = xml.xmlText();
792 }
793 catch( XmlException e )
794 {
795 e.printStackTrace();
796 }
797
798
799 return content;
800 }
801
802 public static NodeList getChildElements( Element elm )
803 {
804 List<Element> list = new ArrayList<Element>();
805
806 NodeList nl = elm.getChildNodes();
807 for( int c = 0; c < nl.getLength(); c++ )
808 {
809 if( nl.item( c ).getNodeType() == Node.ELEMENT_NODE )
810 list.add( ( Element ) nl.item( c ) );
811 }
812
813 return new ElementNodeList( list );
814 }
815
816 private final static class ElementNodeList implements NodeList
817 {
818 private final List<Element> list;
819
820 public ElementNodeList( List<Element> list )
821 {
822 this.list = list;
823 }
824
825 public int getLength()
826 {
827 return list.size();
828 }
829
830 public Node item( int index )
831 {
832 return list.get( index );
833 }}
834
835 public static boolean seemsToBeXml( String requestContent )
836 {
837 try
838 {
839 return requestContent != null && XmlObject.Factory.parse( requestContent ) != null;
840 }
841 catch( XmlException e )
842 {
843 return false;
844 }
845 }
846 }
847