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