]> err.no Git - sope/blob - sope-xml/DOM/DOMElement.m
fixed an uninit var on Cocoa
[sope] / sope-xml / DOM / DOMElement.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include <DOM/DOMElement.h>
23 #include <DOM/DOMNamedNodeMap.h>
24 #include <DOM/DOMAttribute.h>
25 #include <DOM/DOMDocument.h>
26 #include <DOM/DOMNodeWalker.h>
27 #include "common.h"
28
29 @interface _DOMElementAttrNamedNodeMap : NSObject < DOMNamedNodeMap >
30 {
31   NGDOMElement *element; /* non-retained */
32 }
33
34 - (id)initWithElement:(id)_element;
35
36 - (id)objectEnumerator;
37
38 - (void)invalidate;
39
40 @end /* _DOMElementAttrNamedNodeMap */
41
42 @interface NGDOMElement(Privates)
43 - (unsigned)_numberOfAttributes;
44 - (id)_attributeNodeAtIndex:(unsigned)_idx;
45 - (id)attributeNode:(NSString *)_localName;
46 - (id)attributeNode:(NSString *)_localName namespaceURI:(NSString *)_ns;
47 @end
48
49 static NSNull *null = nil;
50
51 @implementation NGDOMElement
52
53 - (id)initWithTagName:(NSString *)_tagName namespaceURI:(NSString *)_uri {
54   if (null == nil)
55     null = [[NSNull null] retain];
56   
57   if ((self = [super init])) {
58     self->tagName      = [_tagName copy];
59     self->namespaceURI = [_uri     copy];
60   }
61   return self;
62 }
63 - (id)initWithTagName:(NSString *)_tagName {
64   return [self initWithTagName:_tagName namespaceURI:nil];
65 }
66
67 - (void)dealloc {
68   [self->attributes makeObjectsPerformSelector:
69                       @selector(_domNodeForgetParentNode:)
70                     withObject:self];
71
72   [self->attrNodeMap invalidate];
73   [self->attrNodeMap    release];
74   [self->keyToAttribute release];
75   [self->attributes     release];
76   [self->tagName        release];
77   [self->namespaceURI   release];
78   [self->prefix         release];
79   [super dealloc];
80 }
81
82 /* attributes */
83
84 - (NSString *)tagName {
85   return self->tagName;
86 }
87
88 - (void)setPrefix:(NSString *)_prefix {
89   id old = self->prefix;
90   self->prefix = [_prefix copy];
91   [old release];
92 }
93 - (NSString *)prefix {
94   return self->prefix;
95 }
96
97 - (NSString *)namespaceURI {
98   return self->namespaceURI;
99 }
100
101 - (void)setLine:(unsigned)_line {
102   self->line = _line;
103 }
104 - (unsigned)line {
105   return self->line;
106 }
107
108 /* lookup */
109
110 - (void)_walk_getElementsByTagName:(id)_walker {
111   id node;
112   
113   node = [_walker currentNode];
114   if ([node nodeType] != DOM_ELEMENT_NODE)
115     return;
116
117   if (![[node tagName] isEqualToString:
118           [(NSArray *)[_walker context] objectAtIndex:0]])
119     /* tagname doesn't match */
120     return;
121   
122   [[(NSArray *)[_walker context] objectAtIndex:1] addObject:node];
123 }
124 - (void)_walk_getElementsByTagNameAddAll:(id)_walker {
125   id node;
126   
127   node = [_walker currentNode];
128   if ([node nodeType] != DOM_ELEMENT_NODE)
129     return;
130   
131   [(NSMutableArray *)[_walker context] addObject:node];
132 }
133 - (id<NSObject,DOMNodeList>)getElementsByTagName:(NSString *)_tagName {
134   /* introduced in DOM2, should return a *live* list ! */
135   NGDOMNodePreorderWalker *walker;
136   NSMutableArray *array;
137   SEL sel;
138   id  ctx;
139   
140   if (![self hasChildNodes])
141     return nil;
142
143   if (_tagName == nil)
144     return nil;
145
146   array = [NSMutableArray arrayWithCapacity:4];
147   
148   if ([_tagName isEqualToString:@"*"]) {
149     _tagName = nil;
150     ctx = array;
151     sel = @selector(_walk_getElementsByTagNameAddAll:);
152   }
153   else {
154     ctx = [NSArray arrayWithObjects:_tagName, array, nil];
155     sel = @selector(_walk_getElementsByTagName:);
156   }
157   
158   walker = [[NGDOMNodePreorderWalker alloc]
159              initWithTarget:self selector:sel context:ctx];
160   
161   [walker walkNode:self];
162
163   [walker release]; walker = nil;
164   return [[array copy] autorelease];
165 }
166 - (id<NSObject,DOMNodeList>)getElementsByTagName:(NSString *)_tagName
167   namespaceURI:(NSString *)_uri
168 {
169   // TODO: implement
170   [self doesNotRecognizeSelector:_cmd];
171   return nil;
172 }
173
174 /* element attributes */
175
176 - (void)_ensureAttrs {
177   if (self->attributes == nil)
178     self->attributes = [[NSMutableArray alloc] init];
179   if (self->keyToAttribute == nil)
180     self->keyToAttribute = [[NSMutableDictionary alloc] init];
181 }
182
183 - (void)_attributeSetChanged {
184 }
185
186 - (unsigned)_numberOfAttributes {
187   return [self->attributes count];
188 }
189 - (id)_attributeNodeAtIndex:(unsigned)_idx {
190   if (_idx >= [self->attributes count])
191     return nil;
192   return [self->attributes objectAtIndex:_idx];
193 }
194
195 - (id)_keyForAttribute:(id<DOMAttr>)_attrNode {
196   return [_attrNode name];
197 }
198 - (id)_nskeyForLocalName:(NSString *)attrName namespaceURI:(NSString *)nsURI {
199   id key;
200   
201   if (attrName == nil)
202     return nil;
203   
204   if (nsURI) {
205     id objs[2];
206
207     objs[0] = attrName;
208     objs[1] = nsURI;
209     key = [NSArray arrayWithObjects:objs count:2];
210   }
211   else
212     key = attrName;
213   
214   return key;
215 }
216 - (id)_nskeyForAttribute:(id<DOMAttr>)_attrNode {
217   NSString *attrName;
218   
219   if ((attrName = [_attrNode name]) == nil) {
220     NSLog(@"WARNING: attribute %@ has no valid attribute name !", _attrNode);
221     return nil;
222   }
223   
224   return [self _nskeyForLocalName:attrName
225                namespaceURI:[_attrNode namespaceURI]];
226 }
227
228 - (BOOL)hasAttribute:(NSString *)_attrName {
229   return [self hasAttribute:_attrName namespaceURI:[self namespaceURI]];
230 }
231
232 - (void)setAttribute:(NSString *)_attrName value:(NSString *)_value {
233   [self setAttribute:_attrName namespaceURI:[self namespaceURI] value:_value];
234
235 #if 0 // ms: ??
236   id node;
237   
238   NSAssert1(_attrName, @"invalid attribute name '%@'", _attrName);
239
240   if ((node = [self->keyToAttribute objectForKey:_attrName]) == nil) {
241     /* create new node */
242     node = [[self ownerDocument] createAttribute:_attrName];
243   }
244   NSAssert(node, @"couldn't find/create node for attribute");
245
246   node = [self setAttributeNode:node];
247   
248   [node setValue:_value];
249 #endif
250 }
251 - (id)attributeNode:(NSString *)_attrName {
252   return [self attributeNode:_attrName namespaceURI:[self namespaceURI]];
253 }
254 - (NSString *)attribute:(NSString *)_attrName {
255   return [[self attributeNode:_attrName] value];
256 }
257
258 - (BOOL)hasAttribute:(NSString *)_localName namespaceURI:(NSString *)_ns {
259   id objs[2];
260   id key;
261
262   if ([_ns isEqualToString:@"*"]) {
263     /* match any namespace */
264     NSEnumerator *e;
265     id attr;
266     
267     if ((attr = [self->keyToAttribute objectForKey:_localName]))
268       return YES;
269     
270     e = [self->keyToAttribute keyEnumerator];
271     while ((key = [e nextObject])) {
272       if ([key isKindOfClass:[NSArray class]]) {
273         if ([[key objectAtIndex:0] isEqualToString:_localName])
274           return YES;
275       }
276     }
277     return NO;
278   }
279   
280   objs[0] = _localName;
281   objs[1] = _ns ? _ns : (id)null;
282   key = [NSArray arrayWithObjects:objs count:2];
283   
284   return [self->keyToAttribute objectForKey:key] ? YES : NO;
285 }
286
287 - (void)setAttribute:(NSString *)_localName namespaceURI:(NSString *)_ns
288   value:(NSString *)_value
289 {
290   id key;
291   id node;
292   
293   key = [self _nskeyForLocalName:_localName namespaceURI:_ns];
294   NSAssert2(key, @"invalid (ns-)attribute name localName='%@', uri='%@'",
295             _localName, _ns);
296   
297   if ((node = [self->keyToAttribute objectForKey:key]) == nil) {
298     /* create new node */
299     node = [[self ownerDocument] createAttribute:_localName namespaceURI:_ns];
300   }
301   NSAssert(node, @"couldn't find/create node for attribute");
302
303   node = [self setAttributeNodeNS:node];
304   
305   [node setValue:_value];
306 }
307 - (id)attributeNode:(NSString *)_localName namespaceURI:(NSString *)_ns {
308   id objs[2];
309   id key;
310   
311   if ([_ns isEqualToString:@"*"]) {
312     /* match any namespace */
313     NSEnumerator *e;
314     id attr;
315     
316     if ((attr = [self->keyToAttribute objectForKey:_localName]))
317       return attr;
318     
319     e = [self->keyToAttribute keyEnumerator];
320     while ((key = [e nextObject])) {
321       if ([key isKindOfClass:[NSArray class]]) {
322         if ([[key objectAtIndex:0] isEqualToString:_localName])
323           return [self->keyToAttribute objectForKey:key];
324       }
325     }
326     return nil;
327   }
328   
329   objs[0] = _localName;
330   objs[1] = _ns ? _ns : (id)null;
331   key = [NSArray arrayWithObjects:objs count:2];
332
333   return [self->keyToAttribute objectForKey:key];
334 }
335 - (NSString *)attribute:(NSString *)_localName namespaceURI:(NSString *)_ns {
336   return [[self attributeNode:_localName namespaceURI:_ns] value];
337 }
338
339 - (id<NSObject, DOMAttr>)setAttributeNodeNS:(id<NSObject, DOMAttr>)_attrNode {
340   id key, oldNode;
341   
342   if (_attrNode == nil)
343     /* invalid node parameters */
344     return nil;
345   
346   if ((key = [self _nskeyForAttribute:_attrNode]) == nil)
347     /* couldn't get key */
348     return nil;
349   
350   [self _ensureAttrs];
351   
352   /* check if the key is already added */
353   
354   if ((oldNode = [self->keyToAttribute objectForKey:key])) {
355     if (oldNode == _attrNode) {
356       /* already contained */
357       // NSLog(@"node is already set !");
358       return _attrNode;
359     }
360     
361     /* replace existing node */
362     [self->attributes replaceObjectAtIndex:
363                         [self->attributes indexOfObject:oldNode]
364                       withObject:_attrNode];
365     [self->keyToAttribute setObject:_attrNode forKey:key];
366     
367     [(id)_attrNode _domNodeRegisterParentNode:self];
368     [self _attributeSetChanged];
369
370     return _attrNode;
371   }
372   else {
373     /* add node */
374
375     NSAssert(self->keyToAttribute, @"missing keyToAttribute");
376     NSAssert(self->attributes,     @"missing attrs");
377     
378     [self->keyToAttribute setObject:_attrNode forKey:key];
379     [self->attributes     addObject:_attrNode];
380     
381     [(id)_attrNode _domNodeRegisterParentNode:self];
382     [self _attributeSetChanged];
383
384     // NSLog(@"added attr %@, elem %@", _attrNode, self);
385     
386     return _attrNode;
387   }
388 }
389
390 - (void)removeAttribute:(NSString *)_attr namespaceURI:(NSString *)_uri {
391   id node;
392   id key;
393   
394   key = [self _nskeyForLocalName:_attr namespaceURI:_uri];
395   NSAssert2(key, @"invalid (ns-)attribute name '%@', '%@'", _attr, _uri);
396
397   node = [self->keyToAttribute objectForKey:key];
398   
399   [self removeAttributeNodeNS:node];
400 }
401 - (id<NSObject,DOMAttr>)removeAttributeNodeNS:(id<NSObject,DOMAttr>)_attrNode {
402   id key, oldNode;
403   
404   if (_attrNode == nil)
405     /* invalid node parameters */
406     return nil;
407   
408   if (self->attributes == nil)
409     /* no attributes are set up */
410     return nil;
411   
412   if ((key = [self _nskeyForAttribute:_attrNode]) == nil)
413     /* couldn't get key for node */
414     return nil;
415
416   if ((oldNode = [self->keyToAttribute objectForKey:key])) {
417     /* the node's key exists */
418     if (oldNode != _attrNode) {
419       /* the node has the same key, but isn't the same */
420       return nil;
421     }
422
423     /* ok, found the node, let's remove ! */
424     [[_attrNode retain] autorelease];
425     [self->keyToAttribute removeObjectForKey:key];
426     [self->attributes removeObjectIdenticalTo:_attrNode];
427     
428     [(id)_attrNode _domNodeForgetParentNode:self];
429     [self _attributeSetChanged];
430     
431     return _attrNode;
432   }
433   else
434     /* no such attribute is stored */
435     return nil;
436 }
437
438 - (id<NSObject,DOMAttr>)setAttributeNode:(id<NSObject,DOMAttr>)_attrNode {
439   [self doesNotRecognizeSelector:_cmd];
440   return nil;
441 }
442 - (id<NSObject,DOMAttr>)removeAttributeNode:(id<NSObject,DOMAttr>)_attrNode {
443   [self doesNotRecognizeSelector:_cmd];
444   return nil;
445 }
446 - (void)removeAttribute:(NSString *)_attr {
447   id node;
448   
449   NSAssert1(_attr, @"invalid attribute name '%@'", _attr);
450
451   node = [self->keyToAttribute objectForKey:_attr];
452   
453   [self removeAttributeNode:node];
454 }
455
456 /* node */
457
458 - (BOOL)_isValidChildNode:(id)_node {
459   switch ([_node nodeType]) {
460     case DOM_ELEMENT_NODE:
461     case DOM_TEXT_NODE:
462     case DOM_COMMENT_NODE:
463     case DOM_PROCESSING_INSTRUCTION_NODE:
464     case DOM_CDATA_SECTION_NODE:
465     case DOM_ENTITY_REFERENCE_NODE:
466       return YES;
467       
468     default:
469       return NO;
470   }
471 }
472
473 - (DOMNodeType)nodeType {
474   return DOM_ELEMENT_NODE;
475 }
476
477 - (id<NSObject,DOMNamedNodeMap>)attributes {
478   /* returns a named-node-map */
479   if (self->attrNodeMap == nil) {
480     self->attrNodeMap =
481       [[_DOMElementAttrNamedNodeMap alloc] initWithElement:self];
482   }
483   return self->attrNodeMap;
484 }
485
486 /* parent node */
487
488 - (void)_domNodeRegisterParentNode:(id)_parent {
489   self->parent = _parent;
490 }
491 - (void)_domNodeForgetParentNode:(id)_parent {
492   if (_parent == self->parent)
493     /* the node's parent was deallocated */
494     self->parent = nil;
495 }
496 - (id<NSObject,DOMNode>)parentNode {
497   return self->parent;
498 }
499
500 /* description */
501
502 - (NSString *)description {
503   return [NSString stringWithFormat:
504                      @"<0x%08X[%@]: name=%@ parent=%@ #attrs=%i #children=%i>",
505                      self, NSStringFromClass([self class]),
506                      [self nodeName],
507                      [[self parentNode] nodeName],
508                      [self _numberOfAttributes],
509                      [self hasChildNodes] ? [[self childNodes] length] : 0];
510 }
511
512 /* QPValues */
513
514 - (NSException *)setQueryPathValue:(id)_value {
515   return [NSException exceptionWithName:@"QueryPathEvalException"
516                       reason:@"cannot set query-path value on DOMElement !"
517                       userInfo:nil];
518 }
519 - (id)queryPathValue {
520   return [self childNodes];
521 }
522
523 @end /* NGDOMElement */
524
525 @implementation _DOMElementAttrNamedNodeMap
526
527 - (id)initWithElement:(id)_element {
528   self->element = _element;
529   return self;
530 }
531
532 - (void)invalidate {
533   self->element = nil;
534 }
535
536 static inline void _checkValid(_DOMElementAttrNamedNodeMap *self) {
537   if (self->element == nil) {
538     NSCAssert(self->element,
539               @"named node map is invalid (element was deallocated) !");
540   }
541 }
542
543 /* access */
544
545 static NSString *_XNSUri(NSString *_name) {
546   NSRange r1;
547
548   if (![_name hasPrefix:@"{"])
549     return nil;
550   
551   r1 = [_name rangeOfString:@"}"];
552   if (r1.length == 0)
553     return nil;
554   
555   r1.length   = (r1.location - 2);
556   r1.location = 1;
557   return [_name substringWithRange:r1];
558 }
559 static NSString *_XNSLocalName(NSString *_name) {
560   NSRange r;
561   
562   r = [_name rangeOfString:@"}"];
563   return r.length == 0
564     ? _name
565     : [_name substringFromIndex:(r.location + r.length)];
566 }
567
568 - (unsigned)length {
569   _checkValid(self);
570   return [self->element _numberOfAttributes];
571 }
572 - (id)objectAtIndex:(unsigned)_idx {
573   _checkValid(self);
574   return [self->element _attributeNodeAtIndex:_idx];
575 }
576
577 - (IDOMNode)namedItem:(NSString *)_name {
578   NSString *nsuri;
579   _checkValid(self);
580   
581   if ((nsuri = _XNSUri(_name)))
582     return [self namedItem:_XNSLocalName(_name) namespaceURI:nsuri];
583   
584   return [self->element attributeNode:_name];
585 }
586 - (IDOMNode)setNamedItem:(IDOMNode)_node {
587   _checkValid(self);
588
589   // TODO: is the cast correct?
590   return [self->element setAttributeNode:(id<NSObject,DOMAttr>)_node];
591 }
592 - (IDOMNode)removeNamedItem:(NSString *)_name {
593   NSString *nsuri;
594   id node;
595   
596   _checkValid(self);
597   if ((nsuri = _XNSUri(_name)))
598     return [self removeNamedItem:_XNSLocalName(_name) namespaceURI:nsuri];
599   
600   if ((node = [self->element attributeNode:_name])) {
601     node = [node retain];
602     [self->element removeAttribute:_name];
603     return [node autorelease];
604   }
605   else
606     return nil;
607 }
608
609 /* DOM2 access */
610
611 - (IDOMNode)namedItem:(NSString *)_name namespaceURI:(NSString *)_uri {
612   return [self->element attributeNode:_name namespaceURI:_uri];
613 }
614 - (IDOMNode)setNamedItemNS:(IDOMNode)_node {
615   _checkValid(self);
616   // TODO: is the cast correct?
617   return [self->element setAttributeNodeNS:(id<NSObject,DOMAttr>)_node];
618 }
619 - (IDOMNode)removeNamedItem:(NSString *)_name namespaceURI:(NSString *)_uri {
620   id node;
621
622   _checkValid(self);
623   if ((node = [self->element attributeNode:_name namespaceURI:_uri])) {
624     node = [node retain];
625     [self->element removeAttribute:_name namespaceURI:_uri];
626     return [node autorelease];
627   }
628   else
629     return nil;
630 }
631
632 /* mimic NSArray */
633
634 - (unsigned)count {
635   _checkValid(self);
636   return [self->element _numberOfAttributes];
637 }
638
639 - (id)objectEnumerator {
640   NSMutableArray *ma;
641   unsigned i, count;
642
643   _checkValid(self);
644   if ((count = [self->element _numberOfAttributes]) == 0)
645     return nil;
646
647   ma = [NSMutableArray arrayWithCapacity:count];
648   
649   for (i = 0; i < count; i++)
650     [ma addObject:[self->element _attributeNodeAtIndex:i]];
651   
652   return [ma objectEnumerator];
653 }
654
655 /* mimic NSDictionary */
656
657 - (void)setObject:(id)_value forKey:(id)_key {
658   _checkValid(self);
659   [self takeValue:_value forKey:[_key stringValue]];
660 }
661 - (id)objectForKey:(id)_key {
662   _checkValid(self);
663   return [self valueForKey:[_key stringValue]];
664 }
665
666 /* KVC */
667
668 - (void)takeValue:(id)_value forKey:(NSString *)_key {
669   id node;
670   _checkValid(self);
671   
672   if ((node = [self->element attributeNode:_key namespaceURI:@"*"])) {
673     [node setValue:[_value stringValue]];
674   }
675   else {
676     [self->element setAttribute:_key namespaceURI:@"xhtml"
677                    value:[_value stringValue]];
678   }
679 }
680 - (id)valueForKey:(NSString *)_key {
681   id v;
682   _checkValid(self);
683   
684   if ((v = [self namedItem:_key]))
685     return [v value];
686   if ((v = [self namedItem:_key namespaceURI:@"*"]))
687     return [v value];
688   
689   return nil;
690 }
691
692 /* JSSupport */
693
694 - (id)_jsprop_length {
695   return [NSNumber numberWithInt:[self length]];
696 }
697
698 - (id)_jsfunc_item:(NSArray *)_args {
699   unsigned count;
700   
701   if ((count = [_args count]) == 0) return nil;
702   return [self objectAtIndex:[[_args objectAtIndex:0] intValue]];
703 }
704
705 - (id)_jsfunc_getNamedItem:(NSArray *)_args {
706   unsigned count;
707   
708   if ((count = [_args count]) == 0) return nil;
709   return [self namedItem:[[_args objectAtIndex:0] stringValue]];
710 }
711 - (id)_jsfunc_getNamedItemNS:(NSArray *)_args {
712   unsigned count;
713   
714   if ((count = [_args count]) == 0) return nil;
715   if (count == 1)
716     return [self namedItem:[[_args objectAtIndex:0] stringValue]];
717   else {
718     return [self namedItem:[[_args objectAtIndex:1] stringValue]
719                  namespaceURI:[[_args objectAtIndex:0] stringValue]];
720   }
721 }
722
723 - (id)_jsfunc_setNamedItem:(NSArray *)_args {
724   unsigned i, count;
725   id last = nil;
726
727   for (i = 0, count = [_args count]; i < count; i++)
728     last = [self setNamedItem:[_args objectAtIndex:i]];
729   return last;
730 }
731 - (id)_jsfunc_setNamedItemNS:(NSArray *)_args {
732   unsigned i, count;
733   id last = nil;
734
735   for (i = 0, count = [_args count]; i < count; i++)
736     last = [self setNamedItemNS:[_args objectAtIndex:i]];
737   return last;
738 }
739
740 - (id)_jsfunc_removeNamedItem:(NSArray *)_args {
741   unsigned count;
742   
743   if ((count = [_args count]) == 0) return nil;
744   return [self namedItem:[[_args objectAtIndex:0] stringValue]];
745 }
746 - (id)_jsfunc_removeNamedItemNS:(NSArray *)_args {
747   unsigned count;
748   
749   if ((count = [_args count]) == 0) return nil;
750   if (count == 1)
751     return [self removeNamedItem:[[_args objectAtIndex:0] stringValue]];
752   else {
753     return [self removeNamedItem:[[_args objectAtIndex:1] stringValue]
754                  namespaceURI:[[_args objectAtIndex:0] stringValue]];
755   }
756 }
757
758 /* description */
759
760 - (NSString *)description {
761   NSMutableString *ms;
762   NSEnumerator *e;
763   id attr;
764   
765   ms = [NSMutableString stringWithCapacity:1024];
766   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
767   [ms appendFormat:@" element=%@", self->element];
768   
769   [ms appendString:@" attributes:\n"];
770   e = [self objectEnumerator];
771   while ((attr = [e nextObject])) {
772     [ms appendString:[attr description]];
773     [ms appendString:@"\n"];
774   }
775   
776   [ms appendString:@">"];
777   return ms;
778 }
779
780 @end /* _DOMElementAttrNamedNodeMap */