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: ToStream.java,v 1.29 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 java.io.OutputStream;
  22. import java.io.UnsupportedEncodingException;
  23. import java.io.Writer;
  24. import java.util.Properties;
  25. import java.util.StringTokenizer;
  26. import java.util.Vector;
  27. import javax.xml.transform.OutputKeys;
  28. import javax.xml.transform.Transformer;
  29. import com.sun.org.apache.xml.internal.res.XMLErrorResources;
  30. import com.sun.org.apache.xml.internal.res.XMLMessages;
  31. import com.sun.org.apache.xml.internal.utils.BoolStack;
  32. import com.sun.org.apache.xml.internal.utils.FastStringBuffer;
  33. import com.sun.org.apache.xml.internal.utils.QName;
  34. import com.sun.org.apache.xml.internal.utils.TreeWalker;
  35. import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException;
  36. import org.w3c.dom.Node;
  37. import org.xml.sax.Attributes;
  38. import org.xml.sax.ContentHandler;
  39. import org.xml.sax.SAXException;
  40. //import com.sun.media.sound.IESecurity;
  41. /**
  42. * This abstract class is a base class for other stream
  43. * serializers (xml, html, text ...) that write output to a stream.
  44. * @author Santiago Pericas-Geertsen
  45. * @author G. Todd Miller
  46. */
  47. abstract public class ToStream extends SerializerBase
  48. {
  49. private static final String COMMENT_BEGIN = "<!--";
  50. private static final String COMMENT_END = "-->";
  51. /** Stack to keep track of disabling output escaping. */
  52. protected BoolStack m_disableOutputEscapingStates = new BoolStack();
  53. /**
  54. * Boolean that tells if we already tried to get the converter.
  55. */
  56. boolean m_triedToGetConverter = false;
  57. /**
  58. * Method reference to the sun.io.CharToByteConverter#canConvert method
  59. * for this encoding. Invalid if m_charToByteConverter is null.
  60. */
  61. java.lang.reflect.Method m_canConvertMeth;
  62. /**
  63. * Opaque reference to the sun.io.CharToByteConverter for this
  64. * encoding.
  65. */
  66. Object m_charToByteConverter = null;
  67. /**
  68. * Stack to keep track of whether or not we need to
  69. * preserve whitespace.
  70. *
  71. * Used to push/pop values used for the field m_ispreserve, but
  72. * m_ispreserve is only relevant if m_doIndent is true.
  73. * If m_doIndent is false this field has no impact.
  74. *
  75. */
  76. protected BoolStack m_preserves = new BoolStack();
  77. /**
  78. * State flag to tell if preservation of whitespace
  79. * is important.
  80. *
  81. * Used only in shouldIndent() but only if m_doIndent is true.
  82. * If m_doIndent is false this flag has no impact.
  83. *
  84. */
  85. protected boolean m_ispreserve = false;
  86. /**
  87. * State flag that tells if the previous node processed
  88. * was text, so we can tell if we should preserve whitespace.
  89. *
  90. * Used in endDocument() and shouldIndent() but
  91. * only if m_doIndent is true.
  92. * If m_doIndent is false this flag has no impact.
  93. */
  94. protected boolean m_isprevtext = false;
  95. /**
  96. * The maximum character size before we have to resort
  97. * to escaping.
  98. */
  99. protected int m_maxCharacter = Encodings.getLastPrintable();
  100. /**
  101. * The system line separator for writing out line breaks.
  102. */
  103. protected final char[] m_lineSep =
  104. System.getProperty("line.separator").toCharArray();
  105. /**
  106. * True if the the system line separator is to be used.
  107. */
  108. protected boolean m_lineSepUse = true;
  109. /**
  110. * The length of the line seperator, since the write is done
  111. * one character at a time.
  112. */
  113. protected final int m_lineSepLen = m_lineSep.length;
  114. /**
  115. * Map that tells which characters should have special treatment, and it
  116. * provides character to entity name lookup.
  117. */
  118. protected CharInfo m_charInfo;
  119. /** True if we control the buffer, and we should flush the output on endDocument. */
  120. boolean m_shouldFlush = true;
  121. /**
  122. * Add space before '/>' for XHTML.
  123. */
  124. protected boolean m_spaceBeforeClose = false;
  125. /**
  126. * Flag to signal that a newline should be added.
  127. *
  128. * Used only in indent() which is called only if m_doIndent is true.
  129. * If m_doIndent is false this flag has no impact.
  130. */
  131. boolean m_startNewLine;
  132. /**
  133. * Tells if we're in an internal document type subset.
  134. */
  135. protected boolean m_inDoctype = false;
  136. /**
  137. * Flag to quickly tell if the encoding is UTF8.
  138. */
  139. boolean m_isUTF8 = false;
  140. /** The xsl:output properties. */
  141. protected Properties m_format;
  142. /**
  143. * remembers if we are in between the startCDATA() and endCDATA() callbacks
  144. */
  145. protected boolean m_cdataStartCalled = false;
  146. /**
  147. * Default constructor
  148. */
  149. public ToStream()
  150. {
  151. }
  152. /**
  153. * This helper method to writes out "]]>" when closing a CDATA section.
  154. *
  155. * @throws org.xml.sax.SAXException
  156. */
  157. protected void closeCDATA() throws org.xml.sax.SAXException
  158. {
  159. try
  160. {
  161. m_writer.write(CDATA_DELIMITER_CLOSE);
  162. // write out a CDATA section closing "]]>"
  163. m_cdataTagOpen = false; // Remember that we have done so.
  164. }
  165. catch (IOException e)
  166. {
  167. throw new SAXException(e);
  168. }
  169. }
  170. /**
  171. * Serializes the DOM node. Throws an exception only if an I/O
  172. * exception occured while serializing.
  173. *
  174. * @param elem The element to serialize
  175. *
  176. * @param node Node to serialize.
  177. * @throws IOException An I/O exception occured while serializing
  178. */
  179. public void serialize(Node node) throws IOException
  180. {
  181. try
  182. {
  183. TreeWalker walker =
  184. new TreeWalker(this, new com.sun.org.apache.xml.internal.utils.DOM2Helper());
  185. walker.traverse(node);
  186. }
  187. catch (org.xml.sax.SAXException se)
  188. {
  189. throw new WrappedRuntimeException(se);
  190. }
  191. }
  192. /**
  193. * Return true if the character is the high member of a surrogate pair.
  194. *
  195. * NEEDSDOC @param c
  196. *
  197. * NEEDSDOC ($objectName$) @return
  198. */
  199. static final boolean isUTF16Surrogate(char c)
  200. {
  201. return (c & 0xFC00) == 0xD800;
  202. }
  203. /**
  204. * Taken from XSLTC
  205. */
  206. private boolean m_escaping = true;
  207. /**
  208. * Flush the formatter's result stream.
  209. *
  210. * @throws org.xml.sax.SAXException
  211. */
  212. protected final void flushWriter() throws org.xml.sax.SAXException
  213. {
  214. final java.io.Writer writer = m_writer;
  215. if (null != writer)
  216. {
  217. try
  218. {
  219. if (writer instanceof WriterToUTF8Buffered)
  220. {
  221. if (m_shouldFlush)
  222. ((WriterToUTF8Buffered) writer).flush();
  223. else
  224. ((WriterToUTF8Buffered) writer).flushBuffer();
  225. }
  226. if (writer instanceof WriterToASCI)
  227. {
  228. if (m_shouldFlush)
  229. writer.flush();
  230. }
  231. else
  232. {
  233. // Flush always.
  234. // Not a great thing if the writer was created
  235. // by this class, but don't have a choice.
  236. writer.flush();
  237. }
  238. }
  239. catch (IOException ioe)
  240. {
  241. throw new org.xml.sax.SAXException(ioe);
  242. }
  243. }
  244. }
  245. /**
  246. * Get the output stream where the events will be serialized to.
  247. *
  248. * @return reference to the result stream, or null of only a writer was
  249. * set.
  250. */
  251. public OutputStream getOutputStream()
  252. {
  253. if (m_writer instanceof WriterToUTF8Buffered)
  254. return ((WriterToUTF8Buffered) m_writer).getOutputStream();
  255. if (m_writer instanceof WriterToASCI)
  256. return ((WriterToASCI) m_writer).getOutputStream();
  257. else
  258. return null;
  259. }
  260. // Implement DeclHandler
  261. /**
  262. * Report an element type declaration.
  263. *
  264. * <p>The content model will consist of the string "EMPTY", the
  265. * string "ANY", or a parenthesised group, optionally followed
  266. * by an occurrence indicator. The model will be normalized so
  267. * that all whitespace is removed,and will include the enclosing
  268. * parentheses.</p>
  269. *
  270. * @param name The element type name.
  271. * @param model The content model as a normalized string.
  272. * @exception SAXException The application may raise an exception.
  273. */
  274. public void elementDecl(String name, String model) throws SAXException
  275. {
  276. // Do not inline external DTD
  277. if (m_inExternalDTD)
  278. return;
  279. try
  280. {
  281. final java.io.Writer writer = m_writer;
  282. if (m_needToOutputDocTypeDecl)
  283. {
  284. outputDocTypeDecl(m_elemContext.m_elementName, false);
  285. m_needToOutputDocTypeDecl = false;
  286. }
  287. if (m_inDoctype)
  288. {
  289. writer.write(" [");
  290. writer.write(m_lineSep, 0, m_lineSepLen);
  291. m_inDoctype = false;
  292. }
  293. writer.write("<!ELEMENT ");
  294. writer.write(name);
  295. writer.write(' ');
  296. writer.write(model);
  297. writer.write('>');
  298. writer.write(m_lineSep, 0, m_lineSepLen);
  299. }
  300. catch (IOException e)
  301. {
  302. throw new SAXException(e);
  303. }
  304. }
  305. /**
  306. * Report an internal entity declaration.
  307. *
  308. * <p>Only the effective (first) declaration for each entity
  309. * will be reported.</p>
  310. *
  311. * @param name The name of the entity. If it is a parameter
  312. * entity, the name will begin with '%'.
  313. * @param value The replacement text of the entity.
  314. * @exception SAXException The application may raise an exception.
  315. * @see #externalEntityDecl
  316. * @see org.xml.sax.DTDHandler#unparsedEntityDecl
  317. */
  318. public void internalEntityDecl(String name, String value)
  319. throws SAXException
  320. {
  321. // Do not inline external DTD
  322. if (m_inExternalDTD)
  323. return;
  324. try
  325. {
  326. if (m_needToOutputDocTypeDecl)
  327. {
  328. outputDocTypeDecl(m_elemContext.m_elementName, false);
  329. m_needToOutputDocTypeDecl = false;
  330. }
  331. if (m_inDoctype)
  332. {
  333. final java.io.Writer writer = m_writer;
  334. writer.write(" [");
  335. writer.write(m_lineSep, 0, m_lineSepLen);
  336. m_inDoctype = false;
  337. }
  338. outputEntityDecl(name, value);
  339. }
  340. catch (IOException e)
  341. {
  342. throw new SAXException(e);
  343. }
  344. }
  345. /**
  346. * Output the doc type declaration.
  347. *
  348. * @param name non-null reference to document type name.
  349. * NEEDSDOC @param value
  350. *
  351. * @throws org.xml.sax.SAXException
  352. */
  353. void outputEntityDecl(String name, String value) throws IOException
  354. {
  355. final java.io.Writer writer = m_writer;
  356. writer.write("<!ENTITY ");
  357. writer.write(name);
  358. writer.write(" \"");
  359. writer.write(value);
  360. writer.write("\">");
  361. writer.write(m_lineSep, 0, m_lineSepLen);
  362. }
  363. /**
  364. * Output a system-dependent line break.
  365. *
  366. * @throws org.xml.sax.SAXException
  367. */
  368. protected final void outputLineSep() throws IOException
  369. {
  370. m_writer.write(m_lineSep, 0, m_lineSepLen);
  371. }
  372. /**
  373. * Specifies an output format for this serializer. It the
  374. * serializer has already been associated with an output format,
  375. * it will switch to the new format. This method should not be
  376. * called while the serializer is in the process of serializing
  377. * a document.
  378. *
  379. * @param format The output format to use
  380. */
  381. public void setOutputFormat(Properties format)
  382. {
  383. boolean shouldFlush = m_shouldFlush;
  384. init(m_writer, format, false, false);
  385. m_shouldFlush = shouldFlush;
  386. }
  387. /**
  388. * Initialize the serializer with the specified writer and output format.
  389. * Must be called before calling any of the serialize methods.
  390. *
  391. * @param writer The writer to use
  392. * @param format The output format
  393. * @param shouldFlush True if the writer should be flushed at EndDocument.
  394. */
  395. private synchronized void init(
  396. Writer writer,
  397. Properties format,
  398. boolean defaultProperties,
  399. boolean shouldFlush)
  400. {
  401. m_shouldFlush = shouldFlush;
  402. // if we are tracing events we need to trace what
  403. // characters are written to the output writer.
  404. if (m_tracer != null
  405. && !(writer instanceof SerializerTraceWriter) )
  406. m_writer = new SerializerTraceWriter(writer, m_tracer);
  407. else
  408. m_writer = writer;
  409. m_format = format;
  410. // m_cdataSectionNames =
  411. // OutputProperties.getQNameProperties(
  412. // OutputKeys.CDATA_SECTION_ELEMENTS,
  413. // format);
  414. setCdataSectionElements(OutputKeys.CDATA_SECTION_ELEMENTS, format);
  415. setIndentAmount(
  416. OutputPropertyUtils.getIntProperty(
  417. OutputPropertiesFactory.S_KEY_INDENT_AMOUNT,
  418. format));
  419. setIndent(
  420. OutputPropertyUtils.getBooleanProperty(OutputKeys.INDENT, format));
  421. boolean shouldNotWriteXMLHeader =
  422. OutputPropertyUtils.getBooleanProperty(
  423. OutputKeys.OMIT_XML_DECLARATION,
  424. format);
  425. setOmitXMLDeclaration(shouldNotWriteXMLHeader);
  426. setDoctypeSystem(format.getProperty(OutputKeys.DOCTYPE_SYSTEM));
  427. String doctypePublic = format.getProperty(OutputKeys.DOCTYPE_PUBLIC);
  428. setDoctypePublic(doctypePublic);
  429. // if standalone was explicitly specified
  430. if (format.get(OutputKeys.STANDALONE) != null)
  431. {
  432. String val = format.getProperty(OutputKeys.STANDALONE);
  433. if (defaultProperties)
  434. setStandaloneInternal(val);
  435. else
  436. setStandalone(val);
  437. }
  438. setMediaType(format.getProperty(OutputKeys.MEDIA_TYPE));
  439. if (null != doctypePublic)
  440. {
  441. if (doctypePublic.startsWith("-//W3C//DTD XHTML"))
  442. m_spaceBeforeClose = true;
  443. }
  444. // initCharsMap();
  445. String encoding = getEncoding();
  446. if (null == encoding)
  447. {
  448. encoding =
  449. Encodings.getMimeEncoding(
  450. format.getProperty(OutputKeys.ENCODING));
  451. setEncoding(encoding);
  452. }
  453. m_isUTF8 = encoding.equals(Encodings.DEFAULT_MIME_ENCODING);
  454. m_maxCharacter = Encodings.getLastPrintable(encoding);
  455. // Access this only from the Hashtable level... we don't want to
  456. // get default properties.
  457. String entitiesFileName =
  458. (String) format.get(OutputPropertiesFactory.S_KEY_ENTITIES);
  459. if (null != entitiesFileName)
  460. {
  461. String method =
  462. (String) format.get(OutputKeys.METHOD);
  463. m_charInfo = CharInfo.getCharInfo(entitiesFileName, method);
  464. }
  465. }
  466. /**
  467. * Initialize the serializer with the specified writer and output format.
  468. * Must be called before calling any of the serialize methods.
  469. *
  470. * @param writer The writer to use
  471. * @param format The output format
  472. */
  473. private synchronized void init(Writer writer, Properties format)
  474. {
  475. init(writer, format, false, false);
  476. }
  477. /**
  478. * Initialize the serializer with the specified output stream and output
  479. * format. Must be called before calling any of the serialize methods.
  480. *
  481. * @param output The output stream to use
  482. * @param format The output format
  483. * @param defaultProperties true if the properties are the default
  484. * properties
  485. *
  486. * @throws UnsupportedEncodingException The encoding specified in the
  487. * output format is not supported
  488. */
  489. protected synchronized void init(
  490. OutputStream output,
  491. Properties format,
  492. boolean defaultProperties)
  493. throws UnsupportedEncodingException
  494. {
  495. String encoding = getEncoding();
  496. if (encoding == null)
  497. {
  498. // if not already set then get it from the properties
  499. encoding =
  500. Encodings.getMimeEncoding(
  501. format.getProperty(OutputKeys.ENCODING));
  502. setEncoding(encoding);
  503. }
  504. if (encoding.equalsIgnoreCase("UTF-8"))
  505. {
  506. m_isUTF8 = true;
  507. // if (output instanceof java.io.BufferedOutputStream)
  508. // {
  509. // init(new WriterToUTF8(output), format, defaultProperties, true);
  510. // }
  511. // else if (output instanceof java.io.FileOutputStream)
  512. // {
  513. // init(new WriterToUTF8Buffered(output), format, defaultProperties, true);
  514. // }
  515. // else
  516. // {
  517. // // Not sure what to do in this case. I'm going to be conservative
  518. // // and not buffer.
  519. // init(new WriterToUTF8(output), format, defaultProperties, true);
  520. // }
  521. init(
  522. new WriterToUTF8Buffered(output),
  523. format,
  524. defaultProperties,
  525. true);
  526. }
  527. else if (
  528. encoding.equals("WINDOWS-1250")
  529. || encoding.equals("US-ASCII")
  530. || encoding.equals("ASCII"))
  531. {
  532. init(new WriterToASCI(output), format, defaultProperties, true);
  533. }
  534. else
  535. {
  536. Writer osw;
  537. try
  538. {
  539. osw = Encodings.getWriter(output, encoding);
  540. }
  541. catch (UnsupportedEncodingException uee)
  542. {
  543. System.out.println(
  544. "Warning: encoding \""
  545. + encoding
  546. + "\" not supported"
  547. + ", using "
  548. + Encodings.DEFAULT_MIME_ENCODING);
  549. encoding = Encodings.DEFAULT_MIME_ENCODING;
  550. setEncoding(encoding);
  551. osw = Encodings.getWriter(output, encoding);
  552. }
  553. m_maxCharacter = Encodings.getLastPrintable(encoding);
  554. init(osw, format, defaultProperties, true);
  555. }
  556. }
  557. /**
  558. * Returns the output format for this serializer.
  559. *
  560. * @return The output format in use
  561. */
  562. public Properties getOutputFormat()
  563. {
  564. return m_format;
  565. }
  566. /**
  567. * Specifies a writer to which the document should be serialized.
  568. * This method should not be called while the serializer is in
  569. * the process of serializing a document.
  570. *
  571. * @param writer The output writer stream
  572. */
  573. public void setWriter(Writer writer)
  574. {
  575. // if we are tracing events we need to trace what
  576. // characters are written to the output writer.
  577. if (m_tracer != null
  578. && !(writer instanceof SerializerTraceWriter) )
  579. m_writer = new SerializerTraceWriter(writer, m_tracer);
  580. else
  581. m_writer = writer;
  582. }
  583. /**
  584. * Set if the operating systems end-of-line line separator should
  585. * be used when serializing. If set false NL character
  586. * (decimal 10) is left alone, otherwise the new-line will be replaced on
  587. * output with the systems line separator. For example on UNIX this is
  588. * NL, while on Windows it is two characters, CR NL, where CR is the
  589. * carriage-return (decimal 13).
  590. *
  591. * @param use_sytem_line_break True if an input NL is replaced with the
  592. * operating systems end-of-line separator.
  593. * @return The previously set value of the serializer.
  594. */
  595. public boolean setLineSepUse(boolean use_sytem_line_break)
  596. {
  597. boolean oldValue = m_lineSepUse;
  598. m_lineSepUse = use_sytem_line_break;
  599. return oldValue;
  600. }
  601. /**
  602. * Specifies an output stream to which the document should be
  603. * serialized. This method should not be called while the
  604. * serializer is in the process of serializing a document.
  605. * <p>
  606. * The encoding specified in the output properties is used, or
  607. * if no encoding was specified, the default for the selected
  608. * output method.
  609. *
  610. * @param output The output stream
  611. */
  612. public void setOutputStream(OutputStream output)
  613. {
  614. try
  615. {
  616. Properties format;
  617. if (null == m_format)
  618. format =
  619. OutputPropertiesFactory.getDefaultMethodProperties(
  620. Method.XML);
  621. else
  622. format = m_format;
  623. init(output, format, true);
  624. }
  625. catch (UnsupportedEncodingException uee)
  626. {
  627. // Should have been warned in init, I guess...
  628. }
  629. }
  630. /**
  631. * @see com.sun.org.apache.xml.internal.serializer.SerializationHandler#setEscaping(boolean)
  632. */
  633. public boolean setEscaping(boolean escape)
  634. {
  635. final boolean temp = m_escaping;
  636. m_escaping = escape;
  637. return temp;
  638. }
  639. /**
  640. * Might print a newline character and the indentation amount
  641. * of the given depth.
  642. *
  643. * @param depth the indentation depth (element nesting depth)
  644. *
  645. * @throws org.xml.sax.SAXException if an error occurs during writing.
  646. */
  647. protected void indent(int depth) throws IOException
  648. {
  649. if (m_startNewLine)
  650. outputLineSep();
  651. /* For m_indentAmount > 0 this extra test might be slower
  652. * but Xalan's default value is 0, so this extra test
  653. * will run faster in that situation.
  654. */
  655. if (m_indentAmount > 0)
  656. printSpace(depth * m_indentAmount);
  657. }
  658. /**
  659. * Indent at the current element nesting depth.
  660. * @throws IOException
  661. */
  662. protected void indent() throws IOException
  663. {
  664. indent(m_elemContext.m_currentElemDepth);
  665. }
  666. /**
  667. * Prints <var>n</var> spaces.
  668. * @param pw The character output stream to use.
  669. * @param n Number of spaces to print.
  670. *
  671. * @throws org.xml.sax.SAXException if an error occurs when writing.
  672. */
  673. private void printSpace(int n) throws IOException
  674. {
  675. final java.io.Writer writer = m_writer;
  676. for (int i = 0; i < n; i++)
  677. {
  678. writer.write(' ');
  679. }
  680. }
  681. /**
  682. * Report an attribute type declaration.
  683. *
  684. * <p>Only the effective (first) declaration for an attribute will
  685. * be reported. The type will be one of the strings "CDATA",
  686. * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY",
  687. * "ENTITIES", or "NOTATION", or a parenthesized token group with
  688. * the separator "|" and all whitespace removed.</p>
  689. *
  690. * @param eName The name of the associated element.
  691. * @param aName The name of the attribute.
  692. * @param type A string representing the attribute type.
  693. * @param valueDefault A string representing the attribute default
  694. * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if
  695. * none of these applies.
  696. * @param value A string representing the attribute's default value,
  697. * or null if there is none.
  698. * @exception SAXException The application may raise an exception.
  699. */
  700. public void attributeDecl(
  701. String eName,
  702. String aName,
  703. String type,
  704. String valueDefault,
  705. String value)
  706. throws SAXException
  707. {
  708. // Do not inline external DTD
  709. if (m_inExternalDTD)
  710. return;
  711. try
  712. {
  713. final java.io.Writer writer = m_writer;
  714. if (m_needToOutputDocTypeDecl)
  715. {
  716. outputDocTypeDecl(m_elemContext.m_elementName, false);
  717. m_needToOutputDocTypeDecl = false;
  718. }
  719. if (m_inDoctype)
  720. {
  721. writer.write(" [");
  722. writer.write(m_lineSep, 0, m_lineSepLen);
  723. m_inDoctype = false;
  724. }
  725. writer.write("<!ATTLIST ");
  726. writer.write(eName);
  727. writer.write(' ');
  728. writer.write(aName);
  729. writer.write(' ');
  730. writer.write(type);
  731. if (valueDefault != null)
  732. {
  733. writer.write(' ');
  734. writer.write(valueDefault);
  735. }
  736. //writer.write(" ");
  737. //writer.write(value);
  738. writer.write('>');
  739. writer.write(m_lineSep, 0, m_lineSepLen);
  740. }
  741. catch (IOException e)
  742. {
  743. throw new SAXException(e);
  744. }
  745. }
  746. /**
  747. * Get the character stream where the events will be serialized to.
  748. *
  749. * @return Reference to the result Writer, or null.
  750. */
  751. public Writer getWriter()
  752. {
  753. return m_writer;
  754. }
  755. /**
  756. * Report a parsed external entity declaration.
  757. *
  758. * <p>Only the effective (first) declaration for each entity
  759. * will be reported.</p>
  760. *
  761. * @param name The name of the entity. If it is a parameter
  762. * entity, the name will begin with '%'.
  763. * @param publicId The declared public identifier of the entity, or
  764. * null if none was declared.
  765. * @param systemId The declared system identifier of the entity.
  766. * @exception SAXException The application may raise an exception.
  767. * @see #internalEntityDecl
  768. * @see org.xml.sax.DTDHandler#unparsedEntityDecl
  769. */
  770. public void externalEntityDecl(
  771. String name,
  772. String publicId,
  773. String systemId)
  774. throws SAXException
  775. {
  776. }
  777. /**
  778. * Tell if this character can be written without escaping.
  779. */
  780. protected boolean escapingNotNeeded(char ch)
  781. {
  782. if (ch < 127)
  783. {
  784. if (ch >= 0x20 || (0x0A == ch || 0x0D == ch || 0x09 == ch))
  785. return true;
  786. else
  787. return false;
  788. }
  789. if (null == m_charToByteConverter && false == m_triedToGetConverter)
  790. {
  791. m_triedToGetConverter = true;
  792. try
  793. {
  794. m_charToByteConverter =
  795. Encodings.getCharToByteConverter(getEncoding());
  796. if (null != m_charToByteConverter)
  797. {
  798. Class argsTypes[] = new Class[1];
  799. argsTypes[0] = Character.TYPE;
  800. Class convClass = m_charToByteConverter.getClass();
  801. m_canConvertMeth =
  802. convClass.getMethod("canConvert", argsTypes);
  803. }
  804. }
  805. catch (Exception e)
  806. {
  807. // This is just an assert: no action at the moment.
  808. System.err.println("Warning: " + e.getMessage());
  809. }
  810. }
  811. if (null != m_charToByteConverter)
  812. {
  813. try
  814. {
  815. Object args[] = new Object[1];
  816. args[0] = new Character(ch);
  817. Boolean bool =
  818. (Boolean) m_canConvertMeth.invoke(
  819. m_charToByteConverter,
  820. args);
  821. return bool.booleanValue()
  822. ? !Character.isISOControl(ch)
  823. : false;
  824. }
  825. catch (java.lang.reflect.InvocationTargetException ite)
  826. {
  827. // This is just an assert: no action at the moment.
  828. System.err.println(
  829. "Warning: InvocationTargetException in canConvert!");
  830. }
  831. catch (java.lang.IllegalAccessException iae)
  832. {
  833. // This is just an assert: no action at the moment.
  834. System.err.println(
  835. "Warning: IllegalAccessException in canConvert!");
  836. }
  837. }
  838. // fallback!
  839. return (ch <= m_maxCharacter);
  840. }
  841. /**
  842. * Once a surrogate has been detected, write out the pair of
  843. * characters as a single character reference.
  844. *
  845. * @param c the first part of the surrogate.
  846. * @param ch Character array.
  847. * @param i position Where the surrogate was detected.
  848. * @param end The end index of the significant characters.
  849. * @throws IOException
  850. * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
  851. */
  852. protected void writeUTF16Surrogate(char c, char ch[], int i, int end)
  853. throws IOException
  854. {
  855. // UTF-16 surrogate
  856. int surrogateValue = getURF16SurrogateValue(c, ch, i, end);
  857. final java.io.Writer writer = m_writer;
  858. writer.write('&');
  859. writer.write('#');
  860. // writer.write('x');
  861. writer.write(Integer.toString(surrogateValue));
  862. writer.write(';');
  863. }
  864. /**
  865. * Once a surrogate has been detected, get the pair as a single integer
  866. * value.
  867. *
  868. * @param c the first part of the surrogate.
  869. * @param ch Character array.
  870. * @param i position Where the surrogate was detected.
  871. * @param end The end index of the significant characters.
  872. * @return the integer value of the UTF-16 surrogate.
  873. * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected.
  874. */
  875. int getURF16SurrogateValue(char c, char ch[], int i, int end)
  876. throws IOException
  877. {
  878. int next;
  879. if (i + 1 >= end)
  880. {
  881. throw new IOException(
  882. XMLMessages.createXMLMessage(
  883. XMLErrorResources.ER_INVALID_UTF16_SURROGATE,
  884. new Object[] { Integer.toHexString((int) c)}));
  885. //"Invalid UTF-16 surrogate detected: "
  886. //+Integer.toHexString((int)c)+ " ?");
  887. }
  888. else
  889. {
  890. next = ch[++i];
  891. if (!(0xdc00 <= next && next < 0xe000))
  892. throw new IOException(
  893. XMLMessages.createXMLMessage(
  894. XMLErrorResources.ER_INVALID_UTF16_SURROGATE,
  895. new Object[] {
  896. Integer.toHexString((int) c)
  897. + " "
  898. + Integer.toHexString(next)}));
  899. //"Invalid UTF-16 surrogate detected: "
  900. //+Integer.toHexString((int)c)+" "+Integer.toHexString(next));
  901. next = ((c - 0xd800) << 10) + next - 0xdc00 + 0x00010000;
  902. }
  903. return next;
  904. }
  905. /**
  906. * Handle one of the default entities, return false if it
  907. * is not a default entity.
  908. *
  909. * @param ch character to be escaped.
  910. * @param i index into character array.
  911. * @param chars non-null reference to character array.
  912. * @param len length of chars.
  913. * @param fromTextNode true if the characters being processed
  914. * are from a text node, false if they are from an attribute value
  915. * @param escLF true if the linefeed should be escaped.
  916. *
  917. * @return i+1 if the character was written, else i.
  918. *
  919. * @throws java.io.IOException
  920. */
  921. protected int accumDefaultEntity(
  922. java.io.Writer writer,
  923. char ch,
  924. int i,
  925. char[] chars,
  926. int len,
  927. boolean fromTextNode,
  928. boolean escLF)
  929. throws IOException
  930. {
  931. if (!escLF && CharInfo.S_LINEFEED == ch)
  932. {
  933. writer.write(m_lineSep, 0, m_lineSepLen);
  934. }
  935. else
  936. {
  937. // if this is text node character and a special one of those,
  938. // or if this is a character from attribute value and a special one of those
  939. if ((fromTextNode && m_charInfo.isSpecialTextChar(ch)) || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch)))
  940. {
  941. String entityRef = m_charInfo.getEntityNameForChar(ch);
  942. if (null != entityRef)
  943. {
  944. writer.write('&');
  945. writer.write(entityRef);
  946. writer.write(';');
  947. }
  948. else
  949. return i;
  950. }
  951. else
  952. return i;
  953. }
  954. return i + 1;
  955. }
  956. /**
  957. * Normalize the characters, but don't escape.
  958. *
  959. * @param ch The characters from the XML document.
  960. * @param start The start position in the array.
  961. * @param length The number of characters to read from the array.
  962. * @param isCData true if a CDATA block should be built around the characters.
  963. * @param useSystemLineSeparator true if the operating systems
  964. * end-of-line separator should be output rather than a new-line character.
  965. *
  966. * @throws IOException
  967. * @throws org.xml.sax.SAXException
  968. */
  969. void writeNormalizedChars(
  970. char ch[],
  971. int start,
  972. int length,
  973. boolean isCData,
  974. boolean useSystemLineSeparator)
  975. throws IOException, org.xml.sax.SAXException
  976. {
  977. final java.io.Writer writer = m_writer;
  978. int end = start + length;
  979. for (int i = start; i < end; i++)
  980. {
  981. char c = ch[i];
  982. if (CharInfo.S_LINEFEED == c && useSystemLineSeparator)
  983. {
  984. writer.write(m_lineSep, 0, m_lineSepLen);
  985. }
  986. else if (isCData && (!escapingNotNeeded(c)))
  987. {
  988. // if (i != 0)
  989. if (m_cdataTagOpen)
  990. closeCDATA();
  991. // This needs to go into a function...
  992. if (isUTF16Surrogate(c))
  993. {
  994. writeUTF16Surrogate(c, ch, i, end);
  995. i++ ; // process two input characters
  996. }
  997. else
  998. {
  999. writer.write("&#");
  1000. String intStr = Integer.toString((int) c);
  1001. writer.write(intStr);
  1002. writer.write(';');
  1003. }
  1004. // if ((i != 0) && (i < (end - 1)))
  1005. // if (!m_cdataTagOpen && (i < (end - 1)))
  1006. // {
  1007. // writer.write(CDATA_DELIMITER_OPEN);
  1008. // m_cdataTagOpen = true;
  1009. // }
  1010. }
  1011. else if (
  1012. isCData
  1013. && ((i < (end - 2))
  1014. && (']' == c)
  1015. && (']' == ch[i + 1])
  1016. && ('>' == ch[i + 2])))
  1017. {
  1018. writer.write(CDATA_CONTINUE);
  1019. i += 2;
  1020. }
  1021. else
  1022. {
  1023. if (escapingNotNeeded(c))
  1024. {
  1025. if (isCData && !m_cdataTagOpen)
  1026. {
  1027. writer.write(CDATA_DELIMITER_OPEN);
  1028. m_cdataTagOpen = true;
  1029. }
  1030. writer.write(c);
  1031. }
  1032. // This needs to go into a function...
  1033. else if (isUTF16Surrogate(c))
  1034. {
  1035. if (m_cdataTagOpen)
  1036. closeCDATA();
  1037. writeUTF16Surrogate(c, ch, i, end);
  1038. i++; // process two input characters
  1039. }
  1040. else
  1041. {
  1042. if (m_cdataTagOpen)
  1043. closeCDATA();
  1044. writer.write("&#");
  1045. String intStr = Integer.toString((int) c);
  1046. writer.write(intStr);
  1047. writer.write(';');
  1048. }
  1049. }
  1050. }
  1051. }
  1052. /**
  1053. * Ends an un-escaping section.
  1054. *
  1055. * @see #startNonEscaping
  1056. *
  1057. * @throws org.xml.sax.SAXException
  1058. */
  1059. public void endNonEscaping() throws org.xml.sax.SAXException
  1060. {
  1061. m_disableOutputEscapingStates.pop();
  1062. }
  1063. /**
  1064. * Starts an un-escaping section. All characters printed within an un-
  1065. * escaping section are printed as is, without escaping special characters
  1066. * into entity references. Only XML and HTML serializers need to support
  1067. * this method.
  1068. * <p> The contents of the un-escaping section will be delivered through the
  1069. * regular <tt>characters</tt> event.
  1070. *
  1071. * @throws org.xml.sax.SAXException
  1072. */
  1073. public void startNonEscaping() throws org.xml.sax.SAXException
  1074. {
  1075. m_disableOutputEscapingStates.push(true);
  1076. }
  1077. /**
  1078. * Receive notification of cdata.
  1079. *
  1080. * <p>The Parser will call this method to report each chunk of
  1081. * character data. SAX parsers may return all contiguous character
  1082. * data in a single chunk, or they may split it into several
  1083. * chunks; however, all of the characters in any single event
  1084. * must come from the same external entity, so that the Locator
  1085. * provides useful information.</p>
  1086. *
  1087. * <p>The application must not attempt to read from the array
  1088. * outside of the specified range.</p>
  1089. *
  1090. * <p>Note that some parsers will report whitespace using the
  1091. * ignorableWhitespace() method rather than this one (validating
  1092. * parsers must do so).</p>
  1093. *
  1094. * @param ch The characters from the XML document.
  1095. * @param start The start position in the array.
  1096. * @param length The number of characters to read from the array.
  1097. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  1098. * wrapping another exception.
  1099. * @see #ignorableWhitespace
  1100. * @see org.xml.sax.Locator
  1101. *
  1102. * @throws org.xml.sax.SAXException
  1103. */
  1104. protected void cdata(char ch[], int start, final int length)
  1105. throws org.xml.sax.SAXException
  1106. {
  1107. try
  1108. {
  1109. final int old_start = start;
  1110. if (m_elemContext.m_startTagOpen)
  1111. {
  1112. closeStartTag();
  1113. m_elemContext.m_startTagOpen = false;
  1114. }
  1115. m_ispreserve = true;
  1116. if (shouldIndent())
  1117. indent();
  1118. boolean writeCDataBrackets =
  1119. (((length >= 1) && escapingNotNeeded(ch[start])));
  1120. /* Write out the CDATA opening delimiter only if
  1121. * we are supposed to, and if we are not already in
  1122. * the middle of a CDATA section
  1123. */
  1124. if (writeCDataBrackets && !m_cdataTagOpen)
  1125. {
  1126. m_writer.write(CDATA_DELIMITER_OPEN);
  1127. m_cdataTagOpen = true;
  1128. }
  1129. // writer.write(ch, start, length);
  1130. if (isEscapingDisabled())
  1131. {
  1132. charactersRaw(ch, start, length);
  1133. }
  1134. else
  1135. writeNormalizedChars(ch, start, length, true, m_lineSepUse);
  1136. /* used to always write out CDATA closing delimiter here,
  1137. * but now we delay, so that we can merge CDATA sections on output.
  1138. * need to write closing delimiter later
  1139. */
  1140. if (writeCDataBrackets)
  1141. {
  1142. /* if the CDATA section ends with ] don't leave it open
  1143. * as there is a chance that an adjacent CDATA sections
  1144. * starts with ]>.
  1145. * We don't want to merge ]] with > , or ] with ]>
  1146. */
  1147. if (ch[start + length - 1] == ']')
  1148. closeCDATA();
  1149. }
  1150. // time to fire off CDATA event
  1151. if (m_tracer != null)
  1152. super.fireCDATAEvent(ch, old_start, length);
  1153. }
  1154. catch (IOException ioe)
  1155. {
  1156. throw new org.xml.sax.SAXException(
  1157. XMLMessages.createXMLMessage(
  1158. XMLErrorResources.ER_OIERROR,
  1159. null),
  1160. ioe);
  1161. //"IO error", ioe);
  1162. }
  1163. }
  1164. /**
  1165. * Tell if the character escaping should be disabled for the current state.
  1166. *
  1167. * @return true if the character escaping should be disabled.
  1168. */
  1169. private boolean isEscapingDisabled()
  1170. {
  1171. return m_disableOutputEscapingStates.peekOrFalse();
  1172. }
  1173. /**
  1174. * If available, when the disable-output-escaping attribute is used,
  1175. * output raw text without escaping.
  1176. *
  1177. * @param ch The characters from the XML document.
  1178. * @param start The start position in the array.
  1179. * @param length The number of characters to read from the array.
  1180. *
  1181. * @throws org.xml.sax.SAXException
  1182. */
  1183. protected void charactersRaw(char ch[], int start, int length)
  1184. throws org.xml.sax.SAXException
  1185. {
  1186. if (m_inEntityRef)
  1187. return;
  1188. try
  1189. {
  1190. if (m_elemContext.m_startTagOpen)
  1191. {
  1192. closeStartTag();
  1193. m_elemContext.m_startTagOpen = false;
  1194. }
  1195. m_ispreserve = true;
  1196. m_writer.write(ch, start, length);
  1197. }
  1198. catch (IOException e)
  1199. {
  1200. throw new SAXException(e);
  1201. }
  1202. }
  1203. /**
  1204. * Receive notification of character data.
  1205. *
  1206. * <p>The Parser will call this method to report each chunk of
  1207. * character data. SAX parsers may return all contiguous character
  1208. * data in a single chunk, or they may split it into several
  1209. * chunks; however, all of the characters in any single event
  1210. * must come from the same external entity, so that the Locator
  1211. * provides useful information.</p>
  1212. *
  1213. * <p>The application must not attempt to read from the array
  1214. * outside of the specified range.</p>
  1215. *
  1216. * <p>Note that some parsers will report whitespace using the
  1217. * ignorableWhitespace() method rather than this one (validating
  1218. * parsers must do so).</p>
  1219. *
  1220. * @param chars The characters from the XML document.
  1221. * @param start The start position in the array.
  1222. * @param length The number of characters to read from the array.
  1223. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  1224. * wrapping another exception.
  1225. * @see #ignorableWhitespace
  1226. * @see org.xml.sax.Locator
  1227. *
  1228. * @throws org.xml.sax.SAXException
  1229. */
  1230. public void characters(final char chars[], final int start, final int length)
  1231. throws org.xml.sax.SAXException
  1232. {
  1233. if (m_elemContext.m_startTagOpen)
  1234. {
  1235. closeStartTag();
  1236. m_elemContext.m_startTagOpen = false;
  1237. }
  1238. else if (m_needToCallStartDocument)
  1239. {
  1240. startDocumentInternal();
  1241. }
  1242. if (m_cdataStartCalled || m_elemContext.m_isCdataSection)
  1243. {
  1244. /* either due to startCDATA() being called or due to
  1245. * cdata-section-elements atribute, we need this as cdata
  1246. */
  1247. cdata(chars, start, length);
  1248. return;
  1249. }
  1250. if (m_cdataTagOpen)
  1251. closeCDATA();
  1252. // the check with _escaping is a bit of a hack for XLSTC
  1253. if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping))
  1254. {
  1255. charactersRaw(chars, start, length);
  1256. // time to fire off characters generation event
  1257. if (m_tracer != null)
  1258. super.fireCharEvent(chars, start, length);
  1259. return;
  1260. }
  1261. if (m_elemContext.m_startTagOpen)
  1262. {
  1263. closeStartTag();
  1264. m_elemContext.m_startTagOpen = false;
  1265. }
  1266. try
  1267. {
  1268. int i;
  1269. char ch1;
  1270. int startClean;
  1271. // skip any leading whitspace
  1272. // don't go off the end and use a hand inlined version
  1273. // of isWhitespace(ch)
  1274. final int end = start + length;
  1275. int lastDirty = start - 1; // last character that needed processing
  1276. for (i = start;
  1277. ((i < end)
  1278. && ((ch1 = chars[i]) == 0x20
  1279. || (ch1 == 0xA && m_lineSepUse)
  1280. || ch1 == 0xD
  1281. || ch1 == 0x09));
  1282. i++)
  1283. {
  1284. /*
  1285. * We are processing leading whitespace, but are doing the same
  1286. * processing for dirty characters here as for non-whitespace.
  1287. *
  1288. */
  1289. if (!m_charInfo.isTextASCIIClean(ch1))
  1290. {
  1291. lastDirty = processDirty(chars,end, i,ch1, lastDirty, true);
  1292. i = lastDirty;
  1293. }
  1294. }
  1295. /* If there is some non-whitespace, mark that we may need
  1296. * to preserve this. This is only important if we have indentation on.
  1297. */
  1298. if (i < end)
  1299. m_ispreserve = true;
  1300. // int lengthClean; // number of clean characters in a row
  1301. // final boolean[] isAsciiClean = m_charInfo.getASCIIClean();
  1302. // we've skipped the leading whitespace, now deal with the rest
  1303. for (; i < end; i++)
  1304. {
  1305. {
  1306. // A tight loop to skip over common clean chars
  1307. // This tight loop makes it easier for the JIT
  1308. // to optimize.
  1309. char ch2;
  1310. while (i<end
  1311. && ((ch2 = chars[i])<127)
  1312. && m_charInfo.isTextASCIIClean(ch2))
  1313. i++;
  1314. if (i == end)
  1315. break;
  1316. }
  1317. final char ch = chars[i];
  1318. if (
  1319. (escapingNotNeeded(ch) && (!m_charInfo.isSpecialTextChar(ch)))
  1320. || ('"' == ch))
  1321. {
  1322. ; // a character needing no special processing
  1323. }
  1324. else
  1325. {
  1326. lastDirty = processDirty(chars,end, i, ch, lastDirty, true);
  1327. i = lastDirty;
  1328. }
  1329. }
  1330. // we've reached the end. Any clean characters at the
  1331. // end of the array than need to be written out?
  1332. startClean = lastDirty + 1;
  1333. if (i > startClean)
  1334. {
  1335. int lengthClean = i - startClean;
  1336. m_writer.write(chars, startClean, lengthClean);
  1337. }
  1338. // For indentation purposes, mark that we've just writen text out
  1339. m_isprevtext = true;
  1340. }
  1341. catch (IOException e)
  1342. {
  1343. throw new SAXException(e);
  1344. }
  1345. // time to fire off characters generation event
  1346. if (m_tracer != null)
  1347. super.fireCharEvent(chars, start, length);
  1348. }
  1349. /**
  1350. * Process a dirty character and any preeceding clean characters
  1351. * that were not yet processed.
  1352. * @param chars array of characters being processed
  1353. * @param end one (1) beyond the last character
  1354. * in chars to be processed
  1355. * @param i the index of the dirty character
  1356. * @param ch the character in chars[i]
  1357. * @param lastDirty the last dirty character previous to i
  1358. * @param fromTextNode true if the characters being processed are
  1359. * from a text node, false if they are from an attribute value.
  1360. * @return the index of the last character processed
  1361. */
  1362. private int processDirty(
  1363. char[] chars,
  1364. int end,
  1365. int i,
  1366. char ch,
  1367. int lastDirty,
  1368. boolean fromTextNode) throws IOException
  1369. {
  1370. int startClean = lastDirty + 1;
  1371. // if we have some clean characters accumulated
  1372. // process them before the dirty one.
  1373. if (i > startClean)
  1374. {
  1375. int lengthClean = i - startClean;
  1376. m_writer.write(chars, startClean, lengthClean);
  1377. }
  1378. // process the "dirty" character
  1379. if (CharInfo.S_LINEFEED == ch && fromTextNode)
  1380. {
  1381. m_writer.write(m_lineSep, 0, m_lineSepLen);
  1382. }
  1383. else
  1384. {
  1385. startClean =
  1386. accumDefaultEscape(
  1387. m_writer,
  1388. (char)ch,
  1389. i,
  1390. chars,
  1391. end,
  1392. fromTextNode,
  1393. false);
  1394. i = startClean - 1;
  1395. }
  1396. // Return the index of the last character that we just processed
  1397. // which is a dirty character.
  1398. return i;
  1399. }
  1400. /**
  1401. * Receive notification of character data.
  1402. *
  1403. * @param s The string of characters to process.
  1404. *
  1405. * @throws org.xml.sax.SAXException
  1406. */
  1407. public void characters(String s) throws org.xml.sax.SAXException
  1408. {
  1409. final int length = s.length();
  1410. if (length > m_charsBuff.length)
  1411. {
  1412. m_charsBuff = new char[length * 2 + 1];
  1413. }
  1414. s.getChars(0, length, m_charsBuff, 0);
  1415. characters(m_charsBuff, 0, length);
  1416. }
  1417. /**
  1418. * Escape and writer.write a character.
  1419. *
  1420. * @param ch character to be escaped.
  1421. * @param i index into character array.
  1422. * @param chars non-null reference to character array.
  1423. * @param len length of chars.
  1424. * @param fromTextNode true if the characters being processed are
  1425. * from a text node, false if the characters being processed are from
  1426. * an attribute value.
  1427. * @param escLF true if the linefeed should be escaped.
  1428. *
  1429. * @return i+1 if a character was written, i+2 if two characters
  1430. * were written out, else return i.
  1431. *
  1432. * @throws org.xml.sax.SAXException
  1433. */
  1434. protected int accumDefaultEscape(
  1435. Writer writer,
  1436. char ch,
  1437. int i,
  1438. char[] chars,
  1439. int len,
  1440. boolean fromTextNode,
  1441. boolean escLF)
  1442. throws IOException
  1443. {
  1444. int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF);
  1445. if (i == pos)
  1446. {
  1447. if (0xd800 <= ch && ch < 0xdc00)
  1448. {
  1449. // UTF-16 surrogate
  1450. int next;
  1451. if (i + 1 >= len)
  1452. {
  1453. throw new IOException(
  1454. XMLMessages.createXMLMessage(
  1455. XMLErrorResources.ER_INVALID_UTF16_SURROGATE,
  1456. new Object[] { Integer.toHexString(ch)}));
  1457. //"Invalid UTF-16 surrogate detected: "
  1458. //+Integer.toHexString(ch)+ " ?");
  1459. }
  1460. else
  1461. {
  1462. next = chars[++i];
  1463. if (!(0xdc00 <= next && next < 0xe000))
  1464. throw new IOException(
  1465. XMLMessages.createXMLMessage(
  1466. XMLErrorResources
  1467. .ER_INVALID_UTF16_SURROGATE,
  1468. new Object[] {
  1469. Integer.toHexString(ch)
  1470. + " "
  1471. + Integer.toHexString(next)}));
  1472. //"Invalid UTF-16 surrogate detected: "
  1473. //+Integer.toHexString(ch)+" "+Integer.toHexString(next));
  1474. next = ((ch - 0xd800) << 10) + next - 0xdc00 + 0x00010000;
  1475. }
  1476. writer.write("&#");
  1477. writer.write(Integer.toString(next));
  1478. writer.write(';');
  1479. pos += 2; // count the two characters that went into writing out this entity
  1480. }
  1481. else
  1482. {
  1483. if (!escapingNotNeeded(ch) ||
  1484. ( (fromTextNode && m_charInfo.isSpecialTextChar(ch))
  1485. || (!fromTextNode && m_charInfo.isSpecialAttrChar(ch))))
  1486. {
  1487. writer.write("&#");
  1488. writer.write(Integer.toString(ch));
  1489. writer.write(';');
  1490. }
  1491. else
  1492. {
  1493. writer.write(ch);
  1494. }
  1495. pos++; // count the single character that was processed
  1496. }
  1497. }
  1498. return pos;
  1499. }
  1500. /**
  1501. * Receive notification of the beginning of an element, although this is a
  1502. * SAX method additional namespace or attribute information can occur before
  1503. * or after this call, that is associated with this element.
  1504. *
  1505. *
  1506. * @param namespaceURI The Namespace URI, or the empty string if the
  1507. * element has no Namespace URI or if Namespace
  1508. * processing is not being performed.
  1509. * @param localName The local name (without prefix), or the
  1510. * empty string if Namespace processing is not being
  1511. * performed.
  1512. * @param name The element type name.
  1513. * @param atts The attributes attached to the element, if any.
  1514. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  1515. * wrapping another exception.
  1516. * @see org.xml.sax.ContentHandler#startElement
  1517. * @see org.xml.sax.ContentHandler#endElement
  1518. * @see org.xml.sax.AttributeList
  1519. *
  1520. * @throws org.xml.sax.SAXException
  1521. */
  1522. public void startElement(
  1523. String namespaceURI,
  1524. String localName,
  1525. String name,
  1526. Attributes atts)
  1527. throws org.xml.sax.SAXException
  1528. {
  1529. if (m_inEntityRef)
  1530. return;
  1531. if (m_needToCallStartDocument)
  1532. {
  1533. startDocumentInternal();
  1534. m_needToCallStartDocument = false;
  1535. }
  1536. else if (m_cdataTagOpen)
  1537. closeCDATA();
  1538. try
  1539. {
  1540. if ((true == m_needToOutputDocTypeDecl)
  1541. && (null != getDoctypeSystem()))
  1542. {
  1543. outputDocTypeDecl(name, true);
  1544. }
  1545. m_needToOutputDocTypeDecl = false;
  1546. /* before we over-write the current elementLocalName etc.
  1547. * lets close out the old one (if we still need to)
  1548. */
  1549. if (m_elemContext.m_startTagOpen)
  1550. {
  1551. closeStartTag();
  1552. m_elemContext.m_startTagOpen = false;
  1553. }
  1554. if (namespaceURI != null)
  1555. ensurePrefixIsDeclared(namespaceURI, name);
  1556. m_ispreserve = false;
  1557. if (shouldIndent() && m_startNewLine)
  1558. {
  1559. indent();
  1560. }
  1561. m_startNewLine = true;
  1562. final java.io.Writer writer = m_writer;
  1563. writer.write('<');
  1564. writer.write(name);
  1565. }
  1566. catch (IOException e)
  1567. {
  1568. throw new SAXException(e);
  1569. }
  1570. // process the attributes now, because after this SAX call they might be gone
  1571. if (atts != null)
  1572. addAttributes(atts);
  1573. m_elemContext = m_elemContext.push(namespaceURI,localName,name);
  1574. m_isprevtext = false;
  1575. if (m_tracer != null)
  1576. firePseudoAttributes();
  1577. }
  1578. /**
  1579. * Receive notification of the beginning of an element, additional
  1580. * namespace or attribute information can occur before or after this call,
  1581. * that is associated with this element.
  1582. *
  1583. *
  1584. * @param namespaceURI The Namespace URI, or the empty string if the
  1585. * element has no Namespace URI or if Namespace
  1586. * processing is not being performed.
  1587. * @param localName The local name (without prefix), or the
  1588. * empty string if Namespace processing is not being
  1589. * performed.
  1590. * @param name The element type name.
  1591. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  1592. * wrapping another exception.
  1593. * @see org.xml.sax.ContentHandler#startElement
  1594. * @see org.xml.sax.ContentHandler#endElement
  1595. * @see org.xml.sax.AttributeList
  1596. *
  1597. * @throws org.xml.sax.SAXException
  1598. */
  1599. public void startElement(
  1600. String elementNamespaceURI,
  1601. String elementLocalName,
  1602. String elementName)
  1603. throws SAXException
  1604. {
  1605. startElement(elementNamespaceURI, elementLocalName, elementName, null);
  1606. }
  1607. public void startElement(String elementName) throws SAXException
  1608. {
  1609. startElement(null, null, elementName, null);
  1610. }
  1611. /**
  1612. * Output the doc type declaration.
  1613. *
  1614. * @param name non-null reference to document type name.
  1615. * NEEDSDOC @param closeDecl
  1616. *
  1617. * @throws java.io.IOException
  1618. */
  1619. void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException
  1620. {
  1621. if (m_cdataTagOpen)
  1622. closeCDATA();
  1623. try
  1624. {
  1625. final java.io.Writer writer = m_writer;
  1626. writer.write("<!DOCTYPE ");
  1627. writer.write(name);
  1628. String doctypePublic = getDoctypePublic();
  1629. if (null != doctypePublic)
  1630. {
  1631. writer.write(" PUBLIC \"");
  1632. writer.write(doctypePublic);
  1633. writer.write('\"');
  1634. }
  1635. String doctypeSystem = getDoctypeSystem();
  1636. if (null != doctypeSystem)
  1637. {
  1638. if (null == doctypePublic)
  1639. writer.write(" SYSTEM \"");
  1640. else
  1641. writer.write(" \"");
  1642. writer.write(doctypeSystem);
  1643. if (closeDecl)
  1644. {
  1645. writer.write("\">");
  1646. writer.write(m_lineSep, 0, m_lineSepLen);
  1647. closeDecl = false; // done closing
  1648. }
  1649. else
  1650. writer.write('\"');
  1651. }
  1652. boolean dothis = false;
  1653. if (dothis)
  1654. {
  1655. // at one point this code seemed right,
  1656. // but not anymore - bjm
  1657. if (closeDecl)
  1658. {
  1659. writer.write('>');
  1660. writer.write(m_lineSep, 0, m_lineSepLen);
  1661. }
  1662. }
  1663. }
  1664. catch (IOException e)
  1665. {
  1666. throw new SAXException(e);
  1667. }
  1668. }
  1669. /**
  1670. * Process the attributes, which means to write out the currently
  1671. * collected attributes to the writer. The attributes are not
  1672. * cleared by this method
  1673. *
  1674. * @param writer the writer to write processed attributes to.
  1675. * @param nAttrs the number of attributes in m_attributes
  1676. * to be processed
  1677. *
  1678. * @throws java.io.IOException
  1679. * @throws org.xml.sax.SAXException
  1680. */
  1681. public void processAttributes(java.io.Writer writer, int nAttrs) throws IOException, SAXException
  1682. {
  1683. /* real SAX attributes are not passed in, so process the
  1684. * attributes that were collected after the startElement call.
  1685. * _attribVector is a "cheap" list for Stream serializer output
  1686. * accumulated over a series of calls to attribute(name,value)
  1687. */
  1688. String encoding = getEncoding();
  1689. for (int i = 0; i < nAttrs; i++)
  1690. {
  1691. // elementAt is JDK 1.1.8
  1692. final String name = m_attributes.getQName(i);
  1693. final String value = m_attributes.getValue(i);
  1694. writer.write(' ');
  1695. writer.write(name);
  1696. writer.write("=\"");
  1697. writeAttrString(writer, value, encoding);
  1698. writer.write('\"');
  1699. }
  1700. }
  1701. /**
  1702. * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>,
  1703. * and UTF-16 surrogates for chracter references <CODE>&#xnn</CODE>.
  1704. *
  1705. * @param string String to convert to XML format.
  1706. * @param encoding CURRENTLY NOT IMPLEMENTED.
  1707. *
  1708. * @throws java.io.IOException
  1709. */
  1710. public void writeAttrString(
  1711. Writer writer,
  1712. String string,
  1713. String encoding)
  1714. throws IOException
  1715. {
  1716. final int len = string.length();
  1717. if (len > m_attrBuff.length)
  1718. {
  1719. m_attrBuff = new char[len*2 + 1];
  1720. }
  1721. string.getChars(0,len, m_attrBuff, 0);
  1722. final char[] stringChars = m_attrBuff;
  1723. for (int i = 0; i < len; i++)
  1724. {
  1725. char ch = stringChars[i];
  1726. if (escapingNotNeeded(ch) && (!m_charInfo.isSpecialAttrChar(ch)))
  1727. {
  1728. writer.write(ch);
  1729. }
  1730. else
  1731. { // I guess the parser doesn't normalize cr/lf in attributes. -sb
  1732. // if ((CharInfo.S_CARRIAGERETURN == ch)
  1733. // && ((i + 1) < len)
  1734. // && (CharInfo.S_LINEFEED == stringChars[i + 1]))
  1735. // {
  1736. // i++;
  1737. // ch = CharInfo.S_LINEFEED;
  1738. // }
  1739. accumDefaultEscape(writer, ch, i, stringChars, len, false, true);
  1740. }
  1741. }
  1742. }
  1743. /**
  1744. * Receive notification of the end of an element.
  1745. *
  1746. *
  1747. * @param namespaceURI The Namespace URI, or the empty string if the
  1748. * element has no Namespace URI or if Namespace
  1749. * processing is not being performed.
  1750. * @param localName The local name (without prefix), or the
  1751. * empty string if Namespace processing is not being
  1752. * performed.
  1753. * @param name The element type name
  1754. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  1755. * wrapping another exception.
  1756. *
  1757. * @throws org.xml.sax.SAXException
  1758. */
  1759. public void endElement(String namespaceURI, String localName, String name)
  1760. throws org.xml.sax.SAXException
  1761. {
  1762. if (m_inEntityRef)
  1763. return;
  1764. // namespaces declared at the current depth are no longer valid
  1765. // so get rid of them
  1766. m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null);
  1767. try
  1768. {
  1769. final java.io.Writer writer = m_writer;
  1770. if (m_elemContext.m_startTagOpen)
  1771. {
  1772. if (m_tracer != null)
  1773. super.fireStartElem(m_elemContext.m_elementName);
  1774. int nAttrs = m_attributes.getLength();
  1775. if (nAttrs > 0)
  1776. {
  1777. processAttributes(m_writer, nAttrs);
  1778. // clear attributes object for re-use with next element
  1779. m_attributes.clear();
  1780. }
  1781. if (m_spaceBeforeClose)
  1782. writer.write(" />");
  1783. else
  1784. writer.write("/>");
  1785. /* don't need to pop cdataSectionState because
  1786. * this element ended so quickly that we didn't get
  1787. * to push the state.
  1788. */
  1789. }
  1790. else
  1791. {
  1792. if (m_cdataTagOpen)
  1793. closeCDATA();
  1794. if (shouldIndent())
  1795. indent(m_elemContext.m_currentElemDepth - 1);
  1796. writer.write('<');
  1797. writer.write('/');
  1798. writer.write(name);
  1799. writer.write('>');
  1800. }
  1801. }
  1802. catch (IOException e)
  1803. {
  1804. throw new SAXException(e);
  1805. }
  1806. if (!m_elemContext.m_startTagOpen && m_doIndent)
  1807. {
  1808. m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
  1809. }
  1810. m_isprevtext = false;
  1811. // fire off the end element event
  1812. if (m_tracer != null)
  1813. super.fireEndElem(name);
  1814. m_elemContext = m_elemContext.m_prev;
  1815. }
  1816. /**
  1817. * Receive notification of the end of an element.
  1818. * @param name The element type name
  1819. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  1820. * wrapping another exception.
  1821. */
  1822. public void endElement(String name) throws org.xml.sax.SAXException
  1823. {
  1824. endElement(null, null, name);
  1825. }
  1826. /**
  1827. * Begin the scope of a prefix-URI Namespace mapping
  1828. * just before another element is about to start.
  1829. * This call will close any open tags so that the prefix mapping
  1830. * will not apply to the current element, but the up comming child.
  1831. *
  1832. * @see org.xml.sax.ContentHandler#startPrefixMapping
  1833. *
  1834. * @param prefix The Namespace prefix being declared.
  1835. * @param uri The Namespace URI the prefix is mapped to.
  1836. *
  1837. * @throws org.xml.sax.SAXException The client may throw
  1838. * an exception during processing.
  1839. *
  1840. */
  1841. public void startPrefixMapping(String prefix, String uri)
  1842. throws org.xml.sax.SAXException
  1843. {
  1844. // the "true" causes the flush of any open tags
  1845. startPrefixMapping(prefix, uri, true);
  1846. }
  1847. /**
  1848. * Handle a prefix/uri mapping, which is associated with a startElement()
  1849. * that is soon to follow. Need to close any open start tag to make
  1850. * sure than any name space attributes due to this event are associated wih
  1851. * the up comming element, not the current one.
  1852. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#startPrefixMapping
  1853. *
  1854. * @param prefix The Namespace prefix being declared.
  1855. * @param uri The Namespace URI the prefix is mapped to.
  1856. * @param shouldFlush true if any open tags need to be closed first, this
  1857. * will impact which element the mapping applies to (open parent, or its up
  1858. * comming child)
  1859. * @return returns true if the call made a change to the current
  1860. * namespace information, false if it did not change anything, e.g. if the
  1861. * prefix/namespace mapping was already in scope from before.
  1862. *
  1863. * @throws org.xml.sax.SAXException The client may throw
  1864. * an exception during processing.
  1865. *
  1866. *
  1867. */
  1868. public boolean startPrefixMapping(
  1869. String prefix,
  1870. String uri,
  1871. boolean shouldFlush)
  1872. throws org.xml.sax.SAXException
  1873. {
  1874. /* Remember the mapping, and at what depth it was declared
  1875. * This is one greater than the current depth because these
  1876. * mappings will apply to the next depth. This is in
  1877. * consideration that startElement() will soon be called
  1878. */
  1879. boolean pushed;
  1880. int pushDepth;
  1881. if (shouldFlush)
  1882. {
  1883. flushPending();
  1884. // the prefix mapping applies to the child element (one deeper)
  1885. pushDepth = m_elemContext.m_currentElemDepth + 1;
  1886. }
  1887. else
  1888. {
  1889. // the prefix mapping applies to the current element
  1890. pushDepth = m_elemContext.m_currentElemDepth;
  1891. }
  1892. pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
  1893. if (pushed)
  1894. {
  1895. /* bjm: don't know if we really needto do this. The
  1896. * callers of this object should have injected both
  1897. * startPrefixMapping and the attributes. We are
  1898. * just covering our butt here.
  1899. */
  1900. String name;
  1901. if (EMPTYSTRING.equals(prefix))
  1902. {
  1903. name = "xmlns";
  1904. addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri);
  1905. }
  1906. else
  1907. {
  1908. if (!EMPTYSTRING.equals(uri))
  1909. // hack for XSLTC attribset16 test
  1910. { // that maps ns1 prefix to "" URI
  1911. name = "xmlns:" + prefix;
  1912. /* for something like xmlns:abc="w3.pretend.org"
  1913. * the uri is the value, that is why we pass it in the
  1914. * value, or 5th slot of addAttributeAlways()
  1915. */
  1916. addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri);
  1917. }
  1918. }
  1919. }
  1920. return pushed;
  1921. }
  1922. /**
  1923. * Receive notification of an XML comment anywhere in the document. This
  1924. * callback will be used for comments inside or outside the document
  1925. * element, including comments in the external DTD subset (if read).
  1926. * @param ch An array holding the characters in the comment.
  1927. * @param start The starting position in the array.
  1928. * @param length The number of characters to use from the array.
  1929. * @throws org.xml.sax.SAXException The application may raise an exception.
  1930. */
  1931. public void comment(char ch[], int start, int length)
  1932. throws org.xml.sax.SAXException
  1933. {
  1934. int start_old = start;
  1935. if (m_inEntityRef)
  1936. return;
  1937. if (m_elemContext.m_startTagOpen)
  1938. {
  1939. closeStartTag();
  1940. m_elemContext.m_startTagOpen = false;
  1941. }
  1942. else if (m_needToCallStartDocument)
  1943. {
  1944. startDocumentInternal();
  1945. m_needToCallStartDocument = false;
  1946. }
  1947. try
  1948. {
  1949. if (shouldIndent())
  1950. indent();
  1951. final int limit = start + length;
  1952. boolean wasDash = false;
  1953. if (m_cdataTagOpen)
  1954. closeCDATA();
  1955. final java.io.Writer writer = m_writer;
  1956. writer.write(COMMENT_BEGIN);
  1957. // Detect occurrences of two consecutive dashes, handle as necessary.
  1958. for (int i = start; i < limit; i++)
  1959. {
  1960. if (wasDash && ch[i] == '-')
  1961. {
  1962. writer.write(ch, start, i - start);
  1963. writer.write(" -");
  1964. start = i + 1;
  1965. }
  1966. wasDash = (ch[i] == '-');
  1967. }
  1968. // if we have some chars in the comment
  1969. if (length > 0)
  1970. {
  1971. // Output the remaining characters (if any)
  1972. final int remainingChars = (limit - start);
  1973. if (remainingChars > 0)
  1974. writer.write(ch, start, remainingChars);
  1975. // Protect comment end from a single trailing dash
  1976. if (ch[limit - 1] == '-')
  1977. writer.write(' ');
  1978. }
  1979. writer.write(COMMENT_END);
  1980. }
  1981. catch (IOException e)
  1982. {
  1983. throw new SAXException(e);
  1984. }
  1985. m_startNewLine = true;
  1986. // time to generate comment event
  1987. if (m_tracer != null)
  1988. super.fireCommentEvent(ch, start_old,length);
  1989. }
  1990. /**
  1991. * Report the end of a CDATA section.
  1992. * @throws org.xml.sax.SAXException The application may raise an exception.
  1993. *
  1994. * @see #startCDATA
  1995. */
  1996. public void endCDATA() throws org.xml.sax.SAXException
  1997. {
  1998. if (m_cdataTagOpen)
  1999. closeCDATA();
  2000. m_cdataStartCalled = false;
  2001. }
  2002. /**
  2003. * Report the end of DTD declarations.
  2004. * @throws org.xml.sax.SAXException The application may raise an exception.
  2005. * @see #startDTD
  2006. */
  2007. public void endDTD() throws org.xml.sax.SAXException
  2008. {
  2009. try
  2010. {
  2011. final java.io.Writer writer = m_writer;
  2012. if (!m_inDoctype){
  2013. writer.write("]>");
  2014. writer.write(m_lineSep, 0, m_lineSepLen);
  2015. }
  2016. }
  2017. catch (IOException e)
  2018. {
  2019. throw new SAXException(e);
  2020. }
  2021. }
  2022. /**
  2023. * End the scope of a prefix-URI Namespace mapping.
  2024. * @see org.xml.sax.ContentHandler#endPrefixMapping
  2025. *
  2026. * @param prefix The prefix that was being mapping.
  2027. * @throws org.xml.sax.SAXException The client may throw
  2028. * an exception during processing.
  2029. */
  2030. public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException
  2031. { // do nothing
  2032. }
  2033. /**
  2034. * Receive notification of ignorable whitespace in element content.
  2035. *
  2036. * Not sure how to get this invoked quite yet.
  2037. *
  2038. * @param ch The characters from the XML document.
  2039. * @param start The start position in the array.
  2040. * @param length The number of characters to read from the array.
  2041. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  2042. * wrapping another exception.
  2043. * @see #characters
  2044. *
  2045. * @throws org.xml.sax.SAXException
  2046. */
  2047. public void ignorableWhitespace(char ch[], int start, int length)
  2048. throws org.xml.sax.SAXException
  2049. {
  2050. if (0 == length)
  2051. return;
  2052. characters(ch, start, length);
  2053. }
  2054. /**
  2055. * Receive notification of a skipped entity.
  2056. * @see org.xml.sax.ContentHandler#skippedEntity
  2057. *
  2058. * @param name The name of the skipped entity. If it is a
  2059. * parameter entity, the name will begin with '%',
  2060. * and if it is the external DTD subset, it will be the string
  2061. * "[dtd]".
  2062. * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping
  2063. * another exception.
  2064. */
  2065. public void skippedEntity(String name) throws org.xml.sax.SAXException
  2066. { // TODO: Should handle
  2067. }
  2068. /**
  2069. * Report the start of a CDATA section.
  2070. *
  2071. * @throws org.xml.sax.SAXException The application may raise an exception.
  2072. * @see #endCDATA
  2073. */
  2074. public void startCDATA() throws org.xml.sax.SAXException
  2075. {
  2076. m_cdataStartCalled = true;
  2077. }
  2078. /**
  2079. * Report the beginning of an entity.
  2080. *
  2081. * The start and end of the document entity are not reported.
  2082. * The start and end of the external DTD subset are reported
  2083. * using the pseudo-name "[dtd]". All other events must be
  2084. * properly nested within start/end entity events.
  2085. *
  2086. * @param name The name of the entity. If it is a parameter
  2087. * entity, the name will begin with '%'.
  2088. * @throws org.xml.sax.SAXException The application may raise an exception.
  2089. * @see #endEntity
  2090. * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
  2091. * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
  2092. */
  2093. public void startEntity(String name) throws org.xml.sax.SAXException
  2094. {
  2095. if (name.equals("[dtd]"))
  2096. m_inExternalDTD = true;
  2097. m_inEntityRef = true;
  2098. }
  2099. /**
  2100. * For the enclosing elements starting tag write out
  2101. * out any attributes followed by ">"
  2102. *
  2103. * @throws org.xml.sax.SAXException
  2104. */
  2105. protected void closeStartTag() throws SAXException
  2106. {
  2107. if (m_elemContext.m_startTagOpen)
  2108. {
  2109. try
  2110. {
  2111. if (m_tracer != null)
  2112. super.fireStartElem(m_elemContext.m_elementName);
  2113. int nAttrs = m_attributes.getLength();
  2114. if (nAttrs > 0)
  2115. {
  2116. processAttributes(m_writer, nAttrs);
  2117. // clear attributes object for re-use with next element
  2118. m_attributes.clear();
  2119. }
  2120. m_writer.write('>');
  2121. }
  2122. catch (IOException e)
  2123. {
  2124. throw new SAXException(e);
  2125. }
  2126. /* whether Xalan or XSLTC, we have the prefix mappings now, so
  2127. * lets determine if the current element is specified in the cdata-
  2128. * section-elements list.
  2129. */
  2130. if (m_cdataSectionElements != null)
  2131. m_elemContext.m_isCdataSection = isCdataSection();
  2132. if (m_doIndent)
  2133. {
  2134. m_isprevtext = false;
  2135. m_preserves.push(m_ispreserve);
  2136. }
  2137. }
  2138. }
  2139. /**
  2140. * Report the start of DTD declarations, if any.
  2141. *
  2142. * Any declarations are assumed to be in the internal subset unless
  2143. * otherwise indicated.
  2144. *
  2145. * @param name The document type name.
  2146. * @param publicId The declared public identifier for the
  2147. * external DTD subset, or null if none was declared.
  2148. * @param systemId The declared system identifier for the
  2149. * external DTD subset, or null if none was declared.
  2150. * @throws org.xml.sax.SAXException The application may raise an
  2151. * exception.
  2152. * @see #endDTD
  2153. * @see #startEntity
  2154. */
  2155. public void startDTD(String name, String publicId, String systemId)
  2156. throws org.xml.sax.SAXException
  2157. {
  2158. setDoctypeSystem(systemId);
  2159. setDoctypePublic(publicId);
  2160. m_elemContext.m_elementName = name;
  2161. m_inDoctype = true;
  2162. }
  2163. /**
  2164. * Returns the m_indentAmount.
  2165. * @return int
  2166. */
  2167. public int getIndentAmount()
  2168. {
  2169. return m_indentAmount;
  2170. }
  2171. /**
  2172. * Sets the m_indentAmount.
  2173. *
  2174. * @param m_indentAmount The m_indentAmount to set
  2175. */
  2176. public void setIndentAmount(int m_indentAmount)
  2177. {
  2178. this.m_indentAmount = m_indentAmount;
  2179. }
  2180. /**
  2181. * Tell if, based on space preservation constraints and the doIndent property,
  2182. * if an indent should occur.
  2183. *
  2184. * @return True if an indent should occur.
  2185. */
  2186. protected boolean shouldIndent()
  2187. {
  2188. return m_doIndent && (!m_ispreserve && !m_isprevtext);
  2189. }
  2190. /**
  2191. * Searches for the list of qname properties with the specified key in the
  2192. * property list. If the key is not found in this property list, the default
  2193. * property list, and its defaults, recursively, are then checked. The
  2194. * method returns <code>null</code> if the property is not found.
  2195. *
  2196. * @param key the property key.
  2197. * @param props the list of properties to search in.
  2198. *
  2199. * Sets the vector of local-name/URI pairs of the cdata section elements
  2200. * specified in the cdata-section-elements property.
  2201. *
  2202. * This method is essentially a copy of getQNameProperties() from
  2203. * OutputProperties. Eventually this method should go away and a call
  2204. * to setCdataSectionElements(Vector v) should be made directly.
  2205. */
  2206. private void setCdataSectionElements(String key, Properties props)
  2207. {
  2208. String s = props.getProperty(key);
  2209. if (null != s)
  2210. {
  2211. // Vector of URI/LocalName pairs
  2212. Vector v = new Vector();
  2213. int l = s.length();
  2214. boolean inCurly = false;
  2215. FastStringBuffer buf = new FastStringBuffer();
  2216. // parse through string, breaking on whitespaces. I do this instead
  2217. // of a tokenizer so I can track whitespace inside of curly brackets,
  2218. // which theoretically shouldn't happen if they contain legal URLs.
  2219. for (int i = 0; i < l; i++)
  2220. {
  2221. char c = s.charAt(i);
  2222. if (Character.isWhitespace(c))
  2223. {
  2224. if (!inCurly)
  2225. {
  2226. if (buf.length() > 0)
  2227. {
  2228. addCdataSectionElement(buf.toString(), v);
  2229. buf.reset();
  2230. }
  2231. continue;
  2232. }
  2233. }
  2234. else if ('{' == c)
  2235. inCurly = true;
  2236. else if ('}' == c)
  2237. inCurly = false;
  2238. buf.append(c);
  2239. }
  2240. if (buf.length() > 0)
  2241. {
  2242. addCdataSectionElement(buf.toString(), v);
  2243. buf.reset();
  2244. }
  2245. // call the official, public method to set the collected names
  2246. setCdataSectionElements(v);
  2247. }
  2248. }
  2249. /**
  2250. * Adds a URI/LocalName pair of strings to the list.
  2251. *
  2252. * @param name String of the form "{uri}local" or "local"
  2253. *
  2254. * @return a QName object
  2255. */
  2256. private void addCdataSectionElement(String URI_and_localName, Vector v)
  2257. {
  2258. StringTokenizer tokenizer =
  2259. new StringTokenizer(URI_and_localName, "{}", false);
  2260. QName qname;
  2261. String s1 = tokenizer.nextToken();
  2262. String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
  2263. if (null == s2)
  2264. {
  2265. // add null URI and the local name
  2266. v.addElement(null);
  2267. v.addElement(s1);
  2268. }
  2269. else
  2270. {
  2271. // add URI, then local name
  2272. v.addElement(s1);
  2273. v.addElement(s2);
  2274. }
  2275. }
  2276. /**
  2277. * Remembers the cdata sections specified in the cdata-section-elements.
  2278. * The "official way to set URI and localName pairs.
  2279. * This method should be used by both Xalan and XSLTC.
  2280. *
  2281. * @param Vector URI_and_localNames a vector of pairs of Strings (URI/local)
  2282. */
  2283. public void setCdataSectionElements(Vector URI_and_localNames)
  2284. {
  2285. m_cdataSectionElements = URI_and_localNames;
  2286. }
  2287. /**
  2288. * Makes sure that the namespace URI for the given qualified attribute name
  2289. * is declared.
  2290. * @param ns the namespace URI
  2291. * @param rawName the qualified name
  2292. * @return returns null if no action is taken, otherwise it returns the
  2293. * prefix used in declaring the namespace.
  2294. * @throws SAXException
  2295. */
  2296. protected String ensureAttributesNamespaceIsDeclared(
  2297. String ns,
  2298. String localName,
  2299. String rawName)
  2300. throws org.xml.sax.SAXException
  2301. {
  2302. if (ns != null && ns.length() > 0)
  2303. {
  2304. // extract the prefix in front of the raw name
  2305. int index = 0;
  2306. String prefixFromRawName =
  2307. (index = rawName.indexOf(":")) < 0
  2308. ? ""
  2309. : rawName.substring(0, index);
  2310. if (index > 0)
  2311. {
  2312. // we have a prefix, lets see if it maps to a namespace
  2313. String uri = m_prefixMap.lookupNamespace(prefixFromRawName);
  2314. if (uri != null && uri.equals(ns))
  2315. {
  2316. // the prefix in the raw name is already maps to the given namespace uri
  2317. // so we don't need to do anything
  2318. return null;
  2319. }
  2320. else
  2321. {
  2322. // The uri does not map to the prefix in the raw name,
  2323. // so lets make the mapping.
  2324. this.startPrefixMapping(prefixFromRawName, ns, false);
  2325. this.addAttribute(
  2326. "http://www.w3.org/2000/xmlns/",
  2327. prefixFromRawName,
  2328. "xmlns:" + prefixFromRawName,
  2329. "CDATA",
  2330. ns);
  2331. return prefixFromRawName;
  2332. }
  2333. }
  2334. else
  2335. {
  2336. // we don't have a prefix in the raw name.
  2337. // Does the URI map to a prefix already?
  2338. String prefix = m_prefixMap.lookupPrefix(ns);
  2339. if (prefix == null)
  2340. {
  2341. // uri is not associated with a prefix,
  2342. // so lets generate a new prefix to use
  2343. prefix = m_prefixMap.generateNextPrefix();
  2344. this.startPrefixMapping(prefix, ns, false);
  2345. this.addAttribute(
  2346. "http://www.w3.org/2000/xmlns/",
  2347. prefix,
  2348. "xmlns:" + prefix,
  2349. "CDATA",
  2350. ns);
  2351. }
  2352. return prefix;
  2353. }
  2354. }
  2355. return null;
  2356. }
  2357. void ensurePrefixIsDeclared(String ns, String rawName)
  2358. throws org.xml.sax.SAXException
  2359. {
  2360. if (ns != null && ns.length() > 0)
  2361. {
  2362. int index;
  2363. String prefix =
  2364. (index = rawName.indexOf(":")) < 0
  2365. ? ""
  2366. : rawName.substring(0, index);
  2367. if (null != prefix)
  2368. {
  2369. String foundURI = m_prefixMap.lookupNamespace(prefix);
  2370. if ((null == foundURI) || !foundURI.equals(ns))
  2371. {
  2372. this.startPrefixMapping(prefix, ns);
  2373. // Bugzilla1133: Generate attribute as well as namespace event.
  2374. // SAX does expect both.
  2375. this.addAttributeAlways(
  2376. "http://www.w3.org/2000/xmlns/",
  2377. prefix,
  2378. "xmlns" + (prefix.length() == 0 ? "" : ":") + prefix,
  2379. "CDATA",
  2380. ns);
  2381. }
  2382. }
  2383. }
  2384. }
  2385. /**
  2386. * This method flushes any pending events, which can be startDocument()
  2387. * closing the opening tag of an element, or closing an open CDATA section.
  2388. */
  2389. public void flushPending() throws SAXException
  2390. {
  2391. if (m_needToCallStartDocument)
  2392. {
  2393. startDocumentInternal();
  2394. m_needToCallStartDocument = false;
  2395. }
  2396. if (m_elemContext.m_startTagOpen)
  2397. {
  2398. closeStartTag();
  2399. m_elemContext.m_startTagOpen = false;
  2400. }
  2401. if (m_cdataTagOpen)
  2402. {
  2403. closeCDATA();
  2404. m_cdataTagOpen = false;
  2405. }
  2406. }
  2407. public void setContentHandler(ContentHandler ch)
  2408. {
  2409. // this method is really only useful in the ToSAXHandler classes but it is
  2410. // in the interface. If the method defined here is ever called
  2411. // we are probably in trouble.
  2412. }
  2413. /**
  2414. * Adds the given attribute to the set of attributes, even if there is
  2415. * nocurrently open element. This is useful if a SAX startPrefixMapping()
  2416. * should need to add an attribute before the element name is seen.
  2417. *
  2418. * This method is a copy of its super classes method, except that some
  2419. * tracing of events is done. This is so the tracing is only done for
  2420. * stream serializers, not for SAX ones.
  2421. *
  2422. * @param uri the URI of the attribute
  2423. * @param localName the local name of the attribute
  2424. * @param rawName the qualified name of the attribute
  2425. * @param type the type of the attribute (probably CDATA)
  2426. * @param value the value of the attribute
  2427. */
  2428. public void addAttributeAlways(
  2429. String uri,
  2430. String localName,
  2431. String rawName,
  2432. String type,
  2433. String value)
  2434. {
  2435. int index;
  2436. index = m_attributes.getIndex(rawName);
  2437. if (index >= 0)
  2438. {
  2439. String old_value = null;
  2440. if (m_tracer != null)
  2441. {
  2442. old_value = m_attributes.getValue(index);
  2443. if (value.equals(old_value))
  2444. old_value = null;
  2445. }
  2446. /* We've seen the attribute before.
  2447. * We may have a null uri or localName, but all we really
  2448. * want to re-set is the value anyway.
  2449. */
  2450. m_attributes.setValue(index, value);
  2451. if (old_value != null)
  2452. firePseudoAttributes();
  2453. }
  2454. else
  2455. {
  2456. // the attribute doesn't exist yet, create it
  2457. m_attributes.addAttribute(uri, localName, rawName, type, value);
  2458. if (m_tracer != null)
  2459. firePseudoAttributes();
  2460. }
  2461. }
  2462. /**
  2463. * To fire off the pseudo characters of attributes, as they currently
  2464. * exist. This method should be called everytime an attribute is added,
  2465. * or when an attribute value is changed, or an element is created.
  2466. */
  2467. protected void firePseudoAttributes()
  2468. {
  2469. if (m_tracer != null)
  2470. {
  2471. try
  2472. {
  2473. // flush out the "<elemName" if not already flushed
  2474. m_writer.flush();
  2475. // make a StringBuffer to write the name="value" pairs to.
  2476. StringBuffer sb = new StringBuffer();
  2477. int nAttrs = m_attributes.getLength();
  2478. if (nAttrs > 0)
  2479. {
  2480. // make a writer that internally appends to the same
  2481. // StringBuffer
  2482. java.io.Writer writer =
  2483. new ToStream.WritertoStringBuffer(sb);
  2484. processAttributes(writer, nAttrs);
  2485. // Don't clear the attributes!
  2486. // We only want to see what would be written out
  2487. // at this point, we don't want to loose them.
  2488. }
  2489. sb.append('>'); // the potential > after the attributes.
  2490. // convert the StringBuffer to a char array and
  2491. // emit the trace event that these characters "might"
  2492. // be written
  2493. char ch[] = sb.toString().toCharArray();
  2494. m_tracer.fireGenerateEvent(
  2495. SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS,
  2496. ch,
  2497. 0,
  2498. ch.length);
  2499. }
  2500. catch (IOException ioe)
  2501. {
  2502. // ignore ?
  2503. }
  2504. catch (SAXException se)
  2505. {
  2506. // ignore ?
  2507. }
  2508. }
  2509. }
  2510. /**
  2511. * This inner class is used only to collect attribute values
  2512. * written by the method writeAttrString() into a string buffer.
  2513. * In this manner trace events, and the real writing of attributes will use
  2514. * the same code.
  2515. */
  2516. private class WritertoStringBuffer extends java.io.Writer
  2517. {
  2518. final private StringBuffer m_stringbuf;
  2519. /**
  2520. * @see java.io.Writer#write(char[], int, int)
  2521. */
  2522. WritertoStringBuffer(StringBuffer sb)
  2523. {
  2524. m_stringbuf = sb;
  2525. }
  2526. public void write(char[] arg0, int arg1, int arg2) throws IOException
  2527. {
  2528. m_stringbuf.append(arg0, arg1, arg2);
  2529. }
  2530. /**
  2531. * @see java.io.Writer#flush()
  2532. */
  2533. public void flush() throws IOException
  2534. {
  2535. }
  2536. /**
  2537. * @see java.io.Writer#close()
  2538. */
  2539. public void close() throws IOException
  2540. {
  2541. }
  2542. public void write(int i)
  2543. {
  2544. m_stringbuf.append((char) i);
  2545. }
  2546. public void write(String s)
  2547. {
  2548. m_stringbuf.append(s);
  2549. }
  2550. }
  2551. /**
  2552. * @see com.sun.org.apache.xml.internal.serializer.SerializationHandler#setTransformer(Transformer)
  2553. */
  2554. public void setTransformer(Transformer transformer) {
  2555. super.setTransformer(transformer);
  2556. if (m_tracer != null
  2557. && !(m_writer instanceof SerializerTraceWriter) )
  2558. m_writer = new SerializerTraceWriter(m_writer, m_tracer);
  2559. }
  2560. /**
  2561. * Try's to reset the super class and reset this class for
  2562. * re-use, so that you don't need to create a new serializer
  2563. * (mostly for performance reasons).
  2564. *
  2565. * @return true if the class was successfuly reset.
  2566. */
  2567. public boolean reset()
  2568. {
  2569. boolean wasReset = false;
  2570. if (super.reset())
  2571. {
  2572. resetToStream();
  2573. wasReset = true;
  2574. }
  2575. return wasReset;
  2576. }
  2577. /**
  2578. * Reset all of the fields owned by ToStream class
  2579. *
  2580. */
  2581. private void resetToStream()
  2582. {
  2583. this.m_canConvertMeth = null;
  2584. this.m_cdataStartCalled = false;
  2585. /* The stream is being reset. It is one of
  2586. * ToXMLStream, ToHTMLStream ... and this type can't be changed
  2587. * so neither should m_charInfo which is associated with the
  2588. * type of Stream. Just leave m_charInfo as-is for the next re-use.
  2589. *
  2590. */
  2591. // this.m_charInfo = null; // don't set to null
  2592. this.m_charToByteConverter = null;
  2593. this.m_disableOutputEscapingStates.clear();
  2594. this.m_escaping = true;
  2595. // Leave m_format alone for now - bjm
  2596. // this.m_format = null;
  2597. this.m_inDoctype = false;
  2598. this.m_ispreserve = false;
  2599. this.m_ispreserve = false;
  2600. this.m_isprevtext = false;
  2601. this.m_isUTF8 = false; // ?? used anywhere ??
  2602. this.m_maxCharacter = Encodings.getLastPrintable();
  2603. this.m_preserves.clear();
  2604. this.m_shouldFlush = true;
  2605. this.m_spaceBeforeClose = false;
  2606. this.m_startNewLine = false;
  2607. this.m_triedToGetConverter = false;
  2608. this.m_lineSepUse = true;
  2609. // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !!
  2610. // this.m_writer = null;
  2611. }
  2612. /**
  2613. * Sets the character encoding coming from the xsl:output encoding stylesheet attribute.
  2614. * @param encoding the character encoding
  2615. */
  2616. public void setEncoding(String encoding)
  2617. {
  2618. super.setEncoding(encoding);
  2619. m_maxCharacter = Encodings.getLastPrintable(encoding);
  2620. return;
  2621. }
  2622. }