1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. /*
  17. * $Id: Output.java,v 1.25 2004/02/24 03:55:48 zongaro Exp $
  18. */
  19. package com.sun.org.apache.xalan.internal.xsltc.compiler;
  20. import java.io.OutputStreamWriter;
  21. import java.util.Properties;
  22. import java.util.StringTokenizer;
  23. import javax.xml.transform.OutputKeys;
  24. import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
  25. import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
  26. import com.sun.org.apache.bcel.internal.generic.InstructionList;
  27. import com.sun.org.apache.bcel.internal.generic.PUSH;
  28. import com.sun.org.apache.bcel.internal.generic.PUTFIELD;
  29. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
  30. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
  31. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
  32. import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;
  33. import com.sun.org.apache.xml.internal.serializer.Encodings;
  34. import com.sun.org.apache.xml.internal.utils.XMLChar;
  35. /**
  36. * @author Jacek Ambroziak
  37. * @author Santiago Pericas-Geertsen
  38. * @author Morten Jorgensen
  39. */
  40. final class Output extends TopLevelElement {
  41. // TODO: use three-value variables for boolean values: true/false/default
  42. // These attributes are extracted from the xsl:output element. They also
  43. // appear as fields (with the same type, only public) in the translet
  44. private String _version;
  45. private String _method;
  46. private String _encoding;
  47. private boolean _omitHeader = false;
  48. private String _standalone;
  49. private String _doctypePublic;
  50. private String _doctypeSystem;
  51. private String _cdata;
  52. private boolean _indent = false;
  53. private String _mediaType;
  54. private String _cdataToMerge;
  55. private String _indentamount;
  56. // Disables this output element (when other element has higher precedence)
  57. private boolean _disabled = false;
  58. // Some global constants
  59. private final static String STRING_SIG = "Ljava/lang/String;";
  60. private final static String XML_VERSION = "1.0";
  61. private final static String HTML_VERSION = "4.0";
  62. /**
  63. * Displays the contents of this element (for debugging)
  64. */
  65. public void display(int indent) {
  66. indent(indent);
  67. Util.println("Output " + _method);
  68. }
  69. /**
  70. * Disables this <xsl:output> element in case where there are some other
  71. * <xsl:output> element (from a different imported/included stylesheet)
  72. * with higher precedence.
  73. */
  74. public void disable() {
  75. _disabled = true;
  76. }
  77. public boolean enabled() {
  78. return !_disabled;
  79. }
  80. public String getCdata() {
  81. return _cdata;
  82. }
  83. public String getOutputMethod() {
  84. return _method;
  85. }
  86. public void mergeCdata(String cdata) {
  87. _cdataToMerge = cdata;
  88. }
  89. /**
  90. * Scans the attribute list for the xsl:output instruction
  91. */
  92. public void parseContents(Parser parser) {
  93. final Properties outputProperties = new Properties();
  94. // Ask the parser if it wants this <xsl:output> element
  95. parser.setOutput(this);
  96. // Do nothing if other <xsl:output> element has higher precedence
  97. if (_disabled) return;
  98. String attrib = null;
  99. // Get the output version
  100. _version = getAttribute("version");
  101. if (_version == null || _version.equals(Constants.EMPTYSTRING)) {
  102. _version = null;
  103. }
  104. else {
  105. outputProperties.setProperty(OutputKeys.VERSION, _version);
  106. }
  107. // Get the output method - "xml", "html", "text" or <qname> (but not ncname)
  108. _method = getAttribute("method");
  109. if (_method.equals(Constants.EMPTYSTRING)) {
  110. _method = null;
  111. }
  112. if (_method != null) {
  113. _method = _method.toLowerCase();
  114. if ((_method.equals("xml"))||
  115. (_method.equals("html"))||
  116. (_method.equals("text"))||
  117. ((XMLChar.isValidQName(_method)&&(_method.indexOf(":") > 0)))) {
  118. outputProperties.setProperty(OutputKeys.METHOD, _method);
  119. } else {
  120. reportError(this, parser, ErrorMsg.INVALID_METHOD_IN_OUTPUT, _method);
  121. }
  122. }
  123. // Get the output encoding - any value accepted here
  124. _encoding = getAttribute("encoding");
  125. if (_encoding.equals(Constants.EMPTYSTRING)) {
  126. _encoding = null;
  127. }
  128. else {
  129. try {
  130. // Create a write to verify encoding support
  131. String canonicalEncoding;
  132. canonicalEncoding = Encodings.convertMime2JavaEncoding(_encoding);
  133. OutputStreamWriter writer =
  134. new OutputStreamWriter(System.out, canonicalEncoding);
  135. }
  136. catch (java.io.UnsupportedEncodingException e) {
  137. ErrorMsg msg = new ErrorMsg(ErrorMsg.UNSUPPORTED_ENCODING,
  138. _encoding, this);
  139. parser.reportError(Constants.WARNING, msg);
  140. }
  141. outputProperties.setProperty(OutputKeys.ENCODING, _encoding);
  142. }
  143. // Should the XML header be omitted - translate to true/false
  144. attrib = getAttribute("omit-xml-declaration");
  145. if (attrib != null && !attrib.equals(Constants.EMPTYSTRING)) {
  146. if (attrib.equals("yes")) {
  147. _omitHeader = true;
  148. }
  149. outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, attrib);
  150. }
  151. // Add 'standalone' decaration to output - use text as is
  152. _standalone = getAttribute("standalone");
  153. if (_standalone.equals(Constants.EMPTYSTRING)) {
  154. _standalone = null;
  155. }
  156. else {
  157. outputProperties.setProperty(OutputKeys.STANDALONE, _standalone);
  158. }
  159. // Get system/public identifiers for output DOCTYPE declaration
  160. _doctypeSystem = getAttribute("doctype-system");
  161. if (_doctypeSystem.equals(Constants.EMPTYSTRING)) {
  162. _doctypeSystem = null;
  163. }
  164. else {
  165. outputProperties.setProperty(OutputKeys.DOCTYPE_SYSTEM, _doctypeSystem);
  166. }
  167. _doctypePublic = getAttribute("doctype-public");
  168. if (_doctypePublic.equals(Constants.EMPTYSTRING)) {
  169. _doctypePublic = null;
  170. }
  171. else {
  172. outputProperties.setProperty(OutputKeys.DOCTYPE_PUBLIC, _doctypePublic);
  173. }
  174. // Names the elements of whose text contents should be output as CDATA
  175. _cdata = getAttribute("cdata-section-elements");
  176. if (_cdata != null && _cdata.equals(Constants.EMPTYSTRING)) {
  177. _cdata = null;
  178. }
  179. else {
  180. StringBuffer expandedNames = new StringBuffer();
  181. StringTokenizer tokens = new StringTokenizer(_cdata);
  182. // Make sure to store names in expanded form
  183. while (tokens.hasMoreTokens()) {
  184. String qname = tokens.nextToken();
  185. if (!XMLChar.isValidQName(qname)) {
  186. ErrorMsg err = new ErrorMsg(ErrorMsg.INVALID_QNAME_ERR, qname, this);
  187. parser.reportError(Constants.ERROR, err);
  188. }
  189. expandedNames.append(
  190. parser.getQName(qname).toString()).append(' ');
  191. }
  192. _cdata = expandedNames.toString();
  193. if (_cdataToMerge != null) {
  194. _cdata = _cdata + _cdataToMerge;
  195. }
  196. outputProperties.setProperty(OutputKeys.CDATA_SECTION_ELEMENTS,
  197. _cdata);
  198. }
  199. // Get the indent setting - only has effect for xml and html output
  200. attrib = getAttribute("indent");
  201. if (attrib != null && !attrib.equals(EMPTYSTRING)) {
  202. if (attrib.equals("yes")) {
  203. _indent = true;
  204. }
  205. outputProperties.setProperty(OutputKeys.INDENT, attrib);
  206. }
  207. else if (_method != null && _method.equals("html")) {
  208. _indent = true;
  209. }
  210. // indent-amount: extension attribute of xsl:output
  211. _indentamount = getAttributes().getValue(lookupPrefix("http://xml.apache.org/xalan"),"indent-amount");
  212. // Hack for supporting Old Namespace URI.
  213. if(_indentamount == null || _indentamount.equals(EMPTYSTRING)){
  214. _indentamount = getAttributes().getValue(lookupPrefix("http://xml.apache.org/xslt"),"indent-amount");
  215. }
  216. if(_indentamount != null && !_indentamount.equals(EMPTYSTRING)) {
  217. outputProperties.setProperty("indent_amount", _indentamount);
  218. }
  219. // Get the MIME type for the output file
  220. _mediaType = getAttribute("media-type");
  221. if (_mediaType.equals(Constants.EMPTYSTRING)) {
  222. _mediaType = null;
  223. }
  224. else {
  225. outputProperties.setProperty(OutputKeys.MEDIA_TYPE, _mediaType);
  226. }
  227. // Implied properties
  228. if (_method != null) {
  229. if (_method.equals("html")) {
  230. if (_version == null) {
  231. _version = HTML_VERSION;
  232. }
  233. if (_mediaType == null) {
  234. _mediaType = "text/html";
  235. }
  236. }
  237. else if (_method.equals("text")) {
  238. if (_mediaType == null) {
  239. _mediaType = "text/plain";
  240. }
  241. }
  242. }
  243. // Set output properties in current stylesheet
  244. parser.getCurrentStylesheet().setOutputProperties(outputProperties);
  245. }
  246. /**
  247. * Compile code that passes the information in this <xsl:output> element
  248. * to the appropriate fields in the translet
  249. */
  250. public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
  251. // Do nothing if other <xsl:output> element has higher precedence
  252. if (_disabled) return;
  253. ConstantPoolGen cpg = classGen.getConstantPool();
  254. InstructionList il = methodGen.getInstructionList();
  255. int field = 0;
  256. il.append(classGen.loadTranslet());
  257. // Only update _version field if set and different from default
  258. if ((_version != null) && (!_version.equals(XML_VERSION))) {
  259. field = cpg.addFieldref(TRANSLET_CLASS, "_version", STRING_SIG);
  260. il.append(DUP);
  261. il.append(new PUSH(cpg, _version));
  262. il.append(new PUTFIELD(field));
  263. }
  264. // Only update _method field if "method" attribute used
  265. if (_method != null) {
  266. field = cpg.addFieldref(TRANSLET_CLASS, "_method", STRING_SIG);
  267. il.append(DUP);
  268. il.append(new PUSH(cpg, _method));
  269. il.append(new PUTFIELD(field));
  270. }
  271. // Only update if _encoding field is "encoding" attribute used
  272. if (_encoding != null) {
  273. field = cpg.addFieldref(TRANSLET_CLASS, "_encoding", STRING_SIG);
  274. il.append(DUP);
  275. il.append(new PUSH(cpg, _encoding));
  276. il.append(new PUTFIELD(field));
  277. }
  278. // Only update if "omit-xml-declaration" used and set to 'yes'
  279. if (_omitHeader) {
  280. field = cpg.addFieldref(TRANSLET_CLASS, "_omitHeader", "Z");
  281. il.append(DUP);
  282. il.append(new PUSH(cpg, _omitHeader));
  283. il.append(new PUTFIELD(field));
  284. }
  285. // Add 'standalone' decaration to output - use text as is
  286. if (_standalone != null) {
  287. field = cpg.addFieldref(TRANSLET_CLASS, "_standalone", STRING_SIG);
  288. il.append(DUP);
  289. il.append(new PUSH(cpg, _standalone));
  290. il.append(new PUTFIELD(field));
  291. }
  292. // Set system/public doctype only if both are set
  293. field = cpg.addFieldref(TRANSLET_CLASS,"_doctypeSystem",STRING_SIG);
  294. il.append(DUP);
  295. il.append(new PUSH(cpg, _doctypeSystem));
  296. il.append(new PUTFIELD(field));
  297. field = cpg.addFieldref(TRANSLET_CLASS,"_doctypePublic",STRING_SIG);
  298. il.append(DUP);
  299. il.append(new PUSH(cpg, _doctypePublic));
  300. il.append(new PUTFIELD(field));
  301. // Add 'medye-type' decaration to output - if used
  302. if (_mediaType != null) {
  303. field = cpg.addFieldref(TRANSLET_CLASS, "_mediaType", STRING_SIG);
  304. il.append(DUP);
  305. il.append(new PUSH(cpg, _mediaType));
  306. il.append(new PUTFIELD(field));
  307. }
  308. // Compile code to set output indentation on/off
  309. if (_indent) {
  310. field = cpg.addFieldref(TRANSLET_CLASS, "_indent", "Z");
  311. il.append(DUP);
  312. il.append(new PUSH(cpg, _indent));
  313. il.append(new PUTFIELD(field));
  314. }
  315. //Compile code to set indent amount.
  316. if(_indentamount != null && !_indentamount.equals(EMPTYSTRING)){
  317. field = cpg.addFieldref(TRANSLET_CLASS, "_indentamount", "I");
  318. il.append(DUP);
  319. il.append(new PUSH(cpg, Integer.parseInt(_indentamount)));
  320. il.append(new PUTFIELD(field));
  321. }
  322. // Forward to the translet any elements that should be output as CDATA
  323. if (_cdata != null) {
  324. int index = cpg.addMethodref(TRANSLET_CLASS,
  325. "addCdataElement",
  326. "(Ljava/lang/String;)V");
  327. StringTokenizer tokens = new StringTokenizer(_cdata);
  328. while (tokens.hasMoreTokens()) {
  329. il.append(DUP);
  330. il.append(new PUSH(cpg, tokens.nextToken()));
  331. il.append(new INVOKEVIRTUAL(index));
  332. }
  333. }
  334. il.append(POP); // Cleanup - pop last translet reference off stack
  335. }
  336. }