]> err.no Git - sope/blob - sope-appserver/NGObjWeb/DynamicElements/WORepetition.m
312620780500b392efca8952bf0c556db3358cb4
[sope] / sope-appserver / NGObjWeb / DynamicElements / WORepetition.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/WODynamicElement.h>
23 #include "WOElement+private.h"
24 #include <NGExtensions/NSString+misc.h>
25 #include "decommon.h"
26
27 @interface WORepetition : WODynamicElement
28 {
29   // WODynamicElement: extraAttributes
30   // WODynamicElement: otherTagString
31 @protected
32   WOElement *template;
33 #if DEBUG
34   NSString  *repName;
35 #endif
36 }
37
38 @end /* WORepetition */
39
40 @interface _WOComplexRepetition : WORepetition
41 {
42   WOAssociation *list;       // array of objects to iterate through
43   WOAssociation *item;       // current item in the array
44   WOAssociation *index;      // current index
45   WOAssociation *identifier; // unique id for element
46   WOAssociation *count;      // number of times the contents will be repeated
47   
48   // non-WO
49   WOAssociation *startIndex;
50   WOAssociation *separator;  // string inserted between repetitions
51 }
52
53 @end
54
55 @interface _WOSimpleRepetition : WORepetition
56 {
57   WOAssociation *list; // array of objects to iterate through
58   WOAssociation *item; // current item in the array
59 }
60
61 @end
62
63 @interface _WOTemporaryRepetition : NSObject
64 @end
65
66 //#define PROF_REPETITION_CLUSTER 1
67
68 static int descriptiveIDs  = -1;
69 static int debugTakeValues = -1;
70
71 #if PROF_REPETITION_CLUSTER
72 static int complexCount = 0;
73 static int simpleCount = 0;
74 #endif
75
76 @implementation _WOTemporaryRepetition
77
78 static inline Class _classForConfig(NSDictionary *_config) {
79   Class repClass = Nil;
80   unsigned c;
81
82   switch ((c = [_config count])) {
83     case 0:
84       repClass = [_WOSimpleRepetition class];
85       break;
86     case 1:
87       if ([_config objectForKey:@"list"])
88         repClass = [_WOSimpleRepetition class];
89       else if ([_config objectForKey:@"item"])
90         repClass = [_WOSimpleRepetition class];
91       break;
92     case 2:
93       if ([_config objectForKey:@"list"] &&
94           [_config objectForKey:@"item"])
95         repClass = [_WOSimpleRepetition class];
96       break;
97     default:
98       repClass = [_WOComplexRepetition class];
99       break;
100   }
101   
102   if (repClass == Nil)
103     repClass = [_WOComplexRepetition class];
104
105   return repClass;
106 }
107
108 - (id)initWithName:(NSString *)_name
109   associations:(NSDictionary *)_config
110   template:(WOElement *)_template
111 {
112   Class repClass = Nil;
113
114   repClass = _classForConfig(_config);
115   
116   return [[repClass alloc]
117                     initWithName:_name
118                     associations:_config
119                     template:_template];
120 }
121
122 - (id)initWithName:(NSString *)_name
123   associations:(NSDictionary *)_config
124   contentElements:(NSArray *)_contents
125 {
126   Class repClass = Nil;
127   
128   repClass = _classForConfig(_config);
129   
130   return [[repClass alloc]
131                     initWithName:_name
132                     associations:_config
133                     contentElements:_contents];
134 }
135
136 @end /* _WOTemporaryRepetition */
137
138 @implementation WORepetition
139
140 + (int)version {
141   return [super version] + 1 /* v3 */;
142 }
143 + (void)initialize {
144   NSAssert2([super version] == 2,
145             @"invalid superclass (%@) version %i !",
146             NSStringFromClass([self superclass]), [super version]);
147
148   if (debugTakeValues == -1) {
149     debugTakeValues = 
150       [[NSUserDefaults standardUserDefaults] boolForKey:@"WODebugTakeValues"]
151       ? 1 : 0;
152     
153     if (debugTakeValues) NSLog(@"WORepetition: WODebugTakeValues on.");
154   }
155 }
156
157 + (id)allocWithZone:(NSZone *)zone {
158   static Class WORepetitionClass = Nil;
159   static _WOTemporaryRepetition *temporaryRepetition = nil;
160   
161   if (WORepetitionClass == Nil)
162     WORepetitionClass = [WORepetition class];
163   if (temporaryRepetition == nil)
164     temporaryRepetition = [_WOTemporaryRepetition allocWithZone:zone];
165   
166   return (self == WORepetitionClass)
167     ? (id)temporaryRepetition
168     : (id)NSAllocateObject(self, 0, zone);
169 }
170
171 - (id)initWithName:(NSString *)_name
172   associations:(NSDictionary *)_config
173   template:(WOElement *)_c
174 {
175   if (descriptiveIDs == -1) {
176     descriptiveIDs = [[[NSUserDefaults standardUserDefaults]
177                                        objectForKey:@"WODescriptiveElementIDs"]
178                                        boolValue] ? 1 : 0;
179   }
180   
181 #if DEBUG
182   self->repName = _name ? [_name copy] : (id)@"R";
183 #endif
184   
185   if ((self = [super initWithName:_name associations:_config template:_c])) {
186     self->template = RETAIN(_c);
187   }
188   return self;
189 }
190
191 - (void)dealloc {
192   [self->template release];
193 #if DEBUG
194   [self->repName release];
195 #endif
196   [super dealloc];
197 }
198
199 @end /* WORepetition */
200
201 @implementation _WOComplexRepetition
202
203 - (id)initWithName:(NSString *)_name
204   associations:(NSDictionary *)_config
205   template:(WOElement *)_c
206 {
207   if ((self = [super initWithName:_name associations:_config template:_c])) {
208 #if PROF_REPETITION_CLUSTER
209     complexCount++;
210
211     if (complexCount % 10 == 0) {
212       NSLog(@"REPETITION CLUSTER: %i simple, %i complex",
213             simpleCount, complexCount);
214     }
215 #endif
216     self->list       = OWGetProperty(_config, @"list");
217     self->item       = OWGetProperty(_config, @"item");
218     self->index      = OWGetProperty(_config, @"index");
219     self->identifier = OWGetProperty(_config, @"identifier");
220     self->count      = OWGetProperty(_config, @"count");
221     self->startIndex = OWGetProperty(_config, @"startIndex");
222     self->separator  = OWGetProperty(_config, @"separator");
223   }
224   return self;
225 }
226
227 - (void)dealloc {
228   [self->separator  release];
229   [self->list       release];
230   [self->item       release];
231   [self->index      release];
232   [self->identifier release];
233   [self->count      release];
234   [self->startIndex release];
235   [super dealloc];
236 }
237
238 /* accessors */
239
240 - (id)template {
241   return self->template;
242 }
243
244 /* OWResponder */
245
246 static inline void
247 _applyIdentifier(_WOComplexRepetition *self,
248                  WOComponent *sComponent,
249                  NSString *_idx)
250 {
251   NSArray *array;
252   unsigned count;
253
254 #if DEBUG
255   NSCAssert(self->identifier, @"this method is only to be called on objects "
256             @"with a specified 'identifier' association !");
257 #endif
258
259   array = [self->list valueInComponent:sComponent];
260   count = [array count];
261
262   if (count > 0) {
263     unsigned cnt;
264
265     /* find subelement for unique id */
266     
267     for (cnt = 0; cnt < count; cnt++) {
268       NSString *ident;
269       
270       if (self->index)
271         [self->index setUnsignedIntValue:cnt inComponent:sComponent];
272
273       if (self->item) {
274         [self->item setValue:[array objectAtIndex:cnt]
275                     inComponent:sComponent];
276       }
277
278       ident = [self->identifier stringValueInComponent:sComponent];
279
280       if ([ident isEqualToString:_idx]) {
281         /* found subelement with unique id */
282         return;
283       }
284     }
285     
286     [sComponent logWithFormat:
287                   @"WORepetition: array did change, "
288                   @"unique-id isn't contained."];
289     [self->item  setValue:nil          inComponent:sComponent];
290     [self->index setUnsignedIntValue:0 inComponent:sComponent];
291   }
292 }
293
294 static inline void
295 _applyIndex(_WOComplexRepetition *self, WOComponent *sComponent, unsigned _idx)
296 {
297   NSArray *array;
298   
299   array = [self->list valueInComponent:sComponent];
300   
301   if (self->index)
302     [self->index setUnsignedIntValue:_idx inComponent:sComponent];
303
304   if (self->item) {
305     unsigned count = [array count];
306
307     if (_idx < count) {
308       [self->item setValue:[array objectAtIndex:_idx]
309                   inComponent:sComponent];
310     }
311     else {
312       [sComponent logWithFormat:
313                     @"WORepetition: array did change, index is invalid."];
314       [self->item setValue:nil inComponent:sComponent];
315     }
316   }
317 }
318
319 - (void)takeValuesFromRequest:(WORequest *)_request
320   inContext:(WOContext *)_ctx
321 {
322   // iterate ..
323   WOComponent *sComponent;
324   NSArray     *array;
325   unsigned    aCount;
326   unsigned    goCount;
327
328 #if DEBUG
329   if (descriptiveIDs)
330     [_ctx appendElementIDComponent:self->repName];
331 #endif
332   
333   sComponent = [_ctx component];
334   array = [self->list valueInContext:_ctx];
335   aCount = [array count];
336   
337   goCount = self->count
338     ? [self->count unsignedIntValueInComponent:sComponent]
339     : aCount;
340   
341   if (goCount > 0) {
342     unsigned startIdx, goUntil;
343     unsigned cnt;
344     
345     startIdx =
346       [self->startIndex unsignedIntValueInComponent:sComponent];
347     
348     if (self->identifier == nil) {
349       if (startIdx == 0)
350         [_ctx appendZeroElementIDComponent];
351       else
352         [_ctx appendIntElementIDComponent:startIdx];
353     }
354     
355     if (self->list) {
356       goUntil = (aCount > (startIdx + goCount))
357         ? startIdx + goCount
358         : aCount;
359     }
360     else
361       goUntil = startIdx + goCount;
362
363 #if DEBUG
364     if (debugTakeValues) {
365       [sComponent debugWithFormat:
366                     @"%@: name=%@ id='%@' start walking rep (%i-%i) ..",
367                     NSStringFromClass([self class]),
368                     self->repName,
369                     [_ctx elementID],
370                     startIdx, goUntil];
371     }
372 #endif
373     
374     for (cnt = startIdx; cnt < goUntil; cnt++) {
375       _applyIndex(self, sComponent, cnt);
376       
377       if (self->identifier) {
378         NSString *s;
379         
380         s = [self->identifier stringValueInComponent:sComponent];
381         [_ctx appendElementIDComponent:s];
382       }
383
384       if (debugTakeValues) {
385         [[_ctx component] debugWithFormat:@"%@<%@>: "
386                                           @"let template take values ..",
387                                             [_ctx elementID],
388                                             NSStringFromClass([self class])];
389       }
390       
391       [self->template takeValuesFromRequest:_request inContext:_ctx];
392       
393       if (self->identifier == nil)
394         [_ctx incrementLastElementIDComponent];
395       else
396         [_ctx deleteLastElementIDComponent];
397     }
398     
399     if (self->identifier == nil)
400       [_ctx deleteLastElementIDComponent]; // Repetition Index
401   }
402   else if (debugTakeValues) {
403     [[_ctx component] debugWithFormat:
404                         @"%@<%@>: takevalues -> no contents to walk ! ..",
405                         [_ctx elementID], NSStringFromClass([self class])];
406   }
407   
408 #if DEBUG
409   if (descriptiveIDs)
410     [_ctx deleteLastElementIDComponent];
411 #endif
412 }
413
414 - (id)invokeActionForRequest:(WORequest *)_request
415   inContext:(WOContext *)_ctx
416 {
417   WOComponent *sComponent;
418   id result = nil;
419   id idxId;
420   
421   sComponent = [_ctx component];
422
423 #if DEBUG
424   if (descriptiveIDs) {
425     if (![[_ctx currentElementID] isEqualToString:self->repName]) {
426       [[_ctx session] logWithFormat:@"%s: %@ missing repetition ID 'R' !",
427                                       __PRETTY_FUNCTION__, self];
428       return nil;
429     }
430     [_ctx consumeElementID]; // consume 'R'
431     [_ctx appendElementIDComponent:self->repName];
432   }
433 #endif
434   
435   if ((idxId  = [_ctx currentElementID])) {
436     [_ctx consumeElementID]; // consume index-id
437     
438     /* this updates the element-id path */
439     [_ctx appendElementIDComponent:idxId];
440     
441     if (self->identifier)
442       _applyIdentifier(self, sComponent, idxId);
443     else
444       _applyIndex(self, sComponent, [idxId intValue]);
445     
446     result = [self->template invokeActionForRequest:_request inContext:_ctx];
447
448     [_ctx deleteLastElementIDComponent];
449   }
450   else {
451     [[_ctx session]
452            logWithFormat:@"%s: %@: MISSING INDEX ID in URL !",
453              __PRETTY_FUNCTION__,
454              self];
455   }
456
457 #if DEBUG
458   if (descriptiveIDs)
459     [_ctx deleteLastElementIDComponent];
460 #endif
461   
462   return result;
463 }
464
465 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
466   static Class NSAutoreleasePoolClass = Nil;
467   WOComponent *sComponent;
468   NSArray  *array;
469   unsigned aCount, goCount, startIdx;
470   NSAutoreleasePool *pool;
471
472 #if DEBUG
473   if (descriptiveIDs)
474     [_ctx appendElementIDComponent:self->repName];
475 #endif
476   
477   if (NSAutoreleasePoolClass == Nil)
478     NSAutoreleasePoolClass = [NSAutoreleasePool class];
479   
480   pool = [[NSAutoreleasePoolClass alloc] init];
481   
482   sComponent = [_ctx component];
483   array    = [self->list valueInContext:_ctx];
484   aCount   = [array count];
485   startIdx = [self->startIndex unsignedIntValueInComponent:sComponent];
486   
487   goCount = self->count
488     ? [self->count unsignedIntValueInComponent:sComponent]
489     : aCount;
490   
491   if (goCount > 0) {
492     unsigned cnt, goUntil;
493
494 #if HTML_DEBUG
495     // append debugging info
496     WOResponse_AddString(_response,
497                          [NSString stringWithFormat:
498                                      @"<!-- WORep. count=%d arraySize=%d -->\n",
499                                      goCount, [array count]]);
500 #endif
501
502     if (self->identifier == nil) {
503       if (startIdx == 0)
504         [_ctx appendZeroElementIDComponent];
505       else
506         [_ctx appendIntElementIDComponent:startIdx];
507     }
508
509     if (self->list) {
510       goUntil = (aCount > (startIdx + goCount))
511         ? startIdx + goCount
512         : aCount;
513     }
514     else
515       goUntil = startIdx + goCount;
516     
517     for (cnt = startIdx; cnt < goUntil; cnt++) {
518       id ident = nil;
519       id lItem;
520       
521       if ((cnt != startIdx) && (self->separator != nil)) {
522         WOResponse_AddString(_response,
523                              [self->separator stringValueInComponent:
524                                                 sComponent]);
525       }
526       
527       if (self->index)
528         [self->index setUnsignedIntValue:cnt inComponent:sComponent];
529
530       lItem = [array objectAtIndex:cnt];
531       
532       if (self->item) {
533         [self->item setValue:lItem inComponent:sComponent];
534       }
535       else {
536         if (!self->index && self->list) {
537           [_ctx pushCursor:lItem];
538         }
539       }
540
541       /* get identifier used for action-links */
542       
543       if (self->identifier) {
544         /* use a unique id for subelement detection */
545         ident = [self->identifier stringValueInComponent:sComponent];
546         ident = [ident stringByEscapingURL];
547         [_ctx appendElementIDComponent:ident];
548       }
549
550 #if HTML_DEBUG
551       /* append debugging info */
552       WOResponse_AddString(_response, [NSString stringWithFormat:
553                                          @"  <!-- iteration=%d -->\n", cnt]);
554 #endif
555
556       /* append child elements */
557       
558       [self->template appendToResponse:_response inContext:_ctx];
559
560       /* cleanup */
561
562       if (self->identifier)
563         [_ctx deleteLastElementIDComponent];
564       else
565         [_ctx incrementLastElementIDComponent];
566     }
567
568     if (self->identifier == nil)
569       [_ctx deleteLastElementIDComponent]; /* repetition index */
570
571     if (!self->item && !self->index &&self->list)
572       [_ctx popCursor];
573
574     //if (self->index) [self->index setUnsignedIntValue:0];
575     //if (self->item)  [self->item  setValue:nil];
576   }
577 #if HTML_DEBUG
578   else {
579     WOResponse_AddCString(_response, "<!-- repetition with no contents -->");
580   }
581 #endif
582   
583   [pool release];
584   
585 #if DEBUG
586   if (descriptiveIDs)
587     [_ctx deleteLastElementIDComponent];
588 #endif
589 }
590
591 /* description */
592
593 - (NSString *)associationDescription {
594   NSMutableString *str = [NSMutableString stringWithCapacity:32];
595
596   if (self->list)       [str appendFormat:@" list=%@",     self->list];
597   if (self->item)       [str appendFormat:@" item=%@",     self->item];
598   if (self->index)      [str appendFormat:@" index=%@",    self->index];
599   if (self->identifier) [str appendFormat:@" id=%@",       self->identifier];
600   if (self->count)      [str appendFormat:@" count=%@",    self->count];
601   if (self->template)   [str appendFormat:@" template=%@", self->template];
602
603   return str;
604 }
605
606 @end /* _WOComplexRepetition */
607
608 @implementation _WOSimpleRepetition
609
610 - (id)initWithName:(NSString *)_name
611   associations:(NSDictionary *)_config
612   template:(WOElement *)_c
613 {
614   if ((self = [super initWithName:_name associations:_config template:_c])) {
615 #if PROF_REPETITION_CLUSTER
616     simpleCount++;
617
618     if (simpleCount % 10 == 0) {
619       NSLog(@"REPETITION CLUSTER: %i simple, %i complex",
620             simpleCount, complexCount);
621     }
622 #endif
623     self->list = OWGetProperty(_config, @"list");
624     self->item = OWGetProperty(_config, @"item");
625   }
626   return self;
627 }
628
629 - (void)dealloc {
630   [self->list release];
631   [self->item release];
632   [super dealloc];
633 }
634
635 /* processing */
636
637 static inline void
638 _sapplyIndex(_WOSimpleRepetition *self, WOComponent *sComponent, NSArray *array, unsigned _idx)
639 {
640   if (self->item) {
641     unsigned count = [array count];
642
643     if (_idx < count) {
644       [self->item setValue:[array objectAtIndex:_idx]
645                   inComponent:sComponent];
646     }
647     else {
648       [sComponent logWithFormat:
649                     @"WORepetition: array did change, index is invalid."];
650       [self->item setValue:nil inComponent:sComponent];
651     }
652   }
653 }
654
655 - (void)takeValuesFromRequest:(WORequest *)_request
656   inContext:(WOContext *)_ctx
657 {
658   // iterate ..
659   WOComponent *sComponent;
660   NSArray     *array;
661   unsigned    aCount;
662   unsigned    goCount;
663
664 #if DEBUG
665   if (descriptiveIDs)
666     [_ctx appendElementIDComponent:self->repName];
667 #endif
668   
669   sComponent = [_ctx component];
670   array      = [self->list valueInContext:_ctx];
671   goCount    = aCount = [array count];
672   
673   if (goCount > 0) {
674     unsigned cnt;
675     
676     [_ctx appendZeroElementIDComponent];
677     
678 #if DEBUG
679     if (debugTakeValues) {
680       NSLog(@"%s: name=%@ id='%@' start walking rep (count=%i) ..",
681             __PRETTY_FUNCTION__, self->repName, [_ctx elementID], aCount);
682     }
683 #endif
684     
685     for (cnt = 0; cnt < aCount; cnt++) {
686       _sapplyIndex(self, sComponent, array, cnt);
687       
688 #if DEBUG
689       if (debugTakeValues) {
690         NSLog(@"%s:    %@<%@>: idx[%i] let template take values ..",
691               __PRETTY_FUNCTION__,
692               [_ctx elementID], NSStringFromClass([self class]), cnt);
693       }
694 #endif
695       
696       [self->template takeValuesFromRequest:_request inContext:_ctx];
697       
698       [_ctx incrementLastElementIDComponent];
699     }
700     
701     [_ctx deleteLastElementIDComponent]; // Repetition Index
702   }
703 #if DEBUG
704   else if (debugTakeValues) {
705     NSLog(@"%s: %@<%@>: takevalues -> no contents to walk:\n"
706           @"  component: %@\n"
707           @"  list:      %@\n"
708           @"  count:     %i",
709           __PRETTY_FUNCTION__,
710           [_ctx elementID], NSStringFromClass([self class]),
711           sComponent, array, goCount);
712   }
713 #endif
714   
715 #if DEBUG
716   if (descriptiveIDs)
717     [_ctx deleteLastElementIDComponent];
718 #endif
719 }
720
721 - (id)invokeActionForRequest:(WORequest *)_request
722   inContext:(WOContext *)_ctx
723 {
724   WOComponent *sComponent;
725   id result = nil;
726   id idxId;
727   
728   sComponent = [_ctx component];
729   
730 #if DEBUG
731   if (descriptiveIDs) {
732     if (![[_ctx currentElementID] isEqualToString:self->repName]) {
733       [[_ctx session]
734              logWithFormat:@"%s: %@ missing repetition ID 'R' !",
735                __PRETTY_FUNCTION__, self];
736       return nil;
737     }
738     [_ctx consumeElementID]; // consume 'R'
739     [_ctx appendElementIDComponent:self->repName];
740   }
741 #endif
742   
743   if ((idxId  = [_ctx currentElementID])) {
744     int idx;
745     
746     idx = [idxId intValue];
747     [_ctx consumeElementID]; // consume index-id
748     
749     /* this updates the element-id path */
750     [_ctx appendElementIDComponent:idxId];
751     
752     _sapplyIndex(self, sComponent, 
753                  [self->list valueInContext:_ctx], idx);
754     
755     result = [self->template invokeActionForRequest:_request inContext:_ctx];
756
757     [_ctx deleteLastElementIDComponent];
758   }
759   else {
760     [[_ctx session]
761            logWithFormat:@"%s: %@: MISSING INDEX ID in URL !",
762              __PRETTY_FUNCTION__,
763              self];
764   }
765
766 #if DEBUG
767   if (descriptiveIDs)
768     [_ctx deleteLastElementIDComponent];
769 #endif
770   
771   return result;
772 }
773
774 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
775   static Class NSAutoreleasePoolClass = Nil;
776   WOComponent *sComponent;
777   NSArray  *array;
778   unsigned aCount;
779   NSAutoreleasePool *pool;
780
781 #if DEBUG
782   if (descriptiveIDs)
783     [_ctx appendElementIDComponent:self->repName];
784 #endif
785   
786   if (NSAutoreleasePoolClass == Nil)
787     NSAutoreleasePoolClass = [NSAutoreleasePool class];
788   
789   pool = [[NSAutoreleasePoolClass alloc] init];
790   
791   sComponent = [_ctx component];
792   array    = [self->list valueInContext:_ctx];
793   aCount   = [array count];
794   
795   if (aCount > 0) {
796     unsigned cnt;
797
798 #if HTML_DEBUG
799     // append debugging info
800     WOResponse_AddString(_response,
801                          [NSString stringWithFormat:
802                                      @"<!-- WORep. count=%d arraySize=%d -->\n",
803                                      goCount, [array count]]);
804 #endif
805
806     [_ctx appendZeroElementIDComponent];
807     
808     for (cnt = 0; cnt < aCount; cnt++) {
809       if (self->item) {
810         [self->item setValue:[array objectAtIndex:cnt]
811                     inComponent:sComponent];
812       }
813       else {
814         [_ctx pushCursor:[array objectAtIndex:cnt]];
815       }
816       
817 #if HTML_DEBUG
818       // append debugging info
819       WOResponse_AddString(_response, [NSString stringWithFormat:
820                                          @"  <!-- iteration=%d -->\n", cnt]);
821 #endif
822       
823       /* append child elements */
824       
825       [self->template appendToResponse:_response inContext:_ctx];
826
827       /* cleanup */
828       
829       [_ctx incrementLastElementIDComponent];
830       
831       if (self->item == nil)
832         [_ctx popCursor];
833     }
834
835     [_ctx deleteLastElementIDComponent]; /* repetition index */
836     
837     //if (self->item)  [self->item  setValue:nil];
838   }
839 #if HTML_DEBUG
840   else {
841     WOResponse_AddCString(_response, "<!-- repetition with no contents -->");
842   }
843 #endif
844   
845   [pool release];
846   
847 #if DEBUG
848   if (descriptiveIDs)
849     [_ctx deleteLastElementIDComponent];
850 #endif
851 }
852
853 /* description */
854
855 - (NSString *)associationDescription {
856   NSMutableString *str;
857   
858   str = [NSMutableString stringWithCapacity:24];
859   if (self->list)     [str appendFormat:@" list=%@",     self->list];
860   if (self->item)     [str appendFormat:@" item=%@",     self->item];
861   if (self->template) [str appendFormat:@" template=%@", self->template];
862   return str;
863 }
864
865 @end /* _WOSimpleRepetition */