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: ToXMLStream.java,v 1.13 2004/02/18 22:57:44 minchau Exp $
  18. */
  19. package com.sun.org.apache.xml.internal.serializer;
  20. import java.io.IOException;
  21. import javax.xml.transform.ErrorListener;
  22. import javax.xml.transform.Result;
  23. import javax.xml.transform.Transformer;
  24. import javax.xml.transform.TransformerException;
  25. import com.sun.org.apache.xml.internal.res.XMLErrorResources;
  26. import com.sun.org.apache.xml.internal.res.XMLMessages;
  27. import org.xml.sax.SAXException;
  28. /**
  29. * @author Santiago Pericas-Geertsen
  30. * @author G. Todd Miller
  31. */
  32. public class ToXMLStream extends ToStream
  33. {
  34. /**
  35. * remembers if we need to write out "]]>" to close the CDATA
  36. */
  37. boolean m_cdataTagOpen = false;
  38. /**
  39. * Map that tells which XML characters should have special treatment, and it
  40. * provides character to entity name lookup.
  41. */
  42. protected static CharInfo m_xmlcharInfo =
  43. // new CharInfo(CharInfo.XML_ENTITIES_RESOURCE);
  44. CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML);
  45. /**
  46. * Default constructor.
  47. */
  48. public ToXMLStream()
  49. {
  50. m_charInfo = m_xmlcharInfo;
  51. initCDATA();
  52. // initialize namespaces
  53. m_prefixMap = new NamespaceMappings();
  54. }
  55. /**
  56. * Copy properties from another SerializerToXML.
  57. *
  58. * @param xmlListener non-null reference to a SerializerToXML object.
  59. */
  60. public void CopyFrom(ToXMLStream xmlListener)
  61. {
  62. m_writer = xmlListener.m_writer;
  63. // m_outputStream = xmlListener.m_outputStream;
  64. String encoding = xmlListener.getEncoding();
  65. setEncoding(encoding);
  66. setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration());
  67. m_ispreserve = xmlListener.m_ispreserve;
  68. m_preserves = xmlListener.m_preserves;
  69. m_isprevtext = xmlListener.m_isprevtext;
  70. m_doIndent = xmlListener.m_doIndent;
  71. setIndentAmount(xmlListener.getIndentAmount());
  72. m_startNewLine = xmlListener.m_startNewLine;
  73. m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
  74. setDoctypeSystem(xmlListener.getDoctypeSystem());
  75. setDoctypePublic(xmlListener.getDoctypePublic());
  76. setStandalone(xmlListener.getStandalone());
  77. setMediaType(xmlListener.getMediaType());
  78. m_maxCharacter = xmlListener.m_maxCharacter;
  79. m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
  80. m_cdataStartCalled = xmlListener.m_cdataStartCalled;
  81. }
  82. /**
  83. * Receive notification of the beginning of a document.
  84. *
  85. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  86. * wrapping another exception.
  87. *
  88. * @throws org.xml.sax.SAXException
  89. */
  90. public void startDocumentInternal() throws org.xml.sax.SAXException
  91. {
  92. if (m_needToCallStartDocument)
  93. {
  94. super.startDocumentInternal();
  95. m_needToCallStartDocument = false;
  96. if (m_inEntityRef)
  97. return;
  98. m_needToOutputDocTypeDecl = true;
  99. m_startNewLine = false;
  100. if (getOmitXMLDeclaration() == false)
  101. {
  102. String encoding = Encodings.getMimeEncoding(getEncoding());
  103. String version = getVersion();
  104. if (version == null)
  105. version = "1.0";
  106. String standalone;
  107. if (m_standaloneWasSpecified)
  108. {
  109. standalone = " standalone=\"" + getStandalone() + "\"";
  110. }
  111. else
  112. {
  113. standalone = "";
  114. }
  115. try
  116. {
  117. final java.io.Writer writer = m_writer;
  118. writer.write("<?xml version=\"");
  119. writer.write(version);
  120. writer.write("\" encoding=\"");
  121. writer.write(encoding);
  122. writer.write('\"');
  123. writer.write(standalone);
  124. writer.write("?>");
  125. if (m_doIndent)
  126. writer.write(m_lineSep, 0, m_lineSepLen);
  127. }
  128. catch(IOException e)
  129. {
  130. throw new SAXException(e);
  131. }
  132. }
  133. }
  134. }
  135. /**
  136. * Receive notification of the end of a document.
  137. *
  138. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  139. * wrapping another exception.
  140. *
  141. * @throws org.xml.sax.SAXException
  142. */
  143. public void endDocument() throws org.xml.sax.SAXException
  144. {
  145. flushPending();
  146. if (m_doIndent && !m_isprevtext)
  147. {
  148. try
  149. {
  150. outputLineSep();
  151. }
  152. catch(IOException e)
  153. {
  154. throw new SAXException(e);
  155. }
  156. }
  157. flushWriter();
  158. if (m_tracer != null)
  159. super.fireEndDoc();
  160. }
  161. /**
  162. * Starts a whitespace preserving section. All characters printed
  163. * within a preserving section are printed without indentation and
  164. * without consolidating multiple spaces. This is equivalent to
  165. * the <tt>xml:space="preserve"</tt> attribute. Only XML
  166. * and HTML serializers need to support this method.
  167. * <p>
  168. * The contents of the whitespace preserving section will be delivered
  169. * through the regular <tt>characters</tt> event.
  170. *
  171. * @throws org.xml.sax.SAXException
  172. */
  173. public void startPreserving() throws org.xml.sax.SAXException
  174. {
  175. // Not sure this is really what we want. -sb
  176. m_preserves.push(true);
  177. m_ispreserve = true;
  178. }
  179. /**
  180. * Ends a whitespace preserving section.
  181. *
  182. * @see #startPreserving
  183. *
  184. * @throws org.xml.sax.SAXException
  185. */
  186. public void endPreserving() throws org.xml.sax.SAXException
  187. {
  188. // Not sure this is really what we want. -sb
  189. m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
  190. }
  191. /**
  192. * Receive notification of a processing instruction.
  193. *
  194. * @param target The processing instruction target.
  195. * @param data The processing instruction data, or null if
  196. * none was supplied.
  197. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  198. * wrapping another exception.
  199. *
  200. * @throws org.xml.sax.SAXException
  201. */
  202. public void processingInstruction(String target, String data)
  203. throws org.xml.sax.SAXException
  204. {
  205. if (m_inEntityRef)
  206. return;
  207. flushPending();
  208. if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING))
  209. {
  210. startNonEscaping();
  211. }
  212. else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING))
  213. {
  214. endNonEscaping();
  215. }
  216. else
  217. {
  218. try
  219. {
  220. if (m_elemContext.m_startTagOpen)
  221. {
  222. closeStartTag();
  223. m_elemContext.m_startTagOpen = false;
  224. }
  225. if (shouldIndent())
  226. indent();
  227. final java.io.Writer writer = m_writer;
  228. writer.write("<?");
  229. writer.write(target);
  230. if (data.length() > 0
  231. && !Character.isSpaceChar(data.charAt(0)))
  232. writer.write(' ');
  233. int indexOfQLT = data.indexOf("?>");
  234. if (indexOfQLT >= 0)
  235. {
  236. // See XSLT spec on error recovery of "?>" in PIs.
  237. if (indexOfQLT > 0)
  238. {
  239. writer.write(data.substring(0, indexOfQLT));
  240. }
  241. writer.write("? >"); // add space between.
  242. if ((indexOfQLT + 2) < data.length())
  243. {
  244. writer.write(data.substring(indexOfQLT + 2));
  245. }
  246. }
  247. else
  248. {
  249. writer.write(data);
  250. }
  251. writer.write('?');
  252. writer.write('>');
  253. // Always output a newline char if not inside of an
  254. // element. The whitespace is not significant in that
  255. // case.
  256. if (m_elemContext.m_currentElemDepth <= 0)
  257. writer.write(m_lineSep, 0, m_lineSepLen);
  258. m_startNewLine = true;
  259. }
  260. catch(IOException e)
  261. {
  262. throw new SAXException(e);
  263. }
  264. }
  265. if (m_tracer != null)
  266. super.fireEscapingEvent(target, data);
  267. }
  268. /**
  269. * Receive notivication of a entityReference.
  270. *
  271. * @param name The name of the entity.
  272. *
  273. * @throws org.xml.sax.SAXException
  274. */
  275. public void entityReference(String name) throws org.xml.sax.SAXException
  276. {
  277. if (m_elemContext.m_startTagOpen)
  278. {
  279. closeStartTag();
  280. m_elemContext.m_startTagOpen = false;
  281. }
  282. try
  283. {
  284. if (shouldIndent())
  285. indent();
  286. final java.io.Writer writer = m_writer;
  287. writer.write('&');
  288. writer.write(name);
  289. writer.write(';');
  290. }
  291. catch(IOException e)
  292. {
  293. throw new SAXException(e);
  294. }
  295. if (m_tracer != null)
  296. super.fireEntityReference(name);
  297. }
  298. /**
  299. * This method is used to add an attribute to the currently open element.
  300. * The caller has guaranted that this attribute is unique, which means that it
  301. * not been seen before and will not be seen again.
  302. *
  303. * @param name the qualified name of the attribute
  304. * @param value the value of the attribute which can contain only
  305. * ASCII printable characters characters in the range 32 to 127 inclusive.
  306. * @param flags the bit values of this integer give optimization information.
  307. */
  308. public void addUniqueAttribute(String name, String value, int flags)
  309. throws SAXException
  310. {
  311. if (m_elemContext.m_startTagOpen)
  312. {
  313. try
  314. {
  315. final String patchedName = patchName(name);
  316. final java.io.Writer writer = m_writer;
  317. if ((flags & NO_BAD_CHARS) > 0 && m_xmlcharInfo.onlyQuotAmpLtGt)
  318. {
  319. // "flags" has indicated that the characters
  320. // '>' '<' '&' and '"' are not in the value and
  321. // m_htmlcharInfo has recorded that there are no other
  322. // entities in the range 32 to 127 so we write out the
  323. // value directly
  324. writer.write(' ');
  325. writer.write(patchedName);
  326. writer.write("=\"");
  327. writer.write(value);
  328. writer.write('"');
  329. }
  330. else
  331. {
  332. writer.write(' ');
  333. writer.write(patchedName);
  334. writer.write("=\"");
  335. writeAttrString(writer, value, this.getEncoding());
  336. writer.write('"');
  337. }
  338. } catch (IOException e) {
  339. throw new SAXException(e);
  340. }
  341. }
  342. }
  343. public void addAttribute(
  344. String uri,
  345. String localName,
  346. String rawName,
  347. String type,
  348. String value)
  349. throws SAXException
  350. {
  351. if (m_elemContext.m_startTagOpen)
  352. {
  353. if (!rawName.startsWith("xmlns"))
  354. {
  355. String prefixUsed =
  356. ensureAttributesNamespaceIsDeclared(
  357. uri,
  358. localName,
  359. rawName);
  360. if (prefixUsed != null
  361. && rawName != null
  362. && !rawName.startsWith(prefixUsed))
  363. {
  364. // use a different raw name, with the prefix used in the
  365. // generated namespace declaration
  366. rawName = prefixUsed + ":" + localName;
  367. }
  368. }
  369. addAttributeAlways(uri, localName, rawName, type, value);
  370. }
  371. else
  372. {
  373. /*
  374. * The startTag is closed, yet we are adding an attribute?
  375. *
  376. * Section: 7.1.3 Creating Attributes Adding an attribute to an
  377. * element after a PI (for example) has been added to it is an
  378. * error. The attributes can be ignored. The spec doesn't explicitly
  379. * say this is disallowed, as it does for child elements, but it
  380. * makes sense to have the same treatment.
  381. *
  382. * We choose to ignore the attribute which is added too late.
  383. */
  384. // Generate a warning of the ignored attributes
  385. // Create the warning message
  386. String msg = XMLMessages.createXMLMessage(
  387. XMLErrorResources.ER_ILLEGAL_ATTRIBUTE_POSITION,new Object[]{ localName });
  388. try {
  389. // Prepare to issue the warning message
  390. Transformer tran = super.getTransformer();
  391. ErrorListener errHandler = tran.getErrorListener();
  392. // Issue the warning message
  393. if (null != errHandler && m_sourceLocator != null)
  394. errHandler.warning(new TransformerException(msg, m_sourceLocator));
  395. else
  396. System.out.println(msg);
  397. }
  398. catch (Exception e){}
  399. }
  400. }
  401. /**
  402. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#endElement(String)
  403. */
  404. public void endElement(String elemName) throws SAXException
  405. {
  406. endElement(null, null, elemName);
  407. }
  408. /**
  409. * From XSLTC
  410. * Related to startPrefixMapping ???
  411. */
  412. public void namespaceAfterStartElement(
  413. final String prefix,
  414. final String uri)
  415. throws SAXException
  416. {
  417. // hack for XSLTC with finding URI for default namespace
  418. if (m_elemContext.m_elementURI == null)
  419. {
  420. String prefix1 = getPrefixPart(m_elemContext.m_elementName);
  421. if (prefix1 == null && EMPTYSTRING.equals(prefix))
  422. {
  423. // the elements URI is not known yet, and it
  424. // doesn't have a prefix, and we are currently
  425. // setting the uri for prefix "", so we have
  426. // the uri for the element... lets remember it
  427. m_elemContext.m_elementURI = uri;
  428. }
  429. }
  430. startPrefixMapping(prefix,uri,false);
  431. return;
  432. }
  433. /**
  434. * From XSLTC
  435. * Declare a prefix to point to a namespace URI. Inform SAX handler
  436. * if this is a new prefix mapping.
  437. */
  438. protected boolean pushNamespace(String prefix, String uri)
  439. {
  440. try
  441. {
  442. if (m_prefixMap.pushNamespace(
  443. prefix, uri, m_elemContext.m_currentElemDepth))
  444. {
  445. startPrefixMapping(prefix, uri);
  446. return true;
  447. }
  448. }
  449. catch (SAXException e)
  450. {
  451. // falls through
  452. }
  453. return false;
  454. }
  455. /**
  456. * Try's to reset the super class and reset this class for
  457. * re-use, so that you don't need to create a new serializer
  458. * (mostly for performance reasons).
  459. *
  460. * @return true if the class was successfuly reset.
  461. */
  462. public boolean reset()
  463. {
  464. boolean wasReset = false;
  465. if (super.reset())
  466. {
  467. resetToXMLStream();
  468. wasReset = true;
  469. }
  470. return wasReset;
  471. }
  472. /**
  473. * Reset all of the fields owned by ToStream class
  474. *
  475. */
  476. private void resetToXMLStream()
  477. {
  478. this.m_cdataTagOpen = false;
  479. }
  480. }