Home | History | Annotate | Download | only in templates
      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  */
     21 package org.apache.xalan.templates;
     22 
     23 import java.util.Enumeration;
     24 import java.util.Properties;
     25 import java.util.Vector;
     26 
     27 import javax.xml.transform.OutputKeys;
     28 import javax.xml.transform.TransformerException;
     29 
     30 import org.apache.xalan.res.XSLMessages;
     31 import org.apache.xalan.res.XSLTErrorResources;
     32 import org.apache.xml.serializer.OutputPropertiesFactory;
     33 import org.apache.xml.serializer.OutputPropertyUtils;
     34 import org.apache.xml.utils.FastStringBuffer;
     35 import 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  */
     49 public 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