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