2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo 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
11 OGo 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.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
23 #include "WOHTMLDynamicElement.h"
24 #include "WOElement+private.h"
26 #import <Foundation/NSNumberFormatter.h>
27 #import <Foundation/NSDateFormatter.h>
32 value = myCalendarDate;
33 dateformat = "%B %Y %T";
35 nilString = "no date is set !";
43 _WOSimpleStaticASCIIString
47 @interface WOString : WOHTMLDynamicElement
50 @interface _WOTemporaryString : NSObject
53 @interface _WOSimpleStaticString : WOString
57 @end /* WOSimpleStaticString */
59 @interface _WOSimpleStaticASCIIString : WOString
61 const unsigned char *value;
63 @end /* _WOSimpleStaticASCIIString */
65 @interface _WOSimpleDynamicString : WOString
69 @end /* WOSimpleStaticString */
71 @interface _WOComplexString : WOString
73 WOAssociation *value; // object
74 WOAssociation *escapeHTML; // BOOL
75 WOAssociation *numberformat; // string
76 WOAssociation *dateformat; // string
77 WOAssociation *formatter; // WO4: NSFormatter object
78 WOAssociation *valueWhenEmpty; // WO4.5
81 WOAssociation *insertBR; // insert <BR> tags for newlines
82 WOAssociation *nilString; // string to use if value is nil - DEPRECATED!
83 WOAssociation *style; // insert surrounding <span class="style">
86 @end /* WOComplexString */
88 @implementation WOString
91 return [super version] + 1 /* v3 */;
94 NSAssert2([super version] == 2,
95 @"invalid superclass (%@) version %i !",
96 NSStringFromClass([self superclass]), [super version]);
99 + (id)allocWithZone:(NSZone *)zone {
100 static Class WOStringClass = Nil;
101 static _WOTemporaryString *temporaryString = nil;
103 if (WOStringClass == Nil)
104 WOStringClass = [WOString class];
105 if (temporaryString == nil)
106 temporaryString = [_WOTemporaryString allocWithZone:zone];
108 return (self == WOStringClass)
109 ? (id)temporaryString
110 : NSAllocateObject(self, 0, zone);
115 @implementation _WOSimpleDynamicString
117 - (id)initWithName:(NSString *)_name
118 associations:(NSDictionary *)_config
119 template:(WOElement *)_t
121 if ((self = [super initWithName:_name associations:_config template:_t])) {
122 self->value = OWGetProperty(_config, @"value");
128 [self->value release];
132 /* generating response */
134 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
135 [_response appendContentHTMLString:[self->value stringValueInContext:_ctx]];
140 - (NSString *)associationDescription {
141 return [NSString stringWithFormat:@" value='%@'", self->value];
144 @end /* _WOSimpleDynamicString */
146 @implementation _WOSimpleStaticString
148 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
149 if ((self = [super initWithName:nil associations:nil template:nil])) {
154 v = [_value stringValueInComponent:nil];
155 length = [v cStringLength];
161 char buffer[length * 6]; /* longest-encoding: '"' */
162 unsigned clen = [v cStringLength];
166 register char *bufPtr;
168 #if NeXT_Foundation_LIBRARY
169 cbuf = malloc(clen + 1);
171 cbuf = NGMallocAtomic(clen + 1);
173 [v getCString:cbuf]; cbuf[clen] = '\0';
176 for (i = 0, bufPtr = buffer; i < length; i++) {
178 case '&': /* & */
179 bufPtr[0] = '&'; bufPtr[1] = 'a'; bufPtr[2] = 'm'; bufPtr[3] = 'p';
185 bufPtr[0] = '&'; bufPtr[1] = 'l'; bufPtr[2] = 't'; bufPtr[3] = ';';
190 bufPtr[0] = '&'; bufPtr[1] = 'g'; bufPtr[2] = 't'; bufPtr[3] = ';';
194 case '"': /* " */
195 bufPtr[0] = '&'; bufPtr[1] = 'q'; bufPtr[2] = 'u';
196 bufPtr[3] = 'o'; bufPtr[4] = 't'; bufPtr[5] = ';';
206 #if NeXT_Foundation_LIBRARY
207 if (cbuf) free(cbuf);
209 if (cbuf) NGFree(cbuf);
211 self->value = [[NSString allocWithZone:[self zone]]
212 initWithCString:&(buffer[0])
213 length:(bufPtr - &(buffer[0]))];
216 self->value = [v copyWithZone:[self zone]];
222 - (id)initWithName:(NSString *)_name
223 associations:(NSDictionary *)_config
224 template:(WOElement *)_t
226 if ((self = [super initWithName:_name associations:_config template:_t])) {
227 WOAssociation *avalue, *aescape;
230 avalue = OWGetProperty(_config, @"value");
231 aescape = OWGetProperty(_config, @"escapeHTML");
233 doEscape = (aescape != nil) ? [aescape boolValueInComponent:nil] : YES;
234 self = [self initWithValue:avalue escapeHTML:doEscape];
240 [self->value release];
244 /* generating response */
246 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
247 WOResponse_AddString(_response, self->value);
252 - (NSString *)associationDescription {
253 return [NSString stringWithFormat:@" value='%@'", self->value];
256 @end /* WOSimpleStaticString */
258 @implementation _WOSimpleStaticASCIIString
260 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
261 if ((self = [super initWithName:nil associations:nil template:nil])) {
266 v = [_value stringValueInComponent:nil];
267 length = [v cStringLength];
273 unsigned char *buffer;
274 register unsigned char *cbuf;
275 register unsigned char *bufPtr;
280 clen = [v cStringLength];
281 cbuf = malloc(clen + 2);
282 [v getCString:cbuf]; cbuf[clen] = '\0';
284 buffer = malloc(clen * 6 + 2); /* longest-encoding: '"' */
286 for (i = 0, bufPtr = buffer; i < length; i++) {
288 case '&': /* & */
289 bufPtr[0] = '&'; bufPtr[1] = 'a'; bufPtr[2] = 'm'; bufPtr[3] = 'p';
296 bufPtr[0] = '&'; bufPtr[1] = 'l'; bufPtr[2] = 't'; bufPtr[3] = ';';
302 bufPtr[0] = '&'; bufPtr[1] = 'g'; bufPtr[2] = 't'; bufPtr[3] = ';';
307 case '"': /* " */
308 bufPtr[0] = '&'; bufPtr[1] = 'q'; bufPtr[2] = 'u';
309 bufPtr[3] = 'o'; bufPtr[4] = 't'; bufPtr[5] = ';';
315 if ((*bufPtr = cbuf[i]) > 127) {
316 NSLog(@"WARNING: string is not ASCII as required for "
317 @"SimpleStaticASCIIString !!! '%@'", v);
324 if (cbuf) free(cbuf);
325 clen = (bufPtr - buffer);
326 self->value = malloc(clen + 2);
327 strncpy((char *)self->value, buffer, clen);
328 ((unsigned char *)self->value)[clen] = '\0';
333 if (buffer) free(buffer);
336 self->value = malloc(length + 2);
337 [v getCString:(char*)self->value];
343 - (id)initWithName:(NSString *)_name
344 associations:(NSDictionary *)_config
345 template:(WOElement *)_t
347 if ((self = [super initWithName:_name associations:_config template:_t])) {
348 WOAssociation *avalue, *aescape;
351 avalue = OWGetProperty(_config, @"value");
352 aescape = OWGetProperty(_config, @"escapeHTML");
354 doEscape = (aescape != nil) ? [aescape boolValueInComponent:nil] : YES;
355 self = [self initWithValue:avalue escapeHTML:doEscape];
361 if (self->value) free((char *)self->value);
365 /* generating response */
367 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
368 if (self->value) WOResponse_AddCString(_response, self->value);
373 - (NSString *)associationDescription {
374 return [NSString stringWithFormat:@" value='%s'",
375 self->value ? self->value : (void*)""];
378 @end /* WOSimpleStaticASCIIString */
380 @implementation _WOComplexString
382 static NSNumber *yesNum = nil;
383 static WOAssociation *yesAssoc = nil;
386 if (yesNum == nil) yesNum = [[NSNumber numberWithBool:YES] retain];
388 yesAssoc = [[WOAssociation associationWithValue:yesNum] retain];
391 - (id)initWithName:(NSString *)_name
392 associations:(NSDictionary *)_config
393 template:(WOElement *)_t
395 if ((self = [super initWithName:_name associations:_config template:_t])) {
396 self->value = OWGetProperty(_config, @"value");
397 self->escapeHTML = OWGetProperty(_config, @"escapeHTML");
398 self->insertBR = OWGetProperty(_config, @"insertBR");
399 self->nilString = OWGetProperty(_config, @"nilString");
400 self->valueWhenEmpty = OWGetProperty(_config, @"valueWhenEmpty");
401 self->style = OWGetProperty(_config, @"style");
403 if (self->nilString != nil && self->valueWhenEmpty != nil) {
405 @"WARNING: 'valueWhenEmpty' AND 'nilString' bindings are set, "
406 @"use only one! (nilString is deprecated!)"];
408 else if (self->nilString != nil) {
409 [self debugWithFormat:
410 @"Note: using deprecated 'nilString' binding, "
411 @"use 'valueWhenEmpty' instead."];
414 self->formatter = OWGetProperty(_config, @"formatter");
415 self->numberformat = OWGetProperty(_config, @"numberformat");
416 self->dateformat = OWGetProperty(_config, @"dateformat");
418 if (self->formatter == nil) {
419 if ([_config objectForKey:@"formatterClass"]) {
420 WOAssociation *assoc;
422 NSFormatter *fmt = nil;
425 assoc = [OWGetProperty(_config, @"formatterClass") autorelease];
426 if (![assoc isValueConstant])
427 [self logWithFormat:@"non-constant 'formatterClass' binding!"];
428 className = [assoc stringValueInComponent:nil];
429 clazz = NSClassFromString(className);
431 if ((assoc = [OWGetProperty(_config, @"format") autorelease])) {
432 NSString *format = nil;
434 if (![assoc isValueConstant])
435 [self logWithFormat:@"non-constant 'format' binding!"];
436 format = [assoc stringValueInComponent:nil];
438 if ([clazz instancesRespondToSelector:@selector(initWithString:)])
439 fmt = [[clazz alloc] initWithString:format];
442 @"cannot instantiate formatter with format: '%@'", format];
443 fmt = [[clazz alloc] init];
447 fmt = [[clazz alloc] init];
449 self->formatter = [[WOAssociation associationWithValue:fmt] retain];
454 if (self->escapeHTML == nil)
455 self->escapeHTML = [yesAssoc retain];
460 if (self->formatter) num++;
461 if (self->numberformat) num++;
462 if (self->dateformat) num++;
464 NSLog(@"WARNING: more than one formats specified in element %@", self);
470 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
471 if ((self = [super initWithName:nil associations:nil template:nil])) {
472 self->value = [_value retain];
473 self->escapeHTML = _flag
475 : [WOAssociation associationWithValue:[NSNumber numberWithBool:NO]];
476 self->escapeHTML = [self->escapeHTML retain];
482 [self->numberformat release];
483 [self->dateformat release];
484 [self->formatter release];
485 [self->nilString release];
486 [self->valueWhenEmpty release];
487 [self->value release];
488 [self->escapeHTML release];
489 [self->insertBR release];
490 [self->style release];
494 /* response generation */
496 - (void)_appendStringLines:(NSString *)_s withSeparator:(NSString *)_br
497 contentSelector:(SEL)_sel
498 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
503 lines = [_s componentsSeparatedByString:@"\n"];
505 for (i = 0, count = [lines count]; i < count; i++) {
508 line = [lines objectAtIndex:i];
509 if (i != 0) WOResponse_AddString(_response, _br);
511 [_response performSelector:_sel withObject:line];
515 - (NSFormatter *)_formatterInContext:(WOContext *)_ctx {
516 if (self->numberformat) {
517 NSNumberFormatter *fmt;
519 fmt = [[[NSNumberFormatter alloc] init] autorelease];
520 [fmt setFormat:[self->numberformat valueInComponent:[_ctx component]]];
524 if (self->dateformat) {
525 NSDateFormatter *fmt;
528 s = [self->dateformat valueInComponent:[_ctx component]];
529 fmt = [[NSDateFormatter alloc] initWithDateFormat:s
530 allowNaturalLanguage:NO];
531 return [fmt autorelease];
535 return [self->formatter valueInComponent:[_ctx component]];
540 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
541 WOComponent *sComponent = [_ctx component];
547 fmt = [self _formatterInContext:_ctx];
549 if (fmt!=nil && ![fmt respondsToSelector:@selector(stringForObjectValue:)]) {
550 [sComponent logWithFormat:
551 @"invalid formatter determined by keypath %@: %@",
552 self->formatter, fmt];
556 obj = [self->value valueInContext:_ctx];
557 addSel = [self->escapeHTML boolValueInComponent:sComponent]
558 ? @selector(appendContentHTMLString:)
559 : @selector(appendContentString:);
562 NSString *formattedObj;
564 formattedObj = [fmt stringForObjectValue:obj];
566 if (formattedObj == nil) {
567 NSLog(@"WARNING: formatter %@ returned nil string for object %@",
575 obj = [obj stringValue];
577 styleName = [self->style stringValueInContext:_ctx];
579 /* handling of empty and nil values */
581 if (self->valueWhenEmpty) {
582 if (obj == nil || [obj length] == 0) {
585 s = [self->valueWhenEmpty stringValueInComponent:sComponent];
587 /* Note: the missing escaping is intentional for WO 4.5 compatibility */
590 [_response appendContentString:@"<span class=\""];
591 [_response appendContentHTMLAttributeValue:styleName];
592 [_response appendContentString:@"\">"];
594 [_response appendContentString:s];
599 else if (self->nilString != nil && obj == nil) {
603 [_response appendContentString:@"<span class=\""];
604 [_response appendContentHTMLAttributeValue:styleName];
605 [_response appendContentString:@"\">"];
607 s = [self->nilString stringValueInComponent:sComponent];
608 [_response performSelector:addSel withObject:s];
614 /* handling of non-empty values */
617 [_response appendContentString:@"<span class=\""];
618 [_response appendContentHTMLAttributeValue:styleName];
619 [_response appendContentString:@"\">"];
622 if (![self->insertBR boolValueInComponent:sComponent]) {
623 [_response performSelector:addSel withObject:obj];
626 [self _appendStringLines:obj withSeparator:@"<br />"
627 contentSelector:addSel toResponse:_response inContext:_ctx];
632 [_response appendContentString:@"</span>"];
638 - (NSString *)associationDescription {
639 NSMutableString *str;
641 str = [NSMutableString stringWithCapacity:64];
643 if (self->value) [str appendFormat:@" value=%@", self->value];
644 if (self->escapeHTML) [str appendFormat:@" escape=%@", self->escapeHTML];
645 if (self->insertBR) [str appendFormat:@" insertBR=%@", self->insertBR];
646 if (self->formatter) [str appendFormat:@" formatter=%@", self->formatter];
647 if (self->dateformat) [str appendFormat:@" dateformat=%@", self->dateformat];
648 if (self->numberformat)
649 [str appendFormat:@" numberformat=%@", self->numberformat];
651 if (self->valueWhenEmpty)
652 [str appendFormat:@" valueWhenEmpty=%@", self->valueWhenEmpty];
654 [str appendFormat:@" style=%@", self->style];
659 @end /* _WOComplexString */
661 @implementation _WOTemporaryString
663 static Class ComplexStringClass = Nil;
664 static Class SimpleStringClass = Nil;
665 static Class SimpleASCIIStringClass = Nil;
666 static Class SimpleDynStringClass = Nil;
668 #define ENSURE_CACHE \
669 if (ComplexStringClass == Nil)\
670 ComplexStringClass = [_WOComplexString class];\
671 if (SimpleStringClass == Nil)\
672 SimpleStringClass = [_WOSimpleStaticString class];\
673 if (SimpleASCIIStringClass == Nil)\
674 SimpleASCIIStringClass = [_WOSimpleStaticASCIIString class];\
675 if (SimpleDynStringClass == Nil)\
676 SimpleDynStringClass = [_WOSimpleDynamicString class];
678 static inline Class _classForConfig(NSDictionary *_config) {
680 WOAssociation *assoc, *assoc2;
683 switch ((c = [_config count])) {
685 sClass = SimpleStringClass;
689 if ((assoc = [_config objectForKey:@"value"])) {
690 if ([assoc isValueConstant])
691 sClass = SimpleStringClass;
693 sClass = SimpleDynStringClass;
696 if ((assoc = [_config objectForKey:@"escapeHTML"])) {
697 if ([assoc isValueConstant])
698 sClass = SimpleStringClass;
704 if ((assoc = [_config objectForKey:@"value"])) {
705 if ((assoc2 = [_config objectForKey:@"escapeHTML"])) {
706 if ([assoc isValueConstant] && [assoc2 isValueConstant])
707 sClass = SimpleStringClass;
715 sClass = ComplexStringClass;
719 return sClass ? sClass : ComplexStringClass;
722 - (id)initWithName:(NSString *)_n
723 associations:(NSDictionary *)_config
724 template:(WOElement *)_t
731 NSLog(@"WARNING: WOString '%@' has contents !", _n);
736 stringClass = _classForConfig(_config);
738 return [[stringClass alloc]
739 initWithName:_n associations:_config template:nil];
741 - (id)initWithName:(NSString *)_name
742 associations:(NSDictionary *)_associations
743 contentElements:(NSArray *)_contents
749 if ([_contents count] > 0) {
750 NSLog(@"WARNING: WOString has contents !");
755 stringClass = _classForConfig(_associations);
757 return [[stringClass alloc]
759 associations:_associations
760 contentElements:nil];
763 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
767 if ((_value == nil) || [_value isValueConstant])
768 stringClass = SimpleStringClass;
770 stringClass = ComplexStringClass;
772 return [[stringClass alloc] initWithValue:_value escapeHTML:_flag];
776 NSLog(@"ERROR: called dealloc on %@", self);
783 @end /* _WOTemporaryString */