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 22package com.sun.org.apache.xml.internal.utils; 23 24import java.util.Stack; 25 26import com.sun.org.apache.xml.internal.res.XMLErrorResources; 27import com.sun.org.apache.xml.internal.res.XMLMessages; 28 29import org.w3c.dom.Document; 30import org.w3c.dom.DocumentFragment; 31import org.w3c.dom.Element; 32import org.w3c.dom.Node; 33import org.w3c.dom.Text; 34import org.w3c.dom.CDATASection; 35 36import org.xml.sax.Attributes; 37import org.xml.sax.ContentHandler; 38import org.xml.sax.Locator; 39import org.xml.sax.ext.LexicalHandler; 40/** 41 * This class takes SAX events (in addition to some extra events 42 * that SAX doesn't handle yet) and adds the result to a document 43 * or document fragment. 44 * @xsl.usage general 45 */ 46public class DOMBuilder 47 implements ContentHandler, LexicalHandler 48{ 49 50 /** Root document */ 51 public Document m_doc; 52 53 /** Current node */ 54 protected Node m_currentNode = null; 55 56 /** The root node */ 57 protected Node m_root = null; 58 59 /** The next sibling node */ 60 protected Node m_nextSibling = null; 61 62 /** First node of document fragment or null if not a DocumentFragment */ 63 public DocumentFragment m_docFrag = null; 64 65 /** Vector of element nodes */ 66 protected Stack m_elemStack = new Stack(); 67 68 /** 69 * DOMBuilder instance constructor... it will add the DOM nodes 70 * to the document fragment. 71 * 72 * @param doc Root document 73 * @param node Current node 74 */ 75 public DOMBuilder(Document doc, Node node) 76 { 77 m_doc = doc; 78 m_currentNode = m_root = node; 79 80 if (node instanceof Element) 81 m_elemStack.push(node); 82 } 83 84 /** 85 * DOMBuilder instance constructor... it will add the DOM nodes 86 * to the document fragment. 87 * 88 * @param doc Root document 89 * @param docFrag Document fragment 90 */ 91 public DOMBuilder(Document doc, DocumentFragment docFrag) 92 { 93 m_doc = doc; 94 m_docFrag = docFrag; 95 } 96 97 /** 98 * DOMBuilder instance constructor... it will add the DOM nodes 99 * to the document. 100 * 101 * @param doc Root document 102 */ 103 public DOMBuilder(Document doc) 104 { 105 m_doc = doc; 106 } 107 108 /** 109 * Get the root document or DocumentFragment of the DOM being created. 110 * 111 * @return The root document or document fragment if not null 112 */ 113 public Node getRootDocument() 114 { 115 return (null != m_docFrag) ? (Node) m_docFrag : (Node) m_doc; 116 } 117 118 /** 119 * Get the root node of the DOM tree. 120 */ 121 public Node getRootNode() 122 { 123 return m_root; 124 } 125 126 /** 127 * Get the node currently being processed. 128 * 129 * @return the current node being processed 130 */ 131 public Node getCurrentNode() 132 { 133 return m_currentNode; 134 } 135 136 /** 137 * Set the next sibling node, which is where the result nodes 138 * should be inserted before. 139 * 140 * @param nextSibling the next sibling node. 141 */ 142 public void setNextSibling(Node nextSibling) 143 { 144 m_nextSibling = nextSibling; 145 } 146 147 /** 148 * Return the next sibling node. 149 * 150 * @return the next sibling node. 151 */ 152 public Node getNextSibling() 153 { 154 return m_nextSibling; 155 } 156 157 /** 158 * Return null since there is no Writer for this class. 159 * 160 * @return null 161 */ 162 public java.io.Writer getWriter() 163 { 164 return null; 165 } 166 167 /** 168 * Append a node to the current container. 169 * 170 * @param newNode New node to append 171 */ 172 protected void append(Node newNode) throws org.xml.sax.SAXException 173 { 174 175 Node currentNode = m_currentNode; 176 177 if (null != currentNode) 178 { 179 if (currentNode == m_root && m_nextSibling != null) 180 currentNode.insertBefore(newNode, m_nextSibling); 181 else 182 currentNode.appendChild(newNode); 183 184 // System.out.println(newNode.getNodeName()); 185 } 186 else if (null != m_docFrag) 187 { 188 if (m_nextSibling != null) 189 m_docFrag.insertBefore(newNode, m_nextSibling); 190 else 191 m_docFrag.appendChild(newNode); 192 } 193 else 194 { 195 boolean ok = true; 196 short type = newNode.getNodeType(); 197 198 if (type == Node.TEXT_NODE) 199 { 200 String data = newNode.getNodeValue(); 201 202 if ((null != data) && (data.trim().length() > 0)) 203 { 204 throw new org.xml.sax.SAXException( 205 XMLMessages.createXMLMessage( 206 XMLErrorResources.ER_CANT_OUTPUT_TEXT_BEFORE_DOC, null)); //"Warning: can't output text before document element! Ignoring..."); 207 } 208 209 ok = false; 210 } 211 else if (type == Node.ELEMENT_NODE) 212 { 213 if (m_doc.getDocumentElement() != null) 214 { 215 ok = false; 216 217 throw new org.xml.sax.SAXException( 218 XMLMessages.createXMLMessage( 219 XMLErrorResources.ER_CANT_HAVE_MORE_THAN_ONE_ROOT, null)); //"Can't have more than one root on a DOM!"); 220 } 221 } 222 223 if (ok) 224 { 225 if (m_nextSibling != null) 226 m_doc.insertBefore(newNode, m_nextSibling); 227 else 228 m_doc.appendChild(newNode); 229 } 230 } 231 } 232 233 /** 234 * Receive an object for locating the origin of SAX document events. 235 * 236 * <p>SAX parsers are strongly encouraged (though not absolutely 237 * required) to supply a locator: if it does so, it must supply 238 * the locator to the application by invoking this method before 239 * invoking any of the other methods in the ContentHandler 240 * interface.</p> 241 * 242 * <p>The locator allows the application to determine the end 243 * position of any document-related event, even if the parser is 244 * not reporting an error. Typically, the application will 245 * use this information for reporting its own errors (such as 246 * character content that does not match an application's 247 * business rules). The information returned by the locator 248 * is probably not sufficient for use with a search engine.</p> 249 * 250 * <p>Note that the locator will return correct information only 251 * during the invocation of the events in this interface. The 252 * application should not attempt to use it at any other time.</p> 253 * 254 * @param locator An object that can return the location of 255 * any SAX document event. 256 * @see org.xml.sax.Locator 257 */ 258 public void setDocumentLocator(Locator locator) 259 { 260 261 // No action for the moment. 262 } 263 264 /** 265 * Receive notification of the beginning of a document. 266 * 267 * <p>The SAX parser will invoke this method only once, before any 268 * other methods in this interface or in DTDHandler (except for 269 * setDocumentLocator).</p> 270 */ 271 public void startDocument() throws org.xml.sax.SAXException 272 { 273 274 // No action for the moment. 275 } 276 277 /** 278 * Receive notification of the end of a document. 279 * 280 * <p>The SAX parser will invoke this method only once, and it will 281 * be the last method invoked during the parse. The parser shall 282 * not invoke this method until it has either abandoned parsing 283 * (because of an unrecoverable error) or reached the end of 284 * input.</p> 285 */ 286 public void endDocument() throws org.xml.sax.SAXException 287 { 288 289 // No action for the moment. 290 } 291 292 /** 293 * Receive notification of the beginning of an element. 294 * 295 * <p>The Parser will invoke this method at the beginning of every 296 * element in the XML document; there will be a corresponding 297 * endElement() event for every startElement() event (even when the 298 * element is empty). All of the element's content will be 299 * reported, in order, before the corresponding endElement() 300 * event.</p> 301 * 302 * <p>If the element name has a namespace prefix, the prefix will 303 * still be attached. Note that the attribute list provided will 304 * contain only attributes with explicit values (specified or 305 * defaulted): #IMPLIED attributes will be omitted.</p> 306 * 307 * 308 * @param ns The namespace of the node 309 * @param localName The local part of the qualified name 310 * @param name The element name. 311 * @param atts The attributes attached to the element, if any. 312 * @see #endElement 313 * @see org.xml.sax.Attributes 314 */ 315 public void startElement( 316 String ns, String localName, String name, Attributes atts) 317 throws org.xml.sax.SAXException 318 { 319 320 Element elem; 321 322 // Note that the namespace-aware call must be used to correctly 323 // construct a Level 2 DOM, even for non-namespaced nodes. 324 if ((null == ns) || (ns.length() == 0)) 325 elem = m_doc.createElementNS(null,name); 326 else 327 elem = m_doc.createElementNS(ns, name); 328 329 append(elem); 330 331 try 332 { 333 int nAtts = atts.getLength(); 334 335 if (0 != nAtts) 336 { 337 for (int i = 0; i < nAtts; i++) 338 { 339 340 //System.out.println("type " + atts.getType(i) + " name " + atts.getLocalName(i) ); 341 // First handle a possible ID attribute 342 if (atts.getType(i).equalsIgnoreCase("ID")) 343 setIDAttribute(atts.getValue(i), elem); 344 345 String attrNS = atts.getURI(i); 346 347 if("".equals(attrNS)) 348 attrNS = null; // DOM represents no-namespace as null 349 350 // System.out.println("attrNS: "+attrNS+", localName: "+atts.getQName(i) 351 // +", qname: "+atts.getQName(i)+", value: "+atts.getValue(i)); 352 // Crimson won't let us set an xmlns: attribute on the DOM. 353 String attrQName = atts.getQName(i); 354 355 // In SAX, xmlns[:] attributes have an empty namespace, while in DOM they 356 // should have the xmlns namespace 357 if (attrQName.startsWith("xmlns:") || attrQName.equals("xmlns")) { 358 attrNS = "http://www.w3.org/2000/xmlns/"; 359 } 360 361 // ALWAYS use the DOM Level 2 call! 362 elem.setAttributeNS(attrNS,attrQName, atts.getValue(i)); 363 } 364 } 365 366 // append(elem); 367 368 m_elemStack.push(elem); 369 370 m_currentNode = elem; 371 372 // append(elem); 373 } 374 catch(java.lang.Exception de) 375 { 376 // de.printStackTrace(); 377 throw new org.xml.sax.SAXException(de); 378 } 379 380 } 381 382 /** 383 384 385 386 * Receive notification of the end of an element. 387 * 388 * <p>The SAX parser will invoke this method at the end of every 389 * element in the XML document; there will be a corresponding 390 * startElement() event for every endElement() event (even when the 391 * element is empty).</p> 392 * 393 * <p>If the element name has a namespace prefix, the prefix will 394 * still be attached to the name.</p> 395 * 396 * 397 * @param ns the namespace of the element 398 * @param localName The local part of the qualified name of the element 399 * @param name The element name 400 */ 401 public void endElement(String ns, String localName, String name) 402 throws org.xml.sax.SAXException 403 { 404 m_elemStack.pop(); 405 m_currentNode = m_elemStack.isEmpty() ? null : (Node)m_elemStack.peek(); 406 } 407 408 /** 409 * Set an ID string to node association in the ID table. 410 * 411 * @param id The ID string. 412 * @param elem The associated ID. 413 */ 414 public void setIDAttribute(String id, Element elem) 415 { 416 417 // Do nothing. This method is meant to be overiden. 418 } 419 420 /** 421 * Receive notification of character data. 422 * 423 * <p>The Parser will call this method to report each chunk of 424 * character data. SAX parsers may return all contiguous character 425 * data in a single chunk, or they may split it into several 426 * chunks; however, all of the characters in any single event 427 * must come from the same external entity, so that the Locator 428 * provides useful information.</p> 429 * 430 * <p>The application must not attempt to read from the array 431 * outside of the specified range.</p> 432 * 433 * <p>Note that some parsers will report whitespace using the 434 * ignorableWhitespace() method rather than this one (validating 435 * parsers must do so).</p> 436 * 437 * @param ch The characters from the XML document. 438 * @param start The start position in the array. 439 * @param length The number of characters to read from the array. 440 * @see #ignorableWhitespace 441 * @see org.xml.sax.Locator 442 */ 443 public void characters(char ch[], int start, int length) throws org.xml.sax.SAXException 444 { 445 if(isOutsideDocElem() 446 && com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer.isWhiteSpace(ch, start, length)) 447 return; // avoid DOM006 Hierarchy request error 448 449 if (m_inCData) 450 { 451 cdata(ch, start, length); 452 453 return; 454 } 455 456 String s = new String(ch, start, length); 457 Node childNode; 458 childNode = m_currentNode != null ? m_currentNode.getLastChild(): null; 459 if( childNode != null && childNode.getNodeType() == Node.TEXT_NODE ){ 460 ((Text)childNode).appendData(s); 461 } 462 else{ 463 Text text = m_doc.createTextNode(s); 464 append(text); 465 } 466 } 467 468 /** 469 * If available, when the disable-output-escaping attribute is used, 470 * output raw text without escaping. A PI will be inserted in front 471 * of the node with the name "lotusxsl-next-is-raw" and a value of 472 * "formatter-to-dom". 473 * 474 * @param ch Array containing the characters 475 * @param start Index to start of characters in the array 476 * @param length Number of characters in the array 477 */ 478 public void charactersRaw(char ch[], int start, int length) 479 throws org.xml.sax.SAXException 480 { 481 if(isOutsideDocElem() 482 && com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer.isWhiteSpace(ch, start, length)) 483 return; // avoid DOM006 Hierarchy request error 484 485 486 String s = new String(ch, start, length); 487 488 append(m_doc.createProcessingInstruction("xslt-next-is-raw", 489 "formatter-to-dom")); 490 append(m_doc.createTextNode(s)); 491 } 492 493 /** 494 * Report the beginning of an entity. 495 * 496 * The start and end of the document entity are not reported. 497 * The start and end of the external DTD subset are reported 498 * using the pseudo-name "[dtd]". All other events must be 499 * properly nested within start/end entity events. 500 * 501 * @param name The name of the entity. If it is a parameter 502 * entity, the name will begin with '%'. 503 * @see #endEntity 504 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl 505 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl 506 */ 507 public void startEntity(String name) throws org.xml.sax.SAXException 508 { 509 510 // Almost certainly the wrong behavior... 511 // entityReference(name); 512 } 513 514 /** 515 * Report the end of an entity. 516 * 517 * @param name The name of the entity that is ending. 518 * @see #startEntity 519 */ 520 public void endEntity(String name) throws org.xml.sax.SAXException{} 521 522 /** 523 * Receive notivication of a entityReference. 524 * 525 * @param name name of the entity reference 526 */ 527 public void entityReference(String name) throws org.xml.sax.SAXException 528 { 529 append(m_doc.createEntityReference(name)); 530 } 531 532 /** 533 * Receive notification of ignorable whitespace in element content. 534 * 535 * <p>Validating Parsers must use this method to report each chunk 536 * of ignorable whitespace (see the W3C XML 1.0 recommendation, 537 * section 2.10): non-validating parsers may also use this method 538 * if they are capable of parsing and using content models.</p> 539 * 540 * <p>SAX parsers may return all contiguous whitespace in a single 541 * chunk, or they may split it into several chunks; however, all of 542 * the characters in any single event must come from the same 543 * external entity, so that the Locator provides useful 544 * information.</p> 545 * 546 * <p>The application must not attempt to read from the array 547 * outside of the specified range.</p> 548 * 549 * @param ch The characters from the XML document. 550 * @param start The start position in the array. 551 * @param length The number of characters to read from the array. 552 * @see #characters 553 */ 554 public void ignorableWhitespace(char ch[], int start, int length) 555 throws org.xml.sax.SAXException 556 { 557 if(isOutsideDocElem()) 558 return; // avoid DOM006 Hierarchy request error 559 560 String s = new String(ch, start, length); 561 562 append(m_doc.createTextNode(s)); 563 } 564 565 /** 566 * Tell if the current node is outside the document element. 567 * 568 * @return true if the current node is outside the document element. 569 */ 570 private boolean isOutsideDocElem() 571 { 572 return (null == m_docFrag) && m_elemStack.size() == 0 && (null == m_currentNode || m_currentNode.getNodeType() == Node.DOCUMENT_NODE); 573 } 574 575 /** 576 * Receive notification of a processing instruction. 577 * 578 * <p>The Parser will invoke this method once for each processing 579 * instruction found: note that processing instructions may occur 580 * before or after the main document element.</p> 581 * 582 * <p>A SAX parser should never report an XML declaration (XML 1.0, 583 * section 2.8) or a text declaration (XML 1.0, section 4.3.1) 584 * using this method.</p> 585 * 586 * @param target The processing instruction target. 587 * @param data The processing instruction data, or null if 588 * none was supplied. 589 */ 590 public void processingInstruction(String target, String data) 591 throws org.xml.sax.SAXException 592 { 593 append(m_doc.createProcessingInstruction(target, data)); 594 } 595 596 /** 597 * Report an XML comment anywhere in the document. 598 * 599 * This callback will be used for comments inside or outside the 600 * document element, including comments in the external DTD 601 * subset (if read). 602 * 603 * @param ch An array holding the characters in the comment. 604 * @param start The starting position in the array. 605 * @param length The number of characters to use from the array. 606 */ 607 public void comment(char ch[], int start, int length) throws org.xml.sax.SAXException 608 { 609 append(m_doc.createComment(new String(ch, start, length))); 610 } 611 612 /** Flag indicating that we are processing a CData section */ 613 protected boolean m_inCData = false; 614 615 /** 616 * Report the start of a CDATA section. 617 * 618 * @see #endCDATA 619 */ 620 public void startCDATA() throws org.xml.sax.SAXException 621 { 622 m_inCData = true; 623 append(m_doc.createCDATASection("")); 624 } 625 626 /** 627 * Report the end of a CDATA section. 628 * 629 * @see #startCDATA 630 */ 631 public void endCDATA() throws org.xml.sax.SAXException 632 { 633 m_inCData = false; 634 } 635 636 /** 637 * Receive notification of cdata. 638 * 639 * <p>The Parser will call this method to report each chunk of 640 * character data. SAX parsers may return all contiguous character 641 * data in a single chunk, or they may split it into several 642 * chunks; however, all of the characters in any single event 643 * must come from the same external entity, so that the Locator 644 * provides useful information.</p> 645 * 646 * <p>The application must not attempt to read from the array 647 * outside of the specified range.</p> 648 * 649 * <p>Note that some parsers will report whitespace using the 650 * ignorableWhitespace() method rather than this one (validating 651 * parsers must do so).</p> 652 * 653 * @param ch The characters from the XML document. 654 * @param start The start position in the array. 655 * @param length The number of characters to read from the array. 656 * @see #ignorableWhitespace 657 * @see org.xml.sax.Locator 658 */ 659 public void cdata(char ch[], int start, int length) throws org.xml.sax.SAXException 660 { 661 if(isOutsideDocElem() 662 && com.sun.org.apache.xml.internal.utils.XMLCharacterRecognizer.isWhiteSpace(ch, start, length)) 663 return; // avoid DOM006 Hierarchy request error 664 665 String s = new String(ch, start, length); 666 667 CDATASection section =(CDATASection) m_currentNode.getLastChild(); 668 section.appendData(s); 669 } 670 671 /** 672 * Report the start of DTD declarations, if any. 673 * 674 * Any declarations are assumed to be in the internal subset 675 * unless otherwise indicated. 676 * 677 * @param name The document type name. 678 * @param publicId The declared public identifier for the 679 * external DTD subset, or null if none was declared. 680 * @param systemId The declared system identifier for the 681 * external DTD subset, or null if none was declared. 682 * @see #endDTD 683 * @see #startEntity 684 */ 685 public void startDTD(String name, String publicId, String systemId) 686 throws org.xml.sax.SAXException 687 { 688 689 // Do nothing for now. 690 } 691 692 /** 693 * Report the end of DTD declarations. 694 * 695 * @see #startDTD 696 */ 697 public void endDTD() throws org.xml.sax.SAXException 698 { 699 700 // Do nothing for now. 701 } 702 703 /** 704 * Begin the scope of a prefix-URI Namespace mapping. 705 * 706 * <p>The information from this event is not necessary for 707 * normal Namespace processing: the SAX XML reader will 708 * automatically replace prefixes for element and attribute 709 * names when the http://xml.org/sax/features/namespaces 710 * feature is true (the default).</p> 711 * 712 * <p>There are cases, however, when applications need to 713 * use prefixes in character data or in attribute values, 714 * where they cannot safely be expanded automatically; the 715 * start/endPrefixMapping event supplies the information 716 * to the application to expand prefixes in those contexts 717 * itself, if necessary.</p> 718 * 719 * <p>Note that start/endPrefixMapping events are not 720 * guaranteed to be properly nested relative to each-other: 721 * all startPrefixMapping events will occur before the 722 * corresponding startElement event, and all endPrefixMapping 723 * events will occur after the corresponding endElement event, 724 * but their order is not guaranteed.</p> 725 * 726 * @param prefix The Namespace prefix being declared. 727 * @param uri The Namespace URI the prefix is mapped to. 728 * @see #endPrefixMapping 729 * @see #startElement 730 */ 731 public void startPrefixMapping(String prefix, String uri) 732 throws org.xml.sax.SAXException 733 { 734 735 /* 736 // Not sure if this is needed or wanted 737 // Also, it fails in the stree. 738 if((null != m_currentNode) 739 && (m_currentNode.getNodeType() == Node.ELEMENT_NODE)) 740 { 741 String qname; 742 if(((null != prefix) && (prefix.length() == 0)) 743 || (null == prefix)) 744 qname = "xmlns"; 745 else 746 qname = "xmlns:"+prefix; 747 748 Element elem = (Element)m_currentNode; 749 String val = elem.getAttribute(qname); // Obsolete, should be DOM2...? 750 if(val == null) 751 { 752 elem.setAttributeNS("http://www.w3.org/XML/1998/namespace", 753 qname, uri); 754 } 755 } 756 */ 757 } 758 759 /** 760 * End the scope of a prefix-URI mapping. 761 * 762 * <p>See startPrefixMapping for details. This event will 763 * always occur after the corresponding endElement event, 764 * but the order of endPrefixMapping events is not otherwise 765 * guaranteed.</p> 766 * 767 * @param prefix The prefix that was being mapping. 768 * @see #startPrefixMapping 769 * @see #endElement 770 */ 771 public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException{} 772 773 /** 774 * Receive notification of a skipped entity. 775 * 776 * <p>The Parser will invoke this method once for each entity 777 * skipped. Non-validating processors may skip entities if they 778 * have not seen the declarations (because, for example, the 779 * entity was declared in an external DTD subset). All processors 780 * may skip external entities, depending on the values of the 781 * http://xml.org/sax/features/external-general-entities and the 782 * http://xml.org/sax/features/external-parameter-entities 783 * properties.</p> 784 * 785 * @param name The name of the skipped entity. If it is a 786 * parameter entity, the name will begin with '%'. 787 */ 788 public void skippedEntity(String name) throws org.xml.sax.SAXException{} 789} 790