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: StylesheetPIHandler.java 468655 2006-10-28 07:12:06Z minchau $
20 */
21package org.apache.xml.utils;
22
23import java.util.StringTokenizer;
24import java.util.Vector;
25
26import javax.xml.transform.Source;
27import javax.xml.transform.TransformerException;
28import javax.xml.transform.URIResolver;
29import javax.xml.transform.sax.SAXSource;
30
31import org.apache.xml.utils.SystemIDResolver;
32
33import org.xml.sax.Attributes;
34import org.xml.sax.InputSource;
35import org.xml.sax.helpers.DefaultHandler;
36
37/**
38 * Search for the xml-stylesheet processing instructions in an XML document.
39 * @see <a href="http://www.w3.org/TR/xml-stylesheet/">Associating Style Sheets with XML documents, Version 1.0</a>
40 */
41public class StylesheetPIHandler extends DefaultHandler
42{
43  /** The baseID of the document being processed.  */
44  String m_baseID;
45
46  /** The desired media criteria. */
47  String m_media;
48
49  /** The desired title criteria.  */
50  String m_title;
51
52  /** The desired character set criteria.   */
53  String m_charset;
54
55  /** A list of SAXSource objects that match the criteria.  */
56  Vector m_stylesheets = new Vector();
57
58  // Add code to use a URIResolver. Patch from Dmitri Ilyin.
59
60  /**
61   * The object that implements the URIResolver interface,
62   * or null.
63   */
64  URIResolver m_uriResolver;
65
66  /**
67   * Get the object that will be used to resolve URIs in href
68   * in xml-stylesheet processing instruction.
69   *
70   * @param resolver An object that implements the URIResolver interface,
71   * or null.
72   */
73  public void setURIResolver(URIResolver resolver)
74  {
75    m_uriResolver = resolver;
76  }
77
78  /**
79   * Get the object that will be used to resolve URIs in href
80   * in xml-stylesheet processing instruction.
81   *
82   * @return The URIResolver that was set with setURIResolver.
83   */
84  public URIResolver getURIResolver()
85  {
86    return m_uriResolver;
87  }
88
89  /**
90   * Construct a StylesheetPIHandler instance that will search
91   * for xml-stylesheet PIs based on the given criteria.
92   *
93   * @param baseID The base ID of the XML document, needed to resolve
94   *               relative IDs.
95   * @param media The desired media criteria.
96   * @param title The desired title criteria.
97   * @param charset The desired character set criteria.
98   */
99  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}
341