/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package servlet;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLConnection;
import javax.xml.transform.OutputKeys;

import org.apache.xalan.templates.Constants;
import org.apache.xalan.templates.StylesheetRoot;
// SAX2 Imports
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.Locator;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.ext.DeclHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

import org.w3c.dom.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import org.apache.xalan.transformer.TransformerImpl;
import org.apache.xpath.objects.XObject;
import org.apache.xpath.objects.XString;
import org.apache.xalan.processor.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.XMLFilterImpl;

/*****************************************************************************************************
 *
 * ApplyXSLT supplies the basic
 * functions for transforming XML data using XSL stylesheets.
 *
 * @author Spencer Shepard (sshepard@us.ibm.com)
 * @author R. Adam King (rak@us.ibm.com)
 * @author Tom Rowe (trowe@us.ibm.com)
 * @author Don Leslie (donald_leslie@lotus.com)
 *
 *****************************************************************************************************/

public class ApplyXSLT extends HttpServlet
{

  /**
   * Operational parameters for this class.
   * <p>Request-time values override init-time values which override class defaults.</p>
   * @see #init
   * @serial
   */
  protected ApplyXSLTProperties ourDefaultParameters = null;

  /**
   * String representing the end of line characters for the System.
   */
  public final static String EOL = System.getProperty("line.separator");

  /**
   * String representing the file separator characters for the System.
   */
  public final static String FS = System.getProperty("file.separator");

   /**
   * String representing the current directory for properties files. See init().
   */
  public final static String ROOT = System.getProperty("server.root");
  public static String CURRENTDIR;

  /**
   * Initialize operational parameters from the configuration.
   * @param config Configuration
   * @exception ServletException Never thrown
   */
  public void init(ServletConfig config)
    throws ServletException
  {
    super.init(config);
    // If the server.root property --see above-- is null, use current working directory
    // as default location for media.properties.
    if (ROOT != null){
      CURRENTDIR= getServletContext().getRealPath("/WEB-INF/classes/servlet/") + FS;
	  System.out.println ( CURRENTDIR);}
    else
      CURRENTDIR = System.getProperty("user.dir")+ FS;
    
	setDefaultParameters(config);
	
    setMediaProps(config.getInitParameter("mediaURL"));	
  }
  
 /**
   * Sets the default parameters for the servlet from the configuration.
   * Also sets required system properties until we figure out why servlet 
   * sometimess fails to read properties from properties files.
   * @param config Configuration
   */
  protected void setDefaultParameters(ServletConfig config)
  {
    ourDefaultParameters = new DefaultApplyXSLTProperties(config);
  }
  
  /**
   *	Loads the media properties file specified by the given string.
   * @param mediaURLstring Location of the media properties file.  Can be either a full URL or a path relative
   * to the System's server.root /servlets directory.  If this parameter is null,
   * server.root/servlets/media.properties will be used.
   * @see ApplyXSL#CURRENTDIR
   */
  protected void setMediaProps(String mediaURLstring)
  {
    if (mediaURLstring != null)
    {
      URL url = null;
      try
      {
        url = new URL(mediaURLstring);
      }
      catch (MalformedURLException mue1)
      {
        try
        {
          url = new URL("file", "", CURRENTDIR + mediaURLstring);
        }
        catch (MalformedURLException mue2)
        {
          writeLog("Unable to find the media properties file based on parameter 'mediaURL' = "
                   + mediaURLstring, HttpServletResponse.SC_ACCEPTED, mue2);
          url = null;
        }
      }
      if (url != null)
      {
        try
        {
          ourMediaProps = new OrderedProps(url.openStream());
        }
        catch (IOException ioe1)
        {
          writeLog("Exception occurred while opening media properties file: " + mediaURLstring +
                   ".  Media table may be invalid.", HttpServletResponse.SC_ACCEPTED, ioe1);
        }
      }
    }
    else
    {
      String defaultProp = CURRENTDIR + "media.properties";
      try
      {
        ourMediaProps = new OrderedProps(new FileInputStream(defaultProp));
      }
      catch (IOException ioe2)
      {
        writeLog("Default media properties file " + defaultProp + " not found.",
                 HttpServletResponse.SC_ACCEPTED, ioe2);
      }
    }
  }

