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 <NGObjWeb/WOxElemBuilder.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"
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;
39 @interface WOAssociation(misc)
40 - (id)initWithScript:(NSString *)_script language:(NSString *)_lang;
43 @implementation WOxElemBuilderComponentInfo
45 - (id)initWithComponentId:(NSString *)_cid
46 componentName:(NSString *)_name
47 bindings:(NSMutableDictionary *)_bindings
49 self->cid = [_cid copy];
50 self->pageName = [_name copy];
51 self->bindings = [_bindings retain];
56 [self->pageName release];
57 [self->bindings release];
63 - (NSString *)componentId {
67 - (NSString *)pageName {
68 return self->pageName;
71 - (NSMutableDictionary *)bindings {
72 return self->bindings;
77 - (id)instantiateWithResourceManager:(WOResourceManager *)_rm
78 languages:(NSArray *)_languages
80 static Class FaultClass = Nil;
81 WOComponentFault *fault;
83 if (FaultClass == Nil)
84 FaultClass = [WOComponentFault class];
86 fault = [FaultClass alloc];
87 NSAssert1(fault, @"couldn't allocated object of class '%@' ..", FaultClass);
89 fault = [fault initWithResourceManager:_rm
90 pageName:self->pageName
92 bindings:self->bindings];
96 @end /* SxElementBuilderComponentInfo */
98 @implementation WOxElemBuilder
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;
118 static BOOL didInit = NO;
123 ud = [NSUserDefaults standardUserDefaults];
124 lm = [NGLoggerManager defaultLoggerManager];
126 logger = [lm loggerForClass:self];
127 [logger setLogLevel:[WOApplication isDebuggingEnabled] ? NGLogLevelDebug
130 StrClass = NSClassFromString(@"_WOSimpleStaticString");
132 [logger errorWithFormat:@"missing class _WOSimpleStaticString !"];
133 AStrClass = NSClassFromString(@"_WOSimpleStaticASCIIString");
134 if (AStrClass == Nil)
135 [logger errorWithFormat:@"missing class _WOSimpleStaticASCIIString !"];
137 logAssocMap = [ud boolForKey:@"WOxElemBuilder_LogAssociationMapping"];
139 [ud boolForKey:@"WOxElemBuilder_LogAssociationCreation"];
141 [logger logWithFormat:@"association mapping is logged!"];
142 if (logAssocCreation)
143 [logger logWithFormat:@"association creation is logged!"];
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!"];
152 ValAssoc = NSClassFromString(@"WOValueAssociation");
154 CompoundElemClass = NSClassFromString(@"WOCompoundElement");
157 yesNum = [[NSNumber numberWithBool:YES] retain];
159 yesAssoc = [[WOAssociation associationWithValue:yesNum] retain];
162 + (WOxElemBuilder *)createBuilderQueue:(NSArray *)_classNames {
164 WOxElemBuilder *first, *current = nil;
165 NSMutableArray *missingBuilders = nil;
167 if ((count = [_classNames count]) == 0)
170 for (first = nil, i = 0; i < count; i++) {
175 cn = [_classNames objectAtIndex:i];
177 NSLog(@"builder class: %@", cn);
180 if ((clazz = NSClassFromString(cn)) == Nil) {
181 if (missingBuilders == nil)
182 missingBuilders = [NSMutableArray arrayWithCapacity:16];
183 [missingBuilders addObject:cn];
187 if ((nx = [[clazz alloc] init])) {
189 first = current = nx;
193 [current setNextBuilder:nx];
194 current = [nx autorelease];
198 NSLog(@"%s: couldn't allocate builder (class=%@)", cn);
203 if (missingBuilders) {
204 NSLog(@"WOxElemBuilder: could not locate builders: %@",
205 [missingBuilders componentsJoinedByString:@","]);
210 + (WOxElemBuilder *)createBuilderQueueV:(NSString *)_className, ... {
211 // TODO: reimplement using createBuilderQueue:
214 WOxElemBuilder *first, *current;
216 if (_className == nil)
217 return [[[self alloc] init] autorelease];
219 first = [[[NSClassFromString(_className) alloc] init] autorelease];
221 va_start(ap, _className);
222 for (current = first; (cn = va_arg(ap, id)); ) {
225 nx = [[NSClassFromString(cn) alloc] init];
226 [current setNextBuilder:nx];
227 current = [nx autorelease];
235 [self->script release];
236 [self->subcomponentInfos release];
237 [self->nsToAssoc release];
238 [self->nextBuilder release];
242 /* building an element (returns a retained object !!!) */
244 - (WOElement *)buildNode:(id<DOMNode>)_node templateBuilder:(id)_builder {
248 switch ([_node nodeType]) {
249 case DOM_ELEMENT_NODE:
250 return [self buildElement:(id<DOMElement>)_node
251 templateBuilder:_builder];
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];
266 if (self->nextBuilder)
267 return [self->nextBuilder buildNode:_node templateBuilder:_builder];
269 NSLog(@"unknown node type %i, node %@", [_node nodeType], _node);
275 - (NSArray *)buildNodes:(id<DOMNodeList>)_nodes templateBuilder:(id)_bld {
276 // Note: returns a regular autoreleased array
277 NSMutableArray *children;
280 if ((count = [_nodes length]) == 0)
283 children = [NSMutableArray arrayWithCapacity:(count + 1)];
285 for (i = 0; i < count; i++) {
288 e = [_bld buildNode:[_nodes objectAtIndex:i] templateBuilder:_bld];
290 [children addObject:e];
297 /* building methods specialized on type (return retained objects !!!) */
299 - (WOElement *)buildDocument:(id<DOMDocument>)_node templateBuilder:(id)_bld {
300 return [self buildElement:[_node documentElement] templateBuilder:_bld];
303 - (WOElement *)buildElement:(id<DOMElement>)_node templateBuilder:(id)_bld {
304 if (self->nextBuilder)
305 return [self->nextBuilder buildElement:_node templateBuilder:_bld];
307 [self logWithFormat:@"cannot build node %@ (template builder %@)",
312 - (WOElement *)buildCharacterData:(id<DOMCharacterData>)_text
313 templateBuilder:(id)_builder
315 static Class ValClass = Nil;
316 WOElement *textElement;
322 if ((len = [str length]) == 0) return nil;
325 we use WOValueAssociation directly, because WOAssociation caches all
329 ValClass = NSClassFromString(@"WOValueAssociation");
332 # warning not using ASCII string !
336 // TODO(perf): improve on that
337 /* not very efficient, but only used during template parsing ... */
338 if ([str dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:NO])
344 isASCII = ([str characterAtIndex:0] < 128) ? YES : NO;
348 str = [[ValClass alloc] initWithString:str];
350 [[(isASCII?AStrClass:StrClass) alloc] initWithValue:str escapeHTML:YES];
354 - (WOElement *)buildText:(id<DOMText>)_node
355 templateBuilder:(id)_builder
357 return [self buildCharacterData:_node templateBuilder:_builder];
359 - (WOElement *)buildCDATASection:(id<DOMCDATASection>)_node
360 templateBuilder:(id)_builder
362 return [self buildCharacterData:_node templateBuilder:_builder];
365 - (WOElement *)buildComment:(id<DOMComment>)_node
366 templateBuilder:(id)_builder
368 /* comments aren't delivered ... */
372 /* building the whole template */
374 - (WOElement *)buildTemplateFromDocument:(id<DOMDocument>)_document {
375 NSAutoreleasePool *pool;
378 pool = [[NSAutoreleasePool alloc] init];
379 result = [[self buildNode:_document templateBuilder:self] retain];
381 return [result autorelease];
384 /* association callbacks */
386 - (WOAssociation *)associationForValue:(id)_value {
387 return [WOAssociation associationWithValue:_value];
390 - (WOAssociation *)associationForKeyPath:(NSString *)_path {
391 return [WOAssociation associationWithKeyPath:_path];
394 - (WOAssociation *)associationForJavaScript:(NSString *)_js {
395 WOAssociation *assoc;
397 assoc = [NSClassFromString(@"WOScriptAssociation") alloc];
398 assoc = [(id)assoc initWithScript:_js language:@"javascript"];
399 return [assoc autorelease];
402 - (WOAssociation *)associationForAttribute:(id<DOMAttr>)_attribute {
405 WOAssociation *assoc;
408 nsuri = [_attribute namespaceURI];
409 value = [_attribute nodeValue];
411 c = [self associationClassForNamespaceURI:[_attribute namespaceURI]];
413 [self warnWithFormat:
414 @"found no association class for attribute %@ (namespace=%@)",
415 _attribute, [_attribute namespaceURI]];
419 [self logWithFormat:@"use class %@ for namespaceURI %@ (attribute %@)",
420 c, [_attribute namespaceURI], [_attribute name]];
423 assoc = [[c alloc] initWithString:value];
424 if (logAssocCreation) {
425 [self logWithFormat:@"created assoc %@ for attribute %@",
426 assoc, [_attribute name]];
429 return [assoc autorelease];
432 - (NSMutableDictionary *)associationsForAttributes:(id<DOMNamedNodeMap>)_attrs{
433 NSMutableDictionary *assocs;
436 if ((count = [_attrs length]) == 0)
439 assocs = [NSMutableDictionary dictionaryWithCapacity:(count + 1)];
441 for (i = 0; i < count; i++) {
443 WOAssociation *assoc;
445 attr = [_attrs objectAtIndex:i];
447 if ((assoc = [self associationForAttribute:attr])) {
451 if ([key characterAtIndex:0] == '_')
452 key = [@"?" stringByAppendingString:[key substringFromIndex:1]];
454 [assocs setObject:assoc forKey:key];
460 - (void)_ensureDefaultAssocMappings {
467 self->nsToAssoc = [[NSMutableDictionary alloc] initWithCapacity:8];
468 e = [defaultAssocMap keyEnumerator];
469 while ((ns = [e nextObject]) != nil) {
473 className = [defaultAssocMap objectForKey:ns];
474 clazz = NSClassFromString(className);
477 [self warnWithFormat:@"did not find association class: '%@'",
483 [self->nsToAssoc setObject:clazz forKey:ns];
486 - (void)registerAssociationClass:(Class)_class forNamespaceURI:(NSString *)_ns{
487 if (_ns == nil) return;
488 if (_class == Nil) return;
490 [self _ensureDefaultAssocMappings];
491 [self->nsToAssoc setObject:_class forKey:_ns];
493 - (Class)associationClassForNamespaceURI:(NSString *)_ns {
496 [self _ensureDefaultAssocMappings];
498 if ((c = [self->nsToAssoc objectForKey:_ns]) == Nil)
499 /* if we have no class mapped for a namespace, we treat it as a value */
503 [self debugWithFormat:@"using class %@ for namespace %@", c, _ns];
507 /* creating unique IDs */
509 - (NSString *)uniqueIDForNode:(id)_node {
510 NSMutableArray *nodePath;
511 NSMutableString *uid;
512 NSEnumerator *topDown;
516 if (_node == nil) return nil;
518 nodePath = [NSMutableArray arrayWithCapacity:16];
520 /* collect all parent nodes in bottom-up form */
522 for (node = _node; node; node = [node parentNode])
523 [nodePath addObject:node];
527 uid = [NSMutableString stringWithCapacity:64];
528 topDown = [nodePath reverseObjectEnumerator];
532 for (isFirst = YES; (node = [topDown nextObject]); parent = node) {
537 [uid appendString:@"."];
539 /* determine index of _node */
541 children = (NSArray *)[parent childNodes];
542 for (i = 0, count = [children count]; i < count; i++) {
543 if ([children objectAtIndex:i] == node)
546 [uid appendFormat:@"%d", i];
549 [uid appendString:@"R"];
554 return [[uid copy] autorelease];
566 - (void)logWithFormat:(NSString *)_format, ... {
567 NSString *value = nil;
570 va_start(ap, _format);
571 value = [[NSString alloc] initWithFormat:_format arguments:ap];
574 NSLog(@"|%@| %@", self, value);
577 - (void)debugWithFormat:(NSString *)_format, ... {
578 static char showDebug = 2;
579 NSString *value = nil;
582 if (showDebug == 2) {
583 showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
587 va_start(ap, _format);
588 value = [[NSString alloc] initWithFormat:_format arguments:ap];
591 NSLog(@"|%@|D %@", self, value);
596 /* managing builder queues */
598 - (void)setNextBuilder:(WOxElemBuilder *)_builder {
599 ASSIGN(self->nextBuilder, _builder);
601 - (WOxElemBuilder *)nextBuilder {
602 return self->nextBuilder;
605 /* component script parts */
607 - (void)addComponentScriptPart:(WOComponentScriptPart *)_part {
608 if (self->script == nil)
609 self->script = [[WOComponentScript alloc] init];
611 [self->script addScriptPart:_part];
613 - (void)addComponentScript:(NSString *)_script line:(unsigned)_line {
614 WOComponentScriptPart *part;
616 part = [[WOComponentScriptPart alloc] initWithURL:nil startLine:_line
618 [self addComponentScriptPart:part];
622 - (WOComponentScript *)componentScript {
626 /* subcomponent registry, created during parsing ... */
628 - (void)registerSubComponentWithId:(NSString *)_cid
629 componentName:(NSString *)_name
630 bindings:(NSMutableDictionary *)_bindings
632 WOxElemBuilderComponentInfo *info;
634 info = [[WOxElemBuilderComponentInfo alloc] initWithComponentId:_cid
638 if (self->subcomponentInfos == nil)
639 self->subcomponentInfos = [[NSMutableArray alloc] initWithCapacity:16];
640 [self->subcomponentInfos addObject:info];
644 - (NSArray *)subcomponentInfos {
645 return self->subcomponentInfos;
649 [self->subcomponentInfos removeAllObjects];
650 [self->script release]; self->script = nil;
653 /* support methods for subclasses */
655 - (id<DOMElement>)lookupUniqueTag:(NSString *)_name
656 inElement:(id<DOMElement>)_elem
658 id<DOMNodeList> list;
660 if ((list = [_elem getElementsByTagName:_name]) == nil)
662 if ([list length] == 0)
664 if ([list length] > 1) {
665 [self warnWithFormat:
666 @"more than once occurence of tag %@ in element: %@",
669 return [list objectAtIndex:0];
672 - (WOElement *)elementForRawString:(NSString *)_rawstr {
673 /* Note: returns a retained element! */
676 if (_rawstr == nil) return nil;
677 a = [WOAssociation associationWithValue:_rawstr];
678 return [[StrClass alloc] initWithValue:a escapeHTML:NO];
681 - (WOElement *)elementForElementsAndStrings:(NSArray *)_elements {
682 /* Note: returns a retained element! */
687 if ((count = [_elements count]) == 0)
690 ma = [[NSMutableArray alloc] initWithCapacity:count];
691 for (i = 0; i < count; i++) {
694 elem = [_elements objectAtIndex:i];
695 if ([elem isKindOfClass:[WOElement class]]) {
700 if ((elem = [self elementForRawString:[elem stringValue]]))
703 if ((count = [ma count]) == 0)
705 else if (count == 1) {
706 element = [[ma objectAtIndex:0] retain];
709 element = [[CompoundElemClass allocForCount:count zone:NULL]
710 initWithContentElements:ma];
716 - (WOElement *)wrapElement:(WOElement *)_element
717 inCondition:(WOAssociation *)_condition
720 // NOTE: *releases* _element parameter!
721 // returns retained conditional
722 static Class WOConditionalClass = Nil;
723 static NSString *key = @"condition";
724 NSMutableDictionary *assocs;
728 if (WOConditionalClass == Nil)
729 WOConditionalClass = NSClassFromString(@"WOConditional");
733 if (_condition == nil)
737 assocs = [[NSMutableDictionary alloc]
738 initWithObjectsAndKeys:_condition, key,
739 yesAssoc, @"negate", nil];
742 assocs = [[NSMutableDictionary alloc] initWithObjects:&_condition
743 forKeys:&key count:1];
745 children = [[NSArray alloc] initWithObjects:&_element count:1];
746 element = [[WOConditionalClass alloc] initWithName:nil
748 contentElements:children];
755 - (WOElement *)wrapElements:(NSArray *)_sub inElementOfClass:(Class)_class {
761 element = [[_class alloc] initWithName:nil
763 contentElements:_sub];
767 - (WOElement *)wrapChildrenOfElement:(id<DOMElement>)_tag
768 inElementOfClass:(Class)_class
769 templateBuilder:(id)_b
773 children = [_tag hasChildNodes]
774 ? [_b buildNodes:[_tag childNodes] templateBuilder:_b]
777 return [self wrapElements:children inElementOfClass:_class];
780 @end /* WOxElemBuilder */