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 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 */ 63 public Properties getOutputFormat() 64 { 65 return null; 66 } 67 68 /** 69 * @see Serializer#getOutputStream() 70 */ 71 public OutputStream getOutputStream() 72 { 73 return null; 74 } 75 76 /** 77 * @see Serializer#getWriter() 78 */ 79 public Writer getWriter() 80 { 81 return null; 82 } 83 84 /** 85 * Do nothing for SAX. 86 */ 87 public void indent(int n) throws SAXException 88 { 89 } 90 91 92 /** 93 * @see DOMSerializer#serialize(Node) 94 */ 95 public void serialize(Node node) throws IOException 96 { 97 } 98 99 /** 100 * @see SerializationHandler#setEscaping(boolean) 101 */ 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 */ 119 public void setOutputFormat(Properties format) 120 { 121 } 122 123 /** 124 * @see Serializer#setOutputStream(OutputStream) 125 */ 126 public void setOutputStream(OutputStream output) 127 { 128 } 129 130 /** 131 * @see Serializer#setWriter(Writer) 132 */ 133 public void setWriter(Writer writer) 134 { 135 } 136 137 /** 138 * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String) 139 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 299 public void setDocumentLocator(Locator arg0) 300 { 301 m_saxHandler.setDocumentLocator(arg0); 302 } 303 304 /** 305 * @see org.xml.sax.ContentHandler#skippedEntity(String) 306 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 471 public ToXMLSAXHandler(ContentHandler handler, String encoding) 472 { 473 super(handler, encoding); 474 475 initCDATA(); 476 // initNamespaces(); 477 m_prefixMap = new NamespaceMappings(); 478 } 479 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 */ 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 } 506 public void startElement(String elementName) throws SAXException 507 { 508 startElement(null, null, elementName, null); 509 } 510 511 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 769 private void resetToXMLSAXHandler() 770 { 771 this.m_escapeSetting = true; 772 } 773 774 } 775