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