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    }