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