  public String getMedia(HttpServletRequest request)
  {
    return ourMediaProps.getValue(request.getHeader(HEADER_NAME));
  }
  
  // doPost removed for security reasons due to the possibility of sending
  // unsecure XML and XSL XSLTInputSources through the request input stream

  /**
   * HTTP Get method passed on to process().
   * @param request The request
   * @param response The response
   * @see #process
   * @exception ServletException Never thrown
   * @exception IOException Never thrown
   */
  public void doGet (HttpServletRequest request,
                     HttpServletResponse response)
    throws ServletException, IOException
  {
    try
    {	
      TransformerFactory tFactory = TransformerFactory.newInstance();
      process(tFactory, request, response);
    }
    catch (Exception e)
    {
    }
  }
  
  /**
   * Coordinates applying an XSL stylesheet to XML data using operational parameters.
   * <p>If successfully applied, the result tree will be streamed to the response object
   * and the content type set according to the XSL stylesheet's &lt;xsl:output> element(s).</p>
   * <p>If there is a problem in parsing the XML/XSL or if there is a problem in applying
   * the XSL to the XML, an exception will be streamed to the response object.  The detail
   * of the information returned in the response object will depend on whether we're
   * running in debug mode or not.</p>
   * @param processor implementation of TRaX processor
   * @param request  May contain information relevant to creating XML and XSL XSLTInputSource's
   * @param response Where to write the transformation result
   * @see #getDocument
   * @see #getStylesheet
   * @see #getContentType
   * @see #displayException
   * @see #setStylesheetParams
   * @exception ServletException Never thrown
   * @exception IOException Never thrown
   */
  
