Source for gnu.xml.dom.DomElement

   1: /* DomElement.java --
   2:    Copyright (C) 1999,2000,2001,2004 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;
  39: 
  40: import java.util.HashSet;
  41: import java.util.Set;
  42: import javax.xml.XMLConstants;
  43: 
  44: import org.w3c.dom.Attr;
  45: import org.w3c.dom.DOMException;
  46: import org.w3c.dom.Element;
  47: import org.w3c.dom.NamedNodeMap;
  48: import org.w3c.dom.Node;
  49: import org.w3c.dom.TypeInfo;
  50: 
  51: /**
  52:  * <p> "Element" implementation.
  53:  *
  54:  * @author David Brownell
  55:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  56:  */
  57: public class DomElement
  58:   extends DomNsNode
  59:   implements Element
  60: {
  61: 
  62:   /**
  63:    * User-defined ID attributes.
  64:    * Used by DomAttr.isId and DomDocument.getElementById
  65:    */
  66:   Set userIdAttrs;
  67: 
  68:   // Attributes are VERY expensive in DOM, and not just for
  69:   // this implementation.  Avoid creating them.
  70:   private DomNamedNodeMap attributes;
  71: 
  72:   // xml:space cache
  73:   String xmlSpace = "";
  74: 
  75:   /**
  76:    * Constructs an Element node associated with the specified document.
  77:    *
  78:    * <p>This constructor should only be invoked by a Document as part
  79:    * of its createElement functionality, or through a subclass which is
  80:    * similarly used in a "Sub-DOM" style layer.
  81:    *
  82:    * @param owner The document with which this node is associated
  83:    * @param namespaceURI Combined with the local part of the name,
  84:    *    this is used to uniquely identify a type of element
  85:    * @param name Name of this element, which may include a prefix
  86:    */
  87:   protected DomElement(DomDocument owner, String namespaceURI, String name)
  88:   {
  89:     super(ELEMENT_NODE, owner, namespaceURI, name);
  90:   }
  91: 
  92:   /**
  93:    * <p>
  94:    * Constructs an Element node associated with the specified document.
  95:    * This constructor should only be invoked by a Document as part
  96:    * of its createElement functionality, or through a subclass which is
  97:    * similarly used in a "Sub-DOM" style layer.
  98:    * </p>
  99:    * <p>
 100:    * With this constructor, the prefix and local part are given explicitly
 101:    * rather than being computed.  This allows them to be explicitly set to
 102:    * {@code null} as required by {@link Document#createElement(String)}.
 103:    * </p>
 104:    *
 105:    * @param owner The document with which this node is associated
 106:    * @param namespaceURI Combined with the local part of the name,
 107:    *    this is used to uniquely identify a type of element
 108:    * @param name Name of this element, which may include a prefix
 109:    * @param prefix the namespace prefix of the name.  May be {@code null}.
 110:    * @param localName the local part of the name.  May be {@code null}.
 111:    */
 112:   protected DomElement(DomDocument owner, String namespaceURI, String name,
 113:                        String prefix, String localName)
 114:   {
 115:     super(ELEMENT_NODE, owner, namespaceURI, name, prefix, localName);
 116:   }
 117: 
 118:   /**
 119:    * <b>DOM L1</b>
 120:    * Returns the element's attributes
 121:    */
 122:   public NamedNodeMap getAttributes()
 123:   {
 124:     if (attributes == null)
 125:       {
 126:         attributes = new DomNamedNodeMap(this, Node.ATTRIBUTE_NODE);
 127:       }
 128:     return attributes;
 129:   }
 130: 
 131:   /**
 132:    * <b>DOM L2></b>
 133:    * Returns true iff this is an element node with attributes.
 134:    */
 135:   public boolean hasAttributes()
 136:   {
 137:     return attributes != null && attributes.length != 0;
 138:   }
 139: 
 140:   /**
 141:    * Shallow clone of the element, except that associated
 142:    * attributes are (deep) cloned.
 143:    */
 144:   public Object clone()
 145:   {
 146:     DomElement node = (DomElement) super.clone();
 147: 
 148:     if (attributes != null)
 149:       {
 150:         node.attributes = new DomNamedNodeMap(node, Node.ATTRIBUTE_NODE);
 151:         for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next)
 152:           {
 153:             node.attributes.setNamedItem(ctx.cloneNode(true), true, true);
 154:           }
 155:       }
 156:     return node;
 157:   }
 158: 
 159:   void setOwner(DomDocument doc)
 160:   {
 161:     if (attributes != null)
 162:       {
 163:         for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next)
 164:           {
 165:             ctx.setOwner(doc);
 166:           }
 167:       }
 168:     super.setOwner(doc);
 169:   }
 170: 
 171:   /**
 172:    * Marks this element, its children, and its associated attributes as
 173:    * readonly.
 174:    */
 175:   public void makeReadonly()
 176:   {
 177:     super.makeReadonly();
 178:     if (attributes != null)
 179:       {
 180:         attributes.makeReadonly();
 181:       }
 182:   }
 183: 
 184:   /**
 185:    * <b>DOM L1</b>
 186:    * Returns the element name (same as getNodeName).
 187:    */
 188:   final public String getTagName()
 189:   {
 190:     return getNodeName();
 191:   }
 192: 
 193:   /**
 194:    * <b>DOM L1</b>
 195:    * Returns the value of the specified attribute, or an
 196:    * empty string.
 197:    */
 198:   public String getAttribute(String name)
 199:   {
 200:     if ("xml:space" == name) // NB only works on interned string
 201:       {
 202:         // Use cached value
 203:         return xmlSpace;
 204:       }
 205:     Attr attr = getAttributeNode(name);
 206:     return (attr == null) ? "" : attr.getValue();
 207:   }
 208: 
 209:   /**
 210:    * <b>DOM L2</b>
 211:    * Returns true if the element has an attribute with the
 212:    * specified name (specified or DTD defaulted).
 213:    */
 214:   public boolean hasAttribute(String name)
 215:   {
 216:     return getAttributeNode(name) != null;
 217:   }
 218: 
 219:   /**
 220:    * <b>DOM L2</b>
 221:    * Returns true if the element has an attribute with the
 222:    * specified name (specified or DTD defaulted).
 223:    */
 224:   public boolean hasAttributeNS(String namespaceURI, String local)
 225:   {
 226:     return getAttributeNodeNS(namespaceURI, local) != null;
 227:   }
 228: 
 229:   /**
 230:    * <b>DOM L2</b>
 231:    * Returns the value of the specified attribute, or an
 232:    * empty string.
 233:    */
 234:   public String getAttributeNS(String namespaceURI, String local)
 235:   {
 236:     Attr attr = getAttributeNodeNS(namespaceURI, local);
 237:     return (attr == null) ? "" : attr.getValue();
 238:   }
 239: 
 240:   /**
 241:    * <b>DOM L1</b>
 242:    * Returns the appropriate attribute node; the name is the
 243:    * nodeName property of the attribute.
 244:    */
 245:   public Attr getAttributeNode(String name)
 246:   {
 247:     return (attributes == null) ? null :
 248:       (Attr) attributes.getNamedItem(name);
 249:   }
 250: 
 251:   /**
 252:    * <b>DOM L2</b>
 253:    * Returns the appropriate attribute node; the name combines
 254:    * the namespace name and the local part.
 255:    */
 256:   public Attr getAttributeNodeNS(String namespace, String localPart)
 257:   {
 258:     return (attributes == null) ? null :
 259:       (Attr) attributes.getNamedItemNS(namespace, localPart);
 260:   }
 261: 
 262:   /**
 263:    * <b>DOM L1</b>
 264:    * Modifies an existing attribute to have the specified value,
 265:    * or creates a new one with that value.  The name used is the
 266:    * nodeName value.
 267:    */
 268:   public void setAttribute(String name, String value)
 269:   {
 270:     Attr attr = getAttributeNode(name);
 271:     if (attr != null)
 272:       {
 273:         attr.setNodeValue(value);
 274:         ((DomAttr) attr).setSpecified(true);
 275:         return;
 276:       }
 277:     attr = owner.createAttribute(name);
 278:     attr.setNodeValue(value);
 279:     setAttributeNode(attr);
 280:   }
 281: 
 282:   /**
 283:    * <b>DOM L2</b>
 284:    * Modifies an existing attribute to have the specified value,
 285:    * or creates a new one with that value.
 286:    */
 287:   public void setAttributeNS(String uri, String aname, String value)
 288:   {
 289:     if (("xmlns".equals (aname) || aname.startsWith ("xmlns:"))
 290:         && !XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals (uri))
 291:       {
 292:         throw new DomDOMException(DOMException.NAMESPACE_ERR,
 293:                         "setting xmlns attribute to illegal value", this, 0);
 294:       }
 295: 
 296:     Attr attr = getAttributeNodeNS(uri, aname);
 297:     if (attr != null)
 298:       {
 299:         attr.setNodeValue(value);
 300:         return;
 301:       }
 302:     attr = owner.createAttributeNS(uri, aname);
 303:     attr.setNodeValue(value);
 304:     setAttributeNodeNS(attr);
 305:   }
 306: 
 307:   /**
 308:    * <b>DOM L1</b>
 309:    * Stores the specified attribute, optionally overwriting any
 310:    * existing one with that name.
 311:    */
 312:   public Attr setAttributeNode(Attr attr)
 313:   {
 314:     return (Attr) getAttributes().setNamedItem(attr);
 315:   }
 316: 
 317:   /**
 318:    * <b>DOM L2</b>
 319:    * Stores the specified attribute, optionally overwriting any
 320:    * existing one with that name.
 321:    */
 322:   public Attr setAttributeNodeNS(Attr attr)
 323:   {
 324:     return (Attr) getAttributes().setNamedItemNS(attr);
 325:   }
 326: 
 327:   /**
 328:    * <b>DOM L1</b>
 329:    * Removes the appropriate attribute node.
 330:    * If there is no such node, this is (bizarrely enough) a NOP so you
 331:    * won't see exceptions if your code deletes non-existent attributes.
 332:    *
 333:    * <p>Note that since there is no portable way for DOM to record
 334:    * DTD information, default values for attributes will never be
 335:    * provided automatically.
 336:    */
 337:   public void removeAttribute(String name)
 338:   {
 339:     if (attributes == null)
 340:       {
 341:         return;
 342:       }
 343: 
 344:     try
 345:       {
 346:         attributes.removeNamedItem(name);
 347:       }
 348:     catch (DomDOMException e)
 349:       {
 350:         if (e.code != DOMException.NOT_FOUND_ERR)
 351:           {
 352:             throw e;
 353:           }
 354:       }
 355:   }
 356: 
 357:   /**
 358:    * <b>DOM L1</b>
 359:    * Removes the appropriate attribute node; the name is the
 360:    * nodeName property of the attribute.
 361:    *
 362:    * <p>Note that since there is no portable way for DOM to record
 363:    * DTD information, default values for attributes will never be
 364:    * provided automatically.
 365:    */
 366:   public Attr removeAttributeNode(Attr node)
 367:   {
 368:     if (attributes == null)
 369:       {
 370:         throw new DomDOMException(DOMException.NOT_FOUND_ERR, null, node, 0);
 371:       }
 372:     return (Attr) attributes.removeNamedItem(node.getNodeName());
 373:   }
 374: 
 375:   /**
 376:    * <b>DOM L2</b>
 377:    * Removes the appropriate attribute node; the name combines
 378:    * the namespace name and the local part.
 379:    *
 380:    * <p>Note that since there is no portable way for DOM to record
 381:    * DTD information, default values for attributes will never be
 382:    * provided automatically.
 383:    */
 384:   public void removeAttributeNS(String namespace, String localPart)
 385:   {
 386:     if (attributes == null)
 387:       {
 388:         throw new DomDOMException(DOMException.NOT_FOUND_ERR, localPart, null, 0);
 389:       }
 390:     attributes.removeNamedItemNS (namespace, localPart);
 391:   }
 392: 
 393:   // DOM Level 3 methods
 394: 
 395:   public String lookupPrefix(String namespaceURI)
 396:   {
 397:     if (namespaceURI == null)
 398:       {
 399:         return null;
 400:       }
 401:     String namespace = getNamespaceURI();
 402:     if (namespace != null && namespace.equals(namespaceURI))
 403:       {
 404:         return getPrefix();
 405:       }
 406:     if (attributes != null)
 407:       {
 408:         for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next)
 409:           {
 410:             if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI
 411:                 .equals(ctx.getNamespaceURI()))
 412:               {
 413:                 String value = ctx.getNodeValue();
 414:                 if (value.equals(namespaceURI))
 415:                   {
 416:                     return ctx.getLocalName();
 417:                   }
 418:               }
 419:           }
 420:       }
 421:     return super.lookupPrefix(namespaceURI);
 422:   }
 423: 
 424:   public boolean isDefaultNamespace(String namespaceURI)
 425:   {
 426:     String namespace = getNamespaceURI();
 427:     if (namespace != null && namespace.equals(namespaceURI))
 428:       {
 429:         return getPrefix() == null;
 430:       }
 431:     if (attributes != null)
 432:       {
 433:         for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next)
 434:           {
 435:             if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI
 436:                 .equals(ctx.getNamespaceURI()))
 437:               {
 438:                 String qName = ctx.getNodeName();
 439:                 return (XMLConstants.XMLNS_ATTRIBUTE.equals(qName));
 440:               }
 441:           }
 442:       }
 443:     return super.isDefaultNamespace(namespaceURI);
 444:   }
 445: 
 446:   public String lookupNamespaceURI(String prefix)
 447:   {
 448:     String namespace = getNamespaceURI();
 449:     if (namespace != null && equal(prefix, getPrefix()))
 450:       {
 451:         return namespace;
 452:       }
 453:     if (attributes != null)
 454:       {
 455:         for (DomNode ctx = attributes.first; ctx != null; ctx = ctx.next)
 456:           {
 457:             if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI
 458:                 .equals(ctx.getNamespaceURI()))
 459:               {
 460:                 if (prefix == null)
 461:                   {
 462:                     if (XMLConstants.XMLNS_ATTRIBUTE.equals(ctx.getNodeName()))
 463:                       {
 464:                         return ctx.getNodeValue();
 465:                       }
 466:                   }
 467:                 else
 468:                   {
 469:                     if (prefix.equals(ctx.getLocalName()))
 470:                       {
 471:                         return ctx.getNodeValue();
 472:                       }
 473:                   }
 474:               }
 475:           }
 476:       }
 477:     return super.lookupNamespaceURI(prefix);
 478:   }
 479: 
 480:   public String getBaseURI()
 481:   {
 482:     if (attributes != null)
 483:       {
 484:         Node xmlBase =
 485:           attributes.getNamedItemNS(XMLConstants.XML_NS_URI, "base");
 486:         if (xmlBase != null)
 487:           {
 488:             return xmlBase.getNodeValue();
 489:           }
 490:       }
 491:     return super.getBaseURI();
 492:   }
 493: 
 494:   public TypeInfo getSchemaTypeInfo()
 495:   {
 496:     // DTD implementation
 497:     DomDoctype doctype = (DomDoctype) owner.getDoctype();
 498:     if (doctype != null)
 499:       {
 500:         return doctype.getElementTypeInfo(getNodeName());
 501:       }
 502:     // TODO XML Schema implementation
 503:     return null;
 504:   }
 505: 
 506:   public void setIdAttribute(String name, boolean isId)
 507:   {
 508:     NamedNodeMap attrs = getAttributes();
 509:     Attr attr = (Attr) attrs.getNamedItem(name);
 510:     setIdAttributeNode(attr, isId);
 511:   }
 512: 
 513:   public void setIdAttributeNode(Attr attr, boolean isId)
 514:   {
 515:     if (readonly)
 516:       {
 517:         throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR);
 518:       }
 519:     if (attr == null || attr.getOwnerElement() != this)
 520:       {
 521:         throw new DomDOMException(DOMException.NOT_FOUND_ERR);
 522:       }
 523:     if (isId)
 524:       {
 525:         if (userIdAttrs == null)
 526:           {
 527:             userIdAttrs = new HashSet();
 528:           }
 529:         userIdAttrs.add(attr);
 530:       }
 531:     else if (userIdAttrs != null)
 532:       {
 533:         userIdAttrs.remove(attr);
 534:         if (userIdAttrs.isEmpty())
 535:           {
 536:             userIdAttrs = null;
 537:           }
 538:       }
 539:   }
 540: 
 541:   public void setIdAttributeNS(String namespaceURI, String localName,
 542:                                boolean isId)
 543:   {
 544:     NamedNodeMap attrs = getAttributes();
 545:     Attr attr = (Attr) attrs.getNamedItemNS(namespaceURI, localName);
 546:     setIdAttributeNode(attr, isId);
 547:   }
 548: 
 549:   public boolean isEqualNode(Node arg)
 550:   {
 551:     if (!super.isEqualNode(arg))
 552:       return false;
 553:     getAttributes();
 554:     NamedNodeMap argAttrs = arg.getAttributes();
 555:     int len = argAttrs.getLength();
 556:     if (argAttrs == null || (len != attributes.length))
 557:       return false;
 558:     for (int i = 0; i < len; i++)
 559:       {
 560:         Node argCtx = argAttrs.item(i);
 561:         // Don't compare namespace nodes
 562:         if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI
 563:             .equals(argCtx.getNamespaceURI()))
 564:           continue;
 565:         // Find corresponding attribute node
 566:         DomNode ctx = attributes.first;
 567:         for (; ctx != null; ctx = ctx.next)
 568:           {
 569:             if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI
 570:                 .equals(ctx.getNamespaceURI()))
 571:               continue;
 572:             if (!ctx.isEqualNode(argCtx))
 573:               continue;
 574:             break;
 575:           }
 576:         if (ctx == null)
 577:           return false; // not found
 578:       }
 579:     return true;
 580:   }
 581: 
 582: }