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