Source for gnu.xml.dom.ls.SAXEventSink

   1: /* SAXEventSink.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.dom.ls;
  39: 
  40: import java.util.HashSet;
  41: import java.util.Iterator;
  42: import java.util.LinkedList;
  43: import java.util.List;
  44: import javax.xml.XMLConstants;
  45: import org.w3c.dom.Attr;
  46: import org.w3c.dom.Document;
  47: import org.w3c.dom.DocumentType;
  48: import org.w3c.dom.Element;
  49: import org.w3c.dom.Entity;
  50: import org.w3c.dom.EntityReference;
  51: import org.w3c.dom.NamedNodeMap;
  52: import org.w3c.dom.Node;
  53: import org.w3c.dom.Text;
  54: import org.xml.sax.Attributes;
  55: import org.xml.sax.ContentHandler;
  56: import org.xml.sax.DTDHandler;
  57: import org.xml.sax.Locator;
  58: import org.xml.sax.SAXException;
  59: import org.xml.sax.SAXNotRecognizedException;
  60: import org.xml.sax.SAXNotSupportedException;
  61: import org.xml.sax.XMLReader;
  62: import org.xml.sax.ext.Attributes2;
  63: import org.xml.sax.ext.DeclHandler;
  64: import org.xml.sax.ext.LexicalHandler;
  65: import org.xml.sax.ext.Locator2;
  66: import gnu.xml.dom.DomAttr;
  67: import gnu.xml.dom.DomDocument;
  68: import gnu.xml.dom.DomDoctype;
  69: import gnu.xml.dom.DomNode;
  70: 
  71: /**
  72:  * A SAX content and lexical handler used to construct a DOM document.
  73:  *
  74:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  75:  */
  76: public class SAXEventSink
  77:   implements ContentHandler, LexicalHandler, DTDHandler, DeclHandler
  78: {
  79: 
  80:   private static final String XMLNS_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
  81:   private static final String XMLNS_PREFIX = XMLConstants.XMLNS_ATTRIBUTE;
  82:   private static final HashSet PREDEFINED_ENTITIES = new HashSet();
  83:   static
  84:   {
  85:     PREDEFINED_ENTITIES.add("amp");
  86:     PREDEFINED_ENTITIES.add("lt");
  87:     PREDEFINED_ENTITIES.add("gt");
  88:     PREDEFINED_ENTITIES.add("quot");
  89:     PREDEFINED_ENTITIES.add("apos");
  90:   }
  91: 
  92:   boolean namespaceAware;
  93:   boolean ignoreWhitespace;
  94:   boolean expandEntityReferences;
  95:   boolean ignoreComments;
  96:   boolean coalescing;
  97: 
  98:   XMLReader reader; // reference back to the parser to get features
  99:   
 100:   DomDocument doc; // document being constructed
 101:   Node ctx; // current context (parent node)
 102:   LinkedList entityCtx; // entity context
 103:   List pending; // namespace nodes waiting for a declaring element
 104:   Locator locator;
 105:   boolean inCDATA;
 106:   boolean inDTD;
 107:   boolean interrupted;
 108: 
 109:   void interrupt()
 110:   {
 111:     interrupted = true;
 112:   }
 113: 
 114:   public Document getDocument()
 115:   {
 116:     return doc;
 117:   }
 118: 
 119:   public void setReader(XMLReader reader)
 120:   {
 121:     this.reader = reader;
 122:   }
 123: 
 124:   // -- ContentHandler2 --
 125:   
 126:   public void setDocumentLocator(Locator locator)
 127:   {
 128:     this.locator = locator;
 129:   }
 130: 
 131:   public void startDocument()
 132:     throws SAXException
 133:   {
 134:     if (namespaceAware)
 135:       {
 136:         pending = new LinkedList();
 137:       }
 138:     doc = new DomDocument();
 139:     doc.setStrictErrorChecking(false);
 140:     doc.setBuilding(true);
 141:     doc.setDefaultAttributes(false);
 142:     ctx = doc;
 143: 
 144:     final String FEATURES = "http://xml.org/sax/features/";
 145:     final String PROPERTIES = "http://xml.org/sax/properties/";
 146:     final String GNU_PROPERTIES = "http://gnu.org/sax/properties/";
 147: 
 148:     if (reader != null)
 149:       {
 150:         boolean standalone = reader.getFeature(FEATURES + "is-standalone");
 151:         doc.setXmlStandalone(standalone);
 152:         try
 153:           {
 154:             String version = (String) reader.getProperty(PROPERTIES +
 155:                     "document-xml-version");
 156:             doc.setXmlVersion(version);
 157:           }
 158:         catch (SAXNotRecognizedException e)
 159:           {
 160:           }
 161:         catch (SAXNotSupportedException e)
 162:           {
 163:           }
 164:         try
 165:           {
 166:               String encoding = (String) reader.getProperty(GNU_PROPERTIES +
 167:                       "document-xml-encoding");
 168:               doc.setXmlEncoding(encoding);
 169:           }
 170:         catch (SAXNotRecognizedException e)
 171:           {
 172:           }
 173:         catch (SAXNotSupportedException e)
 174:           {
 175:           }
 176:       }
 177:     if (locator != null && locator instanceof Locator2)
 178:       {
 179:         String encoding = ((Locator2) locator).getEncoding();
 180:         doc.setInputEncoding(encoding);
 181:       }
 182:   }
 183: 
 184:   public void endDocument()
 185:     throws SAXException
 186:   {
 187:     doc.setStrictErrorChecking(true);
 188:     doc.setBuilding(false);
 189:     doc.setDefaultAttributes(true);
 190:     DomDoctype doctype = (DomDoctype) doc.getDoctype();
 191:     if (doctype != null)
 192:       {
 193:         doctype.makeReadonly();
 194:       }
 195:     ctx = null;
 196:     locator = null;
 197:   }
 198: 
 199:   public void startPrefixMapping(String prefix, String uri)
 200:     throws SAXException
 201:   {
 202:     if (namespaceAware)
 203:       {
 204:         String nsName = (prefix != null && prefix.length() > 0) ?
 205:           XMLNS_PREFIX + ":" + prefix : XMLNS_PREFIX;
 206:         DomAttr ns = (DomAttr) doc.createAttributeNS(XMLNS_URI, nsName);
 207:         ns.setNodeValue(uri);
 208:         if (ctx.getNodeType() == Node.ATTRIBUTE_NODE)
 209:           {
 210:             // Add to owner element
 211:             Node target = ((Attr) ctx).getOwnerElement();
 212:             target.getAttributes().setNamedItemNS(ns);
 213:           }
 214:         else
 215:           {
 216:             // Add to pending list; namespace node will be inserted when
 217:             // element is seen
 218:             pending.add(ns);
 219:           }
 220:       }
 221:   }
 222: 
 223:   public void endPrefixMapping(String prefix)
 224:     throws SAXException
 225:   {
 226:   }
 227: 
 228:   public void startElement(String uri, String localName, String qName,
 229:                            Attributes atts)
 230:     throws SAXException
 231:   {
 232:     if (interrupted)
 233:       {
 234:         return;
 235:       }
 236:     Element element = createElement(uri, localName, qName, atts);
 237:     // add element to context
 238:     ctx.appendChild(element);
 239:     ctx = element;
 240:   }
 241: 
 242:   protected Element createElement(String uri, String localName, String qName,
 243:                                   Attributes atts)
 244:     throws SAXException
 245:   {
 246:     // create element node
 247:     Element element = namespaceAware ?
 248:       doc.createElementNS(uri, qName) :
 249:       doc.createElement(qName);
 250:     NamedNodeMap attrs = element.getAttributes();
 251:     if (namespaceAware && !pending.isEmpty())
 252:       {
 253:         // add pending namespace nodes
 254:         for (Iterator i = pending.iterator(); i.hasNext(); )
 255:           {
 256:             Node ns = (Node) i.next();
 257:             attrs.setNamedItemNS(ns);
 258:           }
 259:         pending.clear();
 260:       }
 261:     // add attributes
 262:     int len = atts.getLength();
 263:     for (int i = 0; i < len; i++)
 264:       {
 265:         // create attribute
 266:         Attr attr = createAttr(atts, i);
 267:         if (attr != null)
 268:           {
 269:             // add attribute to element
 270:             if (namespaceAware)
 271:               {
 272:                 attrs.setNamedItemNS(attr);
 273:               }
 274:             else
 275:               {
 276:                 attrs.setNamedItem(attr);
 277:               }
 278:           }
 279:       }
 280:     return element;
 281:   }
 282: 
 283:   protected Attr createAttr(Attributes atts, int index)
 284:   {
 285:     DomAttr attr;
 286:     if (namespaceAware)
 287:       {
 288:         String a_uri = atts.getURI(index);
 289:         String a_qName = atts.getQName(index);
 290:         attr = (DomAttr) doc.createAttributeNS(a_uri, a_qName);
 291:       }
 292:     else
 293:       {
 294:         String a_qName = atts.getQName(index);
 295:         attr = (DomAttr) doc.createAttribute(a_qName);
 296:       }
 297:     attr.setNodeValue(atts.getValue(index));
 298:     if (atts instanceof Attributes2)
 299:       {
 300:         Attributes2 atts2 = (Attributes2) atts;
 301:         // TODO attr.setDeclared(atts2.isDeclared(index));
 302:         attr.setSpecified(atts2.isSpecified(index));
 303:       }
 304:     return attr;
 305:   }
 306: 
 307:   public void endElement(String uri, String localName, String qName)
 308:     throws SAXException
 309:   {
 310:     if (interrupted)
 311:       {
 312:         return;
 313:       }
 314:     if (namespaceAware)
 315:       {
 316:         pending.clear();
 317:       }
 318:     ctx = ctx.getParentNode();
 319:   }
 320: 
 321:   public void characters(char[] c, int off, int len)
 322:     throws SAXException
 323:   {
 324:     if (interrupted || len < 1)
 325:       {
 326:         return;
 327:       }
 328:     ctx.appendChild(createText(c, off, len));
 329:   }
 330: 
 331:   protected Text createText(char[] c, int off, int len)
 332:     throws SAXException
 333:   {
 334:     Text text = (inCDATA && !coalescing) ?
 335:       doc.createCDATASection(new String(c, off, len)) :
 336:       doc.createTextNode(new String(c, off, len));
 337:     return text;
 338:   }
 339: 
 340:   public void ignorableWhitespace(char[] c, int off, int len)
 341:     throws SAXException
 342:   {
 343:     if (interrupted)
 344:       {
 345:         return;
 346:       }
 347:     if (!ignoreWhitespace)
 348:       {
 349:         characters(c, off, len);
 350:       }
 351:   }
 352: 
 353:   public void processingInstruction(String target, String data)
 354:     throws SAXException
 355:   {
 356:     if (interrupted)
 357:       {
 358:         return;
 359:       }
 360:     Node pi = createProcessingInstruction(target, data);
 361:     ctx.appendChild(pi);
 362:   }
 363: 
 364:   protected Node createProcessingInstruction(String target, String data)
 365:   {
 366:     return doc.createProcessingInstruction(target, data);
 367:   }
 368: 
 369:   public void skippedEntity(String name)
 370:     throws SAXException
 371:   {
 372:     // This callback is totally pointless
 373:   }
 374: 
 375:   // -- LexicalHandler --
 376:   
 377:   public void startDTD(String name, String publicId, String systemId)
 378:     throws SAXException
 379:   {
 380:     if (interrupted)
 381:       {
 382:         return;
 383:       }
 384:     Node doctype = createDocumentType(name, publicId, systemId);
 385:     doc.appendChild(doctype);
 386:     ctx = doctype;
 387:     inDTD = true;
 388:   }
 389: 
 390:   protected Node createDocumentType(String name, String publicId,
 391:                                     String systemId)
 392:   {
 393:     return new DomDoctype(doc, name, publicId, systemId);
 394:   }
 395: 
 396:   public void endDTD()
 397:     throws SAXException
 398:   {
 399:     if (interrupted)
 400:       {
 401:         return;
 402:       }
 403:     inDTD = false;
 404:     ctx = ctx.getParentNode();
 405:   }
 406: 
 407:   public void startEntity(String name)
 408:     throws SAXException
 409:   {
 410:     if (interrupted)
 411:       return;
 412:     DocumentType doctype = doc.getDoctype();
 413:     if (doctype == null)
 414:       {
 415:         throw new SAXException("SAX parser error: " +
 416:                                "reference to entity in undeclared doctype");
 417:       }
 418:     if ("[dtd]".equals(name) || name.charAt(0) == '%')
 419:       return;
 420:     if (PREDEFINED_ENTITIES.contains(name))
 421:       return;
 422:     // Get entity
 423:     NamedNodeMap entities = doctype.getEntities();
 424:     Entity entity = (Entity) entities.getNamedItem(name);
 425:     if (entity == null)
 426:       {
 427:         throw new SAXException("SAX parser error: " +
 428:                                "reference to undeclared entity: " + name);
 429:       }
 430:     EntityReference ref = doc.createEntityReference(name);
 431:     // DomDocument populates with the entity replacement text, remove this
 432:     Node child = ref.getFirstChild();
 433:     while (child != null)
 434:       {
 435:         Node nextChild = child.getNextSibling();
 436:         ref.removeChild(child);
 437:         child = nextChild;
 438:       }
 439:     ctx.appendChild(ref);
 440:     ctx = ref;
 441:   }
 442: 
 443:   public void endEntity(String name)
 444:     throws SAXException
 445:   {
 446:     if (interrupted)
 447:       return;
 448:     if ("[dtd]".equals(name) || name.charAt(0) == '%')
 449:       return;
 450:     if (PREDEFINED_ENTITIES.contains(name))
 451:       return;
 452:     // Get entity reference
 453:     EntityReference ref = (EntityReference) ctx;
 454:     if (!ref.getNodeName().equals(name))
 455:       throw new SAXException("expecting end of "+ref.getNodeName()+" entity");
 456:     ctx = ctx.getParentNode();
 457:     if (ref instanceof DomNode)
 458:       ((DomNode) ref).makeReadonly();
 459:     if (expandEntityReferences)
 460:       {
 461:         // Move entity content from reference node onto context
 462:         Node child = ref.getFirstChild();
 463:         while (child != null)
 464:           {
 465:             Node nextChild = child.getNextSibling();
 466:             ctx.appendChild(child);
 467:             child = nextChild;
 468:           }
 469:         ctx.removeChild(ref);
 470:       }
 471:   }
 472: 
 473:   public void startCDATA()
 474:     throws SAXException
 475:   {
 476:     inCDATA = true;
 477:   }
 478: 
 479:   public void endCDATA()
 480:     throws SAXException
 481:   {
 482:     inCDATA = false;
 483:   }
 484: 
 485:   public void comment(char[] c, int off, int len)
 486:     throws SAXException
 487:   {
 488:     if (interrupted)
 489:       {
 490:         return;
 491:       }
 492:     Node comment = createComment(c, off, len);
 493:     ctx.appendChild(comment);
 494:   }
 495: 
 496:   protected Node createComment(char[] c, int off, int len)
 497:   {
 498:     return doc.createComment(new String(c, off, len));
 499:   }
 500: 
 501:   // -- DTDHandler --
 502: 
 503:   public void notationDecl(String name, String publicId, String systemId)
 504:     throws SAXException
 505:   {
 506:     if (interrupted)
 507:       {
 508:         return;
 509:       }
 510:     if (!inDTD)
 511:       throw new SAXException("notation decl outside DTD");
 512:     DomDoctype doctype = (DomDoctype) ctx;
 513:     doctype.declareNotation(name, publicId, systemId);
 514:   }
 515: 
 516:   public void unparsedEntityDecl(String name, String publicId, String systemId,
 517:                                  String notationName)
 518:     throws SAXException
 519:   {
 520:     if (interrupted)
 521:       {
 522:         return;
 523:       }
 524:     if (!inDTD)
 525:       throw new SAXException("unparsed entity decl outside DTD");
 526:     DomDoctype doctype = (DomDoctype) ctx;
 527:     Entity entity = doctype.declareEntity(name, publicId, systemId,
 528:                                           notationName);
 529:   }
 530: 
 531:   // -- DeclHandler --
 532:   
 533:   public void elementDecl(String name, String model)
 534:     throws SAXException
 535:   {
 536:     if (interrupted)
 537:       {
 538:         return;
 539:       }
 540:     if (!inDTD)
 541:       throw new SAXException("element decl outside DTD");
 542:     // Ignore fake element declarations generated by ValidationConsumer.
 543:     // If an element is not really declared in the DTD it will not be
 544:     // declared in the document model.
 545:     if (!(ctx instanceof DomDoctype))
 546:       {
 547:         return;
 548:       }
 549:     DomDoctype doctype = (DomDoctype) ctx;
 550:     doctype.elementDecl(name, model);
 551:   }
 552: 
 553:   public void attributeDecl(String eName, String aName, String type,
 554:                             String mode, String value)
 555:     throws SAXException
 556:   {
 557:     if (interrupted)
 558:       {
 559:         return;
 560:       }
 561:     if (!inDTD)
 562:       throw new SAXException("attribute decl outside DTD");
 563:     DomDoctype doctype = (DomDoctype) ctx;
 564:     doctype.attributeDecl(eName, aName, type, mode, value);
 565:   }
 566: 
 567:   public void internalEntityDecl(String name, String value)
 568:     throws SAXException
 569:   {
 570:     if (interrupted)
 571:       {
 572:         return;
 573:       }
 574:     if (!inDTD)
 575:       throw new SAXException("internal entity decl outside DTD");
 576:     DomDoctype doctype = (DomDoctype) ctx;
 577:     Entity entity = doctype.declareEntity(name, null, null, null);
 578:     if (entity != null)
 579:       {
 580:         Node text = doc.createTextNode(value);
 581:         entity.appendChild(text);
 582:       }
 583:   }
 584: 
 585:   public void externalEntityDecl(String name, String publicId, String systemId)
 586:     throws SAXException
 587:   {
 588:     if (interrupted)
 589:       {
 590:         return;
 591:       }
 592:     if (!inDTD)
 593:       throw new SAXException("external entity decl outside DTD");
 594:     DomDoctype doctype = (DomDoctype) ctx;
 595:     Entity entity = doctype.declareEntity(name, publicId, systemId, null);
 596:   }
 597:   
 598: }