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: ToXMLSAXHandler.java,v 1.13 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 receives notification of SAX-like events, and with gathered
  33. * information over these calls it will invoke the equivalent SAX methods
  34. * on a handler, the ultimate output is known to be XML.
  35. *
  36. * @author minchau
  37. * @author Santiago Pericas-Geertsen
  38. * @author G. Todd Miller
  39. */
  40. public class ToXMLSAXHandler extends ToSAXHandler
  41. {
  42. /**
  43. * Keeps track of whether output escaping is currently enabled
  44. */
  45. protected boolean m_escapeSetting = false;
  46. public ToXMLSAXHandler()
  47. {
  48. // default constructor (need to set content handler ASAP !)
  49. m_prefixMap = new NamespaceMappings();
  50. initCDATA();
  51. }
  52. /**
  53. * @see com.sun.org.apache.xml.internal.serializer.Serializer#getOutputFormat()
  54. */
  55. public Properties getOutputFormat()
  56. {
  57. return null;
  58. }
  59. /**
  60. * @see com.sun.org.apache.xml.internal.serializer.Serializer#getOutputStream()
  61. */
  62. public OutputStream getOutputStream()
  63. {
  64. return null;
  65. }
  66. /**
  67. * @see com.sun.org.apache.xml.internal.serializer.Serializer#getWriter()
  68. */
  69. public Writer getWriter()
  70. {
  71. return null;
  72. }
  73. /**
  74. * Do nothing for SAX.
  75. */
  76. public void indent(int n) throws SAXException
  77. {
  78. }
  79. /**
  80. * @see com.sun.org.apache.xml.internal.serializer.DOMSerializer#serialize(Node)
  81. */
  82. public void serialize(Node node) throws IOException
  83. {
  84. }
  85. /**
  86. * @see com.sun.org.apache.xml.internal.serializer.SerializationHandler#setEscaping(boolean)
  87. */
  88. public boolean setEscaping(boolean escape) throws SAXException
  89. {
  90. boolean oldEscapeSetting = m_escapeSetting;
  91. m_escapeSetting = escape;
  92. if (escape) {
  93. processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
  94. } else {
  95. processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
  96. }
  97. return oldEscapeSetting;
  98. }
  99. /**
  100. * @see com.sun.org.apache.xml.internal.serializer.Serializer#setOutputFormat(Properties)
  101. */
  102. public void setOutputFormat(Properties format)
  103. {
  104. }
  105. /**
  106. * @see com.sun.org.apache.xml.internal.serializer.Serializer#setOutputStream(OutputStream)
  107. */
  108. public void setOutputStream(OutputStream output)
  109. {
  110. }
  111. /**
  112. * @see com.sun.org.apache.xml.internal.serializer.Serializer#setWriter(Writer)
  113. */
  114. public void setWriter(Writer writer)
  115. {
  116. }
  117. /**
  118. * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
  119. */
  120. public void attributeDecl(
  121. String arg0,
  122. String arg1,
  123. String arg2,
  124. String arg3,
  125. String arg4)
  126. throws SAXException
  127. {
  128. }
  129. /**
  130. * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
  131. */
  132. public void elementDecl(String arg0, String arg1) throws SAXException
  133. {
  134. }
  135. /**
  136. * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
  137. */
  138. public void externalEntityDecl(String arg0, String arg1, String arg2)
  139. throws SAXException
  140. {
  141. }
  142. /**
  143. * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
  144. */
  145. public void internalEntityDecl(String arg0, String arg1)
  146. throws SAXException
  147. {
  148. }
  149. /**
  150. * Receives notification of the end of the document.
  151. * @see org.xml.sax.ContentHandler#endDocument()
  152. */
  153. public void endDocument() throws SAXException
  154. {
  155. flushPending();
  156. // Close output document
  157. m_saxHandler.endDocument();
  158. if (m_tracer != null)
  159. super.fireEndDoc();
  160. }
  161. /**
  162. * This method is called when all the data needed for a call to the
  163. * SAX handler's startElement() method has been gathered.
  164. */
  165. protected void closeStartTag() throws SAXException
  166. {
  167. m_elemContext.m_startTagOpen = false;
  168. final String localName = getLocalName(m_elemContext.m_elementName);
  169. final String uri = getNamespaceURI(m_elemContext.m_elementName, true);
  170. // Now is time to send the startElement event
  171. if (m_needToCallStartDocument)
  172. {
  173. startDocumentInternal();
  174. }
  175. m_saxHandler.startElement(uri, localName, m_elemContext.m_elementName, m_attributes);
  176. // we've sent the official SAX attributes on their way,
  177. // now we don't need them anymore.
  178. m_attributes.clear();
  179. if(m_state != null)
  180. m_state.setCurrentNode(null);
  181. }
  182. /**
  183. * Closes ane open cdata tag, and
  184. * unlike the this.endCDATA() method (from the LexicalHandler) interface,
  185. * this "internal" method will send the endCDATA() call to the wrapped
  186. * handler.
  187. *
  188. */
  189. public void closeCDATA() throws SAXException
  190. {
  191. // Output closing bracket - "]]>"
  192. if (m_lexHandler != null && m_cdataTagOpen) {
  193. m_lexHandler.endCDATA();
  194. }
  195. // There are no longer any calls made to
  196. // m_lexHandler.startCDATA() without a balancing call to
  197. // m_lexHandler.endCDATA()
  198. // so we set m_cdataTagOpen to false to remember this.
  199. m_cdataTagOpen = false;
  200. }
  201. /**
  202. * @see org.xml.sax.ContentHandler#endElement(String, String, String)
  203. */
  204. public void endElement(String namespaceURI, String localName, String qName)
  205. throws SAXException
  206. {
  207. // Close any open elements etc.
  208. flushPending();
  209. if (namespaceURI == null)
  210. {
  211. if (m_elemContext.m_elementURI != null)
  212. namespaceURI = m_elemContext.m_elementURI;
  213. else
  214. namespaceURI = getNamespaceURI(qName, true);
  215. }
  216. if (localName == null)
  217. {
  218. if (m_elemContext.m_elementLocalName != null)
  219. localName = m_elemContext.m_elementLocalName;
  220. else
  221. localName = getLocalName(qName);
  222. }
  223. m_saxHandler.endElement(namespaceURI, localName, qName);
  224. if (m_tracer != null)
  225. super.fireEndElem(qName);
  226. /* Pop all namespaces at the current element depth.
  227. * We are not waiting for official endPrefixMapping() calls.
  228. */
  229. m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth,
  230. m_saxHandler);
  231. m_elemContext = m_elemContext.m_prev;
  232. }
  233. /**
  234. * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
  235. */
  236. public void endPrefixMapping(String prefix) throws SAXException
  237. {
  238. /* poping all prefix mappings should have been done
  239. * in endElement() already
  240. */
  241. return;
  242. }
  243. /**
  244. * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
  245. */
  246. public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
  247. throws SAXException
  248. {
  249. m_saxHandler.ignorableWhitespace(arg0,arg1,arg2);
  250. }
  251. /**
  252. * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
  253. */
  254. public void setDocumentLocator(Locator arg0)
  255. {
  256. m_saxHandler.setDocumentLocator(arg0);
  257. }
  258. /**
  259. * @see org.xml.sax.ContentHandler#skippedEntity(String)
  260. */
  261. public void skippedEntity(String arg0) throws SAXException
  262. {
  263. m_saxHandler.skippedEntity(arg0);
  264. }
  265. /**
  266. * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
  267. * @param prefix The prefix that maps to the URI
  268. * @param uri The URI for the namespace
  269. */
  270. public void startPrefixMapping(String prefix, String uri)
  271. throws SAXException
  272. {
  273. startPrefixMapping(prefix, uri, true);
  274. }
  275. /**
  276. * Remember the prefix/uri mapping at the current nested element depth.
  277. *
  278. * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
  279. * @param prefix The prefix that maps to the URI
  280. * @param uri The URI for the namespace
  281. * @param shouldFlush a flag indicating if the mapping applies to the
  282. * current element or an up coming child (not used).
  283. */
  284. public boolean startPrefixMapping(
  285. String prefix,
  286. String uri,
  287. boolean shouldFlush)
  288. throws org.xml.sax.SAXException
  289. {
  290. /* Remember the mapping, and at what depth it was declared
  291. * This is one greater than the current depth because these
  292. * mappings will apply to the next depth. This is in
  293. * consideration that startElement() will soon be called
  294. */
  295. boolean pushed;
  296. int pushDepth;
  297. if (shouldFlush)
  298. {
  299. flushPending();
  300. // the prefix mapping applies to the child element (one deeper)
  301. pushDepth = m_elemContext.m_currentElemDepth + 1;
  302. }
  303. else
  304. {
  305. // the prefix mapping applies to the current element
  306. pushDepth = m_elemContext.m_currentElemDepth;
  307. }
  308. pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
  309. if (pushed)
  310. {
  311. m_saxHandler.startPrefixMapping(prefix,uri);
  312. if (getShouldOutputNSAttr())
  313. {
  314. /* bjm: don't know if we really needto do this. The
  315. * callers of this object should have injected both
  316. * startPrefixMapping and the attributes. We are
  317. * just covering our butt here.
  318. */
  319. String name;
  320. if (EMPTYSTRING.equals(prefix))
  321. {
  322. name = "xmlns";
  323. addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri);
  324. }
  325. else
  326. {
  327. if (!EMPTYSTRING.equals(uri)) // hack for XSLTC attribset16 test
  328. { // that maps ns1 prefix to "" URI
  329. name = "xmlns:" + prefix;
  330. /* for something like xmlns:abc="w3.pretend.org"
  331. * the uri is the value, that is why we pass it in the
  332. * value, or 5th slot of addAttributeAlways()
  333. */
  334. addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri);
  335. }
  336. }
  337. }
  338. }
  339. return pushed;
  340. }
  341. /**
  342. * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
  343. */
  344. public void comment(char[] arg0, int arg1, int arg2) throws SAXException
  345. {
  346. flushPending();
  347. if (m_lexHandler != null)
  348. m_lexHandler.comment(arg0, arg1, arg2);
  349. if (m_tracer != null)
  350. super.fireCommentEvent(arg0, arg1, arg2);
  351. }
  352. /**
  353. * @see org.xml.sax.ext.LexicalHandler#endCDATA()
  354. */
  355. public void endCDATA() throws SAXException
  356. {
  357. /* Normally we would do somthing with this but we ignore it.
  358. * The neccessary call to m_lexHandler.endCDATA() will be made
  359. * in flushPending().
  360. *
  361. * This is so that if we get calls like these:
  362. * this.startCDATA();
  363. * this.characters(chars1, off1, len1);
  364. * this.endCDATA();
  365. * this.startCDATA();
  366. * this.characters(chars2, off2, len2);
  367. * this.endCDATA();
  368. *
  369. * that we will only make these calls to the wrapped handlers:
  370. *
  371. * m_lexHandler.startCDATA();
  372. * m_saxHandler.characters(chars1, off1, len1);
  373. * m_saxHandler.characters(chars1, off2, len2);
  374. * m_lexHandler.endCDATA();
  375. *
  376. * We will merge adjacent CDATA blocks.
  377. */
  378. }
  379. /**
  380. * @see org.xml.sax.ext.LexicalHandler#endDTD()
  381. */
  382. public void endDTD() throws SAXException
  383. {
  384. if (m_lexHandler != null)
  385. m_lexHandler.endDTD();
  386. }
  387. /**
  388. * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
  389. */
  390. public void startEntity(String arg0) throws SAXException
  391. {
  392. if (m_lexHandler != null)
  393. m_lexHandler.startEntity(arg0);
  394. }
  395. /**
  396. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#characters(String)
  397. */
  398. public void characters(String chars) throws SAXException
  399. {
  400. final int length = chars.length();
  401. if (length > m_charsBuff.length)
  402. {
  403. m_charsBuff = new char[length*2 + 1];
  404. }
  405. chars.getChars(0, length, m_charsBuff, 0);
  406. this.characters(m_charsBuff, 0, length);
  407. }
  408. /////////////////// from XSLTC //////////////
  409. public ToXMLSAXHandler(ContentHandler handler, String encoding)
  410. {
  411. super(handler, encoding);
  412. initCDATA();
  413. // initNamespaces();
  414. m_prefixMap = new NamespaceMappings();
  415. }
  416. public ToXMLSAXHandler(
  417. ContentHandler handler,
  418. LexicalHandler lex,
  419. String encoding)
  420. {
  421. super(handler, lex, encoding);
  422. initCDATA();
  423. // initNamespaces();
  424. m_prefixMap = new NamespaceMappings();
  425. }
  426. /**
  427. * Start an element in the output document. This might be an XML element
  428. * (<elem>data</elem> type) or a CDATA section.
  429. */
  430. public void startElement(
  431. String elementNamespaceURI,
  432. String elementLocalName,
  433. String elementName) throws SAXException
  434. {
  435. startElement(
  436. elementNamespaceURI,elementLocalName,elementName, null);
  437. }
  438. public void startElement(String elementName) throws SAXException
  439. {
  440. startElement(null, null, elementName, null);
  441. }
  442. public void characters(char[] ch, int off, int len) throws SAXException
  443. {
  444. // We do the first two things in flushPending() but we don't
  445. // close any open CDATA calls.
  446. if (m_needToCallStartDocument)
  447. {
  448. startDocumentInternal();
  449. m_needToCallStartDocument = false;
  450. }
  451. if (m_elemContext.m_startTagOpen)
  452. {
  453. closeStartTag();
  454. m_elemContext.m_startTagOpen = false;
  455. }
  456. if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
  457. && m_lexHandler != null)
  458. {
  459. m_lexHandler.startCDATA();
  460. // We have made a call to m_lexHandler.startCDATA() with
  461. // no balancing call to m_lexHandler.endCDATA()
  462. // so we set m_cdataTagOpen true to remember this.
  463. m_cdataTagOpen = true;
  464. }
  465. /* If there are any occurances of "]]>" in the character data
  466. * let m_saxHandler worry about it, we've already warned them with
  467. * the previous call of m_lexHandler.startCDATA();
  468. */
  469. m_saxHandler.characters(ch, off, len);
  470. // time to generate characters event
  471. if (m_tracer != null)
  472. fireCharEvent(ch, off, len);
  473. }
  474. /**
  475. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#endElement(String)
  476. */
  477. public void endElement(String elemName) throws SAXException
  478. {
  479. endElement(null, null, elemName);
  480. }
  481. /**
  482. * Send a namespace declaration in the output document. The namespace
  483. * declaration will not be include if the namespace is already in scope
  484. * with the same prefix.
  485. */
  486. public void namespaceAfterStartElement(
  487. final String prefix,
  488. final String uri)
  489. throws SAXException
  490. {
  491. startPrefixMapping(prefix,uri,false);
  492. }
  493. /**
  494. *
  495. * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
  496. * Send a processing instruction to the output document
  497. */
  498. public void processingInstruction(String target, String data)
  499. throws SAXException
  500. {
  501. flushPending();
  502. // Pass the processing instruction to the SAX handler
  503. m_saxHandler.processingInstruction(target, data);
  504. // we don't want to leave serializer to fire off this event,
  505. // so do it here.
  506. if (m_tracer != null)
  507. super.fireEscapingEvent(target, data);
  508. }
  509. /**
  510. * Undeclare the namespace that is currently pointed to by a given
  511. * prefix. Inform SAX handler if prefix was previously mapped.
  512. */
  513. protected boolean popNamespace(String prefix)
  514. {
  515. try
  516. {
  517. if (m_prefixMap.popNamespace(prefix))
  518. {
  519. m_saxHandler.endPrefixMapping(prefix);
  520. return true;
  521. }
  522. }
  523. catch (SAXException e)
  524. {
  525. // falls through
  526. }
  527. return false;
  528. }
  529. public void startCDATA() throws SAXException
  530. {
  531. /* m_cdataTagOpen can only be true here if we have ignored the
  532. * previous call to this.endCDATA() and the previous call
  533. * this.startCDATA() before that is still "open". In this way
  534. * we merge adjacent CDATA. If anything else happened after the
  535. * ignored call to this.endCDATA() and this call then a call to
  536. * flushPending() would have been made which would have
  537. * closed the CDATA and set m_cdataTagOpen to false.
  538. */
  539. if (!m_cdataTagOpen )
  540. {
  541. flushPending();
  542. if (m_lexHandler != null) {
  543. m_lexHandler.startCDATA();
  544. // We have made a call to m_lexHandler.startCDATA() with
  545. // no balancing call to m_lexHandler.endCDATA()
  546. // so we set m_cdataTagOpen true to remember this.
  547. m_cdataTagOpen = true;
  548. }
  549. }
  550. }
  551. /**
  552. * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
  553. */
  554. public void startElement(
  555. String namespaceURI,
  556. String localName,
  557. String name,
  558. Attributes atts)
  559. throws SAXException
  560. {
  561. flushPending();
  562. super.startElement(namespaceURI, localName, name, atts);
  563. // Handle document type declaration (for first element only)
  564. if (m_needToOutputDocTypeDecl)
  565. {
  566. String doctypeSystem = getDoctypeSystem();
  567. if (doctypeSystem != null && m_lexHandler != null)
  568. {
  569. String doctypePublic = getDoctypePublic();
  570. if (doctypeSystem != null)
  571. m_lexHandler.startDTD(
  572. name,
  573. doctypePublic,
  574. doctypeSystem);
  575. }
  576. m_needToOutputDocTypeDecl = false;
  577. }
  578. m_elemContext = m_elemContext.push(namespaceURI, localName, name);
  579. // ensurePrefixIsDeclared depends on the current depth, so
  580. // the previous increment is necessary where it is.
  581. if (namespaceURI != null)
  582. ensurePrefixIsDeclared(namespaceURI, name);
  583. // add the attributes to the collected ones
  584. if (atts != null)
  585. addAttributes(atts);
  586. // do we really need this CDATA section state?
  587. m_elemContext.m_isCdataSection = isCdataSection();
  588. }
  589. private void ensurePrefixIsDeclared(String ns, String rawName)
  590. throws org.xml.sax.SAXException
  591. {
  592. if (ns != null && ns.length() > 0)
  593. {
  594. int index;
  595. String prefix =
  596. (index = rawName.indexOf(":")) < 0
  597. ? ""
  598. : rawName.substring(0, index);
  599. if (null != prefix)
  600. {
  601. String foundURI = m_prefixMap.lookupNamespace(prefix);
  602. if ((null == foundURI) || !foundURI.equals(ns))
  603. {
  604. this.startPrefixMapping(prefix, ns, false);
  605. if (getShouldOutputNSAttr()) {
  606. // Bugzilla1133: Generate attribute as well as namespace event.
  607. // SAX does expect both.
  608. this.addAttributeAlways(
  609. "http://www.w3.org/2000/xmlns/",
  610. prefix,
  611. "xmlns" + (prefix.length() == 0 ? "" : ":") + prefix,
  612. "CDATA",
  613. ns);
  614. }
  615. }
  616. }
  617. }
  618. }
  619. /**
  620. * Adds the given attribute to the set of attributes, and also makes sure
  621. * that the needed prefix/uri mapping is declared, but only if there is a
  622. * currently open element.
  623. *
  624. * @param uri the URI of the attribute
  625. * @param localName the local name of the attribute
  626. * @param rawName the qualified name of the attribute
  627. * @param type the type of the attribute (probably CDATA)
  628. * @param value the value of the attribute
  629. * @see com.sun.org.apache.xml.internal.serializer.ExtendedContentHandler#addAttribute(String, String, String, String, String)
  630. */
  631. public void addAttribute(
  632. String uri,
  633. String localName,
  634. String rawName,
  635. String type,
  636. String value)
  637. throws SAXException
  638. {
  639. if (m_elemContext.m_startTagOpen)
  640. {
  641. ensurePrefixIsDeclared(uri, rawName);
  642. addAttributeAlways(uri, localName, rawName, type, value);
  643. }
  644. }
  645. /**
  646. * Try's to reset the super class and reset this class for
  647. * re-use, so that you don't need to create a new serializer
  648. * (mostly for performance reasons).
  649. *
  650. * @return true if the class was successfuly reset.
  651. * @see com.sun.org.apache.xml.internal.serializer.Serializer#reset()
  652. */
  653. public boolean reset()
  654. {
  655. boolean wasReset = false;
  656. if (super.reset())
  657. {
  658. resetToXMLSAXHandler();
  659. wasReset = true;
  660. }
  661. return wasReset;
  662. }
  663. /**
  664. * Reset all of the fields owned by ToXMLSAXHandler class
  665. *
  666. */
  667. private void resetToXMLSAXHandler()
  668. {
  669. this.m_escapeSetting = false;
  670. }
  671. }