/* Copyright (C) 2000-2005 SKYRIX Software AG This file is part of SOPE. SOPE is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOPE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SOPE; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include "WOComponentFault.h" #include "common.h" @interface WOElement(UsedPrivates) - (id)initWithValue:(id)_value escapeHTML:(BOOL)_flag; @end @interface WOAssociation(misc) - (id)initWithScript:(NSString *)_script language:(NSString *)_lang; @end @implementation WOxElemBuilderComponentInfo - (id)initWithComponentId:(NSString *)_cid componentName:(NSString *)_name bindings:(NSMutableDictionary *)_bindings { self->cid = [_cid copy]; self->pageName = [_name copy]; self->bindings = [_bindings retain]; return self; } - (void)dealloc { [self->cid release]; [self->pageName release]; [self->bindings release]; [super dealloc]; } /* accessors */ - (NSString *)componentId { return self->cid; } - (NSString *)pageName { return self->pageName; } - (NSMutableDictionary *)bindings { return self->bindings; } /* operations */ - (id)instantiateWithResourceManager:(WOResourceManager *)_rm languages:(NSArray *)_languages { static Class FaultClass = Nil; WOComponentFault *fault; if (FaultClass == Nil) FaultClass = [WOComponentFault class]; fault = [FaultClass alloc]; NSAssert1(fault, @"couldn't allocated object of class '%@' ..", FaultClass); fault = [fault initWithResourceManager:_rm pageName:self->pageName languages:_languages bindings:self->bindings]; return (id)fault; } @end /* SxElementBuilderComponentInfo */ @implementation WOxElemBuilder static Class StrClass = Nil; static Class AStrClass = Nil; static NSDictionary *defaultAssocMap = nil; static Class ValAssoc = Nil; static BOOL logAssocMap = NO; static BOOL logAssocCreation = NO; static BOOL debugOn = NO; static NGLogger *logger = nil; + (int)version { return 1; } + (void)initialize { NSUserDefaults *ud; NGLoggerManager *lm; static BOOL didInit = NO; if (didInit) return; didInit = YES; ud = [NSUserDefaults standardUserDefaults]; lm = [NGLoggerManager defaultLoggerManager]; logger = [lm loggerForClass:self]; [logger setLogLevel:[WOApplication isDebuggingEnabled] ? NGLogLevelDebug : NGLogLevelInfo]; StrClass = NSClassFromString(@"_WOSimpleStaticString"); if (StrClass == Nil) [logger errorWithFormat:@"missing class _WOSimpleStaticString !"]; AStrClass = NSClassFromString(@"_WOSimpleStaticASCIIString"); if (AStrClass == Nil) [logger errorWithFormat:@"missing class _WOSimpleStaticASCIIString !"]; logAssocMap = [ud boolForKey:@"WOxElemBuilder_LogAssociationMapping"]; logAssocCreation = [ud boolForKey:@"WOxElemBuilder_LogAssociationCreation"]; if (logAssocMap) [logger logWithFormat:@"association mapping is logged!"]; if (logAssocCreation) [logger logWithFormat:@"association creation is logged!"]; defaultAssocMap = [[ud dictionaryForKey:@"WOxAssociationClassMapping"] copy]; if (defaultAssocMap == nil) [logger warnWithFormat: @"WOxAssociationClassMapping default is not set!"]; if (ValAssoc == Nil) ValAssoc = NSClassFromString(@"WOValueAssociation"); } + (WOxElemBuilder *)createBuilderQueue:(NSArray *)_classNames { unsigned i, count; WOxElemBuilder *first, *current = nil; NSMutableArray *missingBuilders = nil; if ((count = [_classNames count]) == 0) return nil; for (first = nil, i = 0; i < count; i++) { WOxElemBuilder *nx; NSString *cn; Class clazz; cn = [_classNames objectAtIndex:i]; #if 0 NSLog(@"builder class: %@", cn); #endif if ((clazz = NSClassFromString(cn)) == Nil) { if (missingBuilders == nil) missingBuilders = [NSMutableArray arrayWithCapacity:16]; [missingBuilders addObject:cn]; continue; } if ((nx = [[clazz alloc] init])) { if (first == nil) { first = current = nx; [nx autorelease]; } else { [current setNextBuilder:nx]; current = [nx autorelease]; } } else { NSLog(@"%s: couldn't allocate builder (class=%@)", cn); continue; } } if (missingBuilders) { NSLog(@"WOxElemBuilder: could not locate builders: %@", [missingBuilders componentsJoinedByString:@","]); } return first; } + (WOxElemBuilder *)createBuilderQueueV:(NSString *)_className, ... { // TODO: reimplement using createBuilderQueue: va_list ap; NSString *cn; WOxElemBuilder *first, *current; if (_className == nil) return [[[self alloc] init] autorelease]; first = [[[NSClassFromString(_className) alloc] init] autorelease]; va_start(ap, _className); for (current = first; (cn = va_arg(ap, id)); ) { WOxElemBuilder *nx; nx = [[NSClassFromString(cn) alloc] init]; [current setNextBuilder:nx]; current = [nx autorelease]; } va_end(ap); return first; } - (void)dealloc { [self->script release]; [self->subcomponentInfos release]; [self->nsToAssoc release]; [self->nextBuilder release]; [super dealloc]; } /* building an element (returns a retained object !!!) */ - (WOElement *)buildNode:(id)_node templateBuilder:(id)_builder { if (_node == nil) return nil; switch ([_node nodeType]) { case DOM_ELEMENT_NODE: return [self buildElement:(id)_node templateBuilder:_builder]; case DOM_TEXT_NODE: return [self buildText:(id)_node templateBuilder:_builder]; case DOM_CDATA_SECTION_NODE: return [self buildCDATASection:(id)_node templateBuilder:_builder]; case DOM_COMMENT_NODE: return [self buildComment:(id)_node templateBuilder:_builder]; case DOM_DOCUMENT_NODE: return [self buildDocument:(id)_node templateBuilder:_builder]; default: if (self->nextBuilder) return [self->nextBuilder buildNode:_node templateBuilder:_builder]; else { NSLog(@"unknown node type %i, node %@", [_node nodeType], _node); return nil; } } } - (NSArray *)buildNodes:(id)_nodes templateBuilder:(id)_bld { // Note: returns a regular autoreleased array NSMutableArray *children; unsigned i, count; if ((count = [_nodes length]) == 0) return nil; children = [NSMutableArray arrayWithCapacity:(count + 1)]; for (i = 0; i < count; i++) { WOElement *e; e = [_bld buildNode:[_nodes objectAtIndex:i] templateBuilder:_bld]; if (e) { [children addObject:e]; [e release]; } } return children; } /* building methods specialized on type (return retained objects !!!) */ - (WOElement *)buildDocument:(id)_node templateBuilder:(id)_bld { return [self buildElement:[_node documentElement] templateBuilder:_bld]; } - (WOElement *)buildElement:(id)_node templateBuilder:(id)_bld { if (self->nextBuilder) return [self->nextBuilder buildElement:_node templateBuilder:_bld]; [self logWithFormat:@"cannot build node %@ (template builder %@)", _node, _bld]; return nil; } - (WOElement *)buildCharacterData:(id)_text templateBuilder:(id)_builder { static Class ValClass = Nil; WOElement *textElement; unsigned len; BOOL isASCII = NO; id str; str = [_text data]; if ((len = [str length]) == 0) return nil; /* we use WOValueAssociation directly, because WOAssociation caches all values */ if (ValClass == Nil) ValClass = NSClassFromString(@"WOValueAssociation"); #if 0 # warning not using ASCII string ! isASCII = NO; #else if (len > 1) { // TODO(perf): improve on that /* not very efficient, but only used during template parsing ... */ if ([str dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:NO]) isASCII = YES; else isASCII = NO; } else { isASCII = ([str characterAtIndex:0] < 128) ? YES : NO; } #endif str = [[ValClass alloc] initWithString:str]; textElement = [[(isASCII?AStrClass:StrClass) alloc] initWithValue:str escapeHTML:YES]; [str release]; return textElement; } - (WOElement *)buildText:(id)_node templateBuilder:(id)_builder { return [self buildCharacterData:_node templateBuilder:_builder]; } - (WOElement *)buildCDATASection:(id)_node templateBuilder:(id)_builder { return [self buildCharacterData:_node templateBuilder:_builder]; } - (WOElement *)buildComment:(id)_node templateBuilder:(id)_builder { /* comments aren't delivered ... */ return nil; } /* building the whole template */ - (WOElement *)buildTemplateFromDocument:(id)_document { NSAutoreleasePool *pool; WOElement *result; pool = [[NSAutoreleasePool alloc] init]; result = [[self buildNode:_document templateBuilder:self] retain]; [pool release]; return [result autorelease]; } /* association callbacks */ - (WOAssociation *)associationForValue:(id)_value { return [WOAssociation associationWithValue:_value]; } - (WOAssociation *)associationForKeyPath:(NSString *)_path { return [WOAssociation associationWithKeyPath:_path]; } - (WOAssociation *)associationForJavaScript:(NSString *)_js { WOAssociation *assoc; assoc = [NSClassFromString(@"WOScriptAssociation") alloc]; assoc = [(id)assoc initWithScript:_js language:@"javascript"]; return [assoc autorelease]; } - (WOAssociation *)associationForAttribute:(id)_attribute { NSString *nsuri; NSString *value; WOAssociation *assoc; Class c; nsuri = [_attribute namespaceURI]; value = [_attribute nodeValue]; c = [self associationClassForNamespaceURI:[_attribute namespaceURI]]; if (c == Nil) { [self warnWithFormat: @"found no association class for attribute %@ (namespace=%@)", _attribute, [_attribute namespaceURI]]; return nil; } if (logAssocMap) { [self logWithFormat:@"use class %@ for namespaceURI %@ (attribute %@)", c, [_attribute namespaceURI], [_attribute name]]; } assoc = [[c alloc] initWithString:value]; if (logAssocCreation) { [self logWithFormat:@"created assoc %@ for attribute %@", assoc, [_attribute name]]; } return [assoc autorelease]; } - (NSMutableDictionary *)associationsForAttributes:(id)_attrs{ NSMutableDictionary *assocs; int i, count; if ((count = [_attrs length]) == 0) return nil; assocs = [NSMutableDictionary dictionaryWithCapacity:(count + 1)]; for (i = 0; i < count; i++) { id attr; WOAssociation *assoc; attr = [_attrs objectAtIndex:i]; if ((assoc = [self associationForAttribute:attr])) { NSString *key; key = [attr name]; if ([key characterAtIndex:0] == '_') key = [@"?" stringByAppendingString:[key substringFromIndex:1]]; [assocs setObject:assoc forKey:key]; } } return assocs; } - (void)_ensureDefaultAssocMappings { NSEnumerator *e; NSString *ns; if (self->nsToAssoc) return; self->nsToAssoc = [[NSMutableDictionary alloc] initWithCapacity:8]; e = [defaultAssocMap keyEnumerator]; while ((ns = [e nextObject])) { NSString *className; Class clazz; className = [defaultAssocMap objectForKey:ns]; clazz = NSClassFromString(className); if (clazz == Nil) { [self warnWithFormat:@"did not find association class: '%@'", className]; continue; } /* register */ [self->nsToAssoc setObject:clazz forKey:ns]; } } - (void)registerAssociationClass:(Class)_class forNamespaceURI:(NSString *)_ns{ if (_ns == nil) return; if (_class == Nil) return; [self _ensureDefaultAssocMappings]; [self->nsToAssoc setObject:_class forKey:_ns]; } - (Class)associationClassForNamespaceURI:(NSString *)_ns { Class c; [self _ensureDefaultAssocMappings]; if ((c = [self->nsToAssoc objectForKey:_ns]) == nil) /* if we have no class mapped for a namespace, we treat it as a value */ c = ValAssoc; if (debugOn) [self debugWithFormat:@"using class %@ for namespace %@", c, _ns]; return c; } /* creating unique IDs */ - (NSString *)uniqueIDForNode:(id)_node { NSMutableArray *nodePath; NSMutableString *uid; NSEnumerator *topDown; id node, parent; BOOL isFirst; if (_node == nil) return nil; nodePath = [NSMutableArray arrayWithCapacity:16]; /* collect all parent nodes in bottom-up form */ for (node = _node; node; node = [node parentNode]) [nodePath addObject:node]; /* generate ID */ uid = [NSMutableString stringWithCapacity:64]; topDown = [nodePath reverseObjectEnumerator]; isFirst = YES; parent = nil; for (isFirst = YES; (node = [topDown nextObject]); parent = node) { if (!isFirst) { NSArray *children; unsigned i, count; [uid appendString:@"."]; /* determine index of _node */ children = (NSArray *)[parent childNodes]; for (i = 0, count = [children count]; i < count; i++) { if ([children objectAtIndex:i] == node) break; } [uid appendFormat:@"%d", i]; } else { [uid appendString:@"R"]; isFirst = NO; } } return [[uid copy] autorelease]; } /* logging */ + (id)logger { return logger; } - (id)logger { return logger; } - (void)logWithFormat:(NSString *)_format, ... { NSString *value = nil; va_list ap; va_start(ap, _format); value = [[NSString alloc] initWithFormat:_format arguments:ap]; va_end(ap); NSLog(@"|%@| %@", self, value); [value release]; } - (void)debugWithFormat:(NSString *)_format, ... { static char showDebug = 2; NSString *value = nil; va_list ap; if (showDebug == 2) { showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0; } if (showDebug) { va_start(ap, _format); value = [[NSString alloc] initWithFormat:_format arguments:ap]; va_end(ap); NSLog(@"|%@|D %@", self, value); [value release]; } } /* managing builder queues */ - (void)setNextBuilder:(WOxElemBuilder *)_builder { ASSIGN(self->nextBuilder, _builder); } - (WOxElemBuilder *)nextBuilder { return self->nextBuilder; } /* component script parts */ - (void)addComponentScriptPart:(WOComponentScriptPart *)_part { if (self->script == nil) self->script = [[WOComponentScript alloc] init]; [self->script addScriptPart:_part]; } - (void)addComponentScript:(NSString *)_script line:(unsigned)_line { WOComponentScriptPart *part; part = [[WOComponentScriptPart alloc] initWithURL:nil startLine:_line script:_script]; [self addComponentScriptPart:part]; RELEASE(part); } - (WOComponentScript *)componentScript { return self->script; } /* subcomponent registry, created during parsing ... */ - (void)registerSubComponentWithId:(NSString *)_cid componentName:(NSString *)_name bindings:(NSMutableDictionary *)_bindings { WOxElemBuilderComponentInfo *info; info = [[WOxElemBuilderComponentInfo alloc] initWithComponentId:_cid componentName:_name bindings:_bindings]; if (self->subcomponentInfos == nil) self->subcomponentInfos = [[NSMutableArray alloc] initWithCapacity:16]; [self->subcomponentInfos addObject:info]; RELEASE(info); } - (NSArray *)subcomponentInfos { return self->subcomponentInfos; } - (void)reset { [self->subcomponentInfos removeAllObjects]; ASSIGN(self->script, (id)nil); } @end /* WOxElemBuilder */