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: Output.java 468650 2006-10-28 07:03:30Z minchau $ 020 */ 021 022 package org.apache.xalan.xsltc.compiler; 023 024 import java.io.OutputStreamWriter; 025 import java.util.Properties; 026 import java.util.StringTokenizer; 027 028 import javax.xml.transform.OutputKeys; 029 030 import org.apache.bcel.generic.ConstantPoolGen; 031 import org.apache.bcel.generic.INVOKEVIRTUAL; 032 import org.apache.bcel.generic.InstructionList; 033 import org.apache.bcel.generic.PUSH; 034 import org.apache.bcel.generic.PUTFIELD; 035 import org.apache.xalan.xsltc.compiler.util.ClassGenerator; 036 import org.apache.xalan.xsltc.compiler.util.ErrorMsg; 037 import org.apache.xalan.xsltc.compiler.util.MethodGenerator; 038 import org.apache.xalan.xsltc.compiler.util.Util; 039 import org.apache.xml.serializer.Encodings; 040 import org.apache.xml.utils.XML11Char; 041 042 /** 043 * @author Jacek Ambroziak 044 * @author Santiago Pericas-Geertsen 045 * @author Morten Jorgensen 046 */ 047 final class Output extends TopLevelElement { 048 049 // TODO: use three-value variables for boolean values: true/false/default 050 051 // These attributes are extracted from the xsl:output element. They also 052 // appear as fields (with the same type, only public) in the translet 053 private String _version; 054 private String _method; 055 private String _encoding; 056 private boolean _omitHeader = false; 057 private String _standalone; 058 private String _doctypePublic; 059 private String _doctypeSystem; 060 private String _cdata; 061 private boolean _indent = false; 062 private String _mediaType; 063 private String _indentamount; 064 065 // Disables this output element (when other element has higher precedence) 066 private boolean _disabled = false; 067 068 // Some global constants 069 private final static String STRING_SIG = "Ljava/lang/String;"; 070 private final static String XML_VERSION = "1.0"; 071 private final static String HTML_VERSION = "4.0"; 072 073 /** 074 * Displays the contents of this element (for debugging) 075 */ 076 public void display(int indent) { 077 indent(indent); 078 Util.println("Output " + _method); 079 } 080 081 /** 082 * Disables this <xsl:output> element in case where there are some other 083 * <xsl:output> element (from a different imported/included stylesheet) 084 * with higher precedence. 085 */ 086 public void disable() { 087 _disabled = true; 088 } 089 090 public boolean enabled() { 091 return !_disabled; 092 } 093 094 public String getCdata() { 095 return _cdata; 096 } 097 098 public String getOutputMethod() { 099 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 }