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