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 }