1/* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5/* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 22package com.sun.org.apache.xalan.internal.xsltc.compiler; 23 24import java.io.OutputStreamWriter; 25import java.util.Properties; 26import java.util.StringTokenizer; 27 28import javax.xml.transform.OutputKeys; 29 30import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen; 31import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL; 32import com.sun.org.apache.bcel.internal.generic.InstructionList; 33import com.sun.org.apache.bcel.internal.generic.PUSH; 34import com.sun.org.apache.bcel.internal.generic.PUTFIELD; 35import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator; 36import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg; 37import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator; 38import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util; 39import com.sun.org.apache.xml.internal.serializer.Encodings; 40import com.sun.org.apache.xml.internal.utils.XML11Char; 41 42/** 43 * @author Jacek Ambroziak 44 * @author Santiago Pericas-Geertsen 45 * @author Morten Jorgensen 46 */ 47final class Output extends TopLevelElement { 48 49 // TODO: use three-value variables for boolean values: true/false/default 50 51 // These attributes are extracted from the xsl:output element. They also 52 // appear as fields (with the same type, only public) in the translet 53 private String _version; 54 private String _method; 55 private String _encoding; 56 private boolean _omitHeader = false; 57 private String _standalone; 58 private String _doctypePublic; 59 private String _doctypeSystem; 60 private String _cdata; 61 private boolean _indent = false; 62 private String _mediaType; 63 private String _indentamount; 64 65 // Disables this output element (when other element has higher precedence) 66 private boolean _disabled = false; 67 68 // Some global constants 69 private final static String STRING_SIG = "Ljava/lang/String;"; 70 private final static String XML_VERSION = "1.0"; 71 private final static String HTML_VERSION = "4.0"; 72 73 /** 74 * Displays the contents of this element (for debugging) 75 */ 76 public void display(int indent) { 77 indent(indent); 78 Util.println("Output " + _method); 79 } 80 81 /** 82 * Disables this <xsl:output> element in case where there are some other 83 * <xsl:output> element (from a different imported/included stylesheet) 84 * with higher precedence. 85 */ 86 public void disable() { 87 _disabled = true; 88 } 89 90 public boolean enabled() { 91 return !_disabled; 92 } 93 94 public String getCdata() { 95 return _cdata; 96 } 97 98 public String getOutputMethod() { 99 return _method; 100 } 101 102 private void transferAttribute(Output previous, String qname) { 103 if (!hasAttribute(qname) && previous.hasAttribute(qname)) { 104 addAttribute(qname, previous.getAttribute(qname)); 105 } 106 } 107 108 public void mergeOutput(Output previous) { 109 // Transfer attributes from previous xsl:output 110 transferAttribute(previous, "version"); 111 transferAttribute(previous, "method"); 112 transferAttribute(previous, "encoding"); 113 transferAttribute(previous, "doctype-system"); 114 transferAttribute(previous, "doctype-public"); 115 transferAttribute(previous, "media-type"); 116 transferAttribute(previous, "indent"); 117 transferAttribute(previous, "omit-xml-declaration"); 118 transferAttribute(previous, "standalone"); 119 120 // Merge cdata-section-elements 121 if (previous.hasAttribute("cdata-section-elements")) { 122 // addAttribute works as a setter if it already exists 123 addAttribute("cdata-section-elements", 124 previous.getAttribute("cdata-section-elements") + ' ' + 125 getAttribute("cdata-section-elements")); 126 } 127 128 // Transfer non-standard attributes as well 129 String prefix = lookupPrefix("http://xml.apache.org/xalan"); 130 if (prefix != null) { 131 transferAttribute(previous, prefix + ':' + "indent-amount"); 132 } 133 prefix = lookupPrefix("http://xml.apache.org/xslt"); 134 if (prefix != null) { 135 transferAttribute(previous, prefix + ':' + "indent-amount"); 136 } 137 } 138 139 /** 140 * Scans the attribute list for the xsl:output instruction 141 */ 142 public void parseContents(Parser parser) { 143 final Properties outputProperties = new Properties(); 144 145 // Ask the parser if it wants this <xsl:output> element 146 parser.setOutput(this); 147 148 // Do nothing if other <xsl:output> element has higher precedence 149 if (_disabled) return; 150 151 String attrib = null; 152 153 // Get the output version 154 _version = getAttribute("version"); 155 if (_version.equals(Constants.EMPTYSTRING)) { 156 _version = null; 157 } 158 else { 159 outputProperties.setProperty(OutputKeys.VERSION, _version); 160 } 161 162 // Get the output method - "xml", "html", "text" or <qname> (but not ncname) 163 _method = getAttribute("method"); 164 if (_method.equals(Constants.EMPTYSTRING)) { 165 _method = null; 166 } 167 if (_method != null) { 168 _method = _method.toLowerCase(); 169 if ((_method.equals("xml"))|| 170 (_method.equals("html"))|| 171 (_method.equals("text"))|| 172 ((XML11Char.isXML11ValidQName(_method)&&(_method.indexOf(":") > 0)))) { 173 outputProperties.setProperty(OutputKeys.METHOD, _method); 174 } else { 175 reportError(this, parser, ErrorMsg.INVALID_METHOD_IN_OUTPUT, _method); 176 } 177 } 178 179 // Get the output encoding - any value accepted here 180 _encoding = getAttribute("encoding"); 181 if (_encoding.equals(Constants.EMPTYSTRING)) { 182 _encoding = null; 183 } 184 else { 185 try { 186 // Create a write to verify encoding support 187 String canonicalEncoding; 188 canonicalEncoding = Encodings.convertMime2JavaEncoding(_encoding); 189 OutputStreamWriter writer = 190 new OutputStreamWriter(System.out, canonicalEncoding); 191 } 192 catch (java.io.UnsupportedEncodingException e) { 193 ErrorMsg msg = new ErrorMsg(ErrorMsg.UNSUPPORTED_ENCODING, 194 _encoding, this); 195 parser.reportError(Constants.WARNING, msg); 196 } 197 outputProperties.setProperty(OutputKeys.ENCODING, _encoding); 198 } 199 200 // Should the XML header be omitted - translate to true/false 201 attrib = getAttribute("omit-xml-declaration"); 202 if (!attrib.equals(Constants.EMPTYSTRING)) { 203 if (attrib.equals("yes")) { 204 _omitHeader = true; 205 } 206 outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, attrib); 207 } 208 209 // Add 'standalone' decaration to output - use text as is 210 _standalone = getAttribute("standalone"); 211 if (_standalone.equals(Constants.EMPTYSTRING)) { 212 _standalone = null; 213 } 214 else { 215 outputProperties.setProperty(OutputKeys.STANDALONE, _standalone); 216 } 217 218 // Get system/public identifiers for output DOCTYPE declaration 219 _doctypeSystem = getAttribute("doctype-system"); 220 if (_doctypeSystem.equals(Constants.EMPTYSTRING)) { 221 _doctypeSystem = null; 222 } 223 else { 224 outputProperties.setProperty(OutputKeys.DOCTYPE_SYSTEM, _doctypeSystem); 225 } 226 227 228 _doctypePublic = getAttribute("doctype-public"); 229 if (_doctypePublic.equals(Constants.EMPTYSTRING)) { 230 _doctypePublic = null; 231 } 232 else { 233 outputProperties.setProperty(OutputKeys.DOCTYPE_PUBLIC, _doctypePublic); 234 } 235 236 // Names the elements of whose text contents should be output as CDATA 237 _cdata = getAttribute("cdata-section-elements"); 238 if (_cdata.equals(Constants.EMPTYSTRING)) { 239 _cdata = null; 240 } 241 else { 242 StringBuffer expandedNames = new StringBuffer(); 243 StringTokenizer tokens = new StringTokenizer(_cdata); 244 245 // Make sure to store names in expanded form 246 while (tokens.hasMoreTokens()) { 247 String qname = tokens.nextToken(); 248 if (!XML11Char.isXML11ValidQName(qname)) { 249 ErrorMsg err = new ErrorMsg(ErrorMsg.INVALID_QNAME_ERR, qname, this); 250 parser.reportError(Constants.ERROR, err); 251 } 252 expandedNames.append( 253 parser.getQName(qname).toString()).append(' '); 254 } 255 _cdata = expandedNames.toString(); 256 outputProperties.setProperty(OutputKeys.CDATA_SECTION_ELEMENTS, 257 _cdata); 258 } 259 260 // Get the indent setting - only has effect for xml and html output 261 attrib = getAttribute("indent"); 262 if (!attrib.equals(EMPTYSTRING)) { 263 if (attrib.equals("yes")) { 264 _indent = true; 265 } 266 outputProperties.setProperty(OutputKeys.INDENT, attrib); 267 } 268 else if (_method != null && _method.equals("html")) { 269 _indent = true; 270 } 271 272 // indent-amount: extension attribute of xsl:output 273 _indentamount = getAttribute( 274 lookupPrefix("http://xml.apache.org/xalan"), "indent-amount"); 275 // Hack for supporting Old Namespace URI. 276 if (_indentamount.equals(EMPTYSTRING)){ 277 _indentamount = getAttribute( 278 lookupPrefix("http://xml.apache.org/xslt"), "indent-amount"); 279 } 280 if (!_indentamount.equals(EMPTYSTRING)) { 281 outputProperties.setProperty("indent_amount", _indentamount); 282 } 283 284 // Get the MIME type for the output file 285 _mediaType = getAttribute("media-type"); 286 if (_mediaType.equals(Constants.EMPTYSTRING)) { 287 _mediaType = null; 288 } 289 else { 290 outputProperties.setProperty(OutputKeys.MEDIA_TYPE, _mediaType); 291 } 292 293 // Implied properties 294 if (_method != null) { 295 if (_method.equals("html")) { 296 if (_version == null) { 297 _version = HTML_VERSION; 298 } 299 if (_mediaType == null) { 300 _mediaType = "text/html"; 301 } 302 } 303 else if (_method.equals("text")) { 304 if (_mediaType == null) { 305 _mediaType = "text/plain"; 306 } 307 } 308 } 309 310 // Set output properties in current stylesheet 311 parser.getCurrentStylesheet().setOutputProperties(outputProperties); 312 } 313 314 /** 315 * Compile code that passes the information in this <xsl:output> element 316 * to the appropriate fields in the translet 317 */ 318 public void translate(ClassGenerator classGen, MethodGenerator methodGen) { 319 320 // Do nothing if other <xsl:output> element has higher precedence 321 if (_disabled) return; 322 323 ConstantPoolGen cpg = classGen.getConstantPool(); 324 InstructionList il = methodGen.getInstructionList(); 325 326 int field = 0; 327 il.append(classGen.loadTranslet()); 328 329 // Only update _version field if set and different from default 330 if ((_version != null) && (!_version.equals(XML_VERSION))) { 331 field = cpg.addFieldref(TRANSLET_CLASS, "_version", STRING_SIG); 332 il.append(DUP); 333 il.append(new PUSH(cpg, _version)); 334 il.append(new PUTFIELD(field)); 335 } 336 337 // Only update _method field if "method" attribute used 338 if (_method != null) { 339 field = cpg.addFieldref(TRANSLET_CLASS, "_method", STRING_SIG); 340 il.append(DUP); 341 il.append(new PUSH(cpg, _method)); 342 il.append(new PUTFIELD(field)); 343 } 344 345 // Only update if _encoding field is "encoding" attribute used 346 if (_encoding != null) { 347 field = cpg.addFieldref(TRANSLET_CLASS, "_encoding", STRING_SIG); 348 il.append(DUP); 349 il.append(new PUSH(cpg, _encoding)); 350 il.append(new PUTFIELD(field)); 351 } 352 353 // Only update if "omit-xml-declaration" used and set to 'yes' 354 if (_omitHeader) { 355 field = cpg.addFieldref(TRANSLET_CLASS, "_omitHeader", "Z"); 356 il.append(DUP); 357 il.append(new PUSH(cpg, _omitHeader)); 358 il.append(new PUTFIELD(field)); 359 } 360 361 // Add 'standalone' decaration to output - use text as is 362 if (_standalone != null) { 363 field = cpg.addFieldref(TRANSLET_CLASS, "_standalone", STRING_SIG); 364 il.append(DUP); 365 il.append(new PUSH(cpg, _standalone)); 366 il.append(new PUTFIELD(field)); 367 } 368 369 // Set system/public doctype only if both are set 370 field = cpg.addFieldref(TRANSLET_CLASS,"_doctypeSystem",STRING_SIG); 371 il.append(DUP); 372 il.append(new PUSH(cpg, _doctypeSystem)); 373 il.append(new PUTFIELD(field)); 374 field = cpg.addFieldref(TRANSLET_CLASS,"_doctypePublic",STRING_SIG); 375 il.append(DUP); 376 il.append(new PUSH(cpg, _doctypePublic)); 377 il.append(new PUTFIELD(field)); 378 379 // Add 'medye-type' decaration to output - if used 380 if (_mediaType != null) { 381 field = cpg.addFieldref(TRANSLET_CLASS, "_mediaType", STRING_SIG); 382 il.append(DUP); 383 il.append(new PUSH(cpg, _mediaType)); 384 il.append(new PUTFIELD(field)); 385 } 386 387 // Compile code to set output indentation on/off 388 if (_indent) { 389 field = cpg.addFieldref(TRANSLET_CLASS, "_indent", "Z"); 390 il.append(DUP); 391 il.append(new PUSH(cpg, _indent)); 392 il.append(new PUTFIELD(field)); 393 } 394 395 //Compile code to set indent amount. 396 if(_indentamount != null && !_indentamount.equals(EMPTYSTRING)){ 397 field = cpg.addFieldref(TRANSLET_CLASS, "_indentamount", "I"); 398 il.append(DUP); 399 il.append(new PUSH(cpg, Integer.parseInt(_indentamount))); 400 il.append(new PUTFIELD(field)); 401 } 402 403 // Forward to the translet any elements that should be output as CDATA 404 if (_cdata != null) { 405 int index = cpg.addMethodref(TRANSLET_CLASS, 406 "addCdataElement", 407 "(Ljava/lang/String;)V"); 408 409 StringTokenizer tokens = new StringTokenizer(_cdata); 410 while (tokens.hasMoreTokens()) { 411 il.append(DUP); 412 il.append(new PUSH(cpg, tokens.nextToken())); 413 il.append(new INVOKEVIRTUAL(index)); 414 } 415 } 416 il.append(POP); // Cleanup - pop last translet reference off stack 417 } 418 419} 420