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