Source for gnu.xml.pipeline.DomConsumer

   1: /* DomConsumer.java -- 
   2:    Copyright (C) 1999,2000,2001 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: package gnu.xml.pipeline;
  39: 
  40: import gnu.xml.aelfred2.ContentHandler2;
  41: import gnu.xml.util.DomParser;
  42: 
  43: import org.xml.sax.Attributes;
  44: import org.xml.sax.ContentHandler;
  45: import org.xml.sax.DTDHandler;
  46: import org.xml.sax.ErrorHandler;
  47: import org.xml.sax.Locator;
  48: import org.xml.sax.SAXException;
  49: import org.xml.sax.SAXNotRecognizedException;
  50: import org.xml.sax.SAXParseException;
  51: import org.xml.sax.ext.DeclHandler;
  52: import org.xml.sax.ext.LexicalHandler;
  53: import org.xml.sax.helpers.AttributesImpl;
  54: import org.w3c.dom.Attr;
  55: import org.w3c.dom.CDATASection;
  56: import org.w3c.dom.CharacterData;
  57: import org.w3c.dom.Document;
  58: import org.w3c.dom.DOMImplementation;
  59: import org.w3c.dom.Element;
  60: import org.w3c.dom.EntityReference;
  61: import org.w3c.dom.Node;
  62: import org.w3c.dom.ProcessingInstruction;
  63: import org.w3c.dom.Text;
  64: 
  65: /**
  66:  * This consumer builds a DOM Document from its input, acting either as a
  67:  * pipeline terminus or as an intermediate buffer.  When a document's worth
  68:  * of events has been delivered to this consumer, that document is read with
  69:  * a {@link DomParser} and sent to the next consumer.  It is also available
  70:  * as a read-once property.
  71:  *
  72:  * <p>The DOM tree is constructed as faithfully as possible.  There are some
  73:  * complications since a DOM should expose behaviors that can't be implemented
  74:  * without API backdoors into that DOM, and because some SAX parsers don't
  75:  * report all the information that DOM permits to be exposed.  The general
  76:  * problem areas involve information from the Document Type Declaration (DTD).
  77:  * DOM only represents a limited subset, but has some behaviors that depend
  78:  * on much deeper knowledge of a document's DTD.  You shouldn't have much to
  79:  * worry about unless you change handling of "noise" nodes from its default
  80:  * setting (which ignores them all); note if you use JAXP to populate your
  81:  * DOM trees, it wants to save "noise" nodes by default.  (Such nodes include
  82:  * ignorable whitespace, comments, entity references and CDATA boundaries.)
  83:  * Otherwise, your
  84:  * main worry will be if you use a SAX parser that doesn't flag ignorable
  85:  * whitespace unless it's validating (few don't).
  86:  *
  87:  * <p> The SAX2 events used as input must contain XML Names for elements
  88:  * and attributes, with original prefixes.  In SAX2,
  89:  * this is optional unless the "namespace-prefixes" parser feature is set.
  90:  * Moreover, many application components won't provide completely correct
  91:  * structures anyway.  <em>Before you convert a DOM to an output document,
  92:  * you should plan to postprocess it to create or repair such namespace
  93:  * information.</em> The {@link NSFilter} pipeline stage does such work.
  94:  *
  95:  * <p> <em>Note:  changes late in DOM L2 process made it impractical to
  96:  * attempt to create the DocumentType node in any implementation-neutral way,
  97:  * much less to populate it (L1 didn't support even creating such nodes).
  98:  * To create and populate such a node, subclass the inner
  99:  * {@link DomConsumer.Handler} class and teach it about the backdoors into
 100:  * whatever DOM implementation you want.  It's possible that some revised
 101:  * DOM API (L3?) will make this problem solvable again. </em>
 102:  *
 103:  * @see DomParser
 104:  *
 105:  * @author David Brownell
 106:  */
 107: public class DomConsumer implements EventConsumer
 108: {
 109:     private Class        domImpl;
 110: 
 111:     private boolean        hidingCDATA = true;
 112:     private boolean        hidingComments = true;
 113:     private boolean        hidingWhitespace = true;
 114:     private boolean        hidingReferences = true;
 115: 
 116:     private Handler        handler;
 117:     private ErrorHandler    errHandler;
 118: 
 119:     private EventConsumer    next;
 120: 
 121:     // FIXME:  this can't be a generic pipeline stage just now,
 122:     // since its input became a Class not a String (to be turned
 123:     // into a class, using the right class loader)
 124: 
 125: 
 126:     /**
 127:      * Configures this pipeline terminus to use the specified implementation
 128:      * of DOM when constructing its result value.
 129:      *
 130:      * @param impl class implementing {@link org.w3c.dom.Document Document}
 131:      *    which publicly exposes a default constructor
 132:      *
 133:      * @exception SAXException when there is a problem creating an
 134:      *    empty DOM document using the specified implementation
 135:      */
 136:     public DomConsumer (Class impl)
 137:     throws SAXException
 138:     {
 139:     domImpl = impl;
 140:     handler = new Handler (this);
 141:     }
 142: 
 143:     /**
 144:      * This is the hook through which a subclass provides a handler
 145:      * which knows how to access DOM extensions, specific to some
 146:      * implementation, to record additional data in a DOM.
 147:      * Treat this as part of construction; don't call it except
 148:      * before (or between) parses.
 149:      */
 150:     protected void setHandler (Handler h)
 151:     {
 152:     handler = h;
 153:     }
 154: 
 155: 
 156:     private Document emptyDocument ()
 157:     throws SAXException
 158:     {
 159:     try {
 160:         return (Document) domImpl.newInstance ();
 161:     } catch (IllegalAccessException e) {
 162:         throw new SAXException ("can't access constructor: "
 163:             + e.getMessage ());
 164:     } catch (InstantiationException e) {
 165:         throw new SAXException ("can't instantiate Document: "
 166:             + e.getMessage ());
 167:     }
 168:     }
 169: 
 170: 
 171:     /**
 172:      * Configures this consumer as a buffer/filter, using the specified
 173:      * DOM implementation when constructing its result value.
 174:      *
 175:      * <p> This event consumer acts as a buffer and filter, in that it
 176:      * builds a DOM tree and then writes it out when <em>endDocument</em>
 177:      * is invoked.  Because of the limitations of DOM, much information
 178:      * will as a rule not be seen in that replay.  To get a full fidelity
 179:      * copy of the input event stream, use a {@link TeeConsumer}.
 180:      *
 181:      * @param impl class implementing {@link org.w3c.dom.Document Document}
 182:      *    which publicly exposes a default constructor
 183:      * @param next receives a "replayed" sequence of parse events when
 184:      *    the <em>endDocument</em> method is invoked.
 185:      *
 186:      * @exception SAXException when there is a problem creating an
 187:      *    empty DOM document using the specified DOM implementation
 188:      */
 189:     public DomConsumer (Class impl, EventConsumer n)
 190:     throws SAXException
 191:     {
 192:     this (impl);
 193:     next = n;
 194:     }
 195: 
 196: 
 197:     /**
 198:      * Returns the document constructed from the preceding
 199:      * sequence of events.  This method should not be
 200:      * used again until another sequence of events has been
 201:      * given to this EventConsumer.  
 202:      */
 203:     final public Document getDocument ()
 204:     {
 205:     return handler.clearDocument ();
 206:     }
 207: 
 208:     public void setErrorHandler (ErrorHandler handler)
 209:     {
 210:     errHandler = handler;
 211:     }
 212: 
 213: 
 214:     /**
 215:      * Returns true if the consumer is hiding entity references nodes
 216:      * (the default), and false if EntityReference nodes should
 217:      * instead be created.  Such EntityReference nodes will normally be
 218:      * empty, unless an implementation arranges to populate them and then
 219:      * turn them back into readonly objects.
 220:      *
 221:      * @see #setHidingReferences
 222:      */
 223:     final public boolean    isHidingReferences ()
 224:     { return hidingReferences; }
 225: 
 226:     /**
 227:      * Controls whether the consumer will hide entity expansions,
 228:      * or will instead mark them with entity reference nodes.
 229:      *
 230:      * @see #isHidingReferences
 231:      * @param flag False if entity reference nodes will appear
 232:      */
 233:     final public void        setHidingReferences (boolean flag)
 234:     { hidingReferences = flag; }
 235:     
 236: 
 237:     /**
 238:      * Returns true if the consumer is hiding comments (the default),
 239:      * and false if they should be placed into the output document.
 240:      *
 241:      * @see #setHidingComments
 242:      */
 243:     public final boolean isHidingComments ()
 244:     { return hidingComments; }
 245: 
 246:     /**
 247:      * Controls whether the consumer is hiding comments.
 248:      *
 249:      * @see #isHidingComments
 250:      */
 251:     public final void setHidingComments (boolean flag)
 252:     { hidingComments = flag; }
 253: 
 254: 
 255:     /**
 256:      * Returns true if the consumer is hiding ignorable whitespace
 257:      * (the default), and false if such whitespace should be placed
 258:      * into the output document as children of element nodes.
 259:      *
 260:      * @see #setHidingWhitespace
 261:      */
 262:     public final boolean isHidingWhitespace ()
 263:     { return hidingWhitespace; }
 264: 
 265:     /**
 266:      * Controls whether the consumer hides ignorable whitespace
 267:      *
 268:      * @see #isHidingComments
 269:      */
 270:     public final void setHidingWhitespace (boolean flag)
 271:     { hidingWhitespace = flag; }
 272: 
 273: 
 274:     /**
 275:      * Returns true if the consumer is saving CDATA boundaries, or
 276:      * false (the default) otherwise.
 277:      *
 278:      * @see #setHidingCDATA
 279:      */
 280:     final public boolean    isHidingCDATA ()
 281:     { return hidingCDATA; }
 282: 
 283:     /**
 284:      * Controls whether the consumer will save CDATA boundaries.
 285:      *
 286:      * @see #isHidingCDATA
 287:      * @param flag True to treat CDATA text differently from other
 288:      *    text nodes
 289:      */
 290:     final public void        setHidingCDATA (boolean flag)
 291:     { hidingCDATA = flag; }
 292:     
 293: 
 294: 
 295:     /** Returns the document handler being used. */
 296:     final public ContentHandler getContentHandler ()
 297:     { return handler; }
 298: 
 299:     /** Returns the DTD handler being used. */
 300:     final public DTDHandler getDTDHandler ()
 301:     { return handler; }
 302: 
 303:     /**
 304:      * Returns the lexical handler being used.
 305:      * (DOM construction can't really use declaration handlers.)
 306:      */
 307:     final public Object getProperty (String id)
 308:     throws SAXNotRecognizedException
 309:     {
 310:     if ("http://xml.org/sax/properties/lexical-handler".equals (id))
 311:         return handler;
 312:     if ("http://xml.org/sax/properties/declaration-handler".equals (id))
 313:         return handler;
 314:     throw new SAXNotRecognizedException (id);
 315:     }
 316: 
 317:     EventConsumer getNext () { return next; }
 318: 
 319:     ErrorHandler getErrorHandler () { return errHandler; }
 320: 
 321:     /**
 322:      * Class used to intercept various parsing events and use them to
 323:      * populate a DOM document.  Subclasses would typically know and use
 324:      * backdoors into specific DOM implementations, used to implement 
 325:      * DTD-related functionality.
 326:      *
 327:      * <p> Note that if this ever throws a DOMException (runtime exception)
 328:      * that will indicate a bug in the DOM (e.g. doesn't support something
 329:      * per specification) or the parser (e.g. emitted an illegal name, or
 330:      * accepted illegal input data). </p>
 331:      */
 332:     public static class Handler
 333:     implements ContentHandler2, LexicalHandler,
 334:         DTDHandler, DeclHandler
 335:     {
 336:     protected DomConsumer        consumer;
 337: 
 338:     private DOMImplementation    impl;
 339:     private Document         document;
 340:     private boolean        isL2;
 341: 
 342:     private Locator        locator;
 343:     private Node        top;
 344:     private boolean        inCDATA;
 345:     private boolean        mergeCDATA;
 346:     private boolean        inDTD;
 347:     private String        currentEntity;
 348: 
 349:     private boolean        recreatedAttrs;
 350:     private AttributesImpl    attributes = new AttributesImpl ();
 351: 
 352:     /**
 353:      * Subclasses may use SAX2 events to provide additional
 354:      * behaviors in the resulting DOM.
 355:      */
 356:     protected Handler (DomConsumer consumer)
 357:     throws SAXException
 358:     {
 359:         this.consumer = consumer;
 360:         document = consumer.emptyDocument ();
 361:         impl = document.getImplementation ();
 362:         isL2 = impl.hasFeature ("XML", "2.0");
 363:     }
 364: 
 365:     private void fatal (String message, Exception x)
 366:     throws SAXException
 367:     {
 368:         SAXParseException    e;
 369:         ErrorHandler    errHandler = consumer.getErrorHandler ();;
 370: 
 371:         if (locator == null)
 372:         e = new SAXParseException (message, null, null, -1, -1, x);
 373:         else
 374:         e = new SAXParseException (message, locator, x);
 375:         if (errHandler != null)
 376:         errHandler.fatalError (e);
 377:         throw e;
 378:     }
 379: 
 380:     /**
 381:      * Returns and forgets the document produced.  If the handler is
 382:      * reused, a new document may be created.
 383:      */
 384:     Document clearDocument ()
 385:     {
 386:         Document retval = document;
 387:         document = null;
 388:         locator = null;
 389:         return retval;
 390:     }
 391: 
 392:     /**
 393:      * Returns the document under construction.
 394:      */
 395:     protected Document getDocument ()
 396:         { return document; }
 397:     
 398:     /**
 399:      * Returns the current node being populated.  This is usually
 400:      * an Element or Document, but it might be an EntityReference
 401:      * node if some implementation-specific code knows how to put
 402:      * those into the result tree and later mark them as readonly.
 403:      */
 404:     protected Node getTop ()
 405:         { return top; }
 406: 
 407: 
 408:     // SAX1
 409:     public void setDocumentLocator (Locator locator)
 410:     {
 411:         this.locator = locator;
 412:     }
 413: 
 414:     // SAX1
 415:     public void startDocument ()
 416:     throws SAXException
 417:     {
 418:         if (document == null)
 419:         try {
 420:             if (isL2) {
 421:             // couple to original implementation
 422:             document = impl.createDocument (null, "foo", null);
 423:             document.removeChild (document.getFirstChild ());
 424:             } else {
 425:             document = consumer.emptyDocument ();
 426:             }
 427:         } catch (Exception e) {
 428:             fatal ("DOM create document", e);
 429:         }
 430:         top = document;
 431:     }
 432: 
 433:         // ContentHandler2
 434:         public void xmlDecl(String version,
 435:                             String encoding,
 436:                             boolean standalone,
 437:                             String inputEncoding)
 438:           throws SAXException
 439:         {
 440:           if (document != null)
 441:             {
 442:               document.setXmlVersion(version);
 443:               document.setXmlStandalone(standalone);
 444:             }
 445:         }
 446: 
 447:     // SAX1
 448:     public void endDocument ()
 449:     throws SAXException
 450:     {
 451:         try {
 452:         if (consumer.getNext () != null && document != null) {
 453:             DomParser    parser = new DomParser (document);
 454: 
 455:             EventFilter.bind (parser, consumer.getNext ());
 456:             parser.parse ("ignored");
 457:         }
 458:         } finally {
 459:         top = null;
 460:         }
 461:     }
 462: 
 463:     // SAX1
 464:     public void processingInstruction (String target, String data)
 465:     throws SAXException
 466:     {
 467:         // we can't create populated entity ref nodes using
 468:         // only public DOM APIs (they've got to be readonly)
 469:         if (currentEntity != null)
 470:         return;
 471: 
 472:         ProcessingInstruction    pi;
 473: 
 474:         if (isL2
 475:             // && consumer.isUsingNamespaces ()
 476:             && target.indexOf (':') != -1)
 477:         namespaceError (
 478:             "PI target name is namespace nonconformant: "
 479:             + target);
 480:         if (inDTD)
 481:         return;
 482:         pi = document.createProcessingInstruction (target, data);
 483:         top.appendChild (pi);
 484:     }
 485: 
 486:     /**
 487:      * Subclasses may overrride this method to provide a more efficient
 488:      * way to construct text nodes.
 489:      * Typically, copying the text into a single character array will
 490:      * be more efficient than doing that as well as allocating other
 491:      * needed for a String, including an internal StringBuffer.
 492:      * Those additional memory and CPU costs can be incurred later,
 493:      * if ever needed.
 494:      * Unfortunately the standard DOM factory APIs encourage those costs
 495:      * to be incurred early.
 496:      */
 497:     protected Text createText (
 498:         boolean    isCDATA,
 499:         char    ch [],
 500:         int        start,
 501:         int        length
 502:     ) {
 503:         String    value = new String (ch, start, length);
 504: 
 505:         if (isCDATA)
 506:         return document.createCDATASection (value);
 507:         else
 508:         return document.createTextNode (value);
 509:     }
 510: 
 511:     // SAX1
 512:     public void characters (char ch [], int start, int length)
 513:     throws SAXException
 514:     {
 515:         // we can't create populated entity ref nodes using
 516:         // only public DOM APIs (they've got to be readonly
 517:         // at creation time)
 518:         if (currentEntity != null)
 519:         return;
 520: 
 521:         Node    lastChild = top.getLastChild ();
 522: 
 523:         // merge consecutive text or CDATA nodes if appropriate.
 524:         if (lastChild instanceof Text) {
 525:         if (consumer.isHidingCDATA ()
 526:             // consecutive Text content ... always merge
 527:             || (!inCDATA
 528:                 && !(lastChild instanceof CDATASection))
 529:             // consecutive CDATASection content ... don't
 530:             // merge between sections, only within them
 531:             || (inCDATA && mergeCDATA
 532:                 && lastChild instanceof CDATASection)
 533:                 ) {
 534:             CharacterData    last = (CharacterData) lastChild;
 535:             String        value = new String (ch, start, length);
 536:             
 537:             last.appendData (value);
 538:             return;
 539:         }
 540:         }
 541:         if (inCDATA && !consumer.isHidingCDATA ()) {
 542:         top.appendChild (createText (true, ch, start, length));
 543:         mergeCDATA = true;
 544:         } else
 545:         top.appendChild (createText (false, ch, start, length));
 546:     }
 547: 
 548:     // SAX2
 549:     public void skippedEntity (String name)
 550:     throws SAXException
 551:     {
 552:         // this callback is useless except to report errors, since
 553:         // we can't know if the ref was in content, within an
 554:         // attribute, within a declaration ... only one of those
 555:         // cases supports more intelligent action than a panic.
 556:         fatal ("skipped entity: " + name, null);
 557:     }
 558: 
 559:     // SAX2
 560:     public void startPrefixMapping (String prefix, String uri)
 561:     throws SAXException
 562:     {
 563:         // reconstruct "xmlns" attributes deleted by all
 564:         // SAX2 parsers without "namespace-prefixes" = true
 565:         if ("".equals (prefix))
 566:         attributes.addAttribute ("", "", "xmlns",
 567:             "CDATA", uri);
 568:         else
 569:         attributes.addAttribute ("", "", "xmlns:" + prefix,
 570:             "CDATA", uri);
 571:         recreatedAttrs = true;
 572:     }
 573: 
 574:     // SAX2
 575:     public void endPrefixMapping (String prefix)
 576:     throws SAXException
 577:         { }
 578: 
 579:     // SAX2
 580:     public void startElement (
 581:         String uri,
 582:         String localName,
 583:         String qName,
 584:         Attributes atts
 585:     ) throws SAXException
 586:     {
 587:         // we can't create populated entity ref nodes using
 588:         // only public DOM APIs (they've got to be readonly)
 589:         if (currentEntity != null)
 590:         return;
 591: 
 592:         // parser discarded basic information; DOM tree isn't writable
 593:         // without massaging to assign prefixes to all nodes.
 594:         // the "NSFilter" class does that massaging.
 595:         if (qName.length () == 0)
 596:         qName = localName;
 597: 
 598: 
 599:         Element    element;
 600:         int        length = atts.getLength ();
 601: 
 602:         if (!isL2) {
 603:         element = document.createElement (qName);
 604: 
 605:         // first the explicit attributes ...
 606:         length = atts.getLength ();
 607:         for (int i = 0; i < length; i++)
 608:             element.setAttribute (atts.getQName (i),
 609:                         atts.getValue (i));
 610:         // ... then any recreated ones (DOM deletes duplicates)
 611:         if (recreatedAttrs) {
 612:             recreatedAttrs = false;
 613:             length = attributes.getLength ();
 614:             for (int i = 0; i < length; i++)
 615:             element.setAttribute (attributes.getQName (i),
 616:                         attributes.getValue (i));
 617:             attributes.clear ();
 618:         }
 619: 
 620:         top.appendChild (element);
 621:         top = element;
 622:         return;
 623:         }
 624: 
 625:         // For an L2 DOM when namespace use is enabled, use
 626:         // createElementNS/createAttributeNS except when
 627:         // (a) it's an element in the default namespace, or
 628:         // (b) it's an attribute with no prefix
 629:         String    namespace;
 630:         
 631:         if (localName.length () != 0)
 632:         namespace = (uri.length () == 0) ? null : uri;
 633:         else
 634:         namespace = getNamespace (getPrefix (qName), atts);
 635: 
 636:         if (namespace == null)
 637:         element = document.createElement (qName);
 638:         else
 639:         element = document.createElementNS (namespace, qName);
 640: 
 641:         populateAttributes (element, atts);
 642:         if (recreatedAttrs) {
 643:         recreatedAttrs = false;
 644:         // ... DOM deletes any duplicates
 645:         populateAttributes (element, attributes);
 646:         attributes.clear ();
 647:         }
 648: 
 649:         top.appendChild (element);
 650:         top = element;
 651:     }
 652: 
 653:     final static String    xmlnsURI = "http://www.w3.org/2000/xmlns/";
 654: 
 655:     private void populateAttributes (Element element, Attributes attrs)
 656:     throws SAXParseException
 657:     {
 658:         int        length = attrs.getLength ();
 659: 
 660:         for (int i = 0; i < length; i++) {
 661:         String    type = attrs.getType (i);
 662:         String    value = attrs.getValue (i);
 663:         String    name = attrs.getQName (i);
 664:         String    local = attrs.getLocalName (i);
 665:         String    uri = attrs.getURI (i);
 666: 
 667:         // parser discarded basic information, DOM tree isn't writable
 668:         if (name.length () == 0)
 669:             name = local;
 670: 
 671:         // all attribute types other than these three may not
 672:         // contain scoped names... enumerated attributes get
 673:         // reported as NMTOKEN, except for NOTATION values
 674:         if (!("CDATA".equals (type)
 675:             || "NMTOKEN".equals (type)
 676:             || "NMTOKENS".equals (type))) {
 677:             if (value.indexOf (':') != -1) {
 678:             namespaceError (
 679:                 "namespace nonconformant attribute value: "
 680:                     + "<" + element.getNodeName ()
 681:                     + " " + name + "='" + value + "' ...>");
 682:             }
 683:         }
 684: 
 685:         // xmlns="" is legal (undoes default NS)
 686:         // xmlns:foo="" is illegal
 687:         String prefix = getPrefix (name);
 688:         String namespace;
 689: 
 690:         if ("xmlns".equals (prefix)) {
 691:             if ("".equals (value))
 692:             namespaceError ("illegal null namespace decl, " + name);
 693:             namespace = xmlnsURI;
 694:         } else if ("xmlns".equals (name))
 695:             namespace = xmlnsURI;
 696: 
 697:         else if (prefix == null)
 698:             namespace = null;
 699:         else if (!"".equals(uri) && uri.length () != 0)
 700:             namespace = uri;
 701:         else
 702:             namespace = getNamespace (prefix, attrs);
 703: 
 704:         if (namespace == null)
 705:             element.setAttribute (name, value);
 706:         else
 707:             element.setAttributeNS (namespace, name, value);
 708:         }
 709:     }
 710: 
 711:     private String getPrefix (String name)
 712:     {
 713:         int        temp;
 714: 
 715:         if ((temp = name.indexOf (':')) > 0)
 716:         return name.substring (0, temp);
 717:         return null;
 718:     }
 719: 
 720:     // used with SAX1-level parser output 
 721:     private String getNamespace (String prefix, Attributes attrs)
 722:     throws SAXParseException
 723:     {
 724:         String namespace;
 725:         String decl;
 726: 
 727:         // defaulting 
 728:         if (prefix == null) {
 729:         decl = "xmlns";
 730:         namespace = attrs.getValue (decl);
 731:         if ("".equals (namespace))
 732:             return null;
 733:         else if (namespace != null)
 734:             return namespace;
 735: 
 736:         // "xmlns" is like a keyword
 737:         // ... according to the Namespace REC, but DOM L2 CR2+
 738:         // and Infoset violate that by assigning a namespace.
 739:         // that conflict is resolved elsewhere.
 740:         } else if ("xmlns".equals (prefix))
 741:         return null;
 742: 
 743:         // "xml" prefix is fixed
 744:         else if ("xml".equals (prefix))
 745:         return "http://www.w3.org/XML/1998/namespace";
 746: 
 747:         // otherwise, expect a declaration
 748:         else {
 749:         decl = "xmlns:" + prefix;
 750:         namespace = attrs.getValue (decl);
 751:         }
 752:         
 753:         // if we found a local declaration, great
 754:         if (namespace != null)
 755:         return namespace;
 756: 
 757: 
 758:         // ELSE ... search up the tree we've been building
 759:         for (Node n = top;
 760:             n != null && n.getNodeType () != Node.DOCUMENT_NODE;
 761:             n = (Node) n.getParentNode ()) {
 762:         if (n.getNodeType () == Node.ENTITY_REFERENCE_NODE)
 763:             continue;
 764:         Element e = (Element) n;
 765:         Attr attr = e.getAttributeNode (decl);
 766:         if (attr != null)
 767:             return attr.getNodeValue ();
 768:         }
 769:         // see above re "xmlns" as keyword
 770:         if ("xmlns".equals (decl))
 771:         return null;
 772: 
 773:         namespaceError ("Undeclared namespace prefix: " + prefix);
 774:         return null;
 775:     }
 776: 
 777:     // SAX2
 778:     public void endElement (String uri, String localName, String qName)
 779:     throws SAXException
 780:     {
 781:         // we can't create populated entity ref nodes using
 782:         // only public DOM APIs (they've got to be readonly)
 783:         if (currentEntity != null)
 784:         return;
 785: 
 786:         top = top.getParentNode ();
 787:     }
 788: 
 789:     // SAX1 (mandatory reporting if validating)
 790:     public void ignorableWhitespace (char ch [], int start, int length)
 791:     throws SAXException
 792:     {
 793:         if (consumer.isHidingWhitespace ())
 794:         return;
 795:         characters (ch, start, length);
 796:     }
 797: 
 798:     // SAX2 lexical event
 799:     public void startCDATA ()
 800:     throws SAXException
 801:     {
 802:         inCDATA = true;
 803:         // true except for the first fragment of a cdata section
 804:         mergeCDATA = false;
 805:     }
 806:     
 807:     // SAX2 lexical event
 808:     public void endCDATA ()
 809:     throws SAXException
 810:     {
 811:         inCDATA = false;
 812:     }
 813:     
 814:     // SAX2 lexical event
 815:     //
 816:     // this SAX2 callback merges two unrelated things:
 817:     //    - Declaration of the root element type ... belongs with
 818:     //    the other DTD declaration methods, NOT HERE.
 819:     //    - IDs for the optional external subset ... belongs here
 820:     //    with other lexical information.
 821:     //
 822:     // ...and it doesn't include the internal DTD subset, desired
 823:     // both to support DOM L2 and to enable "pass through" processing
 824:     //
 825:     public void startDTD (String name, String publicId, String SystemId)
 826:     throws SAXException
 827:     {
 828:         // need to filter out comments and PIs within the DTD
 829:         inDTD = true;
 830:     }
 831:     
 832:     // SAX2 lexical event
 833:     public void endDTD ()
 834:     throws SAXException
 835:     {
 836:         inDTD = false;
 837:     }
 838:     
 839:     // SAX2 lexical event
 840:     public void comment (char ch [], int start, int length)
 841:     throws SAXException
 842:     {
 843:         Node    comment;
 844: 
 845:         // we can't create populated entity ref nodes using
 846:         // only public DOM APIs (they've got to be readonly)
 847:         if (consumer.isHidingComments ()
 848:             || inDTD
 849:             || currentEntity != null)
 850:         return;
 851:         comment = document.createComment (new String (ch, start, length));
 852:         top.appendChild (comment);
 853:     }
 854: 
 855:     /**
 856:      * May be overridden by subclasses to return true, indicating
 857:      * that entity reference nodes can be populated and then made
 858:      * read-only.
 859:      */
 860:     public boolean canPopulateEntityRefs ()
 861:         { return false; }
 862: 
 863:     // SAX2 lexical event
 864:     public void startEntity (String name)
 865:     throws SAXException
 866:     {
 867:         // are we ignoring what would be contents of an
 868:         // entity ref, since we can't populate it?
 869:         if (currentEntity != null)
 870:         return;
 871: 
 872:         // Are we hiding all entity boundaries?
 873:         if (consumer.isHidingReferences ())
 874:         return;
 875: 
 876:         // SAX2 shows parameter entities; DOM hides them
 877:         if (name.charAt (0) == '%' || "[dtd]".equals (name))
 878:         return;
 879: 
 880:         // Since we can't create a populated entity ref node in any
 881:         // standard way, we create an unpopulated one.
 882:         EntityReference ref = document.createEntityReference (name);
 883:         top.appendChild (ref);
 884:         top = ref;
 885: 
 886:         // ... allowing subclasses to populate them
 887:         if (!canPopulateEntityRefs ())
 888:         currentEntity = name;
 889:     }
 890: 
 891:     // SAX2 lexical event
 892:     public void endEntity (String name)
 893:     throws SAXException
 894:     {
 895:         if (name.charAt (0) == '%' || "[dtd]".equals (name))
 896:         return;
 897:         if (name.equals (currentEntity))
 898:         currentEntity = null;
 899:         if (!consumer.isHidingReferences ())
 900:         top = top.getParentNode ();
 901:     }
 902: 
 903: 
 904:     // SAX1 DTD event
 905:     public void notationDecl (
 906:         String name,
 907:         String publicId, String SystemId
 908:     ) throws SAXException
 909:     {
 910:         /* IGNORE -- no public DOM API lets us store these
 911:          * into the doctype node
 912:          */
 913:     }
 914: 
 915:     // SAX1 DTD event
 916:     public void unparsedEntityDecl (
 917:         String name,
 918:         String publicId, String SystemId,
 919:         String notationName
 920:     ) throws SAXException
 921:     {
 922:         /* IGNORE -- no public DOM API lets us store these
 923:          * into the doctype node
 924:          */
 925:     }
 926: 
 927:     // SAX2 declaration event
 928:     public void elementDecl (String name, String model)
 929:     throws SAXException
 930:     {
 931:         /* IGNORE -- no content model support in DOM L2 */
 932:     }
 933: 
 934:     // SAX2 declaration event
 935:     public void attributeDecl (
 936:         String eName,
 937:         String aName,
 938:         String type,
 939:         String mode,
 940:         String value
 941:     ) throws SAXException
 942:     {
 943:         /* IGNORE -- no attribute model support in DOM L2 */
 944:     }
 945: 
 946:     // SAX2 declaration event
 947:     public void internalEntityDecl (String name, String value)
 948:     throws SAXException
 949:     {
 950:         /* IGNORE -- no public DOM API lets us store these
 951:          * into the doctype node
 952:          */
 953:     }
 954: 
 955:     // SAX2 declaration event
 956:     public void externalEntityDecl (
 957:         String name,
 958:         String publicId,
 959:         String SystemId
 960:     ) throws SAXException
 961:     {
 962:         /* IGNORE -- no public DOM API lets us store these
 963:          * into the doctype node
 964:          */
 965:     }
 966: 
 967:     //
 968:     // These really should offer the option of nonfatal handling,
 969:     // like other validity errors, though that would cause major
 970:     // chaos in the DOM data structures.  DOM is already spec'd
 971:     // to treat many of these as fatal, so this is consistent.
 972:     //
 973:     private void namespaceError (String description)
 974:     throws SAXParseException
 975:     {
 976:         SAXParseException err;
 977:         
 978:         err = new SAXParseException (description, locator);
 979:         throw err;
 980:     }
 981:     }
 982: }