001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: StylesheetPIHandler.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xml.utils;
022    
023    import java.util.StringTokenizer;
024    import java.util.Vector;
025    
026    import javax.xml.transform.Source;
027    import javax.xml.transform.TransformerException;
028    import javax.xml.transform.URIResolver;
029    import javax.xml.transform.sax.SAXSource;
030    
031    import org.apache.xml.utils.SystemIDResolver;
032    
033    import org.xml.sax.Attributes;
034    import org.xml.sax.InputSource;
035    import org.xml.sax.helpers.DefaultHandler;
036    
037    /**
038     * Search for the xml-stylesheet processing instructions in an XML document.
039     * @see <a href="http://www.w3.org/TR/xml-stylesheet/">Associating Style Sheets with XML documents, Version 1.0</a>
040     */
041    public class StylesheetPIHandler extends DefaultHandler
042    {
043      /** The baseID of the document being processed.  */
044      String m_baseID;
045    
046      /** The desired media criteria. */
047      String m_media;
048    
049      /** The desired title criteria.  */
050      String m_title;
051    
052      /** The desired character set criteria.   */
053      String m_charset;
054    
055      /** A list of SAXSource objects that match the criteria.  */
056      Vector m_stylesheets = new Vector();
057      
058      // Add code to use a URIResolver. Patch from Dmitri Ilyin. 
059      
060      /**
061       * The object that implements the URIResolver interface,
062       * or null.
063       */
064      URIResolver m_uriResolver;
065    
066      /**
067       * Get the object that will be used to resolve URIs in href 
068       * in xml-stylesheet processing instruction.
069       *
070       * @param resolver An object that implements the URIResolver interface,
071       * or null.
072       */
073      public void setURIResolver(URIResolver resolver)
074      {
075        m_uriResolver = resolver;
076      }
077    
078      /**
079       * Get the object that will be used to resolve URIs in href 
080       * in xml-stylesheet processing instruction.
081       *
082       * @return The URIResolver that was set with setURIResolver.
083       */
084      public URIResolver getURIResolver()
085      {
086        return m_uriResolver;
087      }
088    
089      /**
090       * Construct a StylesheetPIHandler instance that will search 
091       * for xml-stylesheet PIs based on the given criteria.
092       *
093       * @param baseID The base ID of the XML document, needed to resolve 
094       *               relative IDs.
095       * @param media The desired media criteria.
096       * @param title The desired title criteria.
097       * @param charset The desired character set criteria.
098       */
099      public StylesheetPIHandler(String baseID, String media, String title,
100                                 String charset)
101      {
102    
103        m_baseID = baseID;
104        m_media = media;
105        m_title = title;
106        m_charset = charset;
107      }
108    
109      /**
110       * Return the last stylesheet found that match the constraints.
111       *
112       * @return Source object that references the last stylesheet reference 
113       *         that matches the constraints.
114       */
115      public Source getAssociatedStylesheet()
116      {
117    
118        int sz = m_stylesheets.size();
119    
120        if (sz > 0)
121        {
122          Source source = (Source) m_stylesheets.elementAt(sz-1);
123          return source;      
124        }
125        else
126          return null;
127      }
128    
129      /**
130       * Handle the xml-stylesheet processing instruction.
131       *
132       * @param target The processing instruction target.
133       * @param data The processing instruction data, or null if
134       *             none is supplied.
135       * @throws org.xml.sax.SAXException Any SAX exception, possibly
136       *            wrapping another exception.
137       * @see org.xml.sax.ContentHandler#processingInstruction
138       * @see <a href="http://www.w3.org/TR/xml-stylesheet/">Associating Style Sheets with XML documents, Version 1.0</a>
139       */
140      public void processingInstruction(String target, String data)
141              throws org.xml.sax.SAXException
142      {
143    
144        if (target.equals("xml-stylesheet"))
145        {
146          String href = null;  // CDATA #REQUIRED
147          String type = null;  // CDATA #REQUIRED
148          String title = null;  // CDATA #IMPLIED
149          String media = null;  // CDATA #IMPLIED
150          String charset = null;  // CDATA #IMPLIED
151          boolean alternate = false;  // (yes|no) "no"
152          StringTokenizer tokenizer = new StringTokenizer(data, " \t=\n", true);
153          boolean lookedAhead = false; 
154          Source source = null;
155    
156          String token = "";
157          while (tokenizer.hasMoreTokens())
158          {        
159            if (!lookedAhead)
160              token = tokenizer.nextToken();
161            else
162              lookedAhead = false;
163            if (tokenizer.hasMoreTokens() && 
164                   (token.equals(" ") || token.equals("\t") || token.equals("=")))
165              continue;
166              
167            String name = token;  
168            if (name.equals("type"))
169            { 
170              token = tokenizer.nextToken();
171              while (tokenizer.hasMoreTokens() && 
172                   (token.equals(" " ) || token.equals("\t") || token.equals("=")))
173                token = tokenizer.nextToken();
174              type = token.substring(1, token.length() - 1);
175              
176            }
177            else if (name.equals("href"))
178            {
179              token = tokenizer.nextToken();
180              while (tokenizer.hasMoreTokens() && 
181                   (token.equals(" " ) || token.equals("\t") || token.equals("=")))
182                token = tokenizer.nextToken();
183              href = token;
184              if (tokenizer.hasMoreTokens())
185              {
186                token = tokenizer.nextToken();
187                // If the href value has parameters to be passed to a 
188                // servlet(something like "foobar?id=12..."), 
189                // we want to make sure we get them added to
190                // the href value. Without this check, we would move on 
191                // to try to process another attribute and that would be
192                // wrong.
193                // We need to set lookedAhead here to flag that we
194                // already have the next token. 
195                while ( token.equals("=") && tokenizer.hasMoreTokens())
196                {  
197                  href = href + token + tokenizer.nextToken();
198                  if (tokenizer.hasMoreTokens())
199                  {  
200                    token = tokenizer.nextToken();
201                    lookedAhead = true;
202                  }
203                  else
204                  {
205                    break;
206                  }
207                }
208              }
209              href = href.substring(1, href.length() - 1);
210              try
211              { 
212                // Add code to use a URIResolver. Patch from Dmitri Ilyin. 
213                if (m_uriResolver != null) 
214                {
215                  source = m_uriResolver.resolve(href, m_baseID);
216                } 
217               else 
218                {
219                  href = SystemIDResolver.getAbsoluteURI(href, m_baseID);
220                  source = new SAXSource(new InputSource(href));
221                }            
222              }
223              catch(TransformerException te)
224              {
225                throw new org.xml.sax.SAXException(te);
226              }
227            }
228            else if (name.equals("title"))
229            {
230              token = tokenizer.nextToken();
231              while (tokenizer.hasMoreTokens() && 
232                   (token.equals(" " ) || token.equals("\t") || token.equals("=")))
233                token = tokenizer.nextToken();
234              title = token.substring(1, token.length() - 1);
235            }
236            else if (name.equals("media"))
237            {
238              token = tokenizer.nextToken();
239              while (tokenizer.hasMoreTokens() && 
240                   (token.equals(" " ) || token.equals("\t") || token.equals("=")))
241                token = tokenizer.nextToken();
242              media = token.substring(1, token.length() - 1);
243            }
244            else if (name.equals("charset"))
245            {
246              token = tokenizer.nextToken();
247              while (tokenizer.hasMoreTokens() && 
248                  (token.equals(" " ) || token.equals("\t") || token.equals("=")))
249                token = tokenizer.nextToken();
250              charset = token.substring(1, token.length() - 1);
251            }
252            else if (name.equals("alternate"))
253            {
254              token = tokenizer.nextToken();
255              while (tokenizer.hasMoreTokens() && 
256                   (token.equals(" " ) || token.equals("\t") || token.equals("=")))
257                token = tokenizer.nextToken();
258              alternate = token.substring(1, token.length()
259                                                 - 1).equals("yes");
260            }
261            
262          }
263    
264          if ((null != type) 
265              && (type.equals("text/xsl") || type.equals("text/xml") || type.equals("application/xml+xslt"))  
266              && (null != href))
267          {
268            if (null != m_media)
269            {
270              if (null != media)
271              {
272                if (!media.equals(m_media))
273                  return;
274              }
275              else
276                return;
277            }
278    
279            if (null != m_charset)
280            {
281              if (null != charset)
282              {
283                if (!charset.equals(m_charset))
284                  return;
285              }
286              else
287                return;
288            }
289    
290            if (null != m_title)
291            {
292              if (null != title)
293              {
294                if (!title.equals(m_title))
295                  return;
296              }
297              else
298                return;
299            }
300    
301            m_stylesheets.addElement(source);
302          }
303        }
304      }
305      
306      
307      /**
308       * The spec notes that "The xml-stylesheet processing instruction is allowed only in the prolog of an XML document.",
309       * so, at least for right now, I'm going to go ahead an throw a TransformerException
310       * in order to stop the parse.
311       *
312       * @param namespaceURI The Namespace URI, or an empty string.
313       * @param localName The local name (without prefix), or empty string if not namespace processing.
314       * @param qName The qualified name (with prefix).
315       * @param atts  The specified or defaulted attributes.
316       *
317       * @throws StopParseException since there can be no valid xml-stylesheet processing 
318       *                            instructions past the first element.
319       */
320      public void startElement(
321              String namespaceURI, String localName, String qName, Attributes atts)
322                throws org.xml.sax.SAXException
323      {
324        throw new StopParseException();
325      }
326    
327      /**
328        * Added additional getter and setter methods for the Base Id
329        * to fix bugzilla bug 24187
330        * 
331        */
332       public void setBaseId(String baseId) {
333           m_baseID = baseId;
334     
335       }
336       public String  getBaseId() {
337           return m_baseID ;
338       }
339    
340    }