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