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