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: OutputProperties.java 468643 2006-10-28 06:56:03Z minchau $
020 */
021 package org.apache.xalan.templates;
022
023 import java.util.Enumeration;
024 import java.util.Properties;
025 import java.util.Vector;
026
027 import javax.xml.transform.OutputKeys;
028 import javax.xml.transform.TransformerException;
029
030 import org.apache.xalan.res.XSLMessages;
031 import org.apache.xalan.res.XSLTErrorResources;
032 import org.apache.xml.serializer.OutputPropertiesFactory;
033 import org.apache.xml.serializer.OutputPropertyUtils;
034 import org.apache.xml.utils.FastStringBuffer;
035 import org.apache.xml.utils.QName;
036
037 /**
038 * This class provides information from xsl:output elements. It is mainly
039 * a wrapper for {@link java.util.Properties}, but can not extend that class
040 * because it must be part of the {@link org.apache.xalan.templates.ElemTemplateElement}
041 * heararchy.
042 * <p>An OutputProperties list can contain another OutputProperties list as
043 * its "defaults"; this second property list is searched if the property key
044 * is not found in the original property list.</p>
045 * @see <a href="http://www.w3.org/TR/xslt#dtd">XSLT DTD</a>
046 * @see <a href="http://www.w3.org/TR/xslt#output">xsl:output in XSLT Specification</a>
047 *
048 */
049 public class OutputProperties extends ElemTemplateElement
050 implements Cloneable
051 {
052 static final long serialVersionUID = -6975274363881785488L;
053 /**
054 * Creates an empty OutputProperties with no default values.
055 */
056 public OutputProperties()
057 {
058 this(org.apache.xml.serializer.Method.XML);
059 }
060
061 /**
062 * Creates an empty OutputProperties with the specified defaults.
063 *
064 * @param defaults the defaults.
065 */
066 public OutputProperties(Properties defaults)
067 {
068 m_properties = new Properties(defaults);
069 }
070
071 /**
072 * Creates an empty OutputProperties with the defaults specified by
073 * a property file. The method argument is used to construct a string of
074 * the form output_[method].properties (for instance, output_html.properties).
075 * The output_xml.properties file is always used as the base.
076 * <p>At the moment, anything other than 'text', 'xml', and 'html', will
077 * use the output_xml.properties file.</p>
078 *
079 * @param method non-null reference to method name.
080 */
081 public OutputProperties(String method)
082 {
083 m_properties = new Properties(
084 OutputPropertiesFactory.getDefaultMethodProperties(method));
085 }
086
087 /**
088 * Clone this OutputProperties, including a clone of the wrapped Properties
089 * reference.
090 *
091 * @return A new OutputProperties reference, mutation of which should not
092 * effect this object.
093 */
094 public Object clone()
095 {
096
097 try
098 {
099 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 }