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
22 #include "WOWrapperTemplateBuilder.h"
23 #include "WODParser.h"
24 #include "WOHTMLParser.h"
25 #include "WOCompoundElement.h"
26 #include "WOChildComponentReference.h"
27 #include <NGObjWeb/WOAssociation.h>
31 .wo components need to know at parsing time whether we are dealing
32 with a component or a dynamic element, the .wod or the .html does
33 not contain this information.
35 What to do ... ?? Always checking the class isn't very nice either ..
38 @interface _WODFileEntry : NSObject
41 NSString *componentName;
42 NSDictionary *associations;
44 signed char isDynamicElement;
47 - (BOOL)isDynamicElement;
48 - (Class)componentClass;
52 @interface WODynamicElement(UsedPrivates)
53 - (id)initWithElementName:(NSString *)_element
54 attributes:(NSDictionary *)_attributes
55 contentElements:(NSArray *)_subElements
56 componentDefinition:(id)_cdef;
57 + (BOOL)isDynamicElement;
60 static Class AssocClass = Nil;
61 static Class StrClass = Nil;
63 @implementation WOWrapperTemplateBuilder
65 static BOOL logExtraAssociations = NO;
66 static BOOL logScriptAdditions = NO;
67 static NSStringEncoding parserEncoding;
70 return [super version] + 0 /* v2 */;
74 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
75 NSAssert2([super version] == 2,
76 @"invalid superclass (%@) version %i !",
77 NSStringFromClass([self superclass]), [super version]);
79 AssocClass = [WOAssociation class];
80 StrClass = [NSString class];
82 if ([ud boolForKey:@"WOParsersUseUTF8"]) {
83 parserEncoding = NSUTF8StringEncoding;
84 NSLog(@"Note: using UTF-8 as wrapper template parser encoding.");
87 parserEncoding = [NSString defaultCStringEncoding];
91 [self->lastException release];
92 [self->definitions release];
93 [self->componentNames release];
94 [self->iTemplate release];
100 - (BOOL)_parseDeclarationsFile:(NSData *)_decl {
104 parser = [[[WODParser alloc] initWithHandler:(id)self] autorelease];
105 defs = [parser parseDeclarationData:_decl];
106 return defs ? YES : NO;
109 - (WOElement *)parseWithHTMLData:(NSData *)_html
110 declarationData:(NSData *)_decl
112 WOHTMLParser *parser;
113 NSArray *topLevel = nil;
114 NSException *exception = nil;
115 WOElement *rootElement;
117 /* parse declarations file */
118 if (![self _parseDeclarationsFile:_decl])
121 /* parse HTML file */
122 parser = [[[WOHTMLParser alloc] initWithHandler:(id)self] autorelease];
123 if ((topLevel = [parser parseHTMLData:_html]) == nil)
124 exception = [parser parsingException];
126 /* setup root element */
128 if ([topLevel count] == 1) {
129 rootElement = [[topLevel objectAtIndex:0] retain];
131 else if ([topLevel count] > 1) {
132 static Class CompoundElemClass = Nil;
133 if (CompoundElemClass == Nil)
134 CompoundElemClass = NSClassFromString(@"WOCompoundElement");
137 [[CompoundElemClass allocForCount:[topLevel count] zone:[self zone]]
138 initWithChildren:topLevel];
140 else /* no topLevel element */
143 if (exception) [exception raise];
148 [self->definitions removeAllObjects];
149 [self->componentNames removeAllObjects];
150 [self->iTemplate release]; self->iTemplate = nil;
153 - (NSData *)rewriteData:(NSData *)_data
154 fromEncoding:(NSStringEncoding)_from
155 toEncoding:(NSStringEncoding)_to
160 if ((s = [[NSString alloc] initWithData:_data encoding:_from]) == nil) {
161 [self logWithFormat:@"ERROR: template file has incorrect encoding!"];
164 if ((d = [s dataUsingEncoding:_to]) == nil) {
166 @"ERROR: could not represent template file in parser encoding!"];
172 - (NSException *)_handleBuildException:(NSException *)_exc atURL:(NSURL *)_url{
173 NSException *newException;
174 NSDictionary *userInfo;
175 NSMutableDictionary *newUserInfo;
179 if ((userInfo = [_exc userInfo]) != nil) {
180 newUserInfo = [[NSMutableDictionary alloc] initWithCapacity:
181 [userInfo count] + 1];
182 [newUserInfo addEntriesFromDictionary:userInfo];
183 [newUserInfo setObject:_url forKey:@"templateURL"];
186 newUserInfo = (NSMutableDictionary *)
187 [[NSDictionary alloc] initWithObjectsAndKeys:_url, @"templateURL", nil];
189 newException = [NSException exceptionWithName:[_exc name]
191 userInfo:newUserInfo];
195 - (WOTemplate *)buildTemplateAtURL:(NSURL *)_url {
196 static NSData *emptyData = nil;
198 WOTemplate *template;
199 WOElement *rootElement;
201 NSData *wodFile = nil;
202 NSData *htmlFile = nil;
203 NSDictionary *wooFile = nil;
205 NSString *path, *name;
206 NSStringEncoding encoding;
207 WOComponentScript *script;
210 NSAssert(self->iTemplate == nil, @"parsing in progress !!!");
212 [self->lastException release]; self->lastException = nil;
217 if (![_url isFileURL]) {
218 [self logWithFormat:@"can only process wrappers at file-URLs: %@", _url];
222 if (emptyData == nil)
223 emptyData = [[NSData alloc] init];
225 fm = [NSFileManager defaultManager];
228 if (self->definitions == nil)
229 self->definitions = [[NSMutableDictionary alloc] initWithCapacity:64];
231 tmpPath = [path lastPathComponent];
232 withLanguage = [[tmpPath pathExtension] isEqualToString:@"lproj"];
235 TODO: can this code handle ".wo" templates without a wrapper? Eg if I place
236 Main.html and Main.wod directly into the bundle resources directory?
237 TODO: can this code handle static names for contained files? (eg
238 template.wod instead of Main.wod) This way we could avoid renaming
239 the individual files if the wrapper name changes.
242 /* eg /a/b/c/a.wo/English.lproj/a.html */
243 tmpPath = [path stringByDeletingLastPathComponent];
244 name = [[tmpPath lastPathComponent] stringByDeletingPathExtension];
247 /* eg /a/b/c/a.wo/a.html */
248 name = [[path lastPathComponent] stringByDeletingPathExtension];
251 tmpPath = [name stringByAppendingPathExtension:@"wod"];
252 tmpPath = [path stringByAppendingPathComponent:tmpPath];
253 wodFile = [NSData dataWithContentsOfFile:tmpPath];
255 tmpPath = [name stringByAppendingPathExtension:@"html"];
256 tmpPath = [path stringByAppendingPathComponent:tmpPath];
257 htmlFile = [NSData dataWithContentsOfFile:tmpPath];
259 tmpPath = [name stringByAppendingPathExtension:@"woo"];
260 tmpPath = [path stringByAppendingPathComponent:tmpPath];
261 if ([fm fileExistsAtPath:tmpPath])
262 wooFile = [NSDictionary dictionaryWithContentsOfFile:tmpPath];
264 /* process language specific pathes */
267 if (wodFile == nil) { /* no .wod, no script (cannot be bound ...) */
269 tmpPath = [name stringByAppendingPathExtension:@"wod"];
270 tmpPath = [[path stringByDeletingLastPathComponent]
271 stringByAppendingPathComponent:tmpPath];
273 if ((wodFile = [NSData dataWithContentsOfFile:tmpPath]) == nil) {
277 @" could not load wod file of component '%@'\n"
281 __PRETTY_FUNCTION__, __LINE__,
282 name, [_url absoluteString], tmpPath, path];
288 @"%s:%i:\n could not load wod file of %@\n"
289 @" path=%@, not with lang.",
290 __PRETTY_FUNCTION__, __LINE__, name, path];
294 /* check for script */
295 NSFileManager *fm = [NSFileManager defaultManager];
297 tmpPath = [name stringByAppendingPathExtension:@"js"];
298 tmpPath = [path stringByAppendingPathComponent:tmpPath];
300 if ([fm fileExistsAtPath:tmpPath])
301 script = [[WOComponentScript alloc] initWithContentsOfFile:tmpPath];
303 if (htmlFile == nil) {
304 [self logWithFormat:@"%s:\n could not load html file of component %@.",
305 __PRETTY_FUNCTION__, name];
309 /* process string encoding */
311 encoding = parserEncoding;
312 if ((tmp = [wooFile objectForKey:@"encoding"])) {
313 // TODO: move to an NSString category, isn't there a method for this in
318 if ([tmp isEqualToString:@"NSASCIIStringEncoding"])
319 encoding = NSASCIIStringEncoding;
320 else if ([tmp isEqualToString:@"NSNEXTSTEPStringEncoding"])
321 encoding = NSNEXTSTEPStringEncoding;
322 else if ([tmp isEqualToString:@"NSUTF8StringEncoding"])
323 encoding = NSUTF8StringEncoding;
324 else if ([tmp isEqualToString:@"NSISOLatin1StringEncoding"])
325 encoding = NSISOLatin1StringEncoding;
326 else if ([tmp isEqualToString:@"NSISOLatin2StringEncoding"])
327 encoding = NSISOLatin2StringEncoding;
328 else if ([tmp isEqualToString:@"NSUnicodeStringEncoding"])
329 encoding = NSUnicodeStringEncoding;
330 else if ([tmp length] == 0)
331 ; // keep platform encoding
332 #if LIB_FOUNDATION_LIBRARY
333 else if ([tmp isEqualToString:@"NSISOLatin9StringEncoding"])
334 encoding = NSISOLatin9StringEncoding;
335 else if ([tmp isEqualToString:@"NSWinLatin1StringEncoding"])
336 encoding = NSWinLatin1StringEncoding;
338 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
340 encoding = [NSString stringEncodingForEncodingNamed:tmp];
345 @"ERROR(%s): cannot deal with template encoding: '%@'",
346 __PRETTY_FUNCTION__, tmp];
347 encoding = parserEncoding;
350 if (encoding != parserEncoding) {
351 // TODO: HACK and slow, the parsers should be able to deal with Unicode
352 // TODO: in case this works, remove the log
354 @"Note: rewriting template NSData for parser encoding (%@=>%@).",
355 [NSString localizedNameOfStringEncoding:encoding],
356 [NSString localizedNameOfStringEncoding:parserEncoding]];
358 htmlFile = [self rewriteData:htmlFile
359 fromEncoding:encoding toEncoding:parserEncoding];
360 wodFile = [self rewriteData:wodFile
361 fromEncoding:encoding toEncoding:parserEncoding];
365 /* instantiate template */
367 self->iTemplate = [[WOTemplate alloc] initWithURL:_url rootElement:nil];
370 rootElement = [self parseWithHTMLData:htmlFile declarationData:wodFile];
372 [[self _handleBuildException:localException atURL:_url] raise];
375 [self->iTemplate setRootElement:rootElement];
376 template = self->iTemplate;
377 self->iTemplate = nil;
381 if ((tmp = [wooFile objectForKey:@"variables"]))
382 [template setKeyValueArchivedTemplateVariables:tmp];
385 if (logScriptAdditions) {
386 [self logWithFormat:@"adding script %@ to template: '%@'",
389 [template setComponentScript:script];
393 return [template autorelease];
396 /* HTML parser callbacks */
398 - (NSString *)_uniqueComponentNameForDefinitionWithName:(NSString *)_element {
402 if (self->componentNames == nil)
403 self->componentNames = [[NSMutableSet alloc] init];
406 while ([self->componentNames containsObject:cname]) {
407 cname = [NSString stringWithFormat:@"%@%i", _element, i];
413 @"more than 200 components for definition named %@ "
414 @"(last name %@) (names=%@) ??",
415 _element, cname, self->componentNames);
417 [self->componentNames addObject:cname];
421 - (WOElement *)componentWithName:(NSString *)_element
422 attributes:(NSDictionary *)_attributes // not the associations !
423 contentElements:(NSArray *)_subElements
425 /* setup a new child component reference */
426 static Class ChildRefClass = Nil;
428 WOChildComponentReference *element = nil;
429 NSString *cname = nil;
431 if ((def = [self->definitions objectForKey:_element]) == nil)
434 if (ChildRefClass == Nil)
435 ChildRefClass = NSClassFromString(@"WOChildComponentReference");
437 cname = [self _uniqueComponentNameForDefinitionWithName:_element];
439 /* add subcomponent info */
441 addSubcomponentWithKey:cname
442 name:def->componentName
443 bindings:def->associations];
445 /* add subcomponent reference */
446 element = [[ChildRefClass alloc]
449 contentElements:_subElements];
450 if (element == nil) {
452 @"ERROR: could not instantiate child component reference."];
458 - (WOElement *)dynamicElementWithName:(NSString *)_element
459 attributes:(NSDictionary *)_attributes // not the associations !
460 contentElements:(NSArray *)_subElements
464 NSMutableDictionary *assoc = nil;
465 WODynamicElement *element;
467 if ((def = [self->definitions objectForKey:_element]) == nil) {
469 @"ERROR: did not find definition of dynamic element '%@'",
471 return [[NSClassFromString(@"WONoContentElement") alloc]
472 initWithElementName:_element
473 attributes:_attributes
474 contentElements:_subElements
475 componentDefinition:nil];
478 if (![def isDynamicElement]) {
479 /* definition describes a component */
480 return [self componentWithName:_element
481 attributes:_attributes
482 contentElements:_subElements];
485 elementClass = [def componentClass];
486 NSAssert1(elementClass, @"got no class for element %@", def);
488 assoc = [def->associations mutableCopy];
490 element = [[elementClass alloc]
491 initWithName:_element
493 contentElements:_subElements];
494 if (element == nil) {
495 NSLog(@"ERROR: could not instantiate dynamic element of class %@",
496 NSStringFromClass(elementClass));
498 if ([assoc count] > 0) {
499 if (logExtraAssociations)
500 [self logWithFormat:@"remaining definition attributes: %@", assoc];
501 [element setExtraAttributes:assoc];
503 [assoc release]; assoc = nil;
508 /* WOTemplate(HTMLParser) */
510 - (BOOL)parser:(id)_parser willParseHTMLData:(NSData *)_data {
514 - (void)parser:(id)_parser finishedParsingHTMLData:(NSData *)_data
515 elements:(NSArray *)_elements
519 - (void)parser:(id)_parser failedParsingHTMLData:(NSData *)_data
520 exception:(NSException *)_exception
524 /* WOTemplate(WODParser) */
526 - (BOOL)parser:(id)_parser willParseDeclarationData:(NSData *)_data {
529 - (void)parser:(id)_parser finishedParsingDeclarationData:(NSData *)_data
530 declarations:(NSDictionary *)_decls
533 - (void)parser:(id)_parser failedParsingDeclarationData:(NSData *)_data
534 exception:(NSException *)_exception
539 - (id)parser:(id)_parser makeAssociationWithValue:(id)_value {
540 return [AssocClass associationWithValue:_value];
542 - (id)parser:(id)_parser makeAssociationWithKeyPath:(NSString *)_keyPath {
543 NSCAssert([_keyPath isKindOfClass:StrClass],
544 @"invalid keypath property (expected string)");
545 return [AssocClass associationWithKeyPath:_keyPath];
547 - (id)parser:(id)_parser makeDefinitionForComponentNamed:(NSString *)_cname
548 associations:(id)_entry
549 elementName:(NSString *)_elemName
553 def = [[[_WODFileEntry alloc] init] autorelease];
554 def->componentName = [_cname copy];
555 def->associations = [_entry retain];
557 [self->definitions setObject:def forKey:_elemName];
562 @end /* WOWrapperTemplateBuilder */
564 @implementation _WODFileEntry
567 self->isDynamicElement = -1;
572 [self->componentName release];
573 [self->associations release];
579 - (NSString *)componentName {
580 return self->componentName;
582 - (NSDictionary *)bindings {
583 return self->associations;
586 - (Class)componentClass {
587 if (self->componentClass == nil)
588 self->componentClass = NSClassFromString(self->componentName);
590 return self->componentClass;
593 - (BOOL)isDynamicElement {
594 if (self->isDynamicElement == -1) {
595 self->isDynamicElement =
596 [[self componentClass] isDynamicElement] ? 1 : 0;
598 return (self->isDynamicElement == 0) ? NO : YES;
601 @end /* _WODFileEntry */