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