Source for gnu.xml.dom.DomAttr

   1: /* DomAttr.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 gnu.java.lang.CPStringBuilder;
  41: 
  42: import org.w3c.dom.Attr;
  43: import org.w3c.dom.DOMException;
  44: import org.w3c.dom.Element;
  45: import org.w3c.dom.Node;
  46: import org.w3c.dom.TypeInfo;
  47: import org.w3c.dom.events.MutationEvent;
  48: 
  49: 
  50: /**
  51:  * <p> "Attr" implementation.  In DOM, attributes cost quite a lot of
  52:  * memory because their values are complex structures rather than just
  53:  * simple strings.  To reduce your costs, avoid having more than one
  54:  * child of an attribute; stick to a single Text node child, and ignore
  55:  * even that by using the attribute's "nodeValue" property.</p>
  56:  *
  57:  * <p> As a bit of general advice, only look at attribute modification
  58:  * events through the DOMAttrModified event (sent to the associated
  59:  * element).  Implementations are not guaranteed to report other events
  60:  * in the same order, so you're very likely to write nonportable code if
  61:  * you monitor events at the "children of Attr" level.</p>
  62:  *
  63:  * <p> At this writing, not all attribute modifications will cause the
  64:  * DOMAttrModified event to be triggered ... only the ones using the string
  65:  * methods (setNodeValue, setValue, and Element.setAttribute) to modify
  66:  * those values.  That is, if you manipulate those children directly,
  67:  * elements won't get notified that attribute values have changed.
  68:  * The natural fix for that will report other modifications, but won't
  69:  * be able to expose "previous" attribute value; it'll need to be cached
  70:  * or something (at which point why bother using child nodes). </p>
  71:  *
  72:  * <p><em>You are strongly advised not to use "children" of any attribute
  73:  * nodes you work with.</em> </p>
  74:  *
  75:  * @author David Brownell
  76:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  77:  */
  78: public class DomAttr
  79:   extends DomNsNode
  80:   implements Attr
  81: {
  82: 
  83:   private boolean specified;
  84:   private String value; // string value cache
  85: 
  86:   /**
  87:    * Constructs an Attr node associated with the specified document.
  88:    * The "specified" flag is initialized to true, since this DOM has
  89:    * no current "back door" mechanisms to manage default values so
  90:    * that every value must effectively be "specified".
  91:    *
  92:    * <p>This constructor should only be invoked by a Document as part of
  93:    * its createAttribute functionality, or through a subclass which is
  94:    * similarly used in a "Sub-DOM" style layer.
  95:    *
  96:    * @param owner The document with which this node is associated
  97:    * @param namespaceURI Combined with the local part of the name,
  98:    *    this is used to uniquely identify a type of attribute
  99:    * @param name Name of this attribute, which may include a prefix
 100:    */
 101:   protected DomAttr(DomDocument owner, String namespaceURI, String name)
 102:   {
 103:     super(ATTRIBUTE_NODE, owner, namespaceURI, name);
 104:     specified = true;
 105:     length = 1;
 106: 
 107:     // XXX register self to get insertion/removal events
 108:     // and character data change events and when they happen,
 109:     // report self-mutation
 110:   }
 111: 
 112:   /**
 113:    * Constructs an Attr node associated with the specified document.
 114:    * The "specified" flag is initialized to true, since this DOM has
 115:    * no current "back door" mechanisms to manage default values so
 116:    * that every value must effectively be "specified".
 117:    *
 118:    * <p>This constructor should only be invoked by a Document as part of
 119:    * its createAttribute functionality, or through a subclass which is
 120:    * similarly used in a "Sub-DOM" style layer.
 121:    * <p>
 122:    * With this constructor, the prefix and local part are given explicitly
 123:    * rather than being computed.  This allows them to be explicitly set to
 124:    * {@code null} as required by {@link Document#createAttribute(String)}.
 125:    * </p>
 126:    *
 127:    * @param owner The document with which this node is associated
 128:    * @param namespaceURI Combined with the local part of the name,
 129:    *    this is used to uniquely identify a type of attribute
 130:    * @param name Name of this attribute, which may include a prefix
 131:    * @param prefix the namespace prefix of the name.  May be {@code null}.
 132:    * @param localName the local part of the name.  May be {@code null}.
 133:    */
 134:   protected DomAttr(DomDocument owner, String namespaceURI, String name,
 135:                     String prefix, String localName)
 136:   {
 137:     super(ATTRIBUTE_NODE, owner, namespaceURI, name, prefix, localName);
 138:     specified = true;
 139:     length = 1;
 140:   }
 141: 
 142:   /**
 143:    * <b>DOM L1</b>
 144:    * Returns the attribute name (same as getNodeName)
 145:    */
 146:   public final String getName()
 147:   {
 148:     return getNodeName();
 149:   }
 150: 
 151:   /**
 152:    * <b>DOM L1</b>
 153:    * Returns true if a parser reported this was in the source text.
 154:    */
 155:   public final boolean getSpecified()
 156:   {
 157:     return specified;
 158:   }
 159: 
 160:   /**
 161:    * Records whether this attribute was in the source text.
 162:    */
 163:   public final void setSpecified(boolean value)
 164:   {
 165:     specified = value;
 166:   }
 167: 
 168:   /**
 169:    * <b>DOM L1</b>
 170:    * Returns the attribute value, with character and entity
 171:    * references substituted.
 172:    * <em>NOTE:  entity refs as children aren't currently handled.</em>
 173:    */
 174:   public String getNodeValue()
 175:   {
 176:     // If we have a simple node-value, use that
 177:     if (first == null)
 178:       {
 179:         return (value == null) ? "" : value;
 180:       }
 181:     // Otherwise collect child node-values
 182:     CPStringBuilder buf = new CPStringBuilder();
 183:     for (DomNode ctx = first; ctx != null; ctx = ctx.next)
 184:       {
 185:         switch (ctx.nodeType)
 186:           {
 187:           case Node.TEXT_NODE:
 188:             buf.append(ctx.getNodeValue());
 189:             break;
 190:           case Node.ENTITY_REFERENCE_NODE:
 191:             // TODO
 192:             break;
 193:           }
 194:       }
 195:     return buf.toString();
 196:   }
 197: 
 198:   /**
 199:    * <b>DOM L1</b>
 200:    * Assigns the value of the attribute; it will have one child,
 201:    * which is a text node with the specified value (same as
 202:    * setNodeValue).
 203:    */
 204:   public final void setValue(String value)
 205:   {
 206:     setNodeValue(value);
 207:   }
 208: 
 209:   /**
 210:    * <b>DOM L1</b>
 211:    * Returns the value of the attribute as a non-null string; same
 212:    * as getNodeValue.
 213:    * <em>NOTE:  entity refs as children aren't currently handled.</em>
 214:    */
 215:   public final String getValue()
 216:   {
 217:     return getNodeValue();
 218:   }
 219: 
 220:   /**
 221:    * <b>DOM L1</b>
 222:    * Assigns the attribute value; using this API, no entity or
 223:    * character references will exist.
 224:    * Causes a DOMAttrModified mutation event to be sent.
 225:    */
 226:   public void setNodeValue(String value)
 227:   {
 228:     if (readonly)
 229:       {
 230:         throw new DomDOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR);
 231:       }
 232:     if (value == null)
 233:       {
 234:         value = "";
 235:       }
 236:     String oldValue = getNodeValue();
 237:     while (last != null)
 238:       {
 239:         removeChild(last);
 240:       }
 241:     // don't create a new node just for this...
 242:     /*
 243:      Node text = owner.createTextNode(value);
 244:      appendChild(text);
 245:      */
 246:     this.value = value;
 247:     length = 1;
 248:     specified = true;
 249: 
 250:     mutating(oldValue, value, MutationEvent.MODIFICATION);
 251:   }
 252: 
 253:   public final Node getFirstChild()
 254:   {
 255:     // Create a child text node if necessary
 256:     if (first == null)
 257:       {
 258:         length = 0;
 259:         Node text = owner.createTextNode((value == null) ? "" : value);
 260:         appendChild(text);
 261:       }
 262:     return first;
 263:   }
 264: 
 265:   public final Node getLastChild()
 266:   {
 267:     // Create a child text node if necessary
 268:     if (last == null)
 269:       {
 270:         length = 0;
 271:         Node text = owner.createTextNode((value == null) ? "" : value);
 272:         appendChild(text);
 273:       }
 274:     return last;
 275:   }
 276: 
 277:   public Node item(int index)
 278:   {
 279:     // Create a child text node if necessary
 280:     if (first == null)
 281:       {
 282:         length = 0;
 283:         Node text = owner.createTextNode((value == null) ? "" : value);
 284:         appendChild(text);
 285:       }
 286:     return super.item(index);
 287:   }
 288: 
 289:   /**
 290:    * <b>DOM L2</b>
 291:    * Returns the element with which this attribute is associated.
 292:    */
 293:   public final Element getOwnerElement()
 294:   {
 295:     return (Element) parent;
 296:   }
 297: 
 298:   public final Node getNextSibling()
 299:   {
 300:     return null;
 301:   }
 302: 
 303:   public final Node getPreviousSibling()
 304:   {
 305:     return null;
 306:   }
 307: 
 308:   public Node getParentNode()
 309:   {
 310:     return null;
 311:   }
 312: 
 313:   /**
 314:    * Records the element with which this attribute is associated.
 315:    */
 316:   public final void setOwnerElement(Element e)
 317:   {
 318:     if (parent != null)
 319:       {
 320:         throw new DomDOMException(DOMException.HIERARCHY_REQUEST_ERR);
 321:       }
 322:     if (!(e instanceof DomElement))
 323:       {
 324:         throw new DomDOMException(DOMException.WRONG_DOCUMENT_ERR);
 325:       }
 326:     parent = (DomElement) e;
 327:     depth = parent.depth + 1;
 328:   }
 329: 
 330:   /**
 331:    * The base URI of an Attr is always <code>null</code>.
 332:    */
 333:   public final String getBaseURI()
 334:   {
 335:     return null;
 336:   }
 337: 
 338:   /**
 339:    * Shallow clone of the attribute, breaking all ties with any
 340:    * elements.
 341:    */
 342:   public Object clone()
 343:   {
 344:     DomAttr retval = (DomAttr) super.clone();
 345:     retval.specified = true;
 346:     return retval;
 347:   }
 348: 
 349:   private void mutating(String oldValue, String newValue, short why)
 350:   {
 351:     if (!reportMutations || parent == null || equal(newValue, oldValue))
 352:       {
 353:         return;
 354:       }
 355: 
 356:     // EVENT:  DOMAttrModified, target = parent,
 357:     //  prev/new values provided, also attr name
 358:     MutationEvent       event;
 359: 
 360:     event = (MutationEvent) createEvent ("MutationEvents");
 361:     event.initMutationEvent ("DOMAttrModified",
 362:                              true /* bubbles */, false /* nocancel */,
 363:                              null, oldValue, newValue, getNodeName (), why);
 364:     parent.dispatchEvent (event);
 365:   }
 366: 
 367:   // DOM Level 3 methods
 368: 
 369:   public TypeInfo getSchemaTypeInfo()
 370:   {
 371:     if (parent != null)
 372:       {
 373:         // DTD implementation
 374:         DomDoctype doctype = (DomDoctype) parent.owner.getDoctype();
 375:         if (doctype != null)
 376:           {
 377:             return doctype.getAttributeTypeInfo(parent.getNodeName(),
 378:                                                 getNodeName());
 379:           }
 380:         // TODO XML Schema implementation
 381:       }
 382:     return null;
 383:   }
 384: 
 385:   public boolean isId()
 386:   {
 387:     if (parent != null)
 388:       {
 389:         DomDoctype doctype = (DomDoctype) parent.owner.getDoctype();
 390:         if (doctype != null)
 391:           {
 392:             DTDAttributeTypeInfo info =
 393:               doctype.getAttributeTypeInfo(parent.getNodeName(),
 394:                                            getNodeName());
 395:             if (info != null && "ID".equals(info.type))
 396:               {
 397:                 return true;
 398:               }
 399:           }
 400:         DomElement element = (DomElement) parent;
 401:         if (element.userIdAttrs != null &&
 402:             element.userIdAttrs.contains(this))
 403:           {
 404:             return true;
 405:           }
 406:         // TODO XML Schema implementation
 407:       }
 408:     return false;
 409:   }
 410: 
 411: }