2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "WOHTMLDynamicElement.h"
23 #include "WOElement+private.h"
25 #import <Foundation/NSNumberFormatter.h>
26 #import <Foundation/NSDateFormatter.h>
31 value = myCalendarDate;
32 dateformat = "%B %Y %T";
34 nilString = "no date is set !";
42 _WOSimpleStaticASCIIString
46 @interface WOString : WOHTMLDynamicElement
49 @interface _WOTemporaryString : NSObject
52 @interface _WOSimpleStaticString : WOString
56 @end /* WOSimpleStaticString */
58 @interface _WOSimpleStaticASCIIString : WOString
60 const unsigned char *value;
62 @end /* _WOSimpleStaticASCIIString */
64 @interface _WOSimpleDynamicString : WOString
68 @end /* WOSimpleStaticString */
70 @interface _WOComplexString : WOString
72 WOAssociation *value; // object
73 WOAssociation *escapeHTML; // BOOL
74 WOAssociation *numberformat; // string
75 WOAssociation *dateformat; // string
76 WOAssociation *formatter; // WO4: NSFormatter object
77 WOAssociation *valueWhenEmpty; // WO4.5
80 WOAssociation *insertBR; // insert <BR> tags for newlines
81 WOAssociation *nilString; // string to use if value is nil - DEPRECATED!
82 WOAssociation *style; // insert surrounding <span class="style">
85 @end /* WOComplexString */
87 @implementation WOString
90 return [super version] + 1 /* v3 */;
93 NSAssert2([super version] == 2,
94 @"invalid superclass (%@) version %i !",
95 NSStringFromClass([self superclass]), [super version]);
98 + (id)allocWithZone:(NSZone *)zone {
99 static Class WOStringClass = Nil;
100 static _WOTemporaryString *temporaryString = nil;
102 if (WOStringClass == Nil)
103 WOStringClass = [WOString class];
104 if (temporaryString == nil)
105 temporaryString = [_WOTemporaryString allocWithZone:zone];
107 return (self == WOStringClass)
108 ? (id)temporaryString
109 : NSAllocateObject(self, 0, zone);
114 @implementation _WOSimpleDynamicString
116 - (id)initWithName:(NSString *)_name
117 associations:(NSDictionary *)_config
118 template:(WOElement *)_t
120 if ((self = [super initWithName:_name associations:_config template:_t])) {
121 self->value = OWGetProperty(_config, @"value");
127 [self->value release];
131 /* generating response */
133 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
134 [_response appendContentHTMLString:[self->value stringValueInContext:_ctx]];
139 - (NSString *)associationDescription {
140 return [NSString stringWithFormat:@" value='%@'", self->value];
143 @end /* _WOSimpleDynamicString */
145 @implementation _WOSimpleStaticString
147 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
148 if ((self = [super initWithName:nil associations:nil template:nil])) {
153 v = [_value stringValueInComponent:nil];
154 length = [v cStringLength];
160 char buffer[length * 6]; /* longest-encoding: '"' */
161 unsigned clen = [v cStringLength];
165 register char *bufPtr;
167 #if NeXT_Foundation_LIBRARY
168 cbuf = malloc(clen + 1);
170 cbuf = NGMallocAtomic(clen + 1);
172 [v getCString:cbuf]; cbuf[clen] = '\0';
175 for (i = 0, bufPtr = buffer; i < length; i++) {
177 case '&': /* & */
178 bufPtr[0] = '&'; bufPtr[1] = 'a'; bufPtr[2] = 'm'; bufPtr[3] = 'p';
184 bufPtr[0] = '&'; bufPtr[1] = 'l'; bufPtr[2] = 't'; bufPtr[3] = ';';
189 bufPtr[0] = '&'; bufPtr[1] = 'g'; bufPtr[2] = 't'; bufPtr[3] = ';';
193 case '"': /* " */
194 bufPtr[0] = '&'; bufPtr[1] = 'q'; bufPtr[2] = 'u';
195 bufPtr[3] = 'o'; bufPtr[4] = 't'; bufPtr[5] = ';';
205 #if NeXT_Foundation_LIBRARY
206 if (cbuf) free(cbuf);
208 if (cbuf) NGFree(cbuf);
210 self->value = [[NSString allocWithZone:[self zone]]
211 initWithCString:&(buffer[0])
212 length:(bufPtr - &(buffer[0]))];
215 self->value = [v copyWithZone:[self zone]];
221 - (id)initWithName:(NSString *)_name
222 associations:(NSDictionary *)_config
223 template:(WOElement *)_t
225 if ((self = [super initWithName:_name associations:_config template:_t])) {
226 WOAssociation *avalue, *aescape;
229 avalue = OWGetProperty(_config, @"value");
230 aescape = OWGetProperty(_config, @"escapeHTML");
232 doEscape = (aescape != nil) ? [aescape boolValueInComponent:nil] : YES;
233 self = [self initWithValue:avalue escapeHTML:doEscape];
239 [self->value release];
243 /* generating response */
245 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
246 WOResponse_AddString(_response, self->value);
251 - (NSString *)associationDescription {
252 return [NSString stringWithFormat:@" value='%@'", self->value];
255 @end /* WOSimpleStaticString */
257 @implementation _WOSimpleStaticASCIIString
259 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
260 if ((self = [super initWithName:nil associations:nil template:nil])) {
265 v = [_value stringValueInComponent:nil];
266 length = [v cStringLength];
272 unsigned char *buffer;
273 register unsigned char *cbuf;
274 register unsigned char *bufPtr;
279 clen = [v cStringLength];
280 cbuf = malloc(clen + 2);
281 [v getCString:cbuf]; cbuf[clen] = '\0';
283 buffer = malloc(clen * 6 + 2); /* longest-encoding: '"' */
285 for (i = 0, bufPtr = buffer; i < length; i++) {
287 case '&': /* & */
288 bufPtr[0] = '&'; bufPtr[1] = 'a'; bufPtr[2] = 'm'; bufPtr[3] = 'p';
295 bufPtr[0] = '&'; bufPtr[1] = 'l'; bufPtr[2] = 't'; bufPtr[3] = ';';
301 bufPtr[0] = '&'; bufPtr[1] = 'g'; bufPtr[2] = 't'; bufPtr[3] = ';';
306 case '"': /* " */
307 bufPtr[0] = '&'; bufPtr[1] = 'q'; bufPtr[2] = 'u';
308 bufPtr[3] = 'o'; bufPtr[4] = 't'; bufPtr[5] = ';';
314 if ((*bufPtr = cbuf[i]) > 127) {
315 NSLog(@"WARNING: string is not ASCII as required for "
316 @"SimpleStaticASCIIString !!! '%@'", v);
323 if (cbuf) free(cbuf);
324 clen = (bufPtr - buffer);
325 self->value = malloc(clen + 2);
326 strncpy((char *)self->value, buffer, clen);
327 ((unsigned char *)self->value)[clen] = '\0';
332 if (buffer) free(buffer);
335 self->value = malloc(length + 2);
336 [v getCString:(char*)self->value];
342 - (id)initWithName:(NSString *)_name
343 associations:(NSDictionary *)_config
344 template:(WOElement *)_t
346 if ((self = [super initWithName:_name associations:_config template:_t])) {
347 WOAssociation *avalue, *aescape;
350 avalue = OWGetProperty(_config, @"value");
351 aescape = OWGetProperty(_config, @"escapeHTML");
353 doEscape = (aescape != nil) ? [aescape boolValueInComponent:nil] : YES;
354 self = [self initWithValue:avalue escapeHTML:doEscape];
360 if (self->value) free((char *)self->value);
364 /* generating response */
366 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
367 if (self->value) WOResponse_AddCString(_response, self->value);
372 - (NSString *)associationDescription {
373 return [NSString stringWithFormat:@" value='%s'",
374 self->value ? self->value : (void*)""];
377 @end /* WOSimpleStaticASCIIString */
379 @implementation _WOComplexString
381 static NSNumber *yesNum = nil;
382 static WOAssociation *yesAssoc = nil;
385 if (yesNum == nil) yesNum = [[NSNumber numberWithBool:YES] retain];
387 yesAssoc = [[WOAssociation associationWithValue:yesNum] retain];
390 - (id)initWithName:(NSString *)_name
391 associations:(NSDictionary *)_config
392 template:(WOElement *)_t
394 if ((self = [super initWithName:_name associations:_config template:_t])) {
395 self->value = OWGetProperty(_config, @"value");
396 self->escapeHTML = OWGetProperty(_config, @"escapeHTML");
397 self->insertBR = OWGetProperty(_config, @"insertBR");
398 self->nilString = OWGetProperty(_config, @"nilString");
399 self->valueWhenEmpty = OWGetProperty(_config, @"valueWhenEmpty");
400 self->style = OWGetProperty(_config, @"style");
402 if (self->nilString != nil && self->valueWhenEmpty != nil) {
404 @"WARNING: 'valueWhenEmpty' AND 'nilString' bindings are set, "
405 @"use only one! ('nilString' is deprecated!)"];
407 else if (self->nilString != nil) {
408 [self debugWithFormat:
409 @"Note: using deprecated 'nilString' binding, "
410 @"use 'valueWhenEmpty' instead."];
413 self->formatter = OWGetProperty(_config, @"formatter");
414 self->numberformat = OWGetProperty(_config, @"numberformat");
415 self->dateformat = OWGetProperty(_config, @"dateformat");
417 if (self->formatter == nil) {
418 if ([_config objectForKey:@"formatterClass"]) {
419 WOAssociation *assoc;
421 NSFormatter *fmt = nil;
424 assoc = [OWGetProperty(_config, @"formatterClass") autorelease];
425 if (![assoc isValueConstant])
426 [self logWithFormat:@"non-constant 'formatterClass' binding!"];
427 className = [assoc stringValueInComponent:nil];
428 clazz = NSClassFromString(className);
430 if ((assoc = [OWGetProperty(_config, @"format") autorelease])) {
431 NSString *format = nil;
433 if (![assoc isValueConstant])
434 [self logWithFormat:@"non-constant 'format' binding!"];
435 format = [assoc stringValueInComponent:nil];
437 if ([clazz instancesRespondToSelector:@selector(initWithString:)])
438 fmt = [[clazz alloc] initWithString:format];
441 @"cannot instantiate formatter with format: '%@'", format];
442 fmt = [[clazz alloc] init];
446 fmt = [[clazz alloc] init];
448 self->formatter = [[WOAssociation associationWithValue:fmt] retain];
453 if (self->escapeHTML == nil)
454 self->escapeHTML = [yesAssoc retain];
459 if (self->formatter) num++;
460 if (self->numberformat) num++;
461 if (self->dateformat) num++;
463 NSLog(@"WARNING: more than one formats specified in element %@", self);
469 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
470 if ((self = [super initWithName:nil associations:nil template:nil])) {
471 self->value = [_value retain];
472 self->escapeHTML = _flag
474 : [WOAssociation associationWithValue:[NSNumber numberWithBool:NO]];
475 self->escapeHTML = [self->escapeHTML retain];
481 [self->numberformat release];
482 [self->dateformat release];
483 [self->formatter release];
484 [self->nilString release];
485 [self->valueWhenEmpty release];
486 [self->value release];
487 [self->escapeHTML release];
488 [self->insertBR release];
489 [self->style release];
493 /* response generation */
495 - (void)_appendStringLines:(NSString *)_s withSeparator:(NSString *)_br
496 contentSelector:(SEL)_sel
497 toResponse:(WOResponse *)_response inContext:(WOContext *)_ctx
502 lines = [_s componentsSeparatedByString:@"\n"];
504 for (i = 0, count = [lines count]; i < count; i++) {
507 line = [lines objectAtIndex:i];
508 if (i != 0) WOResponse_AddString(_response, _br);
510 [_response performSelector:_sel withObject:line];
514 - (NSFormatter *)_formatterInContext:(WOContext *)_ctx {
515 if (self->numberformat) {
516 NSNumberFormatter *fmt;
518 fmt = [[[NSNumberFormatter alloc] init] autorelease];
519 [fmt setFormat:[self->numberformat valueInComponent:[_ctx component]]];
523 if (self->dateformat) {
524 NSDateFormatter *fmt;
527 s = [self->dateformat valueInComponent:[_ctx component]];
528 fmt = [[NSDateFormatter alloc] initWithDateFormat:s
529 allowNaturalLanguage:NO];
530 return [fmt autorelease];
534 return [self->formatter valueInComponent:[_ctx component]];
539 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
540 WOComponent *sComponent = [_ctx component];
546 fmt = [self _formatterInContext:_ctx];
548 if (fmt!=nil && ![fmt respondsToSelector:@selector(stringForObjectValue:)]) {
549 [sComponent logWithFormat:
550 @"invalid formatter determined by keypath %@: %@",
551 self->formatter, fmt];
555 obj = [self->value valueInContext:_ctx];
556 addSel = [self->escapeHTML boolValueInComponent:sComponent]
557 ? @selector(appendContentHTMLString:)
558 : @selector(appendContentString:);
561 NSString *formattedObj;
563 formattedObj = [fmt stringForObjectValue:obj];
565 if (formattedObj == nil) {
566 NSLog(@"WARNING: formatter %@ returned nil string for object %@",
574 obj = [obj stringValue];
576 styleName = [self->style stringValueInContext:_ctx];
578 /* handling of empty and nil values */
580 if (self->valueWhenEmpty) {
581 if (obj == nil || [obj length] == 0) {
584 s = [self->valueWhenEmpty stringValueInComponent:sComponent];
586 /* Note: the missing escaping is intentional for WO 4.5 compatibility */
589 [_response appendContentString:@"<span class=\""];
590 [_response appendContentHTMLAttributeValue:styleName];
591 [_response appendContentString:@"\">"];
593 [_response appendContentString:s];
598 else if (self->nilString != nil && obj == nil) {
602 [_response appendContentString:@"<span class=\""];
603 [_response appendContentHTMLAttributeValue:styleName];
604 [_response appendContentString:@"\">"];
606 s = [self->nilString stringValueInComponent:sComponent];
607 [_response performSelector:addSel withObject:s];
613 /* handling of non-empty values */
616 [_response appendContentString:@"<span class=\""];
617 [_response appendContentHTMLAttributeValue:styleName];
618 [_response appendContentString:@"\">"];
621 if (![self->insertBR boolValueInComponent:sComponent]) {
622 [_response performSelector:addSel withObject:obj];
625 [self _appendStringLines:obj withSeparator:@"<br />"
626 contentSelector:addSel toResponse:_response inContext:_ctx];
631 [_response appendContentString:@"</span>"];
637 - (NSString *)associationDescription {
638 NSMutableString *str;
640 str = [NSMutableString stringWithCapacity:64];
642 if (self->value) [str appendFormat:@" value=%@", self->value];
643 if (self->escapeHTML) [str appendFormat:@" escape=%@", self->escapeHTML];
644 if (self->insertBR) [str appendFormat:@" insertBR=%@", self->insertBR];
645 if (self->formatter) [str appendFormat:@" formatter=%@", self->formatter];
646 if (self->dateformat) [str appendFormat:@" dateformat=%@", self->dateformat];
647 if (self->numberformat)
648 [str appendFormat:@" numberformat=%@", self->numberformat];
650 if (self->valueWhenEmpty)
651 [str appendFormat:@" valueWhenEmpty=%@", self->valueWhenEmpty];
653 [str appendFormat:@" style=%@", self->style];
658 @end /* _WOComplexString */
660 @implementation _WOTemporaryString
662 static Class ComplexStringClass = Nil;
663 static Class SimpleStringClass = Nil;
664 static Class SimpleASCIIStringClass = Nil;
665 static Class SimpleDynStringClass = Nil;
667 #define ENSURE_CACHE \
668 if (ComplexStringClass == Nil)\
669 ComplexStringClass = [_WOComplexString class];\
670 if (SimpleStringClass == Nil)\
671 SimpleStringClass = [_WOSimpleStaticString class];\
672 if (SimpleASCIIStringClass == Nil)\
673 SimpleASCIIStringClass = [_WOSimpleStaticASCIIString class];\
674 if (SimpleDynStringClass == Nil)\
675 SimpleDynStringClass = [_WOSimpleDynamicString class];
677 static inline Class _classForConfig(NSDictionary *_config) {
679 WOAssociation *assoc, *assoc2;
682 switch ((c = [_config count])) {
684 sClass = SimpleStringClass;
688 if ((assoc = [_config objectForKey:@"value"])) {
689 if ([assoc isValueConstant])
690 sClass = SimpleStringClass;
692 sClass = SimpleDynStringClass;
695 if ((assoc = [_config objectForKey:@"escapeHTML"])) {
696 if ([assoc isValueConstant])
697 sClass = SimpleStringClass;
703 if ((assoc = [_config objectForKey:@"value"])) {
704 if ((assoc2 = [_config objectForKey:@"escapeHTML"])) {
705 if ([assoc isValueConstant] && [assoc2 isValueConstant])
706 sClass = SimpleStringClass;
714 sClass = ComplexStringClass;
718 return sClass ? sClass : ComplexStringClass;
721 - (id)initWithName:(NSString *)_n
722 associations:(NSDictionary *)_config
723 template:(WOElement *)_t
730 NSLog(@"WARNING: WOString '%@' has contents !", _n);
735 stringClass = _classForConfig(_config);
737 return [[stringClass alloc]
738 initWithName:_n associations:_config template:nil];
740 - (id)initWithName:(NSString *)_name
741 associations:(NSDictionary *)_associations
742 contentElements:(NSArray *)_contents
748 if ([_contents count] > 0) {
749 NSLog(@"WARNING: WOString has contents !");
754 stringClass = _classForConfig(_associations);
756 return [[stringClass alloc]
758 associations:_associations
759 contentElements:nil];
762 - (id)initWithValue:(WOAssociation *)_value escapeHTML:(BOOL)_flag {
766 if ((_value == nil) || [_value isValueConstant])
767 stringClass = SimpleStringClass;
769 stringClass = ComplexStringClass;
771 return [[stringClass alloc] initWithValue:_value escapeHTML:_flag];
775 [self errorWithFormat:@"called dealloc on %@", self];
782 @end /* _WOTemporaryString */