1/* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5/* 6 * Licensed to the Apache Software Foundation (ASF) under one or more 7 * contributor license agreements. See the NOTICE file distributed with 8 * this work for additional information regarding copyright ownership. 9 * The ASF licenses this file to You under the Apache License, Version 2.0 10 * (the "License"); you may not use this file except in compliance with 11 * the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an "AS IS" BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 */ 21 package com.sun.org.apache.xml.internal.serializer; 22 23import java.io.IOException; 24import java.io.OutputStream; 25import java.io.Writer; 26import java.util.Properties; 27 28import javax.xml.transform.Result; 29 30import org.w3c.dom.Node; 31import org.xml.sax.Attributes; 32import org.xml.sax.ContentHandler; 33import org.xml.sax.Locator; 34import org.xml.sax.SAXException; 35import 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, it is only public because it is used by Xalan. 43 * @xsl.usage internal 44 */ 45public 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 super.setDocumentLocator(arg0); 302 m_saxHandler.setDocumentLocator(arg0); 303 } 304 305 /** 306 * @see org.xml.sax.ContentHandler#skippedEntity(String) 307 */ 308 public void skippedEntity(String arg0) throws SAXException 309 { 310 m_saxHandler.skippedEntity(arg0); 311 } 312 313 /** 314 * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String) 315 * @param prefix The prefix that maps to the URI 316 * @param uri The URI for the namespace 317 */ 318 public void startPrefixMapping(String prefix, String uri) 319 throws SAXException 320 { 321 startPrefixMapping(prefix, uri, true); 322 } 323 324 /** 325 * Remember the prefix/uri mapping at the current nested element depth. 326 * 327 * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String) 328 * @param prefix The prefix that maps to the URI 329 * @param uri The URI for the namespace 330 * @param shouldFlush a flag indicating if the mapping applies to the 331 * current element or an up coming child (not used). 332 */ 333 334 public boolean startPrefixMapping( 335 String prefix, 336 String uri, 337 boolean shouldFlush) 338 throws org.xml.sax.SAXException 339 { 340 341 /* Remember the mapping, and at what depth it was declared 342 * This is one greater than the current depth because these 343 * mappings will apply to the next depth. This is in 344 * consideration that startElement() will soon be called 345 */ 346 347 boolean pushed; 348 int pushDepth; 349 if (shouldFlush) 350 { 351 flushPending(); 352 // the prefix mapping applies to the child element (one deeper) 353 pushDepth = m_elemContext.m_currentElemDepth + 1; 354 } 355 else 356 { 357 // the prefix mapping applies to the current element 358 pushDepth = m_elemContext.m_currentElemDepth; 359 } 360 pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth); 361 362 if (pushed) 363 { 364 m_saxHandler.startPrefixMapping(prefix,uri); 365 366 if (getShouldOutputNSAttr()) 367 { 368 369 /* Brian M.: don't know if we really needto do this. The 370 * callers of this object should have injected both 371 * startPrefixMapping and the attributes. We are 372 * just covering our butt here. 373 */ 374 String name; 375 if (EMPTYSTRING.equals(prefix)) 376 { 377 name = "xmlns"; 378 addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false); 379 } 380 else 381 { 382 if (!EMPTYSTRING.equals(uri)) // hack for XSLTC attribset16 test 383 { // that maps ns1 prefix to "" URI 384 name = "xmlns:" + prefix; 385 386 /* for something like xmlns:abc="w3.pretend.org" 387 * the uri is the value, that is why we pass it in the 388 * value, or 5th slot of addAttributeAlways() 389 */ 390 addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false ); 391 } 392 } 393 } 394 } 395 return pushed; 396 } 397 398 399 /** 400 * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int) 401 */ 402 public void comment(char[] arg0, int arg1, int arg2) throws SAXException 403 { 404 flushPending(); 405 if (m_lexHandler != null) 406 m_lexHandler.comment(arg0, arg1, arg2); 407 408 if (m_tracer != null) 409 super.fireCommentEvent(arg0, arg1, arg2); 410 } 411 412 /** 413 * @see org.xml.sax.ext.LexicalHandler#endCDATA() 414 */ 415 public void endCDATA() throws SAXException 416 { 417 /* Normally we would do somthing with this but we ignore it. 418 * The neccessary call to m_lexHandler.endCDATA() will be made 419 * in flushPending(). 420 * 421 * This is so that if we get calls like these: 422 * this.startCDATA(); 423 * this.characters(chars1, off1, len1); 424 * this.endCDATA(); 425 * this.startCDATA(); 426 * this.characters(chars2, off2, len2); 427 * this.endCDATA(); 428 * 429 * that we will only make these calls to the wrapped handlers: 430 * 431 * m_lexHandler.startCDATA(); 432 * m_saxHandler.characters(chars1, off1, len1); 433 * m_saxHandler.characters(chars1, off2, len2); 434 * m_lexHandler.endCDATA(); 435 * 436 * We will merge adjacent CDATA blocks. 437 */ 438 } 439 440 /** 441 * @see org.xml.sax.ext.LexicalHandler#endDTD() 442 */ 443 public void endDTD() throws SAXException 444 { 445 if (m_lexHandler != null) 446 m_lexHandler.endDTD(); 447 } 448 449 /** 450 * @see org.xml.sax.ext.LexicalHandler#startEntity(String) 451 */ 452 public void startEntity(String arg0) throws SAXException 453 { 454 if (m_lexHandler != null) 455 m_lexHandler.startEntity(arg0); 456 } 457 458 /** 459 * @see ExtendedContentHandler#characters(String) 460 */ 461 public void characters(String chars) throws SAXException 462 { 463 final int length = chars.length(); 464 if (length > m_charsBuff.length) 465 { 466 m_charsBuff = new char[length*2 + 1]; 467 } 468 chars.getChars(0, length, m_charsBuff, 0); 469 this.characters(m_charsBuff, 0, length); 470 } 471 472 /////////////////// from XSLTC ////////////// 473 public ToXMLSAXHandler(ContentHandler handler, String encoding) 474 { 475 super(handler, encoding); 476 477 initCDATA(); 478 // initNamespaces(); 479 m_prefixMap = new NamespaceMappings(); 480 } 481 482 public ToXMLSAXHandler( 483 ContentHandler handler, 484 LexicalHandler lex, 485 String encoding) 486 { 487 super(handler, lex, encoding); 488 489 initCDATA(); 490 // initNamespaces(); 491 m_prefixMap = new NamespaceMappings(); 492 } 493 494 /** 495 * Start an element in the output document. This might be an XML element 496 * (<elem>data</elem> type) or a CDATA section. 497 */ 498 public void startElement( 499 String elementNamespaceURI, 500 String elementLocalName, 501 String elementName) throws SAXException 502 { 503 startElement( 504 elementNamespaceURI,elementLocalName,elementName, null); 505 506 507 } 508 public void startElement(String elementName) throws SAXException 509 { 510 startElement(null, null, elementName, null); 511 } 512 513 514 public void characters(char[] ch, int off, int len) throws SAXException 515 { 516 // We do the first two things in flushPending() but we don't 517 // close any open CDATA calls. 518 if (m_needToCallStartDocument) 519 { 520 startDocumentInternal(); 521 m_needToCallStartDocument = false; 522 } 523 524 if (m_elemContext.m_startTagOpen) 525 { 526 closeStartTag(); 527 m_elemContext.m_startTagOpen = false; 528 } 529 530 if (m_elemContext.m_isCdataSection && !m_cdataTagOpen 531 && m_lexHandler != null) 532 { 533 m_lexHandler.startCDATA(); 534 // We have made a call to m_lexHandler.startCDATA() with 535 // no balancing call to m_lexHandler.endCDATA() 536 // so we set m_cdataTagOpen true to remember this. 537 m_cdataTagOpen = true; 538 } 539 540 /* If there are any occurances of "]]>" in the character data 541 * let m_saxHandler worry about it, we've already warned them with 542 * the previous call of m_lexHandler.startCDATA(); 543 */ 544 m_saxHandler.characters(ch, off, len); 545 546 // time to generate characters event 547 if (m_tracer != null) 548 fireCharEvent(ch, off, len); 549 } 550 551 552 /** 553 * @see ExtendedContentHandler#endElement(String) 554 */ 555 public void endElement(String elemName) throws SAXException 556 { 557 endElement(null, null, elemName); 558 } 559 560 561 /** 562 * Send a namespace declaration in the output document. The namespace 563 * declaration will not be include if the namespace is already in scope 564 * with the same prefix. 565 */ 566 public void namespaceAfterStartElement( 567 final String prefix, 568 final String uri) 569 throws SAXException 570 { 571 startPrefixMapping(prefix,uri,false); 572 } 573 574 /** 575 * 576 * @see org.xml.sax.ContentHandler#processingInstruction(String, String) 577 * Send a processing instruction to the output document 578 */ 579 public void processingInstruction(String target, String data) 580 throws SAXException 581 { 582 flushPending(); 583 584 // Pass the processing instruction to the SAX handler 585 m_saxHandler.processingInstruction(target, data); 586 587 // we don't want to leave serializer to fire off this event, 588 // so do it here. 589 if (m_tracer != null) 590 super.fireEscapingEvent(target, data); 591 } 592 593 /** 594 * Undeclare the namespace that is currently pointed to by a given 595 * prefix. Inform SAX handler if prefix was previously mapped. 596 */ 597 protected boolean popNamespace(String prefix) 598 { 599 try 600 { 601 if (m_prefixMap.popNamespace(prefix)) 602 { 603 m_saxHandler.endPrefixMapping(prefix); 604 return true; 605 } 606 } 607 catch (SAXException e) 608 { 609 // falls through 610 } 611 return false; 612 } 613 614 public void startCDATA() throws SAXException 615 { 616 /* m_cdataTagOpen can only be true here if we have ignored the 617 * previous call to this.endCDATA() and the previous call 618 * this.startCDATA() before that is still "open". In this way 619 * we merge adjacent CDATA. If anything else happened after the 620 * ignored call to this.endCDATA() and this call then a call to 621 * flushPending() would have been made which would have 622 * closed the CDATA and set m_cdataTagOpen to false. 623 */ 624 if (!m_cdataTagOpen ) 625 { 626 flushPending(); 627 if (m_lexHandler != null) { 628 m_lexHandler.startCDATA(); 629 630 // We have made a call to m_lexHandler.startCDATA() with 631 // no balancing call to m_lexHandler.endCDATA() 632 // so we set m_cdataTagOpen true to remember this. 633 m_cdataTagOpen = true; 634 } 635 } 636 } 637 638 /** 639 * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) 640 */ 641 public void startElement( 642 String namespaceURI, 643 String localName, 644 String name, 645 Attributes atts) 646 throws SAXException 647 { 648 flushPending(); 649 super.startElement(namespaceURI, localName, name, atts); 650 651 // Handle document type declaration (for first element only) 652 if (m_needToOutputDocTypeDecl) 653 { 654 String doctypeSystem = getDoctypeSystem(); 655 if (doctypeSystem != null && m_lexHandler != null) 656 { 657 String doctypePublic = getDoctypePublic(); 658 if (doctypeSystem != null) 659 m_lexHandler.startDTD( 660 name, 661 doctypePublic, 662 doctypeSystem); 663 } 664 m_needToOutputDocTypeDecl = false; 665 } 666 m_elemContext = m_elemContext.push(namespaceURI, localName, name); 667 668 // ensurePrefixIsDeclared depends on the current depth, so 669 // the previous increment is necessary where it is. 670 if (namespaceURI != null) 671 ensurePrefixIsDeclared(namespaceURI, name); 672 673 // add the attributes to the collected ones 674 if (atts != null) 675 addAttributes(atts); 676 677 678 // do we really need this CDATA section state? 679 m_elemContext.m_isCdataSection = isCdataSection(); 680 681 } 682 683 private void ensurePrefixIsDeclared(String ns, String rawName) 684 throws org.xml.sax.SAXException 685 { 686 687 if (ns != null && ns.length() > 0) 688 { 689 int index; 690 final boolean no_prefix = ((index = rawName.indexOf(":")) < 0); 691 String prefix = (no_prefix) ? "" : rawName.substring(0, index); 692 693 694 if (null != prefix) 695 { 696 String foundURI = m_prefixMap.lookupNamespace(prefix); 697 698 if ((null == foundURI) || !foundURI.equals(ns)) 699 { 700 this.startPrefixMapping(prefix, ns, false); 701 702 if (getShouldOutputNSAttr()) { 703 // Bugzilla1133: Generate attribute as well as namespace event. 704 // SAX does expect both. 705 this.addAttributeAlways( 706 "http://www.w3.org/2000/xmlns/", 707 no_prefix ? "xmlns" : prefix, // local name 708 no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname 709 "CDATA", 710 ns, 711 false); 712 } 713 } 714 715 } 716 } 717 } 718 /** 719 * Adds the given attribute to the set of attributes, and also makes sure 720 * that the needed prefix/uri mapping is declared, but only if there is a 721 * currently open element. 722 * 723 * @param uri the URI of the attribute 724 * @param localName the local name of the attribute 725 * @param rawName the qualified name of the attribute 726 * @param type the type of the attribute (probably CDATA) 727 * @param value the value of the attribute 728 * @param XSLAttribute true if this attribute is coming from an xsl:attribute element 729 * @see ExtendedContentHandler#addAttribute(String, String, String, String, String) 730 */ 731 public void addAttribute( 732 String uri, 733 String localName, 734 String rawName, 735 String type, 736 String value, 737 boolean XSLAttribute) 738 throws SAXException 739 { 740 if (m_elemContext.m_startTagOpen) 741 { 742 ensurePrefixIsDeclared(uri, rawName); 743 addAttributeAlways(uri, localName, rawName, type, value, false); 744 } 745 746 } 747 748 /** 749 * Try's to reset the super class and reset this class for 750 * re-use, so that you don't need to create a new serializer 751 * (mostly for performance reasons). 752 * 753 * @return true if the class was successfuly reset. 754 * @see Serializer#reset() 755 */ 756 public boolean reset() 757 { 758 boolean wasReset = false; 759 if (super.reset()) 760 { 761 resetToXMLSAXHandler(); 762 wasReset = true; 763 } 764 return wasReset; 765 } 766 767 /** 768 * Reset all of the fields owned by ToXMLSAXHandler class 769 * 770 */ 771 private void resetToXMLSAXHandler() 772 { 773 this.m_escapeSetting = true; 774 } 775 776} 777