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    */
OutputProperties()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    */
OutputProperties(Properties defaults)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    */
OutputProperties(String method)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    */
clone()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    */
setProperty(QName key, String value)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    */
setProperty(String key, String value)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    */
getProperty(QName key)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    */
getProperty(String key)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    */
setBooleanProperty(QName key, boolean value)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    */
setBooleanProperty(String key, boolean value)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    */
getBooleanProperty(QName key)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    */
getBooleanProperty(String key)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    */
setIntProperty(QName key, int value)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    */
setIntProperty(String key, int value)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    */
getIntProperty(QName key)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    */
getIntProperty(String key)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    */
setQNameProperty(QName key, QName value)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    */
setMethodDefaults(String method)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    */
setQNameProperty(String key, QName value)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    */
getQNameProperty(QName key)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    */
getQNameProperty(String key)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    */
getQNameProperty(String key, Properties props)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    */
setQNameProperties(QName key, Vector v)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    */
setQNameProperties(String key, Vector v)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    */
getQNameProperties(QName key)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    */
getQNameProperties(String key)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    */
getQNameProperties(String key, Properties props)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    */
recompose(StylesheetRoot root)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    */
compose(StylesheetRoot sroot)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    */
getProperties()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    */
copyFrom(Properties src)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    */
copyFrom(Properties src, boolean shouldResetDefaults)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    */
copyFrom(OutputProperties opsrc)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    */
isLegalPropertyKey(String key)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      */
getDefaultMethodProperties(String method)685     static public Properties getDefaultMethodProperties(String method)
686     {
687         return org.apache.xml.serializer.OutputPropertiesFactory.getDefaultMethodProperties(method);
688     }
689 }
690