View Javadoc
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 }