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: ToHTMLSAXHandler.java,v 1.8 2004/02/17 04:18:19 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.Writer;
  23. import java.util.Properties;
  24. import javax.xml.transform.Result;
  25. import org.w3c.dom.Node;
  26. import org.xml.sax.Attributes;
  27. import org.xml.sax.ContentHandler;
  28. import org.xml.sax.Locator;
  29. import org.xml.sax.SAXException;
  30. import org.xml.sax.ext.LexicalHandler;
  31. /**
  32. * This class accepts SAX-like calls, then sends true SAX calls to a
  33. * wrapped SAX handler. There is optimization done knowing that the ultimate
  34. * output is HTML.
  35. * @author Santiago Pericas-Geertsen
  36. * @author G. Todd Miller
  37. */
  38. public class ToHTMLSAXHandler extends ToSAXHandler
  39. {
  40. /**
  41. * Keeps track of whether output escaping is currently enabled
  42. */
  43. protected boolean m_escapeSetting = false;
  44. /**
  45. * Returns null.
  46. * @return null
  47. * @see com.sun.org.apache.xml.internal.serializer.Serializer#getOutputFormat()
  48. */
  49. public Properties getOutputFormat()
  50. {
  51. return null;
  52. }
  53. /**
  54. * Reurns null
  55. * @return null
  56. * @see com.sun.org.apache.xml.internal.serializer.Serializer#getOutputStream()
  57. */
  58. public OutputStream getOutputStream()
  59. {
  60. return null;
  61. }
  62. /**
  63. * Returns null
  64. * @return null
  65. * @see com.sun.org.apache.xml.internal.serializer.Serializer#getWriter()
  66. */
  67. public Writer getWriter()
  68. {
  69. return null;
  70. }
  71. /**
  72. * Does nothing.
  73. *
  74. */
  75. public void indent(int n) throws SAXException
  76. {
  77. }
  78. /**
  79. * Does nothing.
  80. * @see com.sun.org.apache.xml.internal.serializer.DOMSerializer#serialize(Node)
  81. */
  82. public void serialize(Node node) throws IOException
  83. {
  84. return;
  85. }
  86. /**
  87. * Turns special character escaping on/off.
  88. *
  89. *
  90. * @param excape true if escaping is to be set on.
  91. *
  92. * @see com.sun.org.apache.xml.internal.serializer.SerializationHandler#setEscaping(boolean)
  93. */
  94. public boolean setEscaping(boolean escape) throws SAXException
  95. {
  96. boolean oldEscapeSetting = m_escapeSetting;
  97. m_escapeSetting = escape;
  98. if (escape) {
  99. processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
  100. } else {
  101. processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
  102. }
  103. return oldEscapeSetting;
  104. }
  105. /**
  106. * Does nothing
  107. * @param indent the number of spaces to indent per indentation level
  108. * (ignored)
  109. * @see com.sun.org.apache.xml.internal.serializer.SerializationHandler#setIndent(boolean)
  110. */
  111. public void setIndent(boolean indent)
  112. {
  113. }
  114. /**
  115. * Does nothing.
  116. * @param format this parameter is not used
  117. * @see com.sun.org.apache.xml.internal.serializer.Serializer#setOutputFormat(Properties)
  118. */
  119. public void setOutputFormat(Properties format)
  120. {
  121. }
  122. /**
  123. * Does nothing.
  124. * @param output this parameter is ignored
  125. * @see com.sun.org.apache.xml.internal.serializer.Serializer#setOutputStream(OutputStream)
  126. */
  127. public void setOutputStream(OutputStream output)
  128. {
  129. }
  130. /**
  131. * Does nothing.
  132. * @param writer this parameter is ignored.
  133. * @see com.sun.org.apache.xml.internal.serializer.Serializer#setWriter(Writer)
  134. */
  135. public void setWriter(Writer writer)
  136. {
  137. }
  138. /**
  139. * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
  140. */
  141. /**
  142. * Does nothing.
  143. *
  144. * @param eName this parameter is ignored
  145. * @param aName this parameter is ignored
  146. * @param type this parameter is ignored
  147. * @param valueDefault this parameter is ignored
  148. * @param value this parameter is ignored
  149. * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String,String,String)
  150. */
  151. public void attributeDecl(
  152. String eName,
  153. String aName,
  154. String type,
  155. String valueDefault,
  156. String value)
  157. throws SAXException
  158. {
  159. }
  160. /**
  161. * Does nothing.
  162. * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
  163. */
  164. public void elementDecl(String name, String model) throws SAXException
  165. {
  166. return;
  167. }
  168. /**
  169. * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
  170. */
  171. public void externalEntityDecl(String arg0, String arg1, String arg2)
  172. throws SAXException
  173. {
  174. }
  175. /**
  176. * Does nothing.
  177. *
  178. * @see org.xml.sax.DTDHandler#unparsedEntityDecl
  179. */
  180. public void internalEntityDecl(String name, String value)
  181. throws SAXException
  182. {
  183. }
  184. /**
  185. * Receive notification of the end of an element.
  186. *
  187. * <p>The SAX parser will invoke this method at the end of every
  188. * element in the XML document; there will be a corresponding
  189. * startElement() event for every endElement() event (even when the
  190. * element is empty).</p>
  191. *
  192. * <p>If the element name has a namespace prefix, the prefix will
  193. * still be attached to the name.</p>
  194. *
  195. *
  196. * @param namespaceURI The Namespace URI, or the empty string if the
  197. * element has no Namespace URI or if Namespace
  198. * processing is not being performed.
  199. * @param localName The local name (without prefix), or the
  200. * empty string if Namespace processing is not being
  201. * performed.
  202. * @param name The qualified name (with prefix), or the
  203. * empty string if qualified names are not available.
  204. * @param name The element type name
  205. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  206. * wrapping another exception.
  207. * @see org.xml.sax.ContentHandler#endElement(String, String, String)
  208. */
  209. public void endElement(String uri, String localName, String qName)
  210. throws SAXException
  211. {
  212. flushPending();
  213. m_saxHandler.endElement(uri, localName, qName);
  214. // time to fire off endElement event
  215. if (m_tracer != null)
  216. super.fireEndElem(qName);
  217. }
  218. /**
  219. * Does nothing.
  220. */
  221. public void endPrefixMapping(String prefix) throws SAXException
  222. {
  223. }
  224. /**
  225. * Does nothing.
  226. * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
  227. */
  228. public void ignorableWhitespace(char[] ch, int start, int length)
  229. throws SAXException
  230. {
  231. }
  232. /**
  233. * Receive notification of a processing instruction.
  234. *
  235. * <p>The Parser will invoke this method once for each processing
  236. * instruction found: note that processing instructions may occur
  237. * before or after the main document element.</p>
  238. *
  239. * <p>A SAX parser should never report an XML declaration (XML 1.0,
  240. * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
  241. * using this method.</p>
  242. *
  243. * @param target The processing instruction target.
  244. * @param data The processing instruction data, or null if
  245. * none was supplied.
  246. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  247. * wrapping another exception.
  248. *
  249. * @throws org.xml.sax.SAXException
  250. * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
  251. */
  252. public void processingInstruction(String arg0, String arg1)
  253. throws SAXException
  254. {
  255. flushPending();
  256. m_saxHandler.processingInstruction(arg0,arg1);
  257. // time to fire off processing instruction event
  258. if (m_tracer != null)
  259. super.fireEscapingEvent(arg0,arg1);
  260. }
  261. /**
  262. * Does nothing.
  263. * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
  264. */
  265. public void setDocumentLocator(Locator arg0)
  266. {
  267. // do nothing
  268. }
  269. /**
  270. * Does nothing.
  271. * @see org.xml.sax.ContentHandler#skippedEntity(String)
  272. */
  273. public void skippedEntity(String arg0) throws SAXException
  274. {
  275. }
  276. /**
  277. * Receive notification of the beginning of an element, although this is a
  278. * SAX method additional namespace or attribute information can occur before
  279. * or after this call, that is associated with this element.
  280. *
  281. *
  282. * @param namespaceURI The Namespace URI, or the empty string if the
  283. * element has no Namespace URI or if Namespace
  284. * processing is not being performed.
  285. * @param localName The local name (without prefix), or the
  286. * empty string if Namespace processing is not being
  287. * performed.
  288. * @param qName The elements name.
  289. * @param atts The attributes attached to the element, if any.
  290. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  291. * wrapping another exception.
  292. * @see org.xml.sax.ContentHandler#startElement
  293. * @see org.xml.sax.ContentHandler#endElement
  294. * @see org.xml.sax.AttributeList
  295. *
  296. * @throws org.xml.sax.SAXException
  297. *
  298. * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
  299. */
  300. public void startElement(
  301. String namespaceURI,
  302. String localName,
  303. String qName,
  304. Attributes atts)
  305. throws SAXException
  306. {
  307. flushPending();
  308. super.startElement(namespaceURI, localName, qName, atts);
  309. m_saxHandler.startElement(namespaceURI, localName, qName, atts);
  310. m_elemContext.m_startTagOpen = false;
  311. }
  312. /**
  313. * Receive notification of a comment anywhere in the document. This callback
  314. * will be used for comments inside or outside the document element.
  315. * @param ch An array holding the characters in the comment.
  316. * @param start The starting position in the array.
  317. * @param length The number of characters to use from the array.
  318. * @throws org.xml.sax.SAXException The application may raise an exception.
  319. *
  320. * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
  321. */
  322. public void comment(char[] ch, int start, int length) throws SAXException
  323. {
  324. flushPending();
  325. m_lexHandler.comment(ch, start, length);
  326. // time to fire off comment event
  327. if (m_tracer != null)
  328. super.fireCommentEvent(ch, start, length);
  329. return;
  330. }
  331. /**
  332. * Does nothing.
  333. * @see org.xml.sax.ext.LexicalHandler#endCDATA()
  334. */
  335. public void endCDATA() throws SAXException
  336. {
  337. return;
  338. }
  339. /**
  340. * Does nothing.
  341. * @see org.xml.sax.ext.LexicalHandler#endDTD()
  342. */
  343. public void endDTD() throws SAXException
  344. {
  345. }
  346. /**
  347. * Does nothing.
  348. * @see org.xml.sax.ext.LexicalHandler#startCDATA()
  349. */
  350. public void startCDATA() throws SAXException
  351. {
  352. }
  353. /**
  354. * Does nothing.
  355. * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
  356. */
  357. public void startEntity(String arg0) throws SAXException
  358. {
  359. }
  360. /**
  361. * Receive notification of the end of a document.
  362. *
  363. * <p>The SAX parser will invoke this method only once, and it will
  364. * be the last method invoked during the parse. The parser shall
  365. * not invoke this method until it has either abandoned parsing
  366. * (because of an unrecoverable error) or reached the end of
  367. * input.</p>
  368. *
  369. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  370. * wrapping another exception.
  371. *
  372. * @throws org.xml.sax.SAXException
  373. *
  374. *
  375. */
  376. public void endDocument() throws SAXException
  377. {
  378. flushPending();
  379. // Close output document
  380. m_saxHandler.endDocument();
  381. if (m_tracer != null)
  382. super.fireEndDoc();
  383. }
  384. /**
  385. * This method is called when all the data needed for a call to the
  386. * SAX handler's startElement() method has been gathered.
  387. */
  388. protected void closeStartTag() throws SAXException
  389. {
  390. m_elemContext.m_startTagOpen = false;
  391. // Now is time to send the startElement event
  392. m_saxHandler.startElement(
  393. EMPTYSTRING,
  394. m_elemContext.m_elementName,
  395. m_elemContext.m_elementName,
  396. m_attributes);
  397. m_attributes.clear();
  398. }
  399. /**
  400. * Do nothing.
  401. * @see com.sun.org.apache.xml.internal.serializer.SerializationHandler#close()
  402. */
  403. public void close()
  404. {
  405. return;
  406. }
  407. /**
  408. * Receive notification of character data.
  409. *
  410. * @param chars The string of characters to process.
  411. *
  412. * @throws org.xml.sax.SAXException
  413. *
  414. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#characters(String)
  415. */
  416. public void characters(final String chars) throws SAXException
  417. {
  418. final int length = chars.length();
  419. if (length > m_charsBuff.length)
  420. {
  421. m_charsBuff = new char[length * 2 + 1];
  422. }
  423. chars.getChars(0, length, m_charsBuff, 0);
  424. this.characters(m_charsBuff, 0, length);
  425. }
  426. /**
  427. * A constructor
  428. * @param handler the wrapped SAX content handler
  429. * @param encoding the encoding of the output HTML document
  430. */
  431. public ToHTMLSAXHandler(ContentHandler handler, String encoding)
  432. {
  433. super(handler,encoding);
  434. }
  435. /**
  436. * A constructor.
  437. * @param handler the wrapped SAX content handler
  438. * @param lex the wrapped lexical handler
  439. * @param encoding the encoding of the output HTML document
  440. */
  441. public ToHTMLSAXHandler(
  442. ContentHandler handler,
  443. LexicalHandler lex,
  444. String encoding)
  445. {
  446. super(handler,lex,encoding);
  447. }
  448. /**
  449. * An element starts, but attributes are not fully known yet.
  450. *
  451. * @param elementNamespaceURI the URI of the namespace of the element
  452. * (optional)
  453. * @param elementLocalName the element name, but without prefix
  454. * (optional)
  455. * @param elementName the element name, with prefix, if any (required)
  456. *
  457. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#startElement(String)
  458. */
  459. public void startElement(
  460. String elementNamespaceURI,
  461. String elementLocalName,
  462. String elementName) throws SAXException
  463. {
  464. super.startElement(elementNamespaceURI, elementLocalName, elementName);
  465. flushPending();
  466. // Handle document type declaration (for first element only)
  467. if (m_lexHandler != null)
  468. {
  469. String doctypeSystem = getDoctypeSystem();
  470. String doctypePublic = getDoctypePublic();
  471. if ((doctypeSystem != null) || (doctypePublic != null))
  472. m_lexHandler.startDTD(
  473. elementName,
  474. doctypePublic,
  475. doctypeSystem);
  476. m_lexHandler = null;
  477. }
  478. m_elemContext = m_elemContext.push(elementNamespaceURI, elementLocalName, elementName);
  479. }
  480. /**
  481. * An element starts, but attributes are not fully known yet.
  482. *
  483. * @param elementName the element name, with prefix, if any
  484. *
  485. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#startElement(String)
  486. */
  487. public void startElement(String elementName) throws SAXException
  488. {
  489. this.startElement(null,null, elementName);
  490. }
  491. /**
  492. * Receive notification of the end of an element.
  493. * @param elementName The element type name
  494. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  495. * wrapping another exception.
  496. *
  497. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#endElement(String)
  498. */
  499. public void endElement(String elementName) throws SAXException
  500. {
  501. flushPending();
  502. m_saxHandler.endElement(EMPTYSTRING, elementName, elementName);
  503. // time to fire off endElement event
  504. if (m_tracer != null)
  505. super.fireEndElem(elementName);
  506. }
  507. /**
  508. * Receive notification of character data.
  509. *
  510. * <p>The Parser will call this method to report each chunk of
  511. * character data. SAX parsers may return all contiguous character
  512. * data in a single chunk, or they may split it into several
  513. * chunks; however, all of the characters in any single event
  514. * must come from the same external entity, so that the Locator
  515. * provides useful information.</p>
  516. *
  517. * <p>The application must not attempt to read from the array
  518. * outside of the specified range.</p>
  519. *
  520. * <p>Note that some parsers will report whitespace using the
  521. * ignorableWhitespace() method rather than this one (validating
  522. * parsers must do so).</p>
  523. *
  524. * @param ch The characters from the XML document.
  525. * @param off The start position in the array.
  526. * @param len The number of characters to read from the array.
  527. * @throws org.xml.sax.SAXException Any SAX exception, possibly
  528. * wrapping another exception.
  529. * @see #ignorableWhitespace
  530. * @see org.xml.sax.Locator
  531. *
  532. * @throws org.xml.sax.SAXException
  533. *
  534. * @see org.xml.sax.ContentHandler#characters(char[], int, int)
  535. */
  536. public void characters(char[] ch, int off, int len) throws SAXException
  537. {
  538. flushPending();
  539. m_saxHandler.characters(ch, off, len);
  540. // time to fire off characters event
  541. if (m_tracer != null)
  542. super.fireCharEvent(ch, off, len);
  543. }
  544. /**
  545. * This method flushes any pending events, which can be startDocument()
  546. * closing the opening tag of an element, or closing an open CDATA section.
  547. */
  548. public void flushPending() throws SAXException
  549. {
  550. if (m_needToCallStartDocument)
  551. {
  552. startDocumentInternal();
  553. m_needToCallStartDocument = false;
  554. }
  555. // Close any open element
  556. if (m_elemContext.m_startTagOpen)
  557. {
  558. closeStartTag();
  559. m_elemContext.m_startTagOpen = false;
  560. }
  561. }
  562. /**
  563. * Handle a prefix/uri mapping, which is associated with a startElement()
  564. * that is soon to follow. Need to close any open start tag to make
  565. * sure than any name space attributes due to this event are associated wih
  566. * the up comming element, not the current one.
  567. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#startPrefixMapping
  568. *
  569. * @param prefix The Namespace prefix being declared.
  570. * @param uri The Namespace URI the prefix is mapped to.
  571. * @param shouldFlush true if any open tags need to be closed first, this
  572. * will impact which element the mapping applies to (open parent, or its up
  573. * comming child)
  574. * @return returns true if the call made a change to the current
  575. * namespace information, false if it did not change anything, e.g. if the
  576. * prefix/namespace mapping was already in scope from before.
  577. *
  578. * @throws org.xml.sax.SAXException The client may throw
  579. * an exception during processing.
  580. */
  581. public boolean startPrefixMapping(
  582. String prefix,
  583. String uri,
  584. boolean shouldFlush)
  585. throws SAXException
  586. {
  587. // no namespace support for HTML
  588. if (shouldFlush)
  589. flushPending();
  590. m_saxHandler.startPrefixMapping(prefix,uri);
  591. return false;
  592. }
  593. /**
  594. * Begin the scope of a prefix-URI Namespace mapping
  595. * just before another element is about to start.
  596. * This call will close any open tags so that the prefix mapping
  597. * will not apply to the current element, but the up comming child.
  598. *
  599. * @see org.xml.sax.ContentHandler#startPrefixMapping
  600. *
  601. * @param prefix The Namespace prefix being declared.
  602. * @param uri The Namespace URI the prefix is mapped to.
  603. *
  604. * @throws org.xml.sax.SAXException The client may throw
  605. * an exception during processing.
  606. *
  607. */
  608. public void startPrefixMapping(String prefix, String uri)
  609. throws org.xml.sax.SAXException
  610. {
  611. startPrefixMapping(prefix,uri,true);
  612. }
  613. /**
  614. * This method is used when a prefix/uri namespace mapping
  615. * is indicated after the element was started with a
  616. * startElement() and before and endElement().
  617. * startPrefixMapping(prefix,uri) would be used before the
  618. * startElement() call.
  619. * @param prefix the prefix associated with the given URI.
  620. * @param uri the URI of the namespace
  621. *
  622. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#namespaceAfterStartElement(String, String)
  623. */
  624. public void namespaceAfterStartElement(
  625. final String prefix,
  626. final String uri)
  627. throws SAXException
  628. {
  629. // hack for XSLTC with finding URI for default namespace
  630. if (m_elemContext.m_elementURI == null)
  631. {
  632. String prefix1 = getPrefixPart(m_elemContext.m_elementName);
  633. if (prefix1 == null && EMPTYSTRING.equals(prefix))
  634. {
  635. // the elements URI is not known yet, and it
  636. // doesn't have a prefix, and we are currently
  637. // setting the uri for prefix "", so we have
  638. // the uri for the element... lets remember it
  639. m_elemContext.m_elementURI = uri;
  640. }
  641. }
  642. startPrefixMapping(prefix,uri,false);
  643. }
  644. /**
  645. * Try's to reset the super class and reset this class for
  646. * re-use, so that you don't need to create a new serializer
  647. * (mostly for performance reasons).
  648. *
  649. * @return true if the class was successfuly reset.
  650. * @see com.sun.org.apache.xml.internal.serializer.Serializer#reset()
  651. */
  652. public boolean reset()
  653. {
  654. boolean wasReset = false;
  655. if (super.reset())
  656. {
  657. resetToHTMLSAXHandler();
  658. wasReset = true;
  659. }
  660. return wasReset;
  661. }
  662. /**
  663. * Reset all of the fields owned by ToHTMLSAXHandler class
  664. *
  665. */
  666. private void resetToHTMLSAXHandler()
  667. {
  668. this.m_escapeSetting = false;
  669. }
  670. }