001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.support.xml;
014:
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.OutputStreamWriter;
018: import java.io.StringReader;
019: import java.io.StringWriter;
020: import java.io.Writer;
021: import java.util.ArrayList;
022: import java.util.Collection;
023: import java.util.HashMap;
024: import java.util.HashSet;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029: import java.util.StringTokenizer;
030:
031: import javax.xml.namespace.QName;
032: import javax.xml.parsers.DocumentBuilder;
033: import javax.xml.parsers.DocumentBuilderFactory;
034: import javax.xml.parsers.ParserConfigurationException;
035:
036: import org.apache.log4j.Logger;
037: import org.apache.xmlbeans.XmlCursor;
038: import org.apache.xmlbeans.XmlException;
039: import org.apache.xmlbeans.XmlObject;
040: import org.apache.xmlbeans.XmlOptions;
041: import org.w3c.dom.Attr;
042: import org.w3c.dom.Document;
043: import org.w3c.dom.DocumentFragment;
044: import org.w3c.dom.Element;
045: import org.w3c.dom.NamedNodeMap;
046: import org.w3c.dom.Node;
047: import org.w3c.dom.NodeList;
048: import org.w3c.dom.Text;
049: import org.xml.sax.InputSource;
050: import org.xml.sax.SAXException;
051:
052: import com.eviware.soapui.SoapUI;
053: import com.eviware.soapui.impl.wsdl.WsdlInterface;
054: import com.eviware.soapui.impl.wsdl.support.soap.SoapVersion;
055: import com.eviware.soapui.support.types.StringToStringMap;
056:
057: /**
058: * General XML-related utilities
059: */
060:
061: public final class XmlUtils {
062: private static DocumentBuilder documentBuilder;
063: private final static Logger log = Logger.getLogger(XmlUtils.class);
064:
065: static public Document parse(InputStream in) {
066: try {
067: return ensureDocumentBuilder().parse(in);
068: } catch (Exception e) {
069: log
070: .error("Error parsing InputStream; "
071: + e.getMessage(), e);
072: }
073:
074: return null;
075: }
076:
077: static public Document parse(String fileName) throws IOException {
078: try {
079: return ensureDocumentBuilder().parse(fileName);
080: } catch (SAXException e) {
081: log.error("Error parsing fileName [" + fileName + "]; "
082: + e.getMessage(), e);
083: }
084:
085: return null;
086: }
087:
088: public static String entitize(String xml) {
089: return xml.replaceAll("&", "&").replaceAll("<", "<")
090: .replaceAll(">", ">").replaceAll("\"", """)
091: .replaceAll("'", "'");
092: }
093:
094: static public Document parse(InputSource inputSource)
095: throws IOException {
096: try {
097: return ensureDocumentBuilder().parse(inputSource);
098: } catch (SAXException e) {
099: throw new IOException(e.toString());
100: }
101: }
102:
103: private static DocumentBuilder ensureDocumentBuilder() {
104: if (documentBuilder == null) {
105: try {
106: DocumentBuilderFactory dbf = DocumentBuilderFactory
107: .newInstance();
108: dbf.setNamespaceAware(true);
109: documentBuilder = dbf.newDocumentBuilder();
110: } catch (ParserConfigurationException e) {
111: log.error("Error creating DocumentBuilder; "
112: + e.getMessage());
113: }
114: }
115:
116: return documentBuilder;
117: }
118:
119: public static void serializePretty(Document document) {
120: try {
121: serializePretty(document,
122: new OutputStreamWriter(System.out));
123: } catch (IOException e) {
124: log.error("Failed to seraialize: " + e);
125: }
126: }
127:
128: public static void serializePretty(Document dom, Writer writer)
129: throws IOException {
130: try {
131: XmlObject xmlObject = XmlObject.Factory.parse(dom
132: .getDocumentElement());
133: serializePretty(xmlObject, writer);
134: } catch (Exception e) {
135: throw new IOException(e.toString());
136: }
137: }
138:
139: public static void serializePretty(XmlObject xmlObject,
140: Writer writer) throws IOException {
141: XmlOptions options = new XmlOptions();
142: options.setSavePrettyPrint();
143: options.setSavePrettyPrintIndent(3);
144: options.setSaveNoXmlDecl();
145: options.setSaveAggressiveNamespaces();
146: xmlObject.save(writer, options);
147: }
148:
149: public static void serialize(Document dom, Writer writer)
150: throws IOException {
151: serialize(dom.getDocumentElement(), writer);
152: }
153:
154: public static void serialize(Element elm, Writer writer)
155: throws IOException {
156: try {
157: XmlObject xmlObject = XmlObject.Factory.parse(elm);
158: xmlObject.save(writer);
159: } catch (XmlException e) {
160: throw new IOException(e.toString());
161: }
162: }
163:
164: static public void setElementText(Element elm, String text) {
165: Node node = elm.getFirstChild();
166: if (node == null) {
167: if (text != null)
168: elm.appendChild(elm.getOwnerDocument().createTextNode(
169: text));
170: } else if (node.getNodeType() == Node.TEXT_NODE) {
171: if (text == null)
172: node.getParentNode().removeChild(node);
173: else
174: node.setNodeValue(text);
175: } else if (text != null) {
176: Text textNode = node.getOwnerDocument()
177: .createTextNode(text);
178: elm.insertBefore(textNode, elm.getFirstChild());
179: }
180: }
181:
182: public static String getChildElementText(Element elm, String name) {
183: Element child = getFirstChildElement(elm, name);
184: return child == null ? null : getElementText(child);
185: }
186:
187: public static Element getFirstChildElement(Element elm) {
188: return getFirstChildElement(elm, null);
189: }
190:
191: public static Element getFirstChildElement(Element elm, String name) {
192: NodeList nl = elm.getChildNodes();
193: for (int c = 0; c < nl.getLength(); c++) {
194: Node node = nl.item(c);
195: if (node.getNodeType() == Node.ELEMENT_NODE
196: && (name == null || node.getNodeName().equals(name)))
197: return (Element) node;
198: }
199:
200: return null;
201: }
202:
203: public static Element getFirstChildElementNS(Element elm,
204: String tns, String localName) {
205: if (tns == null && localName == null)
206: return getFirstChildElement(elm);
207:
208: if (tns == null)
209: return getFirstChildElement(elm, localName);
210:
211: NodeList nl = elm.getChildNodes();
212: for (int c = 0; c < nl.getLength(); c++) {
213: Node node = nl.item(c);
214: if (node.getNodeType() != Node.ELEMENT_NODE)
215: continue;
216:
217: if (localName == null && tns.equals(node.getNamespaceURI()))
218: return (Element) node;
219:
220: if (localName != null && tns.equals(node.getNamespaceURI())
221: && localName.equals(node.getLocalName()))
222: return (Element) node;
223: }
224:
225: return null;
226: }
227:
228: static public String getElementText(Element elm) {
229: Node node = elm.getFirstChild();
230: if (node != null && node.getNodeType() == Node.TEXT_NODE)
231: return node.getNodeValue();
232:
233: return null;
234: }
235:
236: static public String getFragmentText(DocumentFragment elm) {
237: Node node = elm.getFirstChild();
238: if (node != null && node.getNodeType() == Node.TEXT_NODE)
239: return node.getNodeValue();
240:
241: return null;
242: }
243:
244: public static String getChildElementText(Element elm, String name,
245: String defaultValue) {
246: String result = getChildElementText(elm, name);
247: return result == null ? defaultValue : result;
248: }
249:
250: static public String getNodeValue(Node node) {
251: if (node.getNodeType() == Node.ELEMENT_NODE)
252: return getElementText((Element) node);
253: else if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE)
254: return getFragmentText((DocumentFragment) node);
255: else
256: return node.getNodeValue();
257: }
258:
259: public static Node createNodeFromPath(Element modelElement,
260: String path) {
261: Document document = modelElement.getOwnerDocument();
262: StringTokenizer st = new StringTokenizer(path, "/");
263: while (st.hasMoreTokens()) {
264: String t = st.nextToken();
265:
266: if (st.hasMoreTokens()) {
267: if (t.equals("..")) {
268: modelElement = (Element) modelElement
269: .getParentNode();
270: } else {
271: Element elm = getFirstChildElement(modelElement, t);
272: if (elm == null)
273: modelElement = (Element) modelElement
274: .insertBefore(
275: document.createElement(t),
276: getFirstChildElement(
277: modelElement, t));
278: else
279: modelElement = elm;
280: }
281: } else {
282: modelElement = (Element) modelElement.insertBefore(
283: document.createElement(t),
284: getFirstChildElement(modelElement, t));
285: }
286: }
287:
288: return modelElement;
289: }
290:
291: public static Element addChildElement(Element element, String name,
292: String text) {
293: Document document = element.getOwnerDocument();
294: Element result = (Element) element.appendChild(document
295: .createElement(name));
296: if (text != null)
297: result.appendChild(document.createTextNode(text));
298:
299: return result;
300: }
301:
302: public static void setChildElementText(Element element,
303: String name, String text) {
304: Element elm = getFirstChildElement(element, name);
305: if (elm == null) {
306: elm = element.getOwnerDocument().createElement(name);
307: element.appendChild(elm);
308: }
309:
310: setElementText(elm, text);
311: }
312:
313: public static Document parseXml(String xmlString)
314: throws IOException {
315: return parse(new InputSource(new StringReader(xmlString)));
316: }
317:
318: public static void dumpParserErrors(XmlObject xmlObject) {
319: List errors = new ArrayList();
320: xmlObject.validate(new XmlOptions().setErrorListener(errors));
321: for (Iterator i = errors.iterator(); i.hasNext();) {
322: System.out.println(i.next());
323: }
324: }
325:
326: public static String transferValues(String source, String dest) {
327: XmlCursor cursor = null;
328: try {
329: XmlObject sourceXml = XmlObject.Factory.parse(source);
330: XmlObject destXml = XmlObject.Factory.parse(dest);
331:
332: cursor = sourceXml.newCursor();
333: cursor.toNextToken();
334: while (!cursor.isEnddoc()) {
335: while (!cursor.isContainer() && !cursor.isEnddoc())
336: cursor.toNextToken();
337:
338: if (cursor.isContainer()) {
339: Element elm = (Element) cursor.getDomNode();
340: String path = createXPath(elm);
341: XmlObject[] paths = destXml.selectPath(path);
342: if (paths != null && paths.length > 0) {
343: Element elm2 = (Element) paths[0].getDomNode();
344:
345: // transfer attributes
346: NamedNodeMap attributes = elm.getAttributes();
347: for (int c = 0; c < attributes.getLength(); c++) {
348: Attr attr = (Attr) attributes.item(c);
349: elm2.setAttribute(attr.getNodeName(), attr
350: .getNodeValue());
351: }
352:
353: // transfer text
354: setElementText(elm2, getElementText(elm));
355: }
356:
357: cursor.toNextToken();
358: }
359: }
360:
361: return destXml.xmlText();
362: } catch (Exception e) {
363: SoapUI.logError(e);
364: } finally {
365: if (cursor != null)
366: cursor.dispose();
367: }
368:
369: return dest;
370: }
371:
372: /**
373: * Returns absolute xpath for specified element, ignores namespaces
374: *
375: * @param elm the element to create for
376: * @return the elements path in its containing document
377: */
378:
379: public static String getElementPath(Element element) {
380: Node elm = element;
381:
382: String result = elm.getNodeName() + "[" + getElementIndex(elm)
383: + "]";
384: while (elm.getParentNode() != null
385: && elm.getParentNode().getNodeType() != Node.DOCUMENT_NODE) {
386: elm = elm.getParentNode();
387: result = elm.getNodeName() + "[" + getElementIndex(elm)
388: + "]/" + result;
389: }
390:
391: return "/" + result;
392: }
393:
394: /**
395: * Gets the index of the specified element amongst elements with the same name
396: *
397: * @param element the element to get for
398: * @return the index of the element, will be >= 1
399: */
400:
401: public static int getElementIndex(Node element) {
402: int result = 1;
403:
404: Node elm = element.getPreviousSibling();
405: while (elm != null) {
406: if (elm.getNodeType() == Node.ELEMENT_NODE
407: && elm.getNodeName().equals(element.getNodeName()))
408: result++;
409: elm = elm.getPreviousSibling();
410: }
411:
412: return result;
413: }
414:
415: public static String declareXPathNamespaces(String xmlString)
416: throws XmlException {
417: return declareXPathNamespaces(XmlObject.Factory
418: .parse(xmlString));
419: }
420:
421: public static synchronized String prettyPrintXml(String xml) {
422: try {
423: if (xml == null)
424: return null;
425:
426: StringWriter writer = new StringWriter();
427: XmlUtils.serializePretty(XmlObject.Factory.parse(xml),
428: writer);
429: return writer.toString();
430: } catch (Exception e) {
431: log.warn("Failed to prettyPrint xml: " + e);
432: return xml;
433: }
434: }
435:
436: public static synchronized String prettyPrintXml(XmlObject xml) {
437: try {
438: if (xml == null)
439: return null;
440:
441: StringWriter writer = new StringWriter();
442: XmlUtils.serializePretty(xml, writer);
443: return writer.toString();
444: } catch (Exception e) {
445: log.warn("Failed to prettyPrint xml: " + e);
446: return xml.xmlText();
447: }
448: }
449:
450: public static String declareXPathNamespaces(WsdlInterface iface) {
451: StringBuffer buf = new StringBuffer();
452: buf.append("declare namespace soap='");
453: buf.append(iface.getSoapVersion().getEnvelopeNamespace());
454: buf.append("';\n");
455:
456: try {
457: Collection<String> namespaces = iface.getWsdlContext()
458: .getDefinedNamespaces();
459: int c = 1;
460: for (Iterator<String> i = namespaces.iterator(); i
461: .hasNext();) {
462: buf.append("declare namespace ns");
463: buf.append(c++);
464: buf.append("='");
465: buf.append(i.next());
466: buf.append("';\n");
467: }
468: } catch (Exception e) {
469: SoapUI.logError(e);
470: }
471:
472: return buf.toString();
473: }
474:
475: public static String createXPath(Node node) {
476: return createXPath(node, false, false, null);
477: }
478:
479: public static String createXPath(Node node, boolean anonymous,
480: boolean selectText, XPathModifier modifier) {
481: StringToStringMap nsMap = new StringToStringMap();
482: int nsCnt = 1;
483: List<String> pathComponents = new ArrayList<String>();
484:
485: String namespaceURI = node.getNamespaceURI();
486: if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
487: if (namespaceURI.length() > 0) {
488: String prefix = node.getPrefix();
489: if (prefix == null || prefix.length() == 0)
490: prefix = "ns" + nsCnt++;
491:
492: nsMap.put(namespaceURI, prefix);
493: pathComponents.add("@" + prefix + ":"
494: + node.getLocalName());
495: } else {
496: pathComponents.add("@" + node.getLocalName());
497: }
498: node = ((Attr) node).getOwnerElement();
499: }
500:
501: if (node.getNodeType() == Node.ELEMENT_NODE) {
502: int index = anonymous ? 0 : findNodeIndex(node);
503:
504: String pc = null;
505:
506: namespaceURI = node.getNamespaceURI();
507: if (namespaceURI.length() > 0) {
508: String prefix = node.getPrefix();
509: if (prefix == null || prefix.length() == 0)
510: prefix = "ns" + nsCnt++;
511:
512: nsMap.put(namespaceURI, prefix);
513: pc = prefix + ":" + node.getLocalName();
514: } else {
515: pc = node.getLocalName();
516: }
517:
518: String elementText = XmlUtils
519: .getElementText((Element) node);
520:
521: // not an attribute?
522: if (selectText && pathComponents.isEmpty()
523: && elementText != null
524: && elementText.trim().length() > 0)
525: pathComponents.add("text()");
526:
527: pathComponents.add(pc
528: + ((index == 0) ? "" : "[" + index + "]"));
529: } else
530: return null;
531:
532: node = node.getParentNode();
533: namespaceURI = node.getNamespaceURI();
534: while (node != null
535: && node.getNodeType() == Node.ELEMENT_NODE
536: && !node.getNodeName().equals("Body")
537: && !namespaceURI.equals(SoapVersion.Soap11
538: .getEnvelopeNamespace())
539: && !namespaceURI.equals(SoapVersion.Soap12
540: .getEnvelopeNamespace())) {
541: int index = anonymous ? 0 : findNodeIndex(node);
542:
543: String ns = nsMap.get(namespaceURI);
544: String pc = null;
545:
546: if (ns == null && namespaceURI.length() > 0) {
547: String prefix = node.getPrefix();
548: if (prefix == null || prefix.length() == 0)
549: prefix = "ns" + nsCnt++;
550:
551: nsMap.put(namespaceURI, prefix);
552: ns = nsMap.get(namespaceURI);
553:
554: pc = prefix + ":" + node.getLocalName();
555: } else if (ns != null) {
556: pc = ns + ":" + node.getLocalName();
557: } else {
558: pc = node.getLocalName();
559: }
560:
561: pathComponents.add(pc
562: + ((index == 0) ? "" : "[" + index + "]"));
563: node = node.getParentNode();
564: namespaceURI = node.getNamespaceURI();
565: }
566:
567: StringBuffer xpath = new StringBuffer();
568:
569: for (Iterator<String> i = nsMap.keySet().iterator(); i
570: .hasNext();) {
571: String ns = i.next();
572: xpath.append("declare namespace " + nsMap.get(ns) + "='"
573: + ns + "';\n");
574: }
575:
576: if (modifier != null)
577: modifier.beforeSelector(xpath);
578:
579: xpath.append("/");
580:
581: for (int c = pathComponents.size() - 1; c >= 0; c--) {
582: xpath.append("/").append(pathComponents.get(c));
583: }
584:
585: if (modifier != null)
586: modifier.afterSelector(xpath);
587:
588: return xpath.toString();
589: }
590:
591: private static int findNodeIndex(Node node) {
592: String nm = node.getLocalName();
593: String ns = node.getNamespaceURI();
594:
595: Node parentNode = node.getParentNode();
596: if (parentNode.getNodeType() != Node.ELEMENT_NODE)
597: return 1;
598:
599: NodeList nl = ((Element) parentNode).getElementsByTagNameNS(ns,
600: nm);
601:
602: if (nl.getLength() == 1)
603: return 0;
604:
605: int mod = 0;
606: for (int c = 0; c < nl.getLength(); c++) {
607: if (nl.item(c).getParentNode() != node.getParentNode())
608: mod++;
609: else if (nl.item(c) == node)
610: return c + 1 - mod;
611: }
612:
613: throw new RuntimeException("Child node not found in parent!?");
614: }
615:
616: public static boolean setNodeValue(Node domNode, String string) {
617: short nodeType = domNode.getNodeType();
618: if (nodeType == Node.ELEMENT_NODE) {
619: setElementText((Element) domNode, string);
620: return true;
621: } else if (nodeType == Node.ATTRIBUTE_NODE
622: || nodeType == Node.TEXT_NODE) {
623: domNode.setNodeValue(string);
624: return true;
625: }
626:
627: return false;
628: }
629:
630: public static String declareXPathNamespaces(XmlObject xmlObject) {
631: Map<QName, String> map = new HashMap<QName, String>();
632: XmlCursor cursor = xmlObject.newCursor();
633:
634: while (cursor.hasNextToken()) {
635: if (cursor.toNextToken().isNamespace())
636: map.put(cursor.getName(), cursor.getTextValue());
637: }
638:
639: Iterator<QName> i = map.keySet().iterator();
640: int nsCnt = 0;
641:
642: StringBuffer buf = new StringBuffer();
643: Set<String> prefixes = new HashSet<String>();
644: Set<String> usedPrefixes = new HashSet<String>();
645:
646: while (i.hasNext()) {
647: QName name = i.next();
648: String prefix = name.getLocalPart();
649: if (prefix.length() == 0)
650: prefix = "ns" + Integer.toString(++nsCnt);
651: else if (prefix.equals("xsd") || prefix.equals("xsi"))
652: continue;
653:
654: if (usedPrefixes.contains(prefix)) {
655: int c = 1;
656: while (usedPrefixes.contains(prefix + c))
657: c++;
658:
659: prefix = prefix + Integer.toString(c);
660: } else
661: prefixes.add(prefix);
662:
663: buf.append("declare namespace ");
664: buf.append(prefix);
665: buf.append("='");
666: buf.append(map.get(name));
667: buf.append("';\n");
668:
669: usedPrefixes.add(prefix);
670: }
671:
672: return buf.toString();
673: }
674:
675: public static String setXPathContent(String emptyResponse,
676: String string, String actor) {
677: try {
678: XmlObject xmlObject = XmlObject.Factory
679: .parse(emptyResponse);
680:
681: String namespaces = declareXPathNamespaces(xmlObject);
682: if (namespaces != null && namespaces.trim().length() > 0)
683: string = namespaces + string;
684:
685: XmlObject[] path = xmlObject.selectPath(string);
686: for (XmlObject xml : path) {
687: setNodeValue(xml.getDomNode(), actor);
688: }
689:
690: return xmlObject.toString();
691: } catch (Exception e) {
692: SoapUI.logError(e);
693: }
694:
695: return emptyResponse;
696: }
697:
698: public static QName getQName(Node node) {
699: if (node.getNamespaceURI() == null)
700: return new QName(node.getNodeName());
701: else
702: return new QName(node.getNamespaceURI(), node
703: .getLocalName());
704: }
705:
706: public static String removeXPathNamespaceDeclarations(String xpath) {
707: while (xpath.startsWith("declare namespace")) {
708: int ix = xpath.indexOf(';');
709: if (ix == -1)
710: break;
711:
712: xpath = xpath.substring(ix + 1).trim();
713: }
714: return xpath;
715: }
716:
717: public static String stripWhitespaces(String content) {
718: try {
719: XmlObject xml = XmlObject.Factory.parse(content,
720: new XmlOptions().setLoadStripWhitespace()
721: .setLoadStripComments());
722: content = xml.xmlText();
723: } catch (Exception e) {
724: SoapUI.logError(e);
725: }
726:
727: return content;
728: }
729:
730: public static NodeList getChildElements(Element elm) {
731: List<Element> list = new ArrayList<Element>();
732:
733: NodeList nl = elm.getChildNodes();
734: for (int c = 0; c < nl.getLength(); c++) {
735: if (nl.item(c).getNodeType() == Node.ELEMENT_NODE)
736: list.add((Element) nl.item(c));
737: }
738:
739: return new ElementNodeList(list);
740: }
741:
742: private final static class ElementNodeList implements NodeList {
743: private final List<Element> list;
744:
745: public ElementNodeList(List<Element> list) {
746: this .list = list;
747: }
748:
749: public int getLength() {
750: return list.size();
751: }
752:
753: public Node item(int index) {
754: return list.get(index);
755: }
756: }
757:
758: public static boolean seemsToBeXml(String str) {
759: try {
760: return str != null && XmlObject.Factory.parse(str) != null;
761: } catch (Exception e) {
762: return false;
763: }
764: }
765:
766: public static String extractNamespaces(String xpath) {
767: String result = xpath;
768: int ix = xpath.lastIndexOf("declare namespace");
769: if (ix != -1) {
770: ix = xpath.indexOf('\'', ix + 1);
771: if (ix != -1) {
772: ix = xpath.indexOf('\'', ix + 1);
773: if (ix != -1) {
774: ix = xpath.indexOf(';');
775: if (ix != -1) {
776: result = xpath.substring(0, ix + 1);
777: }
778: }
779: }
780: }
781:
782: return result;
783: }
784: }
|