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: OutputProperties.java 468643 2006-10-28 06:56:03Z minchau $
20 */
21package org.apache.xalan.templates;
22
23import java.util.Enumeration;
24import java.util.Properties;
25import java.util.Vector;
26
27import javax.xml.transform.OutputKeys;
28import javax.xml.transform.TransformerException;
29
30import org.apache.xalan.res.XSLMessages;
31import org.apache.xalan.res.XSLTErrorResources;
32import org.apache.xml.serializer.OutputPropertiesFactory;
33import org.apache.xml.serializer.OutputPropertyUtils;
34import org.apache.xml.utils.FastStringBuffer;
35import org.apache.xml.utils.QName;
36
37/**
38 * This class provides information from xsl:output elements. It is mainly
39 * a wrapper for {@link java.util.Properties}, but can not extend that class
40 * because it must be part of the {@link org.apache.xalan.templates.ElemTemplateElement}
41 * heararchy.
42 * <p>An OutputProperties list can contain another OutputProperties list as
43 * its "defaults"; this second property list is searched if the property key
44 * is not found in the original property list.</p>
45 * @see <a href="http://www.w3.org/TR/xslt#dtd">XSLT DTD</a>
46 * @see <a href="http://www.w3.org/TR/xslt#output">xsl:output in XSLT Specification</a>
47 *
48 */
49public class OutputProperties extends ElemTemplateElement
50        implements Cloneable
51{
52    static final long serialVersionUID = -6975274363881785488L;
53  /**
54   * Creates an empty OutputProperties with no default values.
55   */
56  public OutputProperties()
57  {
58    this(org.apache.xml.serializer.Method.XML);
59  }
60
61  /**
62   * Creates an empty OutputProperties with the specified defaults.
63   *
64   * @param   defaults   the defaults.
65   */
66  public OutputProperties(Properties defaults)
67  {
68    m_properties = new Properties(defaults);
69  }
70
71  /**
72   * Creates an empty OutputProperties with the defaults specified by
73   * a property file.  The method argument is used to construct a string of
74   * the form output_[method].properties (for instance, output_html.properties).
75   * The output_xml.properties file is always used as the base.
76   * <p>At the moment, anything other than 'text', 'xml', and 'html', will
77   * use the output_xml.properties file.</p>
78   *
79   * @param   method non-null reference to method name.
80   */
81  public OutputProperties(String method)
82  {
83    m_properties = new Properties(
84        OutputPropertiesFactory.getDefaultMethodProperties(method));
85  }
86
87  /**
88   * Clone this OutputProperties, including a clone of the wrapped Properties
89   * reference.
90   *
91   * @return A new OutputProperties reference, mutation of which should not
92   *         effect this object.
93   */
94  public Object clone()
95  {
96
97    try
98    {
99      OutputProperties cloned = (OutputProperties) super.clone();
100
101      cloned.m_properties = (Properties) cloned.m_properties.clone();
102
103      return cloned;
104    }
105    catch (CloneNotSupportedException e)
106    {
107      return null;
108    }
109  }
110
111  /**
112   * Set an output property.
113   *
114   * @param key the key to be placed into the property list.
115   * @param value the value corresponding to <tt>key</tt>.
116   * @see javax.xml.transform.OutputKeys
117   */
118  public void setProperty(QName key, String value)
119  {
120    setProperty(key.toNamespacedString(), value);
121  }
122
123  /**
124   * Set an output property.
125   *
126   * @param key the key to be placed into the property list.
127   * @param value the value corresponding to <tt>key</tt>.
128   * @see javax.xml.transform.OutputKeys
129   */
130  public void setProperty(String key, String value)
131  {
132    if(key.equals(OutputKeys.METHOD))
133    {
134      setMethodDefaults(value);
135    }
136
137    if (key.startsWith(OutputPropertiesFactory.S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL))
138      key = OutputPropertiesFactory.S_BUILTIN_EXTENSIONS_UNIVERSAL
139         + key.substring(OutputPropertiesFactory.S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN);
140
141    m_properties.put(key, value);
142  }
143
144  /**
145   * Searches for the property with the specified key in the property list.
146   * If the key is not found in this property list, the default property list,
147   * and its defaults, recursively, are then checked. The method returns
148   * <code>null</code> if the property is not found.
149   *
150   * @param   key   the property key.
151   * @return  the value in this property list with the specified key value.
152   */
153  public String getProperty(QName key)
154  {
155    return m_properties.getProperty(key.toNamespacedString());
156  }
157
158  /**
159   * Searches for the property with the specified key in the property list.
160   * If the key is not found in this property list, the default property list,
161   * and its defaults, recursively, are then checked. The method returns
162   * <code>null</code> if the property is not found.
163   *
164   * @param   key   the property key.
165   * @return  the value in this property list with the specified key value.
166   */
167  public String getProperty(String key)
168  {
169    if (key.startsWith(OutputPropertiesFactory.S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL))
170      key = OutputPropertiesFactory.S_BUILTIN_EXTENSIONS_UNIVERSAL
171        + key.substring(OutputPropertiesFactory.S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN);
172    return m_properties.getProperty(key);
173  }
174
175  /**
176   * Set an output property.
177   *
178   * @param key the key to be placed into the property list.
179   * @param value the value corresponding to <tt>key</tt>.
180   * @see javax.xml.transform.OutputKeys
181   */
182  public void setBooleanProperty(QName key, boolean value)
183  {
184    m_properties.put(key.toNamespacedString(), value ? "yes" : "no");
185  }
186
187  /**
188   * Set an output property.
189   *
190   * @param key the key to be placed into the property list.
191   * @param value the value corresponding to <tt>key</tt>.
192   * @see javax.xml.transform.OutputKeys
193   */
194  public void setBooleanProperty(String key, boolean value)
195  {
196    m_properties.put(key, value ? "yes" : "no");
197  }
198
199  /**
200   * Searches for the boolean property with the specified key in the property list.
201   * If the key is not found in this property list, the default property list,
202   * and its defaults, recursively, are then checked. The method returns
203   * <code>false</code> if the property is not found, or if the value is other
204   * than "yes".
205   *
206   * @param   key   the property key.
207   * @return  the value in this property list as a boolean value, or false
208   * if null or not "yes".
209   */
210  public boolean getBooleanProperty(QName key)
211  {
212    return getBooleanProperty(key.toNamespacedString());
213  }
214
215  /**
216   * Searches for the boolean property with the specified key in the property list.
217   * If the key is not found in this property list, the default property list,
218   * and its defaults, recursively, are then checked. The method returns
219   * <code>false</code> if the property is not found, or if the value is other
220   * than "yes".
221   *
222   * @param   key   the property key.
223   * @return  the value in this property list as a boolean value, or false
224   * if null or not "yes".
225   */
226  public boolean getBooleanProperty(String key)
227  {
228    return OutputPropertyUtils.getBooleanProperty(key, m_properties);
229  }
230
231  /**
232   * Set an output property.
233   *
234   * @param key the key to be placed into the property list.
235   * @param value the value corresponding to <tt>key</tt>.
236   * @see javax.xml.transform.OutputKeys
237   */
238  public void setIntProperty(QName key, int value)
239  {
240    setIntProperty(key.toNamespacedString(), value);
241  }
242
243  /**
244   * Set an output property.
245   *
246   * @param key the key to be placed into the property list.
247   * @param value the value corresponding to <tt>key</tt>.
248   * @see javax.xml.transform.OutputKeys
249   */
250  public void setIntProperty(String key, int value)
251  {
252    m_properties.put(key, Integer.toString(value));
253  }
254
255  /**
256   * Searches for the int property with the specified key in the property list.
257   * If the key is not found in this property list, the default property list,
258   * and its defaults, recursively, are then checked. The method returns
259   * <code>false</code> if the property is not found, or if the value is other
260   * than "yes".
261   *
262   * @param   key   the property key.
263   * @return  the value in this property list as a int value, or false
264   * if null or not a number.
265   */
266  public int getIntProperty(QName key)
267  {
268    return getIntProperty(key.toNamespacedString());
269  }
270
271  /**
272   * Searches for the int property with the specified key in the property list.
273   * If the key is not found in this property list, the default property list,
274   * and its defaults, recursively, are then checked. The method returns
275   * <code>false</code> if the property is not found, or if the value is other
276   * than "yes".
277   *
278   * @param   key   the property key.
279   * @return  the value in this property list as a int value, or false
280   * if null or not a number.
281   */
282  public int getIntProperty(String key)
283  {
284    return OutputPropertyUtils.getIntProperty(key, m_properties);
285  }
286
287
288  /**
289   * Set an output property with a QName value.  The QName will be turned
290   * into a string with the namespace in curly brackets.
291   *
292   * @param key the key to be placed into the property list.
293   * @param value the value corresponding to <tt>key</tt>.
294   * @see javax.xml.transform.OutputKeys
295   */
296  public void setQNameProperty(QName key, QName value)
297  {
298    setQNameProperty(key.toNamespacedString(), value);
299  }
300
301  /**
302   * Reset the default properties based on the method.
303   *
304   * @param method the method value.
305   * @see javax.xml.transform.OutputKeys
306   */
307  public void setMethodDefaults(String method)
308  {
309        String defaultMethod = m_properties.getProperty(OutputKeys.METHOD);
310
311        if((null == defaultMethod) || !defaultMethod.equals(method)
312         // bjm - add the next condition as a hack
313         // but it is because both output_xml.properties and
314         // output_unknown.properties have the same method=xml
315         // for their default. Otherwise we end up with
316         // a ToUnknownStream wraping a ToXMLStream even
317         // when the users says method="xml"
318         //
319         || defaultMethod.equals("xml")
320         )
321        {
322            Properties savedProps = m_properties;
323            Properties newDefaults =
324                OutputPropertiesFactory.getDefaultMethodProperties(method);
325            m_properties = new Properties(newDefaults);
326            copyFrom(savedProps, false);
327        }
328  }
329
330
331  /**
332   * Set an output property with a QName value.  The QName will be turned
333   * into a string with the namespace in curly brackets.
334   *
335   * @param key the key to be placed into the property list.
336   * @param value the value corresponding to <tt>key</tt>.
337   * @see javax.xml.transform.OutputKeys
338   */
339  public void setQNameProperty(String key, QName value)
340  {
341    setProperty(key, value.toNamespacedString());
342  }
343
344  /**
345   * Searches for the qname property with the specified key in the property list.
346   * If the key is not found in this property list, the default property list,
347   * and its defaults, recursively, are then checked. The method returns
348   * <code>null</code> if the property is not found.
349   *
350   * @param   key   the property key.
351   * @return  the value in this property list as a QName value, or false
352   * if null or not "yes".
353   */
354  public QName getQNameProperty(QName key)
355  {
356    return getQNameProperty(key.toNamespacedString());
357  }
358
359  /**
360   * Searches for the qname property with the specified key in the property list.
361   * If the key is not found in this property list, the default property list,
362   * and its defaults, recursively, are then checked. The method returns
363   * <code>null</code> if the property is not found.
364   *
365   * @param   key   the property key.
366   * @return  the value in this property list as a QName value, or false
367   * if null or not "yes".
368   */
369  public QName getQNameProperty(String key)
370  {
371    return getQNameProperty(key, m_properties);
372  }
373
374  /**
375   * Searches for the qname property with the specified key in the property list.
376   * If the key is not found in this property list, the default property list,
377   * and its defaults, recursively, are then checked. The method returns
378   * <code>null</code> if the property is not found.
379   *
380   * @param   key   the property key.
381   * @param props the list of properties to search in.
382   * @return  the value in this property list as a QName value, or false
383   * if null or not "yes".
384   */
385  public static QName getQNameProperty(String key, Properties props)
386  {
387
388    String s = props.getProperty(key);
389
390    if (null != s)
391      return QName.getQNameFromString(s);
392    else
393      return null;
394  }
395
396  /**
397   * Set an output property with a QName list value.  The QNames will be turned
398   * into strings with the namespace in curly brackets.
399   *
400   * @param key the key to be placed into the property list.
401   * @param v non-null list of QNames corresponding to <tt>key</tt>.
402   * @see javax.xml.transform.OutputKeys
403   */
404  public void setQNameProperties(QName key, Vector v)
405  {
406    setQNameProperties(key.toNamespacedString(), v);
407  }
408
409  /**
410   * Set an output property with a QName list value.  The QNames will be turned
411   * into strings with the namespace in curly brackets.
412   *
413   * @param key the key to be placed into the property list.
414   * @param v non-null list of QNames corresponding to <tt>key</tt>.
415   * @see javax.xml.transform.OutputKeys
416   */
417  public void setQNameProperties(String key, Vector v)
418  {
419
420    int s = v.size();
421
422    // Just an initial guess at reasonable tuning parameters
423    FastStringBuffer fsb = new FastStringBuffer(9,9);
424
425    for (int i = 0; i < s; i++)
426    {
427      QName qname = (QName) v.elementAt(i);
428
429      fsb.append(qname.toNamespacedString());
430      // Don't append space after last value
431      if (i < s-1)
432        fsb.append(' ');
433    }
434
435    m_properties.put(key, fsb.toString());
436  }
437
438  /**
439   * Searches for the list of qname properties with the specified key in
440   * the property list.
441   * If the key is not found in this property list, the default property list,
442   * and its defaults, recursively, are then checked. The method returns
443   * <code>null</code> if the property is not found.
444   *
445   * @param   key   the property key.
446   * @return  the value in this property list as a vector of QNames, or false
447   * if null or not "yes".
448   */
449  public Vector getQNameProperties(QName key)
450  {
451    return getQNameProperties(key.toNamespacedString());
452  }
453
454  /**
455   * Searches for the list of qname properties with the specified key in
456   * the property list.
457   * If the key is not found in this property list, the default property list,
458   * and its defaults, recursively, are then checked. The method returns
459   * <code>null</code> if the property is not found.
460   *
461   * @param   key   the property key.
462   * @return  the value in this property list as a vector of QNames, or false
463   * if null or not "yes".
464   */
465  public Vector getQNameProperties(String key)
466  {
467    return getQNameProperties(key, m_properties);
468  }
469
470  /**
471   * Searches for the list of qname properties with the specified key in
472   * the property list.
473   * If the key is not found in this property list, the default property list,
474   * and its defaults, recursively, are then checked. The method returns
475   * <code>null</code> if the property is not found.
476   *
477   * @param   key   the property key.
478   * @param props the list of properties to search in.
479   * @return  the value in this property list as a vector of QNames, or false
480   * if null or not "yes".
481   */
482  public static Vector getQNameProperties(String key, Properties props)
483  {
484
485    String s = props.getProperty(key);
486
487    if (null != s)
488    {
489      Vector v = new Vector();
490      int l = s.length();
491      boolean inCurly = false;
492      FastStringBuffer buf = new FastStringBuffer();
493
494      // parse through string, breaking on whitespaces.  I do this instead
495      // of a tokenizer so I can track whitespace inside of curly brackets,
496      // which theoretically shouldn't happen if they contain legal URLs.
497      for (int i = 0; i < l; i++)
498      {
499        char c = s.charAt(i);
500
501        if (Character.isWhitespace(c))
502        {
503          if (!inCurly)
504          {
505            if (buf.length() > 0)
506            {
507              QName qname = QName.getQNameFromString(buf.toString());
508              v.addElement(qname);
509              buf.reset();
510            }
511            continue;
512          }
513        }
514        else if ('{' == c)
515          inCurly = true;
516        else if ('}' == c)
517          inCurly = false;
518
519        buf.append(c);
520      }
521
522      if (buf.length() > 0)
523      {
524        QName qname = QName.getQNameFromString(buf.toString());
525        v.addElement(qname);
526        buf.reset();
527      }
528
529      return v;
530    }
531    else
532      return null;
533  }
534
535  /**
536   * This function is called to recompose all of the output format extended elements.
537   *
538   * @param root non-null reference to the stylesheet root object.
539   */
540  public void recompose(StylesheetRoot root)
541    throws TransformerException
542  {
543    root.recomposeOutput(this);
544  }
545
546  /**
547   * This function is called after everything else has been
548   * recomposed, and allows the template to set remaining
549   * values that may be based on some other property that
550   * depends on recomposition.
551   */
552  public void compose(StylesheetRoot sroot) throws TransformerException
553  {
554
555    super.compose(sroot);
556
557  }
558
559  /**
560   * Get the Properties object that this class wraps.
561   *
562   * @return non-null reference to Properties object.
563   */
564  public Properties getProperties()
565  {
566    return m_properties;
567  }
568
569  /**
570   * Copy the keys and values from the source to this object.  This will
571   * not copy the default values.  This is meant to be used by going from
572   * a higher precedence object to a lower precedence object, so that if a
573   * key already exists, this method will not reset it.
574   *
575   * @param src non-null reference to the source properties.
576   */
577  public void copyFrom(Properties src)
578  {
579    copyFrom(src, true);
580  }
581
582  /**
583   * Copy the keys and values from the source to this object.  This will
584   * not copy the default values.  This is meant to be used by going from
585   * a higher precedence object to a lower precedence object, so that if a
586   * key already exists, this method will not reset it.
587   *
588   * @param src non-null reference to the source properties.
589   * @param shouldResetDefaults true if the defaults should be reset based on
590   *                            the method property.
591   */
592  public void copyFrom(Properties src, boolean shouldResetDefaults)
593  {
594
595    Enumeration keys = src.keys();
596
597    while (keys.hasMoreElements())
598    {
599      String key = (String) keys.nextElement();
600
601      if (!isLegalPropertyKey(key))
602        throw new IllegalArgumentException(XSLMessages.createMessage(XSLTErrorResources.ER_OUTPUT_PROPERTY_NOT_RECOGNIZED, new Object[]{key})); //"output property not recognized: "
603
604      Object oldValue = m_properties.get(key);
605      if (null == oldValue)
606      {
607        String val = (String) src.get(key);
608
609        if(shouldResetDefaults && key.equals(OutputKeys.METHOD))
610        {
611          setMethodDefaults(val);
612        }
613
614        m_properties.put(key, val);
615      }
616      else if (key.equals(OutputKeys.CDATA_SECTION_ELEMENTS))
617      {
618        m_properties.put(key, (String) oldValue + " " + (String) src.get(key));
619      }
620    }
621  }
622
623  /**
624   * Copy the keys and values from the source to this object.  This will
625   * not copy the default values.  This is meant to be used by going from
626   * a higher precedence object to a lower precedence object, so that if a
627   * key already exists, this method will not reset it.
628   *
629   * @param opsrc non-null reference to an OutputProperties.
630   */
631  public void copyFrom(OutputProperties opsrc)
632    throws TransformerException
633  {
634   // Bugzilla 6157: recover from xsl:output statements
635    // checkDuplicates(opsrc);
636    copyFrom(opsrc.getProperties());
637  }
638
639  /**
640   * Report if the key given as an argument is a legal xsl:output key.
641   *
642   * @param key non-null reference to key name.
643   *
644   * @return true if key is legal.
645   */
646  public static boolean isLegalPropertyKey(String key)
647  {
648
649    return (key.equals(OutputKeys.CDATA_SECTION_ELEMENTS)
650            || key.equals(OutputKeys.DOCTYPE_PUBLIC)
651            || key.equals(OutputKeys.DOCTYPE_SYSTEM)
652            || key.equals(OutputKeys.ENCODING)
653            || key.equals(OutputKeys.INDENT)
654            || key.equals(OutputKeys.MEDIA_TYPE)
655            || key.equals(OutputKeys.METHOD)
656            || key.equals(OutputKeys.OMIT_XML_DECLARATION)
657            || key.equals(OutputKeys.STANDALONE)
658            || key.equals(OutputKeys.VERSION)
659            || (key.length() > 0)
660                  && (key.charAt(0) == '{')
661                  && (key.lastIndexOf('{') == 0)
662                  && (key.indexOf('}') > 0)
663                  && (key.lastIndexOf('}') == key.indexOf('}')));
664  }
665
666  /** The output properties.
667   *  @serial */
668  private Properties m_properties = null;
669
670    /**
671     * Creates an empty OutputProperties with the defaults specified by
672     * a property file.  The method argument is used to construct a string of
673     * the form output_[method].properties (for instance, output_html.properties).
674     * The output_xml.properties file is always used as the base.
675     * <p>At the moment, anything other than 'text', 'xml', and 'html', will
676     * use the output_xml.properties file.</p>
677     *
678     * @param   method non-null reference to method name.
679     *
680     * @return Properties object that holds the defaults for the given method.
681     *
682     * @deprecated Use org.apache.xml.serializer.OuputPropertiesFactory.
683     * getDefaultMethodProperties directly.
684     */
685    static public Properties getDefaultMethodProperties(String method)
686    {
687        return org.apache.xml.serializer.OutputPropertiesFactory.getDefaultMethodProperties(method);
688    }
689}
690