  public void process(TransformerFactory tFactory, 
					  HttpServletRequest request,
                      HttpServletResponse response)
    throws ServletException, IOException, SAXException
  {
    boolean debug = ourDefaultParameters.isDebug(request);

    long time = 0;
    if (debug)
      time = System.currentTimeMillis();

    // Listener to be used for all reporting
    ApplyXSLTListener listener = new ApplyXSLTListener();
	listener.out.println("debug is " + debug);

    StreamSource xmlSource = null;
	StreamSource xslSource = null;
    try
    {
      if ((xmlSource = getDocument(request, listener)) == null)
        throw new ApplyXSLTException("getDocument() returned null",
                                     new NullPointerException(),
                                     response.SC_NOT_FOUND);
    }
    catch (ApplyXSLTException axe)
    {
      axe.appendMessage(EOL + "getDocument() resulted in ApplyXSLTException" + EOL
                        + listener.getMessage());
      if (debug) writeLog(axe);
      displayException(response, axe, debug);
      xmlSource = null;
    }
    // creating XSL Stylesheet
    if (xmlSource != null)
	{
      try
      {
	    if ((xslSource = getStylesheet(tFactory, request, xmlSource, listener)) == null)
		{
          throw new ApplyXSLTException("getStylesheet() returned null",
                                       new NullPointerException(),
                                       response.SC_NOT_FOUND);
        }
        // For time being, must "reset" xmlSource after extracting stylesheet PI
		xmlSource = getDocument(request, listener); 
      }
      catch (ApplyXSLTException axe)
      {
        axe.appendMessage(EOL + "getStylesheet() resulted in ApplyXSLTException" + EOL
                          + listener.getMessage());
        if (debug) writeLog(axe);
        displayException(response, axe, debug);
        xslSource = null;
      }
    // perform Transformation
	  
    if ((xmlSource != null) && (xslSource != null))
    {
	  try
	  {
        listener.out.println("Performing transformation...");
		
        Templates templates = tFactory.newTemplates(xslSource);
        Transformer transformer = templates.newTransformer();
        {
          try
          {
            String contentType = null;
			      contentType = getContentType(templates);
            if (contentType != null);
              response.setContentType(contentType);

			      if (transformer instanceof TransformerImpl)
			      {
			        TransformerImpl transformerImpl = (TransformerImpl)transformer;
              transformerImpl.setQuietConflictWarnings(ourDefaultParameters.isNoCW(request));
			      }
			
			      setStylesheetParams(transformer, request);			
	          transformer.transform(xmlSource, new StreamResult(response.getOutputStream()));
			
			      if (debug)              
              writeLog(listener.getMessage(), response.SC_OK);
          }
          catch (Exception exc)
          {
            ApplyXSLTException axe = new ApplyXSLTException
				                     ("Exception occurred during Transformation:"
                                          + EOL + listener.getMessage() + EOL
                                          + exc.getMessage(), 
									              exc,
                                response.SC_INTERNAL_SERVER_ERROR);
            if (debug) writeLog(axe);
            displayException(response, axe, debug);
          }
          finally
          {
            // transformer.reset();
          } // end of try ... catch ... finally
		}
	  }
      catch (/*org.xml.sax.SAX*/Exception saxExc)
      {
        ApplyXSLTException axe = new ApplyXSLTException
			                     ("Exception occurred during ctor/Transformation:"
                                             + EOL + listener.getMessage() + EOL
                                             + saxExc.getMessage(), 
			                					  saxExc,
                                  response.SC_INTERNAL_SERVER_ERROR);
        if (debug) writeLog(axe);
        displayException(response, axe, debug);
      } // end of new try ... catch
    } // end of if((stylesheetRoot != null) ...
    if (debug)
    {
      time = System.currentTimeMillis() - time;
      writeLog("  No Conflict Warnings = " + ourDefaultParameters.isNoCW(request) +
               "  Transformation time: " + time + " ms", response.SC_OK);
    }
  }
  }  

