]> err.no Git - sope/blob - sope-appserver/NGObjWeb/Templates/WOxElemBuilder.m
fixed some NGMail framework build issue
[sope] / sope-appserver / NGObjWeb / Templates / WOxElemBuilder.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 <NGObjWeb/WOxElemBuilder.h>
23 #include <DOM/EDOM.h>
24 #include <SaxObjC/XMLNamespaces.h>
25 #include <NGObjWeb/WOApplication.h>
26 #include <NGObjWeb/WOElement.h>
27 #include <NGObjWeb/WOAssociation.h>
28 #include <NGObjWeb/WOComponentScript.h>
29 #include <NGObjWeb/WODynamicElement.h>
30 #include "WOComponentFault.h"
31 #include "common.h"
32
33 @interface WOElement(UsedPrivates)
34 - (id)initWithValue:(id)_value escapeHTML:(BOOL)_flag;
35 + (id)allocForCount:(int)_count zone:(NSZone *)_zone;
36 - (id)initWithContentElements:(NSArray *)_elements;
37 @end
38
39 @interface WOAssociation(misc)
40 - (id)initWithScript:(NSString *)_script language:(NSString *)_lang;
41 @end
42
43 @implementation WOxElemBuilderComponentInfo
44
45 - (id)initWithComponentId:(NSString *)_cid
46   componentName:(NSString *)_name
47   bindings:(NSMutableDictionary *)_bindings
48 {
49   self->cid      = [_cid copy];
50   self->pageName = [_name copy];
51   self->bindings = [_bindings retain];
52   return self;
53 }
54 - (void)dealloc {
55   [self->cid      release];
56   [self->pageName release];
57   [self->bindings release];
58   [super dealloc];
59 }
60
61 /* accessors */
62
63 - (NSString *)componentId {
64   return self->cid;
65 }
66
67 - (NSString *)pageName {
68   return self->pageName;
69 }
70
71 - (NSMutableDictionary *)bindings {
72   return self->bindings;
73 }
74
75 /* operations */
76
77 - (id)instantiateWithResourceManager:(WOResourceManager *)_rm
78   languages:(NSArray *)_languages
79 {
80   static Class FaultClass = Nil;
81   WOComponentFault *fault;
82   
83   if (FaultClass == Nil)
84     FaultClass = [WOComponentFault class];
85   
86   fault = [FaultClass alloc];
87   NSAssert1(fault, @"couldn't allocated object of class '%@' ..", FaultClass);
88   
89   fault = [fault initWithResourceManager:_rm
90                  pageName:self->pageName
91                  languages:_languages
92                  bindings:self->bindings];
93   return (id)fault;
94 }
95
96 @end /* SxElementBuilderComponentInfo */
97
98 @implementation WOxElemBuilder
99
100 static Class         StrClass          = Nil;
101 static Class         AStrClass         = Nil;
102 static NSDictionary  *defaultAssocMap  = nil;
103 static Class         ValAssoc          = Nil;
104 static BOOL          logAssocMap       = NO;
105 static BOOL          logAssocCreation  = NO;
106 static BOOL          debugOn           = NO;
107 static NGLogger      *logger           = nil;
108 static Class         CompoundElemClass = Nil;
109 static NSNumber      *yesNum   = nil;
110 static WOAssociation *yesAssoc = nil;
111
112 + (int)version {
113   return 1;
114 }
115 + (void)initialize {
116   NSUserDefaults  *ud;
117   NGLoggerManager *lm;
118   static BOOL didInit = NO;
119
120   if (didInit) return;
121   didInit = YES;
122
123   ud = [NSUserDefaults standardUserDefaults];
124   lm = [NGLoggerManager defaultLoggerManager];
125
126   logger = [lm loggerForClass:self];
127   [logger setLogLevel:[WOApplication isDebuggingEnabled] ? NGLogLevelDebug
128                                                          : NGLogLevelInfo];
129
130   StrClass = NSClassFromString(@"_WOSimpleStaticString");
131   if (StrClass == Nil)
132     [logger errorWithFormat:@"missing class _WOSimpleStaticString !"];
133   AStrClass = NSClassFromString(@"_WOSimpleStaticASCIIString");
134   if (AStrClass == Nil)
135     [logger errorWithFormat:@"missing class _WOSimpleStaticASCIIString !"];
136
137   logAssocMap = [ud boolForKey:@"WOxElemBuilder_LogAssociationMapping"];
138   logAssocCreation = 
139     [ud boolForKey:@"WOxElemBuilder_LogAssociationCreation"];
140   if (logAssocMap)
141     [logger logWithFormat:@"association mapping is logged!"];
142   if (logAssocCreation)
143     [logger logWithFormat:@"association creation is logged!"];
144
145   // TODO: improve extensibility of this (remember WOWrapperTemplateBuilder)
146   defaultAssocMap = [[ud dictionaryForKey:@"WOxAssociationClassMapping"] copy];
147   if (defaultAssocMap == nil)
148     [logger warnWithFormat:
149       @"WOxAssociationClassMapping default is not set!"];
150   
151   if (ValAssoc == Nil)
152     ValAssoc = NSClassFromString(@"WOValueAssociation");
153   
154   CompoundElemClass = NSClassFromString(@"WOCompoundElement");
155
156   if (yesNum   == nil) 
157     yesNum = [[NSNumber numberWithBool:YES] retain];
158   if (yesAssoc == nil)
159     yesAssoc = [[WOAssociation associationWithValue:yesNum] retain];
160 }
161
162 + (WOxElemBuilder *)createBuilderQueue:(NSArray *)_classNames {
163   unsigned           i, count;
164   WOxElemBuilder *first, *current = nil;
165   NSMutableArray *missingBuilders = nil;
166   
167   if ((count = [_classNames count]) == 0)
168     return nil;
169   
170   for (first = nil, i = 0; i < count; i++) {
171     WOxElemBuilder *nx;
172     NSString *cn;
173     Class    clazz;
174     
175     cn = [_classNames objectAtIndex:i];
176 #if 0
177     NSLog(@"builder class: %@", cn);
178 #endif
179     
180     if ((clazz = NSClassFromString(cn)) == Nil) {
181       if (missingBuilders == nil) 
182         missingBuilders = [NSMutableArray arrayWithCapacity:16];
183       [missingBuilders addObject:cn];
184       continue;
185     }
186     
187     if ((nx = [[clazz alloc] init])) {
188       if (first == nil) {
189         first = current = nx;
190         [nx autorelease];
191       }
192       else {
193         [current setNextBuilder:nx];
194         current = [nx autorelease];
195       }
196     }
197     else {
198       NSLog(@"%s: couldn't allocate builder (class=%@)", cn);
199       continue;
200     }
201   }
202   
203   if (missingBuilders) {
204     NSLog(@"WOxElemBuilder: could not locate builders: %@", 
205           [missingBuilders componentsJoinedByString:@","]);
206   }
207   return first;
208 }
209
210 + (WOxElemBuilder *)createBuilderQueueV:(NSString *)_className, ... {
211   // TODO: reimplement using createBuilderQueue:
212   va_list       ap;
213   NSString      *cn;
214   WOxElemBuilder *first, *current;
215   
216   if (_className == nil)
217     return [[[self alloc] init] autorelease];
218     
219   first = [[[NSClassFromString(_className) alloc] init] autorelease];
220     
221   va_start(ap, _className);
222   for (current = first; (cn = va_arg(ap, id)); ) {
223     WOxElemBuilder *nx;
224
225     nx = [[NSClassFromString(cn) alloc] init];
226     [current setNextBuilder:nx];
227     current = [nx autorelease];
228   }
229   va_end(ap);
230     
231   return first;
232 }
233
234 - (void)dealloc {
235   [self->script            release];
236   [self->subcomponentInfos release];
237   [self->nsToAssoc         release];
238   [self->nextBuilder       release];
239   [super dealloc];
240 }
241
242 /* building an element (returns a retained object !!!) */
243
244 - (WOElement *)buildNode:(id<DOMNode>)_node templateBuilder:(id)_builder {
245   if (_node == nil)
246     return nil;
247
248   switch ([_node nodeType]) {
249     case DOM_ELEMENT_NODE:
250       return [self buildElement:(id<DOMElement>)_node
251                    templateBuilder:_builder];
252     case DOM_TEXT_NODE:
253       return [self buildText:(id<DOMText>)_node
254                    templateBuilder:_builder];
255     case DOM_CDATA_SECTION_NODE:
256       return [self buildCDATASection:(id<DOMCDATASection>)_node
257                    templateBuilder:_builder];
258     case DOM_COMMENT_NODE:
259       return [self buildComment:(id<DOMComment>)_node
260                    templateBuilder:_builder];
261     case DOM_DOCUMENT_NODE:
262       return [self buildDocument:(id<DOMDocument>)_node
263                    templateBuilder:_builder];
264       
265     default:
266       if (self->nextBuilder)
267         return [self->nextBuilder buildNode:_node templateBuilder:_builder];
268       else {
269         NSLog(@"unknown node type %i, node %@", [_node nodeType], _node);
270         return nil;
271       }
272   }
273 }
274
275 - (NSArray *)buildNodes:(id<DOMNodeList>)_nodes templateBuilder:(id)_bld {
276   // Note: returns a regular autoreleased array
277   NSMutableArray *children;
278   unsigned       i, count;
279   
280   if ((count = [_nodes length]) == 0)
281     return nil;
282   
283   children = [NSMutableArray arrayWithCapacity:(count + 1)];
284   
285   for (i = 0; i < count; i++) {
286     WOElement *e;
287
288     e = [_bld buildNode:[_nodes objectAtIndex:i] templateBuilder:_bld];
289     if (e) {
290       [children addObject:e];
291       [e release];
292     }
293   }
294   return children;
295 }
296
297 /* building methods specialized on type (return retained objects !!!) */
298
299 - (WOElement *)buildDocument:(id<DOMDocument>)_node templateBuilder:(id)_bld {
300   return [self buildElement:[_node documentElement] templateBuilder:_bld];
301 }
302
303 - (WOElement *)buildElement:(id<DOMElement>)_node templateBuilder:(id)_bld {
304   if (self->nextBuilder)
305     return [self->nextBuilder buildElement:_node templateBuilder:_bld];
306
307   [self logWithFormat:@"cannot build node %@ (template builder %@)",
308           _node, _bld];
309   return nil;
310 }
311
312 - (WOElement *)buildCharacterData:(id<DOMCharacterData>)_text
313   templateBuilder:(id)_builder
314 {
315   static Class ValClass = Nil;
316   WOElement *textElement;
317   unsigned len;
318   BOOL     isASCII = NO;
319   id       str;
320   
321   str = [_text data];
322   if ((len = [str length]) == 0) return nil;
323   
324   /* 
325      we use WOValueAssociation directly, because WOAssociation caches all
326      values
327   */
328   if (ValClass == Nil)
329     ValClass = NSClassFromString(@"WOValueAssociation");
330
331 #if 0
332 #  warning not using ASCII string !
333   isASCII = NO;
334 #else
335   if (len > 1) {
336     // TODO(perf): improve on that
337     /* not very efficient, but only used during template parsing ... */
338     if ([str dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:NO])
339       isASCII = YES;
340     else
341       isASCII = NO;
342   }
343   else {
344     isASCII = ([str characterAtIndex:0] < 128) ? YES : NO;
345   }
346 #endif
347   
348   str = [[ValClass alloc] initWithString:str];
349   textElement = 
350     [[(isASCII?AStrClass:StrClass) alloc] initWithValue:str escapeHTML:YES];
351   [str release];
352   return textElement;
353 }
354 - (WOElement *)buildText:(id<DOMText>)_node
355   templateBuilder:(id)_builder
356 {
357   return [self buildCharacterData:_node templateBuilder:_builder];
358 }
359 - (WOElement *)buildCDATASection:(id<DOMCDATASection>)_node
360   templateBuilder:(id)_builder
361 {
362   return [self buildCharacterData:_node templateBuilder:_builder];
363 }
364
365 - (WOElement *)buildComment:(id<DOMComment>)_node
366   templateBuilder:(id)_builder
367 {
368   /* comments aren't delivered ... */
369   return nil;
370 }
371
372 /* building the whole template */
373
374 - (WOElement *)buildTemplateFromDocument:(id<DOMDocument>)_document {
375   NSAutoreleasePool *pool;
376   WOElement *result;
377   
378   pool   = [[NSAutoreleasePool alloc] init];
379   result = [[self buildNode:_document templateBuilder:self] retain];
380   [pool release];
381   return [result autorelease];
382 }
383
384 /* association callbacks */
385
386 - (WOAssociation *)associationForValue:(id)_value {
387   return [WOAssociation associationWithValue:_value];
388 }
389
390 - (WOAssociation *)associationForKeyPath:(NSString *)_path {
391   return [WOAssociation associationWithKeyPath:_path];
392 }
393
394 - (WOAssociation *)associationForJavaScript:(NSString *)_js {
395   WOAssociation *assoc;
396   
397   assoc = [NSClassFromString(@"WOScriptAssociation") alloc];
398   assoc = [(id)assoc initWithScript:_js language:@"javascript"];
399   return [assoc autorelease];
400 }
401
402 - (WOAssociation *)associationForAttribute:(id<DOMAttr>)_attribute {
403   NSString      *nsuri;
404   NSString      *value;
405   WOAssociation *assoc;
406   Class c;
407   
408   nsuri = [_attribute namespaceURI];
409   value = [_attribute nodeValue];
410   
411   c = [self associationClassForNamespaceURI:[_attribute namespaceURI]];
412   if (c == Nil) {
413     [self warnWithFormat:
414             @"found no association class for attribute %@ (namespace=%@)",
415             _attribute, [_attribute namespaceURI]];
416     return nil;
417   }
418   if (logAssocMap) {
419     [self logWithFormat:@"use class %@ for namespaceURI %@ (attribute %@)",
420             c, [_attribute namespaceURI], [_attribute name]];
421   }
422   
423   assoc = [[c alloc] initWithString:value];
424   if (logAssocCreation) {
425     [self logWithFormat:@"created assoc %@ for attribute %@", 
426             assoc, [_attribute name]];
427   }
428   
429   return [assoc autorelease];
430 }
431
432 - (NSMutableDictionary *)associationsForAttributes:(id<DOMNamedNodeMap>)_attrs{
433   NSMutableDictionary *assocs;
434   int i, count;
435   
436   if ((count = [_attrs length]) == 0)
437     return nil;
438   
439   assocs = [NSMutableDictionary dictionaryWithCapacity:(count + 1)];
440
441   for (i = 0; i < count; i++) {
442     id<DOMAttr>   attr;
443     WOAssociation *assoc;
444
445     attr = [_attrs objectAtIndex:i];
446     
447     if ((assoc = [self associationForAttribute:attr])) {
448       NSString *key;
449       
450       key = [attr name];
451       if ([key characterAtIndex:0] == '_')
452         key = [@"?" stringByAppendingString:[key substringFromIndex:1]];
453       
454       [assocs setObject:assoc forKey:key];
455     }
456   }
457   return assocs;
458 }
459
460 - (void)_ensureDefaultAssocMappings {
461   NSEnumerator *e;
462   NSString     *ns;
463   
464   if (self->nsToAssoc) 
465     return;
466   
467   self->nsToAssoc = [[NSMutableDictionary alloc] initWithCapacity:8];
468   e = [defaultAssocMap keyEnumerator];
469   while ((ns = [e nextObject]) != nil) {
470     NSString *className;
471     Class    clazz;
472     
473     className = [defaultAssocMap objectForKey:ns];
474     clazz = NSClassFromString(className);
475     
476     if (clazz == Nil) {
477       [self warnWithFormat:@"did not find association class: '%@'",
478               className];
479       continue;
480     }
481     
482     /* register */
483     [self->nsToAssoc setObject:clazz forKey:ns];
484   }
485 }
486 - (void)registerAssociationClass:(Class)_class forNamespaceURI:(NSString *)_ns{
487   if (_ns    == nil) return;
488   if (_class == Nil) return;
489   
490   [self _ensureDefaultAssocMappings];
491   [self->nsToAssoc setObject:_class forKey:_ns];
492 }
493 - (Class)associationClassForNamespaceURI:(NSString *)_ns {
494   Class c;
495   
496   [self _ensureDefaultAssocMappings];
497   
498   if ((c = [self->nsToAssoc objectForKey:_ns]) == Nil)
499     /* if we have no class mapped for a namespace, we treat it as a value */
500     c = ValAssoc;
501   
502   if (debugOn)
503     [self debugWithFormat:@"using class %@ for namespace %@", c, _ns];
504   return c;
505 }
506
507 /* creating unique IDs */
508
509 - (NSString *)uniqueIDForNode:(id)_node {
510   NSMutableArray  *nodePath;
511   NSMutableString *uid;
512   NSEnumerator    *topDown;
513   id   node, parent;
514   BOOL isFirst;
515
516   if (_node == nil) return nil;
517
518   nodePath = [NSMutableArray arrayWithCapacity:16];
519
520   /* collect all parent nodes in bottom-up form */
521
522   for (node = _node; node; node = [node parentNode])
523     [nodePath addObject:node];
524
525   /* generate ID */
526
527   uid     = [NSMutableString stringWithCapacity:64];
528   topDown = [nodePath reverseObjectEnumerator];
529   isFirst = YES;
530   parent  = nil;
531
532   for (isFirst = YES; (node = [topDown nextObject]); parent = node) {
533     if (!isFirst) {
534       NSArray  *children;
535       unsigned i, count;
536
537       [uid appendString:@"."];
538
539       /* determine index of _node */
540
541       children = (NSArray *)[parent childNodes];
542       for (i = 0, count = [children count]; i < count; i++) {
543         if ([children objectAtIndex:i] == node)
544           break;
545       }
546       [uid appendFormat:@"%d", i];
547     }
548     else {
549       [uid appendString:@"R"];
550       isFirst = NO;
551     }
552   }
553
554   return [[uid copy] autorelease];
555 }
556
557 /* logging */
558
559 + (id)logger {
560   return logger;
561 }
562 - (id)logger {
563   return logger;
564 }
565
566 - (void)logWithFormat:(NSString *)_format, ... {
567   NSString *value = nil;
568   va_list  ap;
569
570   va_start(ap, _format);
571   value = [[NSString alloc] initWithFormat:_format arguments:ap];
572   va_end(ap);
573
574   NSLog(@"|%@| %@", self, value);
575   [value release];
576 }
577 - (void)debugWithFormat:(NSString *)_format, ... {
578   static char showDebug = 2;
579   NSString *value = nil;
580   va_list  ap;
581   
582   if (showDebug == 2) {
583     showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
584   }
585   
586   if (showDebug) {
587     va_start(ap, _format);
588     value = [[NSString alloc] initWithFormat:_format arguments:ap];
589     va_end(ap);
590     
591     NSLog(@"|%@|D %@", self, value);
592     [value release];
593   }
594 }
595
596 /* managing builder queues */
597
598 - (void)setNextBuilder:(WOxElemBuilder *)_builder {
599   ASSIGN(self->nextBuilder, _builder);
600 }
601 - (WOxElemBuilder *)nextBuilder {
602   return self->nextBuilder;
603 }
604
605 /* component script parts */
606
607 - (void)addComponentScriptPart:(WOComponentScriptPart *)_part {
608   if (self->script == nil)
609     self->script = [[WOComponentScript alloc] init];
610   
611   [self->script addScriptPart:_part];
612 }
613 - (void)addComponentScript:(NSString *)_script line:(unsigned)_line {
614   WOComponentScriptPart *part;
615   
616   part = [[WOComponentScriptPart alloc] initWithURL:nil startLine:_line
617                                         script:_script];
618   [self addComponentScriptPart:part];
619   RELEASE(part);
620 }
621
622 - (WOComponentScript *)componentScript {
623   return self->script;
624 }
625
626 /* subcomponent registry, created during parsing ... */
627
628 - (void)registerSubComponentWithId:(NSString *)_cid
629   componentName:(NSString *)_name
630   bindings:(NSMutableDictionary *)_bindings
631 {
632   WOxElemBuilderComponentInfo *info;
633   
634   info = [[WOxElemBuilderComponentInfo alloc] initWithComponentId:_cid
635     componentName:_name
636     bindings:_bindings];
637     
638   if (self->subcomponentInfos == nil)
639     self->subcomponentInfos = [[NSMutableArray alloc] initWithCapacity:16];
640   [self->subcomponentInfos addObject:info];
641   RELEASE(info);
642 }
643
644 - (NSArray *)subcomponentInfos {
645   return self->subcomponentInfos;
646 }
647
648 - (void)reset {
649   [self->subcomponentInfos removeAllObjects];
650   [self->script release]; self->script = nil;
651 }
652
653 /* support methods for subclasses */
654
655 - (id<DOMElement>)lookupUniqueTag:(NSString *)_name
656   inElement:(id<DOMElement>)_elem
657 {
658   id<DOMNodeList> list;
659   
660   if ((list = [_elem getElementsByTagName:_name]) == nil)
661     return nil;
662   if ([list length] == 0)
663     return nil;
664   if ([list length] > 1) {
665     [self warnWithFormat:
666             @"more than once occurence of tag %@ in element: %@", 
667             _name, _elem];
668   }
669   return [list objectAtIndex:0];
670 }
671
672 - (WOElement *)elementForRawString:(NSString *)_rawstr {
673   /* Note: returns a retained element! */
674   WOAssociation *a;
675   
676   if (_rawstr == nil) return nil;
677   a = [WOAssociation associationWithValue:_rawstr];
678   return [[StrClass alloc] initWithValue:a escapeHTML:NO];
679 }
680
681 - (WOElement *)elementForElementsAndStrings:(NSArray *)_elements {
682   /* Note: returns a retained element! */
683   NSMutableArray *ma;
684   WOElement *element;
685   unsigned  i, count;
686   
687   if ((count = [_elements count]) == 0)
688     return nil;
689   
690   ma = [[NSMutableArray alloc] initWithCapacity:count];
691   for (i = 0; i < count; i++) {
692     id elem;
693     
694     elem = [_elements objectAtIndex:i];
695     if ([elem isKindOfClass:[WOElement class]]) {
696       [ma addObject:elem];
697       continue;
698     }
699     
700     if ((elem = [self elementForRawString:[elem stringValue]]))
701       [ma addObject:elem];
702   }
703   if ((count = [ma count]) == 0)
704     element = nil;
705   else if (count == 1) {
706     element = [[ma objectAtIndex:0] retain];
707   }
708   else {
709     element = [[CompoundElemClass allocForCount:count zone:NULL]
710                 initWithContentElements:ma];
711   }
712   [ma release];
713   return element;
714 }
715
716 - (WOElement *)wrapElement:(WOElement *)_element 
717   inCondition:(WOAssociation *)_condition
718   negate:(BOOL)_flag
719 {
720   // NOTE: *releases* _element parameter!
721   //       returns retained conditional
722   static Class WOConditionalClass = Nil;
723   static NSString *key = @"condition";
724   NSMutableDictionary *assocs;
725   WOElement *element;
726   NSArray   *children;
727
728   if (WOConditionalClass == Nil)
729     WOConditionalClass = NSClassFromString(@"WOConditional");
730   
731   if (_element == nil)
732     return nil;
733   if (_condition == nil)
734     return _element;
735   
736   if (_flag) {
737     assocs = [[NSMutableDictionary alloc] 
738                initWithObjectsAndKeys:_condition, key,
739                yesAssoc, @"negate", nil];
740   }
741   else {
742     assocs = [[NSMutableDictionary alloc] initWithObjects:&_condition
743                                           forKeys:&key count:1];
744   }
745   children = [[NSArray alloc] initWithObjects:&_element count:1];
746   element = [[WOConditionalClass alloc] initWithName:nil
747                                         associations:assocs
748                                         contentElements:children];
749   [children release];
750   [_element release];
751   [assocs   release];
752   return element;
753 }
754
755 - (WOElement *)wrapElements:(NSArray *)_sub inElementOfClass:(Class)_class {
756   WOElement *element;
757   
758   if (_sub == nil)
759     return nil;
760   
761   element = [[_class alloc] initWithName:nil
762                             associations:nil
763                             contentElements:_sub];
764   return element;
765 }
766
767 - (WOElement *)wrapChildrenOfElement:(id<DOMElement>)_tag
768   inElementOfClass:(Class)_class
769   templateBuilder:(id)_b
770 {
771   NSArray *children;
772   
773   children = [_tag hasChildNodes]
774     ? [_b buildNodes:[_tag childNodes] templateBuilder:_b]
775     : nil;
776   
777   return [self wrapElements:children inElementOfClass:_class];
778 }
779
780 @end /* WOxElemBuilder */