1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id: ApplyXSLT.java 470245 2006-11-02 06:34:33Z minchau $ 20 */ 21 package servlet; 22 23 import java.io.*; 24 import java.util.*; 25 import javax.servlet.*; 26 import javax.servlet.http.*; 27 import java.net.URL; 28 import java.net.MalformedURLException; 29 import java.net.URLConnection; 30 import javax.xml.transform.OutputKeys; 31 32 import org.apache.xalan.templates.Constants; 33 import org.apache.xalan.templates.StylesheetRoot; 34 // SAX2 Imports 35 import org.xml.sax.ContentHandler; 36 import org.xml.sax.SAXException; 37 import org.xml.sax.XMLReader; 38 import org.xml.sax.Locator; 39 import org.xml.sax.helpers.XMLReaderFactory; 40 import org.xml.sax.ext.DeclHandler; 41 import org.xml.sax.ext.LexicalHandler; 42 import org.xml.sax.SAXNotRecognizedException; 43 import org.xml.sax.SAXNotSupportedException; 44 45 import org.w3c.dom.*; 46 import javax.xml.transform.*; 47 import javax.xml.transform.stream.*; 48 import org.apache.xalan.transformer.TransformerImpl; 49 import org.apache.xpath.objects.XObject; 50 import org.apache.xpath.objects.XString; 51 import org.apache.xalan.processor.*; 52 53 import javax.xml.parsers.DocumentBuilder; 54 import javax.xml.parsers.DocumentBuilderFactory; 55 56 import org.xml.sax.XMLReader; 57 import org.xml.sax.helpers.XMLReaderFactory; 58 import org.xml.sax.helpers.XMLFilterImpl; 59 60 /***************************************************************************************************** 61 * 62 * ApplyXSLT supplies the basic 63 * functions for transforming XML data using XSL stylesheets. 64 * 65 * @author Spencer Shepard (sshepard@us.ibm.com) 66 * @author R. Adam King (rak@us.ibm.com) 67 * @author Tom Rowe (trowe@us.ibm.com) 68 * @author Don Leslie (donald_leslie@lotus.com) 69 * 70 *****************************************************************************************************/ 71 72 public class ApplyXSLT extends HttpServlet 73 { 74 75 /** 76 * Operational parameters for this class. 77 * <p>Request-time values override init-time values which override class defaults.</p> 78 * @see #init 79 * @serial 80 */ 81 protected ApplyXSLTProperties ourDefaultParameters = null; 82 83 /** 84 * String representing the end of line characters for the System. 85 */ 86 public final static String EOL = System.getProperty("line.separator"); 87 88 /** 89 * String representing the file separator characters for the System. 90 */ 91 public final static String FS = System.getProperty("file.separator"); 92 93 /** 94 * String representing the current directory for properties files. See init(). 95 */ 96 public final static String ROOT = System.getProperty("server.root"); 97 public static String CURRENTDIR; 98 99 /** 100 * Initialize operational parameters from the configuration. 101 * @param config Configuration 102 * @exception ServletException Never thrown 103 */ init(ServletConfig config)104 public void init(ServletConfig config) 105 throws ServletException 106 { 107 super.init(config); 108 // If the server.root property --see above-- is null, use current working directory 109 // as default location for media.properties. 110 if (ROOT != null){ 111 CURRENTDIR= getServletContext().getRealPath("/WEB-INF/classes/servlet/") + FS; 112 System.out.println ( CURRENTDIR);} 113 else 114 CURRENTDIR = System.getProperty("user.dir")+ FS; 115 116 setDefaultParameters(config); 117 118 setMediaProps(config.getInitParameter("mediaURL")); 119 } 120 121 /** 122 * Sets the default parameters for the servlet from the configuration. 123 * Also sets required system properties until we figure out why servlet 124 * sometimess fails to read properties from properties files. 125 * @param config Configuration 126 */ setDefaultParameters(ServletConfig config)127 protected void setDefaultParameters(ServletConfig config) 128 { 129 ourDefaultParameters = new DefaultApplyXSLTProperties(config); 130 } 131 132 /** 133 * Loads the media properties file specified by the given string. 134 * @param mediaURLstring Location of the media properties file. Can be either a full URL or a path relative 135 * to the System's server.root /servlets directory. If this parameter is null, 136 * server.root/servlets/media.properties will be used. 137 * @see ApplyXSL#CURRENTDIR 138 */ setMediaProps(String mediaURLstring)139 protected void setMediaProps(String mediaURLstring) 140 { 141 if (mediaURLstring != null) 142 { 143 URL url = null; 144 try 145 { 146 url = new URL(mediaURLstring); 147 } 148 catch (MalformedURLException mue1) 149 { 150 try 151 { 152 url = new URL("file", "", CURRENTDIR + mediaURLstring); 153 } 154 catch (MalformedURLException mue2) 155 { 156 writeLog("Unable to find the media properties file based on parameter 'mediaURL' = " 157 + mediaURLstring, HttpServletResponse.SC_ACCEPTED, mue2); 158 url = null; 159 } 160 } 161 if (url != null) 162 { 163 try 164 { 165 ourMediaProps = new OrderedProps(url.openStream()); 166 } 167 catch (IOException ioe1) 168 { 169 writeLog("Exception occurred while opening media properties file: " + mediaURLstring + 170 ". Media table may be invalid.", HttpServletResponse.SC_ACCEPTED, ioe1); 171 } 172 } 173 } 174 else 175 { 176 String defaultProp = CURRENTDIR + "media.properties"; 177 try 178 { 179 ourMediaProps = new OrderedProps(new FileInputStream(defaultProp)); 180 } 181 catch (IOException ioe2) 182 { 183 writeLog("Default media properties file " + defaultProp + " not found.", 184 HttpServletResponse.SC_ACCEPTED, ioe2); 185 } 186 } 187 } 188 getMedia(HttpServletRequest request)189 public String getMedia(HttpServletRequest request) 190 { 191 return ourMediaProps.getValue(request.getHeader(HEADER_NAME)); 192 } 193 194 // doPost removed for security reasons due to the possibility of sending 195 // unsecure XML and XSL XSLTInputSources through the request input stream 196 197 /** 198 * HTTP Get method passed on to process(). 199 * @param request The request 200 * @param response The response 201 * @see #process 202 * @exception ServletException Never thrown 203 * @exception IOException Never thrown 204 */ doGet(HttpServletRequest request, HttpServletResponse response)205 public void doGet (HttpServletRequest request, 206 HttpServletResponse response) 207 throws ServletException, IOException 208 { 209 try 210 { 211 TransformerFactory tFactory = TransformerFactory.newInstance(); 212 process(tFactory, request, response); 213 } 214 catch (Exception e) 215 { 216 } 217 } 218 219 /** 220 * Coordinates applying an XSL stylesheet to XML data using operational parameters. 221 * <p>If successfully applied, the result tree will be streamed to the response object 222 * and the content type set according to the XSL stylesheet's <xsl:output> element(s).</p> 223 * <p>If there is a problem in parsing the XML/XSL or if there is a problem in applying 224 * the XSL to the XML, an exception will be streamed to the response object. The detail 225 * of the information returned in the response object will depend on whether we're 226 * running in debug mode or not.</p> 227 * @param processor implementation of TRaX processor 228 * @param request May contain information relevant to creating XML and XSL XSLTInputSource's 229 * @param response Where to write the transformation result 230 * @see #getDocument 231 * @see #getStylesheet 232 * @see #getContentType 233 * @see #displayException 234 * @see #setStylesheetParams 235 * @exception ServletException Never thrown 236 * @exception IOException Never thrown 237 */ 238 process(TransformerFactory tFactory, HttpServletRequest request, HttpServletResponse response)239 public void process(TransformerFactory tFactory, 240 HttpServletRequest request, 241 HttpServletResponse response) 242 throws ServletException, IOException, SAXException 243 { 244 boolean debug = ourDefaultParameters.isDebug(request); 245 246 long time = 0; 247 if (debug) 248 time = System.currentTimeMillis(); 249 250 // Listener to be used for all reporting 251 ApplyXSLTListener listener = new ApplyXSLTListener(); 252 listener.out.println("debug is " + debug); 253 254 StreamSource xmlSource = null; 255 StreamSource xslSource = null; 256 try 257 { 258 if ((xmlSource = getDocument(request, listener)) == null) 259 throw new ApplyXSLTException("getDocument() returned null", 260 new NullPointerException(), 261 response.SC_NOT_FOUND); 262 } 263 catch (ApplyXSLTException axe) 264 { 265 axe.appendMessage(EOL + "getDocument() resulted in ApplyXSLTException" + EOL 266 + listener.getMessage()); 267 if (debug) writeLog(axe); 268 displayException(response, axe, debug); 269 xmlSource = null; 270 } 271 // creating XSL Stylesheet 272 if (xmlSource != null) 273 { 274 try 275 { 276 if ((xslSource = getStylesheet(tFactory, request, xmlSource, listener)) == null) 277 { 278 throw new ApplyXSLTException("getStylesheet() returned null", 279 new NullPointerException(), 280 response.SC_NOT_FOUND); 281 } 282 // For time being, must "reset" xmlSource after extracting stylesheet PI 283 xmlSource = getDocument(request, listener); 284 } 285 catch (ApplyXSLTException axe) 286 { 287 axe.appendMessage(EOL + "getStylesheet() resulted in ApplyXSLTException" + EOL 288 + listener.getMessage()); 289 if (debug) writeLog(axe); 290 displayException(response, axe, debug); 291 xslSource = null; 292 } 293 // perform Transformation 294 295 if ((xmlSource != null) && (xslSource != null)) 296 { 297 try 298 { 299 listener.out.println("Performing transformation..."); 300 301 Templates templates = tFactory.newTemplates(xslSource); 302 Transformer transformer = templates.newTransformer(); 303 { 304 try 305 { 306 String contentType = null; 307 contentType = getContentType(templates); 308 if (contentType != null); 309 response.setContentType(contentType); 310 311 if (transformer instanceof TransformerImpl) 312 { 313 TransformerImpl transformerImpl = (TransformerImpl)transformer; 314 transformerImpl.setQuietConflictWarnings(ourDefaultParameters.isNoCW(request)); 315 } 316 317 setStylesheetParams(transformer, request); 318 transformer.transform(xmlSource, new StreamResult(response.getOutputStream())); 319 320 if (debug) 321 writeLog(listener.getMessage(), response.SC_OK); 322 } 323 catch (Exception exc) 324 { 325 ApplyXSLTException axe = new ApplyXSLTException 326 ("Exception occurred during Transformation:" 327 + EOL + listener.getMessage() + EOL 328 + exc.getMessage(), 329 exc, 330 response.SC_INTERNAL_SERVER_ERROR); 331 if (debug) writeLog(axe); 332 displayException(response, axe, debug); 333 } 334 finally 335 { 336 // transformer.reset(); 337 } // end of try ... catch ... finally 338 } 339 } 340 catch (/*org.xml.sax.SAX*/Exception saxExc) 341 { 342 ApplyXSLTException axe = new ApplyXSLTException 343 ("Exception occurred during ctor/Transformation:" 344 + EOL + listener.getMessage() + EOL 345 + saxExc.getMessage(), 346 saxExc, 347 response.SC_INTERNAL_SERVER_ERROR); 348 if (debug) writeLog(axe); 349 displayException(response, axe, debug); 350 } // end of new try ... catch 351 } // end of if((stylesheetRoot != null) ... 352 if (debug) 353 { 354 time = System.currentTimeMillis() - time; 355 writeLog(" No Conflict Warnings = " + ourDefaultParameters.isNoCW(request) + 356 " Transformation time: " + time + " ms", response.SC_OK); 357 } 358 } 359 } 360 361 /** 362 * Returns an XML XSLTInputSource DOM. Attempts will be make to create the DOM from the following 363 * sources: 364 * <ol> 365 * <li>A relative URL specified in the HTTP request's path information. This capability is intended 366 * for use by <b>servlet engines that map</b> some or all XML data to be processed at the server.</li> 367 * <li>A URL specified in the HTTP request's <code>URL=</code> parameter. This capability 368 * is intended for <b>clients wishing to selectively process</b> XML data at the server. For 369 * security reasons, this URL will be forced to the local IP host.</li> 370 * <li>The HTTP request's XML input stream. This capability is intended for use by chained servlets.</li> 371 * </ol> 372 * @param request May contain or point to the XML XSLTInputSource 373 * @param listener To record detailed parsing messages for possible return to requestor 374 * @return XML XSLTInputSource DOM, or null if the XSLTInputSource could not be parsed 375 * @exception ApplyXSLTException Thrown if exception occurs while handling request 376 */ getDocument(HttpServletRequest request, ApplyXSLTListener listener)377 protected StreamSource getDocument(HttpServletRequest request, 378 ApplyXSLTListener listener) 379 throws ApplyXSLTException 380 { 381 try 382 { 383 String xmlURL = null; 384 // document from PathInfo 385 if ((xmlURL = request.getPathInfo()) != null) 386 { 387 listener.out.println("Parsing XML Document from PathInfo: " + xmlURL); 388 return new StreamSource(new URL("http", ((DefaultApplyXSLTProperties) 389 ourDefaultParameters).getLocalHost(), 390 request.getServerPort(), 391 xmlURL.replace('\\', '/')).openStream()); 392 } 393 // document from Request parameter 394 if ((xmlURL = ourDefaultParameters.getXMLurl(request)) != null) 395 { 396 listener.out.println("Parsing XML Document from request parameter: " + xmlURL); 397 return new StreamSource(new URL(xmlURL).openStream()); 398 } 399 // document from chain 400 String contentType = request.getContentType(); 401 if ((contentType != null) && contentType.startsWith("text/xml")) 402 { 403 listener.out.println("Parsing XML Document from request chain"); 404 return new StreamSource(request.getInputStream()); 405 } 406 } 407 catch (IOException ioe) 408 { 409 throw new ApplyXSLTException(ioe, HttpServletResponse.SC_NOT_FOUND); 410 } 411 catch (Exception e) 412 { 413 throw new ApplyXSLTException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 414 } 415 return null; 416 } 417 418 /** 419 * Returns a Templates (StylesheetRoot) object. Attempts will be make to create the Stylesheet 420 * from the followingsources: 421 * <ol> 422 * <li>A URL specified in the HTTP request's <code>xslURL=</code> parameter. This capability 423 * is intended for clients wishing to selectively override the server algorithm for applying XSL 424 * stylesheets. For security reasons, this URL will be forced to the local IP host.</li> 425 * <li>XML association. XML documents may contain references to one or more stylesheets using 426 * <a HREF="http://www.w3.org/TR/1999/PR-xml-stylesheet-19990114">this</a> W3C proposed recommendation. 427 * If the XML document does contain such references, a best match will be chosen based on the browser 428 * type making the request and the default association. This capability enables relationships to be 429 * defined between client capabilities and stylesheets capable of acting on these capabilities.</li> 430 * <li>A configured default stylesheet URL</li> 431 * </ol> 432 * @param request May contain or point to the XSL XSLTInputSource 433 * @param xmlSource May point to the XSL XSLTInputSource 434 * @param listener To record detailed parsing messages for possible return to requestor 435 * @return XSL XSLTInputSource, or null if the request could not be parsed 436 * @see #makeDocument 437 * @see #getMedia 438 * @see #STYLESHEET_ATTRIBUTE 439 * @see #getXSLURLfromDoc 440 * @see #toAcceptLanguageConnection 441 * @exception ApplyXSLTException Thrown if exception occurs while handling request 442 */ getStylesheet(TransformerFactory tFactory, HttpServletRequest request, StreamSource xmlSource, ApplyXSLTListener listener)443 protected StreamSource getStylesheet(TransformerFactory tFactory, 444 HttpServletRequest request, 445 StreamSource xmlSource, 446 ApplyXSLTListener listener) 447 throws ApplyXSLTException 448 { 449 try 450 { 451 //stylesheet URL from request 452 String xslURL = ((DefaultApplyXSLTProperties) ourDefaultParameters).getXSLRequestURL(request); 453 454 if (xslURL != null) 455 listener.out.println("Parsing XSL Stylesheet Document from request parameter: " 456 + xslURL); 457 else 458 { 459 // find stylesheet from XML Document, Media tag preference 460 if (xmlSource != null){ 461 listener.out.println("calling getXSLURLfromDoc and getMedia " + getMedia(request) ); 462 xslURL = getXSLURLfromDoc(xmlSource, STYLESHEET_ATTRIBUTE, getMedia(request), tFactory); 463 } 464 if (xslURL != null) 465 listener.out.println("Parsing XSL Stylesheet Document from XML Document tag: " + xslURL); 466 else 467 // Configuration Default 468 if ((xslURL = ourDefaultParameters.getXSLurl(null)) != null) 469 listener.out.println("Parsing XSL Stylesheet Document from configuration: " + xslURL); 470 } 471 return new StreamSource(xslURL); 472 } 473 catch (IOException ioe) 474 { 475 throw new ApplyXSLTException(ioe, HttpServletResponse.SC_NOT_FOUND); 476 } 477 catch (Exception e) 478 { 479 throw new ApplyXSLTException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 480 } 481 } 482 483 /** 484 * Returns the response content type specified by the media-type and encoding attributes of 485 * the <xsl:output> element(s) of the stylesheet. 486 * @param xslSourceRoot XSL Stylesheet to be examined for <xsl:output> elements. 487 * @return The response content type (MIME type and charset) of the stylesheet output 488 * @see #process 489 */ getContentType(Templates templates)490 public String getContentType(Templates templates) 491 { 492 Properties oprops = templates.getOutputProperties(); 493 String encoding = oprops.getProperty(OutputKeys.ENCODING); 494 String media = oprops.getProperty(OutputKeys.MEDIA_TYPE); 495 if (media != null) 496 { 497 if (encoding != null) 498 return media + "; charset=" + encoding; 499 return media; 500 } 501 else 502 { 503 String method = oprops.getProperty(OutputKeys.METHOD); 504 if (method.equals("html")) 505 return "text/html"; 506 else if (method.equals("text")) 507 return "text/plain"; 508 else 509 return "text/xml"; 510 } 511 } 512 513 514 /** 515 * Defines and sets select top-level XSL stylesheet variables from the HTTP request, which 516 * can be evaluated using <xsl:param-variable>. The following variables will be 517 * automatically set: 518 * <dl> 519 * <dt><i>ParameterName</i></dt> 520 * <dd>Each non-reserved request parameter returned from request.getParameterNames(). If a 521 * parameter contains more than a single value, only the first value is available.</dd> 522 * <dt>servlet-RemoteAddr</dt> 523 * <dd>Contains String output from request.getRemoteAddr(), which is the IP address 524 * of the client machine.</dd> 525 * <dt>servlet-RemoteHost</dt> 526 * <dd>Contains String output from request.getRemoteHost(), which is the host name 527 * of the client machine.</dd> 528 * <dt>servlet-RemoteUser</dt> 529 * <dd>Contains String output from request.getRemoteUser(), which was the user name 530 * accepted by the server to grant access to this servlet.</dd> 531 * <dt>servlet-Request</dt> 532 * <dd>Contains the request object.</dd> 533 * </dl> 534 * @param xslprocessor Where to register parameters to be set 535 * @param request Provides access to all meaningful parameters to set 536 * @see #process 537 */ setStylesheetParams(Transformer transformer, HttpServletRequest request)538 public void setStylesheetParams(Transformer transformer, HttpServletRequest request) 539 { 540 Enumeration paramNames = request.getParameterNames(); 541 while (paramNames.hasMoreElements()) 542 { 543 String paramName = (String) paramNames.nextElement(); 544 try 545 { 546 String[] paramVals = request.getParameterValues(paramName); 547 if (paramVals != null) 548 transformer.setParameter(paramName, new XString(paramVals[0])); 549 550 } 551 catch (Exception e) 552 { 553 } 554 } 555 try 556 { 557 transformer.setParameter("servlet-RemoteAddr", new XString(request.getRemoteAddr())); 558 559 } 560 catch (Exception e) 561 { 562 } 563 try 564 { 565 transformer.setParameter("servlet-RemoteHost", new XString(request.getRemoteHost())); 566 567 } 568 catch (Exception e) 569 { 570 } 571 try 572 { 573 transformer.setParameter("servlet-RemoteUser", new XString(request.getRemoteUser())); 574 575 } 576 catch (Exception e) 577 { 578 } 579 } 580 581 582 /** 583 * Writes the following information to the servlet log: 584 * <ol> 585 * <li>HTTP status code</li> 586 * <li>Message</li> 587 * <li>Stack trace</li> 588 * </ol> 589 * @param axe Contains valid HTTP status code, message, and stack trace (optional) 590 */ writeLog(ApplyXSLTException axe)591 protected void writeLog(ApplyXSLTException axe) 592 { 593 writeLog(axe.getMessage(), axe.getStatusCode(), axe.getException()); 594 } 595 596 /** 597 * Writes the following information to the servlet log: 598 * <ol> 599 * <li>HTTP status code</li> 600 * <li>Message</li> 601 * <li>Stack trace</li> 602 * </ol> 603 * @param msg Message to be logged 604 * @param statusCode Valid status code from javax.servlet.http.HttpServletResponse 605 * @param t Used to generate stack trace (may be =null to suppress stack trace) 606 */ writeLog(String msg, int statusCode, Throwable t)607 protected void writeLog(String msg, int statusCode, Throwable t) 608 { 609 if (t == null) 610 writeLog(msg, statusCode); 611 else 612 { 613 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 614 PrintWriter writer = new PrintWriter(bytes, true); 615 System.out.println("Exception is " + t.getClass().getName()); 616 t.printStackTrace(writer); 617 log("HTTP Status Code: " + statusCode + " - " + msg + EOL + bytes.toString()); 618 } 619 } 620 621 /** 622 * Writes the following information to the servlet log: 623 * <ol> 624 * <li>HTTP status code</li> 625 * <li>Message</li> 626 * </ol> 627 * @param msg Message to be logged 628 * @param statusCode Valid status code from javax.servlet.http.HttpServletResponse 629 */ writeLog(String msg, int statusCode)630 protected void writeLog(String msg, int statusCode) 631 { 632 log("HTTP Status Code: " + statusCode + " - " + msg); 633 } 634 635 /** 636 * Invokes response.sendError setting an HTTP status code and optionally an error message 637 * as an HTML page. 638 * <p>If running in debug mode, also try to return a stack trace of the exception and 639 * and xml/xsl processor messages.</p> 640 * @param response Where to stream the exception to 641 * @param xse The wrapper which contains the exception and its HTTP status code 642 * @param debug Indicates whether to include stack trace, etc. 643 */ displayException(HttpServletResponse response, ApplyXSLTException xse, boolean debug)644 protected void displayException(HttpServletResponse response, ApplyXSLTException xse, boolean debug) 645 { 646 String mesg = xse.getMessage(); 647 if (mesg == null) 648 mesg = ""; 649 else mesg = "<B>" + mesg + "</B>"; 650 StringTokenizer tokens = new StringTokenizer(mesg, EOL); 651 StringBuffer strBuf = new StringBuffer(); 652 while (tokens.hasMoreTokens()) 653 strBuf.append(tokens.nextToken() + EOL + "<BR>"); 654 mesg = strBuf.toString(); 655 if (debug) 656 { 657 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 658 PrintWriter writer = new PrintWriter(bytes, true); 659 xse.getException().printStackTrace(writer); 660 mesg += " <PRE> " + bytes.toString() + " </PRE> "; 661 } 662 response.setContentType("text/html"); 663 try 664 { 665 response.sendError(xse.getStatusCode(), mesg); 666 } 667 catch (IOException ioe) 668 { 669 System.err.println("IOException is occurring when sendError is called"); 670 } 671 } 672 673 /** 674 * Mapping of HTTP request's user-Agent values to stylesheet media= values. 675 * <p>This mapping is defined by a file pointed to by the operational parameter "mediaURL" which can 676 * either contain a full URL or a path relative to the System's server.root /servlets directory.</p> 677 * @see #setMediaProps 678 * @see #getMedia 679 * @serial 680 */ 681 protected OrderedProps ourMediaProps = null; 682 683 /** 684 * Returns a connection which respects the Accept-Language header of the HTTP request. This 685 * is useful when XSL files are internationalized for use with Web servers which respect this 686 * header. 687 * <p>For example, Apache 1.3.6 may be configured for multiviews. Under this configuration, 688 * requests for http://myhost/index.html would return http://myhost/index.html.fr to French browsers 689 * and http://myhost/index.html.en to English browsers.</p> 690 * @param url Location to connect to 691 * @param request Could contain an Accept-Language header 692 * @return An Accept-Language-enabled URL connection 693 * @see #getStylesheet 694 */ toAcceptLanguageConnection(URL url, HttpServletRequest request)695 protected URLConnection toAcceptLanguageConnection(URL url, HttpServletRequest request) 696 throws Exception 697 { 698 URLConnection tempConnection = url.openConnection(); 699 tempConnection.setRequestProperty("Accept-Language", request.getHeader("Accept-Language")); 700 return tempConnection; 701 } 702 703 704 /** 705 * Returns the XSL stylesheet URL associated with the specified XML document. If multiple XSL 706 * stylesheets are associated with the XML document, preference will be given to the stylesheet 707 * which contains an attribute name/value pair that corresponds to the specified attributeName 708 * and attributeValue. 709 * @param xmlSource XML XSLTInputSource to be searched for associated XSL stylesheets 710 * @param attributeName Attribute name to provide preferential matching 711 * @param attributeValue Attribute value to provide preferential matching 712 * @return The preferred XSL stylesheet URL, or null if no XSL stylesheet association is found 713 * @see #getStylesheet 714 */ getXSLURLfromDoc(StreamSource xmlSource, String attributeName, String attributeValue, TransformerFactory tFactory)715 public static String getXSLURLfromDoc(StreamSource xmlSource, 716 String attributeName, 717 String attributeValue, 718 TransformerFactory tFactory) 719 { 720 String tempURL = null, returnURL = null; 721 try 722 { 723 DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance(); 724 DocumentBuilder docBuilder = dfactory.newDocumentBuilder(); 725 Node sourceTree = docBuilder.parse(xmlSource.getInputStream()); 726 for(Node child=sourceTree.getFirstChild(); null != child; child=child.getNextSibling()) 727 { 728 if(Node.PROCESSING_INSTRUCTION_NODE == child.getNodeType()) 729 { 730 ProcessingInstruction pi = (ProcessingInstruction)child; 731 if(pi.getNodeName().equals("xml-stylesheet")) 732 { 733 PIA pia = new PIA(pi); 734 if("text/xsl".equals(pia.getAttribute("type"))) 735 { 736 tempURL = pia.getAttribute("href"); 737 String attribute = pia.getAttribute(attributeName); 738 if ((attribute != null) && (attribute.indexOf(attributeValue) > -1)) 739 return tempURL; 740 if (!"yes".equals(pia.getAttribute("alternate"))) 741 returnURL = tempURL; 742 } 743 } 744 } 745 } 746 } 747 catch(Exception saxExc) 748 { 749 } 750 return returnURL; 751 } 752 753 /** 754 * The attribute name in the <?xml-stylesheet> tag used in stylesheet selection. 755 */ 756 protected static final String STYLESHEET_ATTRIBUTE = "media"; 757 758 /** 759 * The HTTP Header used for matching the Stylesheet attribute via the 760 * media properties file selected. 761 */ 762 protected static final String HEADER_NAME = "user-Agent"; 763 } 764 765 /** 766 * Stores the keys and values from a file (similar to a properties file) and 767 * can return the first value which has a key contained in its string. 768 * File can have comment lines starting with '#" and for each line the entries are 769 * separated by tabs and '=' char. 770 */ 771 class OrderedProps 772 { 773 774 /** 775 * Stores the Key and Values as an array of Strings 776 */ 777 private Vector attVec = new Vector(15); 778 779 /** 780 * Constructor. 781 * @param inputStream Stream containing the properties file. 782 * @exception IOException Thrown if unable to read from stream 783 */ OrderedProps(InputStream inputStream)784 OrderedProps(InputStream inputStream) 785 throws IOException 786 { 787 BufferedReader input = new BufferedReader(new InputStreamReader(inputStream)); 788 String currentLine, Key = null; 789 StringTokenizer currentTokens; 790 while ((currentLine = input.readLine()) != null) 791 { 792 currentTokens = new StringTokenizer(currentLine, "=\t\r\n"); 793 if (currentTokens.hasMoreTokens()) Key = currentTokens.nextToken().trim(); 794 if ((Key != null) && !Key.startsWith("#") && currentTokens.hasMoreTokens()) 795 { 796 String temp[] = new String[2]; 797 temp[0] = Key; temp[1] = currentTokens.nextToken().trim(); 798 attVec.addElement(temp); 799 } 800 } 801 } 802 803 /** 804 * Iterates through the Key list and returns the first value for whose 805 * key the given string contains. Returns "unknown" if no key is contained 806 * in the string. 807 * @param s String being searched for a key. 808 * @return Value for key found in string, otherwise "unknown" 809 */ getValue(String s)810 String getValue(String s) 811 { 812 int i, j = attVec.size(); 813 for (i = 0; i < j; i++) 814 { 815 String temp[] = (String[]) attVec.elementAt(i); 816 if (s.indexOf(temp[0]) > -1) 817 return temp[1]; 818 } 819 return "unknown"; 820 } 821 } 822 823 /** 824 * Parses a processing instruction's (PI) attributes for easy retrieval. 825 */ 826 class PIA 827 { 828 829 private Hashtable piAttributes = null; 830 831 /** 832 * Constructor. 833 * @param pi The processing instruction whose attributes are to be parsed 834 */ PIA(ProcessingInstruction pi)835 PIA(ProcessingInstruction pi) 836 { 837 piAttributes = new Hashtable(); 838 StringTokenizer tokenizer = new StringTokenizer(pi.getNodeValue(), "=\""); 839 while(tokenizer.hasMoreTokens()) 840 { 841 piAttributes.put(tokenizer.nextToken().trim(), tokenizer.nextToken().trim()); 842 } 843 } 844 845 /** 846 * Returns value of specified attribute. 847 * @param name Attribute name 848 * @return Attribute value, or null if the attribute name does not exist 849 */ getAttribute(String name)850 String getAttribute(String name) 851 { 852 return (String) piAttributes.get(name); 853 } 854 } 855