  /**
   * Returns an XML XSLTInputSource DOM.  Attempts will be make to create the DOM from the following
   * sources:
   * <ol>
   * <li>A relative URL specified in the HTTP request's path information. This capability is intended
   * for use by <b>servlet engines that map</b> some or all XML data to be processed at the server.</li>
   * <li>A URL specified in the HTTP request's <code>URL=</code> parameter.  This capability
   * is intended for <b>clients wishing to selectively process</b> XML data at the server.  For
   * security reasons, this URL will be forced to the local IP host.</li>
   * <li>The HTTP request's XML input stream. This capability is intended for use by chained servlets.</li>
   * </ol>
   * @param request May contain or point to the XML XSLTInputSource
   * @param listener To record detailed parsing messages for possible return to requestor
   * @return XML XSLTInputSource DOM, or null if the XSLTInputSource could not be parsed
   * @exception ApplyXSLTException Thrown if exception occurs while handling request
   */
  protected StreamSource getDocument(HttpServletRequest request,
                                     ApplyXSLTListener listener)
    throws ApplyXSLTException
  {
    try
    {
      String xmlURL = null;
      // document from PathInfo
      if ((xmlURL = request.getPathInfo()) != null)
      {
        listener.out.println("Parsing XML Document from PathInfo: " + xmlURL);
        return new StreamSource(new URL("http", ((DefaultApplyXSLTProperties)
                                         ourDefaultParameters).getLocalHost(),
                                         request.getServerPort(),
                                         xmlURL.replace('\\', '/')).openStream());                          
      }
      // document from Request parameter
      if ((xmlURL = ourDefaultParameters.getXMLurl(request)) != null)
      {
        listener.out.println("Parsing XML Document from request parameter: " + xmlURL);
        return new StreamSource(new URL(xmlURL).openStream());
      }
      // document from chain
      String contentType = request.getContentType();
      if ((contentType != null) && contentType.startsWith("text/xml"))
      {
        listener.out.println("Parsing XML Document from request chain");
        return new StreamSource(request.getInputStream());
      }
    }
    catch (IOException ioe)
    {
      throw new ApplyXSLTException(ioe, HttpServletResponse.SC_NOT_FOUND);
    }
    catch (Exception e)
    {
      throw new ApplyXSLTException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
    return null;
  }

  /**
   * Returns a Templates (StylesheetRoot) object.  Attempts will be make to create the Stylesheet 
   * from the followingsources:
   * <ol>
   * <li>A URL specified in the HTTP request's <code>xslURL=</code> parameter.  This capability
   * is intended for clients wishing to selectively override the server algorithm for applying XSL
   * stylesheets.  For security reasons, this URL will be forced to the local IP host.</li>
   * <li>XML association.  XML documents may contain references to one or more stylesheets using
   * <a HREF="http://www.w3.org/TR/1999/PR-xml-stylesheet-19990114">this</a> W3C proposed recommendation.
   * If the XML document does contain such references, a best match will be chosen based on the browser
   * type making the request and the default association.  This capability enables relationships to be
   * defined between client capabilities and stylesheets capable of acting on these capabilities.</li>
   * <li>A configured default stylesheet URL</li>
   * </ol>
   * @param request May contain or point to the XSL XSLTInputSource
   * @param xmlSource  May point to the XSL XSLTInputSource
   * @param listener To record detailed parsing messages for possible return to requestor
   * @return XSL XSLTInputSource, or null if the request could not be parsed
   * @see #makeDocument
   * @see #getMedia
   * @see #STYLESHEET_ATTRIBUTE
   * @see #getXSLURLfromDoc
   * @see #toAcceptLanguageConnection
   * @exception ApplyXSLTException Thrown if exception occurs while handling request
   */
  protected StreamSource getStylesheet(TransformerFactory tFactory,
				   		  			   HttpServletRequest request,
                                       StreamSource xmlSource,
                                       ApplyXSLTListener listener)
    throws ApplyXSLTException
  {
    try
    {
      //stylesheet URL from request
      String xslURL = ((DefaultApplyXSLTProperties) ourDefaultParameters).getXSLRequestURL(request);

      if (xslURL != null)
        listener.out.println("Parsing XSL Stylesheet Document from request parameter: "
                             + xslURL);
      else
      {
        // find stylesheet from XML Document, Media tag preference
        if (xmlSource != null){
          listener.out.println("calling getXSLURLfromDoc and getMedia " + getMedia(request) );
          xslURL = getXSLURLfromDoc(xmlSource, STYLESHEET_ATTRIBUTE, getMedia(request), tFactory);
        }
        if (xslURL != null)
          listener.out.println("Parsing XSL Stylesheet Document from XML Document tag: " + xslURL);
        else
          // Configuration Default
          if ((xslURL = ourDefaultParameters.getXSLurl(null)) != null)
            listener.out.println("Parsing XSL Stylesheet Document from configuration: " + xslURL);
      }
      return new StreamSource(xslURL);
    }
    catch (IOException ioe)
    {
      throw new ApplyXSLTException(ioe, HttpServletResponse.SC_NOT_FOUND);
    }
    catch (Exception e)
    {
      throw new ApplyXSLTException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Returns the response content type specified by the media-type and encoding attributes of
   * the &lt;xsl:output> element(s) of the stylesheet.
   * @param xslSourceRoot XSL Stylesheet to be examined for &lt;xsl:output> elements.
   * @return The response content type (MIME type and charset) of the stylesheet output
   * @see #process
   */
  public String getContentType(Templates templates)
  {
    Properties oprops = templates.getOutputProperties();
    String encoding = oprops.getProperty(OutputKeys.ENCODING);  
          String media = oprops.getProperty(OutputKeys.MEDIA_TYPE);
          if (media != null)
          {
      if (encoding != null)
        return media + "; charset=" + encoding;
      return media;
          }
          else
          {
            String method = oprops.getProperty(OutputKeys.METHOD);
            if (method.equals("html"))
                    return "text/html";
            else if (method.equals("text"))
                    return "text/plain";
            else 
                    return "text/xml";
          }
  }  
  

  /**
   * Defines and sets select top-level XSL stylesheet variables from the HTTP request, which
   * can be evaluated using &lt;xsl:param-variable&gt;.  The following variables will be
   * automatically set:
   * <dl>
   * <dt><i>ParameterName</i></dt>
   * <dd>Each non-reserved request parameter returned from request.getParameterNames().  If a
   *     parameter contains more than a single value, only the first value is available.</dd>
   * <dt>servlet-RemoteAddr</dt>
   * <dd>Contains String output from request.getRemoteAddr(), which is the IP address
   *     of the client machine.</dd>
   * <dt>servlet-RemoteHost</dt>
   * <dd>Contains String output from request.getRemoteHost(), which is the host name
   *     of the client machine.</dd>
   * <dt>servlet-RemoteUser</dt>
   * <dd>Contains String output from request.getRemoteUser(), which was the user name
   *     accepted by the server to grant access to this servlet.</dd>
   * <dt>servlet-Request</dt>
   * <dd>Contains the request object.</dd>
   * </dl>
   * @param xslprocessor Where to register parameters to be set
   * @param request Provides access to all meaningful parameters to set
   * @see #process
   */
  public void setStylesheetParams(Transformer transformer, HttpServletRequest request)
  {
    Enumeration paramNames = request.getParameterNames();
    while (paramNames.hasMoreElements())
    {
      String paramName = (String) paramNames.nextElement();
      try
      {
        String[] paramVals = request.getParameterValues(paramName);
        if (paramVals != null)
            transformer.setParameter(paramName, new XString(paramVals[0]));
                                            
      }
      catch (Exception e)
      {
      }
    }
    try
    {
      transformer.setParameter("servlet-RemoteAddr", new XString(request.getRemoteAddr()));
                                      
    }
    catch (Exception e)
    {
    }
    try
    {
      transformer.setParameter("servlet-RemoteHost", new XString(request.getRemoteHost()));
                                      
    }
    catch (Exception e)
    {
    }
    try
    {
      transformer.setParameter("servlet-RemoteUser", new XString(request.getRemoteUser()));
                                      
    }
    catch (Exception e)
    {
    }
  }


  /**
   * Writes the following information to the servlet log:
   * <ol>
   * <li>HTTP status code</li>
   * <li>Message</li>
   * <li>Stack trace</li>
   * </ol>
   * @param axe Contains valid HTTP status code, message, and stack trace (optional)
   */
  protected void writeLog(ApplyXSLTException axe)
  {
    writeLog(axe.getMessage(), axe.getStatusCode(), axe.getException());
  }

  /**
   * Writes the following information to the servlet log:
   * <ol>
   * <li>HTTP status code</li>
   * <li>Message</li>
   * <li>Stack trace</li>
   * </ol>
   * @param msg Message to be logged
   * @param statusCode Valid status code from javax.servlet.http.HttpServletResponse
   * @param t Used to generate stack trace (may be =null to suppress stack trace)
   */
  protected void writeLog(String msg, int statusCode, Throwable t)
  {
    if (t == null)
      writeLog(msg, statusCode);
    else
    {
      ByteArrayOutputStream bytes = new ByteArrayOutputStream();
      PrintWriter writer = new PrintWriter(bytes, true);
      System.out.println("Exception is " + t.getClass().getName());
      t.printStackTrace(writer);
      log("HTTP Status Code: " + statusCode + " - " + msg + EOL + bytes.toString());
    }
  }

  /**
   * Writes the following information to the servlet log:
   * <ol>
   * <li>HTTP status code</li>
   * <li>Message</li>
   * </ol>
   * @param msg Message to be logged
   * @param statusCode Valid status code from javax.servlet.http.HttpServletResponse
   */
  protected void writeLog(String msg, int statusCode)
  {
    log("HTTP Status Code: " + statusCode + " - " + msg);
  }

  /**
   * Invokes response.sendError setting an HTTP status code and optionally an error message
   * as an HTML page.
   * <p>If running in debug mode, also try to return a stack trace of the exception and
   * and xml/xsl processor messages.</p>
   * @param response Where to stream the exception to
   * @param xse The wrapper which contains the exception and its HTTP status code
   * @param debug Indicates whether to include stack trace, etc.
   */
  protected void displayException(HttpServletResponse response, ApplyXSLTException xse, boolean debug)
  {
    String mesg = xse.getMessage();
    if (mesg == null)
      mesg = "";
    else mesg = "<B>" + mesg + "</B>";
    StringTokenizer tokens = new StringTokenizer(mesg, EOL);
    StringBuffer strBuf = new StringBuffer();
    while (tokens.hasMoreTokens())
      strBuf.append(tokens.nextToken() + EOL + "<BR>");
    mesg = strBuf.toString();
    if (debug)
    {
      ByteArrayOutputStream bytes = new ByteArrayOutputStream();
      PrintWriter writer = new PrintWriter(bytes, true);
      xse.getException().printStackTrace(writer);
      mesg += " <PRE> " + bytes.toString() + " </PRE> ";
    }
    response.setContentType("text/html");
    try
    {
      response.sendError(xse.getStatusCode(), mesg);
    }
    catch (IOException ioe)
    {
      System.err.println("IOException is occurring when sendError is called");
    }
  }

  /**
   * Mapping of HTTP request's user-Agent values to stylesheet media= values.
   * <p>This mapping is defined by a file pointed to by the operational parameter "mediaURL" which can
   *  either contain a full URL or a path relative to the System's server.root /servlets directory.</p>
   * @see #setMediaProps
   * @see #getMedia
   * @serial
   */
  protected OrderedProps ourMediaProps = null;

  /**
   * Returns a connection which respects the Accept-Language header of the HTTP request.  This
   * is useful when XSL files are internationalized for use with Web servers which respect this
   * header.
   * <p>For example, Apache 1.3.6 may be configured for multiviews.  Under this configuration,
   * requests for http://myhost/index.html would return http://myhost/index.html.fr to French browsers
   * and http://myhost/index.html.en to English browsers.</p>
   * @param url Location to connect to
   * @param request Could contain an Accept-Language header
   * @return An Accept-Language-enabled URL connection
   * @see #getStylesheet
   */
  protected URLConnection toAcceptLanguageConnection(URL url, HttpServletRequest request)
    throws Exception
  {
    URLConnection tempConnection = url.openConnection();
    tempConnection.setRequestProperty("Accept-Language", request.getHeader("Accept-Language"));
    return tempConnection;
  }


  /**
   * Returns the XSL stylesheet URL associated with the specified XML document.  If multiple XSL
   * stylesheets are associated with the XML document, preference will be given to the stylesheet
   * which contains an attribute name/value pair that corresponds to the specified attributeName
   * and attributeValue.
   * @param xmlSource XML XSLTInputSource to be searched for associated XSL stylesheets
   * @param attributeName  Attribute name to provide preferential matching
   * @param attributeValue Attribute value to provide preferential matching
   * @return The preferred XSL stylesheet URL, or null if no XSL stylesheet association is found
   * @see #getStylesheet
   */
  public static String getXSLURLfromDoc(StreamSource xmlSource,
                                        String attributeName,
                                        String attributeValue,
                                        TransformerFactory tFactory)
  {
    String tempURL = null, returnURL = null;
    try
    {
	  DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
      Node sourceTree = docBuilder.parse(xmlSource.getInputStream());
      for(Node child=sourceTree.getFirstChild(); null != child; child=child.getNextSibling())
      {
        if(Node.PROCESSING_INSTRUCTION_NODE == child.getNodeType())
        {
          ProcessingInstruction pi = (ProcessingInstruction)child;
          if(pi.getNodeName().equals("xml-stylesheet"))
          {
            PIA pia = new PIA(pi);
            if("text/xsl".equals(pia.getAttribute("type")))
            {
              tempURL = pia.getAttribute("href");
              String attribute = pia.getAttribute(attributeName);
              if ((attribute != null) && (attribute.indexOf(attributeValue) > -1))
                return tempURL;
              if (!"yes".equals(pia.getAttribute("alternate")))
                returnURL = tempURL;
            }
          }
        }
      }
    }
    catch(Exception saxExc)
    {
    }
    return returnURL;
  }  

 /**
   * The attribute name in the <?xml-stylesheet> tag used in stylesheet selection.
   */
  protected static final String STYLESHEET_ATTRIBUTE = "media";

  /**
   *	The HTTP Header used for matching the Stylesheet attribute via the
   * media properties file selected.
   */
  protected static final String HEADER_NAME = "user-Agent";
}

/**
 *  Stores the keys and values from a file (similar to a properties file) and
 *  can return the first value which has a key contained in its string.
 *  File can have comment lines starting with '#" and for each line the entries are
 *  separated by tabs and '=' char.
 */
class OrderedProps
{

  /**
   * Stores the Key and Values as an array of Strings
   */
  private Vector attVec = new Vector(15);

  /**
   * Constructor.
   * @param inputStream Stream containing the properties file.
   * @exception IOException Thrown if unable to read from stream
   */
  OrderedProps(InputStream inputStream)
    throws IOException
  {
    BufferedReader input  = new BufferedReader(new InputStreamReader(inputStream));
    String currentLine, Key = null;
    StringTokenizer currentTokens;
    while ((currentLine = input.readLine()) != null)
    {
      currentTokens = new StringTokenizer(currentLine, "=\t\r\n");
      if (currentTokens.hasMoreTokens()) Key = currentTokens.nextToken().trim();
      if ((Key != null) && !Key.startsWith("#") && currentTokens.hasMoreTokens())
      {
        String temp[] = new String[2];
        temp[0] = Key; temp[1] = currentTokens.nextToken().trim();
        attVec.addElement(temp);
      }
    }
  }

  /**
   * Iterates through the Key list and returns the first value for whose
   * key the given string contains.  Returns "unknown" if no key is contained
   * in the string.
   * @param s String being searched for a key.
   * @return Value for key found in string, otherwise "unknown"
   */
  String getValue(String s)
  {
    int i, j = attVec.size();
    for (i = 0; i < j; i++)
    {
      String temp[] = (String[]) attVec.elementAt(i);
      if (s.indexOf(temp[0]) > -1)
        return temp[1];
    }
    return "unknown";
  }
}

/**
 * Parses a processing instruction's (PI) attributes for easy retrieval.
 */
class PIA
{

  private Hashtable piAttributes = null;

  /**
   * Constructor.
   * @param pi The processing instruction whose attributes are to be parsed
   */
  PIA(ProcessingInstruction pi)
  {
    piAttributes = new Hashtable();
    StringTokenizer tokenizer = new StringTokenizer(pi.getNodeValue(), "=\"");
    while(tokenizer.hasMoreTokens())
    {
      piAttributes.put(tokenizer.nextToken().trim(), tokenizer.nextToken().trim());
    }
  }

  /**
   * Returns value of specified attribute.
   *  @param name Attribute name
   *  @return Attribute value, or null if the attribute name does not exist
   */
  String getAttribute(String name)
  {
    return (String) piAttributes.get(name);
  }  
}
