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