001    /*
002    // $Id: //open/mondrian/src/main/mondrian/tui/XmlUtil.java#13 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2005-2008 Julian Hyde and others
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    
011    package mondrian.tui;
012    
013    import org.apache.xerces.dom.DocumentImpl;
014    import org.apache.xerces.impl.Constants;
015    import org.apache.xerces.parsers.DOMParser;
016    import org.apache.xml.serialize.OutputFormat;
017    import org.apache.xml.serialize.XMLSerializer;
018    import org.apache.xpath.domapi.XPathEvaluatorImpl;
019    import org.w3c.dom.*;
020    import org.w3c.dom.xpath.*;
021    import org.xml.sax.*;
022    
023    import javax.xml.parsers.*;
024    import javax.xml.transform.*;
025    import javax.xml.transform.dom.DOMResult;
026    import javax.xml.transform.dom.DOMSource;
027    import java.io.*;
028    import java.util.*;
029    
030    /**
031     * Some XML parsing, validation and transform utility methods used
032     * to valiate XMLA responses.
033     *
034     * @author Richard M. Emberson
035     * @version $Id: //open/mondrian/src/main/mondrian/tui/XmlUtil.java#13 $
036     */
037    public class XmlUtil {
038    
039        public static final String LINE_SEP =
040                System.getProperty("line.separator", "\n");
041    
042    
043        public static final String SOAP_PREFIX = "SOAP-ENV";
044        public static final String XSD_PREFIX = "xsd";
045    
046        public static final String XMLNS = "xmlns";
047    
048        public static final String NAMESPACES_FEATURE_ID =
049                  "http://xml.org/sax/features/namespaces";
050        public static final String VALIDATION_FEATURE_ID =
051                  "http://xml.org/sax/features/validation";
052    
053        public static final String SCHEMA_VALIDATION_FEATURE_ID =
054                  "http://apache.org/xml/features/validation/schema";
055        public static final String FULL_SCHEMA_VALIDATION_FEATURE_ID =
056                  "http://apache.org/xml/features/validation/schema-full-checking";
057    
058        public static final String DEFER_NODE_EXPANSION =
059                  "http://apache.org/xml/features/dom/defer-node-expansion";
060    
061        public static final String SCHEMA_LOCATION =
062                    Constants.XERCES_PROPERTY_PREFIX + Constants.SCHEMA_LOCATION;
063    
064        /**
065         * This is the xslt that can extract the "data" part of a SOAP
066         * XMLA response.
067         */
068        public static final String getSoapXmlaXds2xd(String xmlaPrefix) {
069            return
070            "<?xml version='1.0'?>" + LINE_SEP +
071            "<xsl:stylesheet " + LINE_SEP +
072                "xmlns:xsl='http://www.w3.org/1999/XSL/Transform' " + LINE_SEP +
073                "xmlns:xalan='http://xml.apache.org/xslt' " + LINE_SEP +
074                "xmlns:xsd='http://www.w3.org/2001/XMLSchema' " + LINE_SEP +
075                "xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset' " + LINE_SEP +
076                "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' " + LINE_SEP +
077                "xmlns:xmla='urn:schemas-microsoft-com:xml-analysis' " + LINE_SEP +
078                "version='1.0' " + LINE_SEP +
079                    "> " + LINE_SEP +
080            "<xsl:output method='xml'  " + LINE_SEP +
081                "encoding='UTF-8' " + LINE_SEP +
082                "indent='yes'  " + LINE_SEP +
083                "xalan:indent-amount='2'/> " + LINE_SEP +
084                "  " + LINE_SEP +
085                "<!-- consume '/' and apply --> " + LINE_SEP +
086                "<xsl:template match='/'> " + LINE_SEP +
087                    "<xsl:apply-templates/> " + LINE_SEP +
088                "</xsl:template> " + LINE_SEP +
089                "<!-- consume 'Envelope' and apply --> " + LINE_SEP +
090                "<xsl:template match='SOAP-ENV:Envelope'> " + LINE_SEP +
091                    "<xsl:apply-templates/> " + LINE_SEP +
092                "</xsl:template> " + LINE_SEP +
093                "<!-- consume 'Header' --> " + LINE_SEP +
094                "<xsl:template match='SOAP-ENV:Header'> " + LINE_SEP +
095                "</xsl:template> " + LINE_SEP +
096                "<!-- consume 'Body' and apply --> " + LINE_SEP +
097                "<xsl:template match='SOAP-ENV:Body'> " + LINE_SEP +
098                    "<xsl:apply-templates/> " + LINE_SEP +
099                "</xsl:template> " + LINE_SEP +
100                "<!-- consume 'DiscoverResponse' and apply --> " + LINE_SEP +
101                "<xsl:template match='" + xmlaPrefix + ":DiscoverResponse'> " + LINE_SEP +
102                    "<xsl:apply-templates/> " + LINE_SEP +
103                "</xsl:template> " + LINE_SEP +
104                "<!-- consume 'return' and apply --> " + LINE_SEP +
105                "<xsl:template match='" + xmlaPrefix + ":return'> " + LINE_SEP +
106                    "<xsl:apply-templates/> " + LINE_SEP +
107                "</xsl:template> " + LINE_SEP +
108                "<!-- consume 'xsd:schema' --> " + LINE_SEP +
109                "<xsl:template match='xsd:schema'> " + LINE_SEP +
110                "</xsl:template> " + LINE_SEP +
111                "<!-- copy everything else --> " + LINE_SEP +
112                "<xsl:template match='*|@*'> " + LINE_SEP +
113                    "<xsl:copy> " + LINE_SEP +
114                        "<xsl:apply-templates select='@*|node()'/> " + LINE_SEP +
115                    "</xsl:copy> " + LINE_SEP +
116                "</xsl:template> " + LINE_SEP +
117            "</xsl:stylesheet>";
118        }
119    
120        /**
121         * This is the xslt that can extract the "schema" part of a SOAP
122         * XMLA response.
123         */
124        public static final String getSoapXmlaXds2xs(String xmlaPrefix) {
125            return
126            "<?xml version='1.0'?> " + LINE_SEP +
127            "<xsl:stylesheet  " + LINE_SEP +
128                "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'  " + LINE_SEP +
129                "xmlns:xalan='http://xml.apache.org/xslt' " + LINE_SEP +
130                "xmlns:xsd='http://www.w3.org/2001/XMLSchema' " + LINE_SEP +
131                "xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset' " + LINE_SEP +
132                "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'  " + LINE_SEP +
133                "xmlns:xmla='urn:schemas-microsoft-com:xml-analysis' " + LINE_SEP +
134                "version='1.0' " + LINE_SEP +
135                    "> " + LINE_SEP +
136            "<xsl:output method='xml'  " + LINE_SEP +
137                "encoding='UTF-8' " + LINE_SEP +
138                "indent='yes'  " + LINE_SEP +
139                "xalan:indent-amount='2'/> " + LINE_SEP +
140                "<!-- consume '/' and apply --> " + LINE_SEP +
141                "<xsl:template match='/'> " + LINE_SEP +
142                    "<xsl:apply-templates/> " + LINE_SEP +
143                "</xsl:template> " + LINE_SEP +
144                "<!-- consume 'Envelope' and apply --> " + LINE_SEP +
145                "<xsl:template match='SOAP-ENV:Envelope'> " + LINE_SEP +
146                    "<xsl:apply-templates/> " + LINE_SEP +
147                "</xsl:template> " + LINE_SEP +
148                "<!-- consume 'Header' --> " + LINE_SEP +
149                "<xsl:template match='SOAP-ENV:Header'> " + LINE_SEP +
150                "</xsl:template> " + LINE_SEP +
151                "<!-- consume 'Body' and apply --> " + LINE_SEP +
152                "<xsl:template match='SOAP-ENV:Body'> " + LINE_SEP +
153                    "<xsl:apply-templates/> " + LINE_SEP +
154                "</xsl:template> " + LINE_SEP +
155                "<!-- consume 'DiscoverResponse' and apply --> " + LINE_SEP +
156                "<xsl:template match='" + xmlaPrefix + ":DiscoverResponse'> " + LINE_SEP +
157                    "<xsl:apply-templates/> " + LINE_SEP +
158                "</xsl:template> " + LINE_SEP +
159                "<!-- consume 'return' and apply --> " + LINE_SEP +
160                "<xsl:template match='" + xmlaPrefix + ":return'> " + LINE_SEP +
161                    "<xsl:apply-templates/> " + LINE_SEP +
162                "</xsl:template> " + LINE_SEP +
163                "<!-- consume 'root' and apply --> " + LINE_SEP +
164                "<xsl:template match='ROW:root'> " + LINE_SEP +
165                    "<xsl:apply-templates/> " + LINE_SEP +
166                "</xsl:template> " + LINE_SEP +
167                "<!-- consume 'row' --> " + LINE_SEP +
168                "<xsl:template match='ROW:row'> " + LINE_SEP +
169                "</xsl:template> " + LINE_SEP +
170                "<!-- copy everything else --> " + LINE_SEP +
171                "<xsl:template match='*|@*'> " + LINE_SEP +
172                    "<xsl:copy> " + LINE_SEP +
173                        "<xsl:apply-templates select='@*|node()'/> " + LINE_SEP +
174                    "</xsl:copy> " + LINE_SEP +
175                "</xsl:template> " + LINE_SEP +
176            "</xsl:stylesheet>";
177        }
178    
179        /**
180         * This is the xslt that can extract the "data" part of a XMLA response.
181         */
182        public static final String getXmlaXds2xd(String ns) {
183            String xmlaPrefix = (ns == null) ? "" : (ns + ":");
184            return
185            "<?xml version='1.0'?>" + LINE_SEP +
186            "<xsl:stylesheet " + LINE_SEP +
187                "xmlns:xsl='http://www.w3.org/1999/XSL/Transform' " + LINE_SEP +
188                "xmlns:xalan='http://xml.apache.org/xslt' " + LINE_SEP +
189                "xmlns:xsd='http://www.w3.org/2001/XMLSchema' " + LINE_SEP +
190                "xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset' " + LINE_SEP +
191                "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' " + LINE_SEP +
192                "xmlns:xmla='urn:schemas-microsoft-com:xml-analysis' " + LINE_SEP +
193                "version='1.0' " + LINE_SEP +
194                    "> " + LINE_SEP +
195            "<xsl:output method='xml'  " + LINE_SEP +
196                "encoding='UTF-8' " + LINE_SEP +
197                "indent='yes'  " + LINE_SEP +
198                "xalan:indent-amount='2'/> " + LINE_SEP +
199                "  " + LINE_SEP +
200                "<!-- consume '/' and apply --> " + LINE_SEP +
201                "<xsl:template match='/'> " + LINE_SEP +
202                    "<xsl:apply-templates/> " + LINE_SEP +
203                "</xsl:template> " + LINE_SEP +
204                "<!-- consume 'DiscoverResponse' and apply --> " + LINE_SEP +
205                "<xsl:template match='" + xmlaPrefix + "DiscoverResponse'> " + LINE_SEP +
206                    "<xsl:apply-templates/> " + LINE_SEP +
207                "</xsl:template> " + LINE_SEP +
208                "<!-- consume 'return' and apply --> " + LINE_SEP +
209                "<xsl:template match='" + xmlaPrefix + "return'> " + LINE_SEP +
210                    "<xsl:apply-templates/> " + LINE_SEP +
211                "</xsl:template> " + LINE_SEP +
212                "<!-- consume 'xsd:schema' --> " + LINE_SEP +
213                "<xsl:template match='xsd:schema'> " + LINE_SEP +
214                "</xsl:template> " + LINE_SEP +
215                "<!-- copy everything else --> " + LINE_SEP +
216                "<xsl:template match='*|@*'> " + LINE_SEP +
217                    "<xsl:copy> " + LINE_SEP +
218                        "<xsl:apply-templates select='@*|node()'/> " + LINE_SEP +
219                    "</xsl:copy> " + LINE_SEP +
220                "</xsl:template> " + LINE_SEP +
221            "</xsl:stylesheet>";
222        }
223    
224        /**
225         * This is the xslt that can extract the "schema" part of a XMLA response.
226         */
227        public static final String getXmlaXds2xs(String ns) {
228            String xmlaPrefix = (ns == null) ? "" : (ns + ":");
229            return
230            "<?xml version='1.0'?> " + LINE_SEP +
231            "<xsl:stylesheet  " + LINE_SEP +
232                "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'  " + LINE_SEP +
233                "xmlns:xalan='http://xml.apache.org/xslt' " + LINE_SEP +
234                "xmlns:xsd='http://www.w3.org/2001/XMLSchema' " + LINE_SEP +
235                "xmlns:ROW='urn:schemas-microsoft-com:xml-analysis:rowset' " + LINE_SEP +
236                "xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'  " + LINE_SEP +
237                "xmlns:xmla='urn:schemas-microsoft-com:xml-analysis' " + LINE_SEP +
238                "version='1.0' " + LINE_SEP +
239                    "> " + LINE_SEP +
240            "<xsl:output method='xml'  " + LINE_SEP +
241                "encoding='UTF-8' " + LINE_SEP +
242                "indent='yes'  " + LINE_SEP +
243                "xalan:indent-amount='2'/> " + LINE_SEP +
244                "<!-- consume '/' and apply --> " + LINE_SEP +
245                "<xsl:template match='/'> " + LINE_SEP +
246                    "<xsl:apply-templates/> " + LINE_SEP +
247                "</xsl:template> " + LINE_SEP +
248                "<!-- consume 'DiscoverResponse' and apply --> " + LINE_SEP +
249                "<xsl:template match='" + xmlaPrefix + "DiscoverResponse'> " + LINE_SEP +
250                    "<xsl:apply-templates/> " + LINE_SEP +
251                "</xsl:template> " + LINE_SEP +
252                "<!-- consume 'return' and apply --> " + LINE_SEP +
253                "<xsl:template match='" + xmlaPrefix + "return'> " + LINE_SEP +
254                    "<xsl:apply-templates/> " + LINE_SEP +
255                "</xsl:template> " + LINE_SEP +
256                "<!-- consume 'root' and apply --> " + LINE_SEP +
257                "<xsl:template match='ROW:root'> " + LINE_SEP +
258                    "<xsl:apply-templates/> " + LINE_SEP +
259                "</xsl:template> " + LINE_SEP +
260                "<!-- consume 'row' --> " + LINE_SEP +
261                "<xsl:template match='ROW:row'> " + LINE_SEP +
262                "</xsl:template> " + LINE_SEP +
263                "<!-- copy everything else --> " + LINE_SEP +
264                "<xsl:template match='*|@*'> " + LINE_SEP +
265                    "<xsl:copy> " + LINE_SEP +
266                        "<xsl:apply-templates select='@*|node()'/> " + LINE_SEP +
267                    "</xsl:copy> " + LINE_SEP +
268                "</xsl:template> " + LINE_SEP +
269            "</xsl:stylesheet>";
270        }
271    
272        /**
273         * Error handler plus helper methods.
274         */
275        public static class SAXErrorHandler implements ErrorHandler {
276            public static final String WARNING_STRING        = "WARNING";
277            public static final String ERROR_STRING          = "ERROR";
278            public static final String FATAL_ERROR_STRING    = "FATAL";
279    
280            // DOMError values
281            public static final short SEVERITY_WARNING      = 1;
282            public static final short SEVERITY_ERROR        = 2;
283            public static final short SEVERITY_FATAL_ERROR  = 3;
284    
285            public void printErrorInfos(PrintStream out) {
286                if (errors != null) {
287                    for (ErrorInfo error : errors) {
288                        out.println(formatErrorInfo(error));
289                    }
290                }
291            }
292    
293            public static String formatErrorInfos(SAXErrorHandler saxEH) {
294                if (! saxEH.hasErrors()) {
295                    return "";
296                }
297                StringBuilder buf = new StringBuilder(512);
298                for (ErrorInfo error : saxEH.getErrors()) {
299                    buf.append(formatErrorInfo(error));
300                    buf.append(LINE_SEP);
301                }
302                return buf.toString();
303            }
304    
305            public static String formatErrorInfo(ErrorInfo ei) {
306                StringBuilder buf = new StringBuilder(128);
307                buf.append("[");
308                switch (ei.severity) {
309                case SEVERITY_WARNING:
310                    buf.append(WARNING_STRING);
311                    break;
312                case SEVERITY_ERROR:
313                    buf.append(ERROR_STRING);
314                    break;
315                case SEVERITY_FATAL_ERROR:
316                    buf.append(FATAL_ERROR_STRING);
317                    break;
318                }
319                buf.append(']');
320                String systemId = ei.exception.getSystemId();
321                if (systemId != null) {
322                    int index = systemId.lastIndexOf('/');
323                    if (index != -1) {
324                        systemId = systemId.substring(index + 1);
325                    }
326                    buf.append(systemId);
327                }
328                buf.append(':');
329                buf.append(ei.exception.getLineNumber());
330                buf.append(':');
331                buf.append(ei.exception.getColumnNumber());
332                buf.append(": ");
333                buf.append(ei.exception.getMessage());
334                return buf.toString();
335            }
336            public static class ErrorInfo {
337                public SAXParseException exception;
338                public short severity;
339                ErrorInfo(short severity, SAXParseException exception) {
340                    this.severity = severity;
341                    this.exception = exception;
342                }
343            }
344            private List<ErrorInfo> errors;
345            public SAXErrorHandler() {
346            }
347            public List<ErrorInfo> getErrors() {
348                return this.errors;
349            }
350            public boolean hasErrors() {
351                return (this.errors != null);
352            }
353            public void warning(SAXParseException exception) throws SAXException {
354                addError(new ErrorInfo(SEVERITY_WARNING, exception));
355            }
356            public void error(SAXParseException exception) throws SAXException {
357                addError(new ErrorInfo(SEVERITY_ERROR, exception));
358            }
359            public void fatalError(SAXParseException exception)
360                                                            throws SAXException {
361                addError(new ErrorInfo(SEVERITY_FATAL_ERROR, exception));
362            }
363            protected void addError(ErrorInfo ei) {
364                if (this.errors == null) {
365                    this.errors = new ArrayList<ErrorInfo>();
366                }
367                this.errors.add(ei);
368            }
369    
370            public String getFirstError() {
371                return (hasErrors())
372                    ? formatErrorInfo(errors.get(0))
373                    : "";
374            }
375        }
376    
377        public static Document newDocument(Node firstElement, boolean deepcopy) {
378            Document newDoc = new DocumentImpl();
379            newDoc.appendChild(newDoc.importNode(firstElement, deepcopy));
380            return newDoc;
381        }
382    
383    
384        //////////////////////////////////////////////////////////////////////////
385        // parse
386        //////////////////////////////////////////////////////////////////////////
387    
388        /**
389         * Get your non-cached DOM parser which can be configured to do schema
390         * based validation of the instance Document.
391         *
392         */
393        public static DOMParser getParser(
394                String schemaLocationPropertyValue,
395                EntityResolver entityResolver,
396                boolean validate)
397                throws SAXNotRecognizedException, SAXNotSupportedException {
398    
399            boolean doingValidation =
400                (validate || (schemaLocationPropertyValue != null));
401    
402            DOMParser parser = new DOMParser();
403    
404            parser.setEntityResolver(entityResolver);
405            parser.setErrorHandler(new SAXErrorHandler());
406            parser.setFeature(DEFER_NODE_EXPANSION, false);
407            parser.setFeature(NAMESPACES_FEATURE_ID, true);
408            parser.setFeature(SCHEMA_VALIDATION_FEATURE_ID, doingValidation);
409            parser.setFeature(VALIDATION_FEATURE_ID, doingValidation);
410    
411            if (schemaLocationPropertyValue != null) {
412                parser.setProperty(SCHEMA_LOCATION,
413                    schemaLocationPropertyValue.replace('\\', '/'));
414            }
415    
416            return parser;
417        }
418    
419        /**
420         * See if the DOMParser after parsing a Document has any errors and,
421         * if so, throw a RuntimeException exception containing the errors.
422         *
423         */
424        private static void checkForParseError(final DOMParser parser, Throwable t) {
425            final ErrorHandler errorHandler = parser.getErrorHandler();
426    
427            if (errorHandler instanceof SAXErrorHandler) {
428                final SAXErrorHandler saxEH = (SAXErrorHandler) errorHandler;
429                final List<SAXErrorHandler.ErrorInfo> errors = saxEH.getErrors();
430    
431                if (errors != null && errors.size() > 0) {
432                    String errorStr = SAXErrorHandler.formatErrorInfos(saxEH);
433                    throw new RuntimeException(errorStr, t);
434                }
435            } else {
436                System.out.println("errorHandler=" + errorHandler);
437            }
438        }
439    
440        private static void checkForParseError(final DOMParser parser) {
441            checkForParseError(parser, null);
442        }
443    
444    
445        /**
446         * Parse a String into a Document (no validation).
447         *
448         */
449        public static Document parseString(String s)
450                throws SAXException, IOException {
451            // Hack to workaround bug #622 until 1.4.2_08
452            final int length = s.length();
453    
454            if (length > 16777216 && length % 4 == 1) {
455                final StringBuilder buf = new StringBuilder(length + 1);
456    
457                buf.append(s);
458                buf.append('\n');
459                s = buf.toString();
460            }
461    
462            return XmlUtil.parse(s.getBytes());
463        }
464    
465        /**
466         * Parse a byte array into a Document (no validation).
467         *
468         */
469        public static Document parse(byte[] bytes)
470                throws SAXException, IOException {
471            return XmlUtil.parse(new ByteArrayInputStream(bytes));
472        }
473    
474        public static Document parse(File file)
475                throws SAXException, IOException {
476    
477            return parse(new FileInputStream(file));
478        }
479    
480        /**
481         * Parse a stream into a Document (no validation).
482         *
483         */
484        public static Document parse(InputStream in)
485                throws SAXException, IOException {
486    
487            InputSource source = new InputSource(in);
488    
489            DOMParser parser = XmlUtil.getParser(null, null, false);
490            try {
491                parser.parse(source);
492                checkForParseError(parser);
493            } catch (SAXParseException ex) {
494                checkForParseError(parser, ex);
495            }
496    
497            Document document = parser.getDocument();
498            return document;
499        }
500    
501    
502        //////////////////////////////////////////////////////////////////////////
503        // xpath
504        //////////////////////////////////////////////////////////////////////////
505        /**
506         * Create a context document for use in performing XPath operations.
507         * An array of prefix/namespace-urls are provided as input. These
508         * namespace-urls should be all of those that will appear in the
509         * document against which an xpath is to be applied.
510         * Importantly, it is, in fact, each element of the Document that
511         * has a default namespace these namespaces MUST have
512         * prefix/namespace-urls pairs in the context document and the prefix
513         * provided MUST also be used in the xpath.
514         * Elements with explicit namespaces don't have to have pairs in the
515         * context Document as long as the xpath uses the same prefixes
516         * that appear in the target Document.
517         *
518         */
519        public static Document createContextDocument(String[][] nsArray)
520                throws SAXException, IOException {
521            StringBuilder buf = new StringBuilder(256);
522            buf.append("<?xml version='1.0' encoding='utf-8'?>");
523            buf.append("<DOES_NOT_MATTER");
524            for (int i = 0; i < nsArray.length; i++) {
525                String prefix = nsArray[i][0];
526                String nsURI = nsArray[i][1];
527    
528                buf.append(" xmlns:");
529                buf.append(prefix);
530                buf.append("=\"");
531                buf.append(nsURI);
532                buf.append("\"");
533            }
534            buf.append(" />");
535    
536            String docStr = buf.toString();
537            return parseString(docStr);
538        }
539    
540        public static String makeSoapPath() {
541            return XmlUtil.makeSoapPath(SOAP_PREFIX);
542        }
543    
544        // '/soapX:Envelope/soapX:Body/*'
545        public static String makeSoapPath(String prefix) {
546            StringBuilder buf = new StringBuilder(20);
547            buf.append('/');
548            if (prefix != null) {
549                buf.append(prefix);
550                buf.append(':');
551            }
552            buf.append("Envelope");
553            buf.append('/');
554            if (prefix != null) {
555                buf.append(prefix);
556                buf.append(':');
557            }
558            buf.append("Body");
559            buf.append('/');
560            buf.append('*');
561    
562            return buf.toString();
563        }
564    
565        public static String makeRootPathInSoapBody() {
566            return makeRootPathInSoapBody("xmla", XSD_PREFIX);
567        }
568    
569        // '/xmla:DiscoverResponse/xmla:return/ROW/root/*'
570        public static String makeRootPathInSoapBody(
571            String xmlaPrefix,
572            String xsdPrefix)
573        {
574            StringBuilder buf = new StringBuilder(20);
575            buf.append("/").append(xmlaPrefix).append(":DiscoverResponse");
576            buf.append("/").append(xmlaPrefix).append(":return");
577            buf.append("/ROW:root");
578            buf.append('/');
579            buf.append('*');
580    /*
581            if (xsdPrefix != null) {
582                buf.append(xsdPrefix);
583                buf.append(':');
584            }
585            buf.append("schema");
586    */
587    
588            return buf.toString();
589        }
590    
591        public static String selectAsString(Node node, String xpath)
592                                                throws XPathException {
593            return XmlUtil.selectAsString(node, xpath, node);
594        }
595        public static String selectAsString(Node node, String xpath,
596                            Node namespaceNode) throws XPathException {
597    
598            XPathResult xpathResult = XmlUtil.select(node, xpath, namespaceNode);
599            return XmlUtil.convertToString(xpathResult, false);
600        }
601    
602        public static Node[] selectAsNodes(Node node, String xpath)
603                                                throws XPathException {
604            return XmlUtil.selectAsNodes(node, xpath, node);
605        }
606        public static Node[] selectAsNodes(Node node, String xpath,
607                            Node namespaceNode) throws XPathException {
608    
609            XPathResult xpathResult = XmlUtil.select(node, xpath, namespaceNode);
610            return XmlUtil.convertToNodes(xpathResult);
611        }
612    
613        public static XPathResult select(Node contextNode, String xpath,
614                            Node namespaceNode) throws XPathException {
615            XPathEvaluator evaluator = new XPathEvaluatorImpl();
616            XPathNSResolver resolver = evaluator.createNSResolver(namespaceNode);
617    
618            return (XPathResult) evaluator.evaluate(xpath, contextNode, resolver,
619                    XPathResult.ANY_TYPE, null);
620        }
621    
622        /**
623         * Convert an XPathResult object to String.
624         *
625         */
626        public static String convertToString(XPathResult xpathResult,
627                                             boolean prettyPrint) {
628            switch (xpathResult.getResultType()) {
629            case XPathResult.NUMBER_TYPE:
630                double d = xpathResult.getNumberValue();
631                return Double.toString(d);
632    
633            case XPathResult.STRING_TYPE:
634                String s = xpathResult.getStringValue();
635                return s;
636    
637            case XPathResult.BOOLEAN_TYPE:
638                boolean b = xpathResult.getBooleanValue();
639                return String.valueOf(b);
640    
641            case XPathResult.FIRST_ORDERED_NODE_TYPE:
642            case XPathResult.ANY_UNORDERED_NODE_TYPE:
643            {
644                Node node = xpathResult.getSingleNodeValue();
645                return XmlUtil.toString(node, prettyPrint);
646            }
647            case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
648            case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
649            {
650                StringBuilder buf = new StringBuilder(512);
651                Node node = xpathResult.iterateNext();
652                while (node != null) {
653                    buf.append(XmlUtil.toString(node, prettyPrint));
654                    node = xpathResult.iterateNext();
655                }
656                return buf.toString();
657            }
658            case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
659            case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
660            {
661                StringBuilder buf = new StringBuilder(512);
662                int len = xpathResult.getSnapshotLength();
663                for (int i = 0; i < len; i++) {
664                    Node node = xpathResult.snapshotItem(i);
665                    buf.append(XmlUtil.toString(node, prettyPrint));
666                }
667                return buf.toString();
668            }
669            default:
670                String msg = "Unknown xpathResult.type = "
671                            + xpathResult.getResultType();
672                throw new XPathException(XPathException.TYPE_ERR ,msg);
673            }
674        }
675    
676        private static final Node[] NULL_NODE_ARRAY = new Node[0];
677    
678        /**
679         * Convert an XPathResult to an array of Nodes.
680         *
681         */
682        public static Node[] convertToNodes(XPathResult xpathResult) {
683            switch (xpathResult.getResultType()) {
684            case XPathResult.NUMBER_TYPE:
685                return NULL_NODE_ARRAY;
686    
687            case XPathResult.STRING_TYPE:
688                return NULL_NODE_ARRAY;
689    
690            case XPathResult.BOOLEAN_TYPE:
691                return NULL_NODE_ARRAY;
692    
693            case XPathResult.FIRST_ORDERED_NODE_TYPE:
694            case XPathResult.ANY_UNORDERED_NODE_TYPE:
695            {
696                Node node = xpathResult.getSingleNodeValue();
697                return new Node[] { node };
698            }
699            case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
700            case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
701            {
702                List<Node> list = new ArrayList<Node>();
703                Node node = xpathResult.iterateNext();
704                while (node != null) {
705                    list.add(node);
706                    node = xpathResult.iterateNext();
707                }
708                return (Node[]) list.toArray(NULL_NODE_ARRAY);
709            }
710            case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
711            case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
712            {
713                int len = xpathResult.getSnapshotLength();
714                Node[] nodes = new Node[len];
715                for (int i = 0; i < len; i++) {
716                    Node node = xpathResult.snapshotItem(i);
717                    nodes[i] = node;
718                }
719                return nodes;
720            }
721            default:
722                String msg = "Unknown xpathResult.type = "
723                            + xpathResult.getResultType();
724                throw new XPathException(XPathException.TYPE_ERR ,msg);
725            }
726        }
727    
728        /**
729         * Convert a Node to a String.
730         *
731         */
732        public static String toString(Node node, boolean prettyPrint) {
733            if (node == null) {
734                return null;
735            }
736            try {
737                Document doc = node.getOwnerDocument();
738                OutputFormat format  = null;
739                if (doc != null) {
740                    format = new OutputFormat(doc, null, prettyPrint);
741                } else {
742                    format = new OutputFormat("xml", null, prettyPrint);
743                }
744                if (prettyPrint) {
745                    format.setLineSeparator(LINE_SEP);
746                } else {
747                    format.setLineSeparator("");
748                }
749                StringWriter writer = new StringWriter(1000);
750                XMLSerializer serial = new XMLSerializer(writer, format);
751                serial.asDOMSerializer();
752                if (node instanceof Document) {
753                    serial.serialize((Document) node);
754                } else if (node instanceof Element) {
755                    format.setOmitXMLDeclaration(true);
756                    serial.serialize((Element) node);
757                } else if (node instanceof DocumentFragment) {
758                    format.setOmitXMLDeclaration(true);
759                    serial.serialize((DocumentFragment) node);
760                } else if (node instanceof Text) {
761                    Text text = (Text) node;
762                    return text.getData();
763                } else if (node instanceof Attr) {
764                    Attr attr = (Attr) node;
765                    String name = attr.getName();
766                    String value = attr.getValue();
767                    writer.write(name);
768                    writer.write("=\"");
769                    writer.write(value);
770                    writer.write("\"");
771                    if (prettyPrint) {
772                        writer.write(LINE_SEP);
773                    }
774                } else {
775                    writer.write("node class = " + node.getClass().getName());
776                    if (prettyPrint) {
777                        writer.write(LINE_SEP);
778                    } else {
779                        writer.write(' ');
780                    }
781                    writer.write("XmlUtil.toString: fix me: ");
782                    writer.write(node.toString());
783                    if (prettyPrint) {
784                        writer.write(LINE_SEP);
785                    }
786                }
787                return writer.toString();
788            } catch (Exception ex) {
789                // ignore
790                return null;
791            }
792        }
793    
794        //////////////////////////////////////////////////////////////////////////
795        // validate
796        //////////////////////////////////////////////////////////////////////////
797    
798        /**
799         * This can be extened to have a map from publicId/systemId
800         * to InputSource.
801         */
802        public static class Resolver implements EntityResolver {
803            private InputSource source;
804    
805            protected Resolver() {
806                this((InputSource) null);
807            }
808            public Resolver(Document doc) {
809                this(XmlUtil.toString(doc, false));
810            }
811            public Resolver(String str) {
812                this(new InputSource(new StringReader(str)));
813            }
814            public Resolver(InputSource source) {
815                this.source = source;
816            }
817            public InputSource resolveEntity(String publicId,
818                                             String systemId)
819                    throws SAXException, IOException {
820                return source;
821            }
822        }
823    
824        /**
825         * Get the Xerces version being used.
826         *
827         * @return Xerces version being used
828         */
829        public static String getXercesVersion() {
830            try {
831                return org.apache.xerces.impl.Version.getVersion();
832            } catch (java.lang.NoClassDefFoundError ex) {
833                return "Xerces-J 2.2.0";
834            }
835        }
836    
837        /**
838         * Get the number part of the Xerces Version string.
839         *
840         * @return number part of the Xerces Version string
841         */
842        public static String getXercesVersionNumberString() {
843            String version = getXercesVersion();
844            int index = version.indexOf(' ');
845            return (index == -1)
846                ? "0.0.0" : version.substring(index + 1);
847        }
848    
849        private static int[] versionNumbers = null;
850    
851        /**
852         * Gets the Xerces version numbers as a three part array of ints where
853         * the first element is the major release number, the second is the
854         * minor release number, and the third is the patch number.
855         *
856         * @return Xerces version number as int array
857         */
858        public static synchronized int[] getXercesVersionNumbers() {
859            if (versionNumbers == null) {
860                int[] verNums = new int[3];
861                String verNumStr = getXercesVersionNumberString();
862                int index = verNumStr.indexOf('.');
863                verNums[0] = Integer.parseInt(verNumStr.substring(0, index));
864    
865                verNumStr = verNumStr.substring(index + 1);
866                index = verNumStr.indexOf('.');
867                verNums[1] = Integer.parseInt(verNumStr.substring(0, index));
868    
869                verNumStr = verNumStr.substring(index + 1);
870                verNums[2] = Integer.parseInt(verNumStr);
871    
872                versionNumbers = verNums;
873            }
874    
875            return versionNumbers;
876    
877        }
878    
879        /**
880         * I could not get Xerces 2.2 to validate. So, I hard coded allowing
881         * Xerces 2.6 and above to validate and by setting the following
882         * System property one can test validating with earlier versions
883         * of Xerces.
884         */
885        private static final String ALWAYS_ATTEMPT_VALIDATION =
886                    "mondrian.xml.always.attempt.validation";
887        private static final boolean alwaysAttemptValidation;
888        static {
889            alwaysAttemptValidation =
890                Boolean.getBoolean(ALWAYS_ATTEMPT_VALIDATION);
891        }
892    
893        /**
894         * Returns whether the XML parser supports validation.
895         *
896         * <p>I could not get validation to work with Xerces 2.2 so I put in
897         * this check. If you want to test on an earlier version of Xerces
898         * simply define the above property:
899         *   "mondrian.xml.always.attempt.validation",
900         * to true.
901         *
902         * @return whether the XML parser supports validation
903         */
904        public static boolean supportsValidation() {
905            if (alwaysAttemptValidation) {
906                return true;
907            } else {
908                int[] verNums = getXercesVersionNumbers();
909                return ((verNums[0] >= 3) ||
910                    ((verNums[0] == 2) && (verNums[1] >= 6)));
911            }
912        }
913    
914        public static void validate(
915            Document doc,
916            String schemaLocationPropertyValue,
917            EntityResolver resolver)
918            throws IOException,
919            SAXException
920        {
921    
922            OutputFormat format  = new OutputFormat(doc, null, true);
923            StringWriter writer = new StringWriter(1000);
924            XMLSerializer serial = new XMLSerializer(writer, format);
925            serial.asDOMSerializer();
926            serial.serialize(doc);
927            String docString = writer.toString();
928    
929            validate(docString, schemaLocationPropertyValue, resolver);
930        }
931    
932        public static void validate(
933            String docStr,
934            String schemaLocationPropertyValue,
935            EntityResolver resolver)
936            throws IOException ,
937            SAXException
938        {
939            if (resolver == null) {
940                resolver = new Resolver();
941            }
942            DOMParser parser =
943                getParser(schemaLocationPropertyValue, resolver, true);
944    
945            try {
946                parser.parse(new InputSource(new StringReader(docStr)));
947                checkForParseError(parser);
948            } catch (SAXParseException ex) {
949                checkForParseError(parser, ex);
950            }
951    
952        }
953    
954        /**
955         * This is used to get a Document's namespace attribute value.
956         *
957         */
958        public static String getNamespaceAttributeValue(Document doc) {
959            Element el = doc.getDocumentElement();
960            String prefix = el.getPrefix();
961            Attr attr = (prefix == null)
962                        ? el.getAttributeNode(XMLNS)
963                        : el.getAttributeNode(XMLNS + ':' + prefix);
964            return (attr == null) ? null : attr.getValue();
965        }
966    
967        //////////////////////////////////////////////////////////////////////////
968        // transform
969        //////////////////////////////////////////////////////////////////////////
970    
971        private static TransformerFactory tfactory = null;
972        public static TransformerFactory getTransformerFactory()
973                throws TransformerFactoryConfigurationError {
974            if (tfactory == null) {
975                tfactory = TransformerFactory.newInstance();
976            }
977            return tfactory;
978        }
979    
980        /**
981         * Transform a Document and return the transformed Node.
982         *
983         */
984        public static Node transform(Document inDoc,
985            String xslFileName,
986            String[][] namevalueParameters)
987                throws ParserConfigurationException,
988                    SAXException,
989                    IOException,
990                    TransformerConfigurationException,
991                    TransformerException {
992    
993            DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
994            dfactory.setNamespaceAware(true);
995    
996            DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
997            Node xslDOM = docBuilder.parse(new InputSource(xslFileName));
998    
999            TransformerFactory tfactory = getTransformerFactory();
1000            Templates stylesheet = tfactory.newTemplates(
1001                                new DOMSource(xslDOM, xslFileName));
1002            Transformer transformer = stylesheet.newTransformer();
1003            if (namevalueParameters != null) {
1004                for (int i = 0; i < namevalueParameters.length; i++) {
1005                    String name = namevalueParameters[i][0];
1006                    String value = namevalueParameters[i][1];
1007    
1008                    transformer.setParameter(name, value);
1009                }
1010            }
1011            DOMResult domResult = new DOMResult();
1012            transformer.transform(new DOMSource(inDoc, null), domResult);
1013    
1014            return domResult.getNode();
1015        }
1016        public static Node transform(Document inDoc,
1017            String xslFileName)
1018                throws ParserConfigurationException,
1019                    SAXException,
1020                    IOException,
1021                    TransformerConfigurationException,
1022                    TransformerException {
1023            return transform(inDoc, xslFileName, null);
1024        }
1025    
1026        /**
1027         * Transform a Document and return the transformed Node.
1028         *
1029         */
1030        public static Node transform(Document inDoc,
1031            Reader xslReader,
1032            String[][] namevalueParameters)
1033                throws ParserConfigurationException,
1034                    SAXException,
1035                    IOException,
1036                    TransformerConfigurationException,
1037                    TransformerException {
1038    
1039            DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
1040            dfactory.setNamespaceAware(true);
1041    
1042            DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
1043            Node xslDOM = docBuilder.parse(new InputSource(xslReader));
1044    
1045            TransformerFactory tfactory = getTransformerFactory();
1046            Templates stylesheet = tfactory.newTemplates(new DOMSource(xslDOM));
1047            Transformer transformer = stylesheet.newTransformer();
1048            if (namevalueParameters != null) {
1049                for (int i = 0; i < namevalueParameters.length; i++) {
1050                    String name = namevalueParameters[i][0];
1051                    String value = namevalueParameters[i][1];
1052    
1053                    transformer.setParameter(name, value);
1054                }
1055            }
1056    
1057            DOMResult domResult = new DOMResult();
1058            transformer.transform(new DOMSource(inDoc, null), domResult);
1059    
1060            return domResult.getNode();
1061        }
1062        public static Node transform(Document inDoc,
1063            Reader xslReader)
1064                throws ParserConfigurationException,
1065                    SAXException,
1066                    IOException,
1067                    TransformerConfigurationException,
1068                    TransformerException {
1069            return transform(inDoc, xslReader, null);
1070        }
1071    
1072    
1073        private XmlUtil() {
1074        }
1075    }
1076    
1077    // End XmlUtil.java