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 "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 debugOn = NO;
66 static BOOL logExtraAssociations = NO;
67 static BOOL logScriptAdditions = NO;
68 static NSStringEncoding parserEncoding;
71 return [super version] + 0 /* v2 */;
75 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
76 NSAssert2([super version] == 2,
77 @"invalid superclass (%@) version %i !",
78 NSStringFromClass([self superclass]), [super version]);
80 AssocClass = [WOAssociation class];
81 StrClass = [NSString class];
83 if ([ud boolForKey:@"WOParsersUseUTF8"]) {
84 parserEncoding = NSUTF8StringEncoding;
85 NSLog(@"Note: using UTF-8 as wrapper template parser encoding.");
88 parserEncoding = [NSString defaultCStringEncoding];
92 [self->lastException release];
93 [self->definitions release];
94 [self->componentNames release];
95 [self->iTemplate release];
101 - (BOOL)_parseDeclarationsFile:(NSData *)_decl {
105 parser = [[[WODParser alloc] initWithHandler:(id)self] autorelease];
106 defs = [parser parseDeclarationData:_decl];
107 return defs ? YES : NO;
110 - (WOElement *)parseWithHTMLData:(NSData *)_html
111 declarationData:(NSData *)_decl
113 WOHTMLParser *parser;
114 NSArray *topLevel = nil;
115 NSException *exception = nil;
116 WOElement *rootElement;
118 /* parse declarations file */
119 if (![self _parseDeclarationsFile:_decl])
122 /* parse HTML file */
123 parser = [[[WOHTMLParser alloc] initWithHandler:(id)self] autorelease];
124 if ((topLevel = [parser parseHTMLData:_html]) == nil)
125 exception = [parser parsingException];
127 /* setup root element */
129 if ([topLevel count] == 1) {
130 rootElement = [[topLevel objectAtIndex:0] retain];
132 else if ([topLevel count] > 1) {
133 static Class CompoundElemClass = Nil;
134 if (CompoundElemClass == Nil)
135 CompoundElemClass = NSClassFromString(@"WOCompoundElement");
138 [[CompoundElemClass allocForCount:[topLevel count] zone:[self zone]]
139 initWithChildren:topLevel];
141 else /* no topLevel element */
144 if (exception) [exception raise];
149 [self->definitions removeAllObjects];
150 [self->componentNames removeAllObjects];
151 [self->iTemplate release]; self->iTemplate = nil;
154 - (NSData *)rewriteData:(NSData *)_data
155 fromEncoding:(NSStringEncoding)_from
156 toEncoding:(NSStringEncoding)_to
161 if ((s = [[NSString alloc] initWithData:_data encoding:_from]) == nil) {
162 [self errorWithFormat:@"template file has incorrect encoding!"];
165 if ((d = [s dataUsingEncoding:_to]) == nil) {
166 [self errorWithFormat:
167 @"could not represent template file in parser encoding!"];
173 - (NSException *)_handleBuildException:(NSException *)_exc atURL:(NSURL *)_url{
174 NSException *newException;
175 NSDictionary *userInfo;
176 NSMutableDictionary *newUserInfo;
180 if ((userInfo = [_exc userInfo]) != nil) {
181 newUserInfo = [[NSMutableDictionary alloc] initWithCapacity:
182 [userInfo count] + 1];
183 [newUserInfo addEntriesFromDictionary:userInfo];
184 [newUserInfo setObject:_url forKey:@"templateURL"];
187 newUserInfo = (NSMutableDictionary *)
188 [[NSDictionary alloc] initWithObjectsAndKeys:_url, @"templateURL", nil];
190 newException = [NSException exceptionWithName:[_exc name]
192 userInfo:newUserInfo];
196 - (NSStringEncoding)encodingForString:(NSString *)_enc {
197 NSStringEncoding encoding;
201 if ([_enc isEqualToString:@"NSASCIIStringEncoding"])
202 encoding = NSASCIIStringEncoding;
203 else if ([_enc isEqualToString:@"NSNEXTSTEPStringEncoding"])
204 encoding = NSNEXTSTEPStringEncoding;
205 else if ([_enc isEqualToString:@"NSUTF8StringEncoding"])
206 encoding = NSUTF8StringEncoding;
207 else if ([_enc isEqualToString:@"NSISOLatin1StringEncoding"])
208 encoding = NSISOLatin1StringEncoding;
209 else if ([_enc isEqualToString:@"NSISOLatin2StringEncoding"])
210 encoding = NSISOLatin2StringEncoding;
211 else if ([_enc isEqualToString:@"NSUnicodeStringEncoding"])
212 encoding = NSUnicodeStringEncoding;
213 else if ([_enc length] == 0)
214 ; // keep platform encoding
215 #if LIB_FOUNDATION_LIBRARY
216 else if ([_enc isEqualToString:@"NSISOLatin9StringEncoding"])
217 encoding = NSISOLatin9StringEncoding;
218 else if ([_enc isEqualToString:@"NSWinLatin1StringEncoding"])
219 encoding = NSWinLatin1StringEncoding;
221 #if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY
223 encoding = [NSString stringEncodingForEncodingNamed:_enc];
228 - (WOTemplate *)buildTemplateAtURL:(NSURL *)_url {
229 static NSData *emptyData = nil;
231 WOTemplate *template;
232 WOElement *rootElement;
234 NSData *wodFile = nil;
235 NSData *htmlFile = nil;
236 NSDictionary *wooFile = nil;
238 NSString *path, *name;
239 NSStringEncoding encoding;
240 WOComponentScript *script;
243 NSAssert(self->iTemplate == nil, @"parsing in progress !!!");
245 [self->lastException release]; self->lastException = nil;
250 if (![_url isFileURL]) {
251 [self logWithFormat:@"can only process wrappers at file-URLs: %@", _url];
255 /* setup local and common objects */
257 if (emptyData == nil)
258 emptyData = [[NSData alloc] init];
260 if (self->definitions == nil)
261 self->definitions = [[NSMutableDictionary alloc] initWithCapacity:64];
265 fm = [NSFileManager defaultManager];
268 tmpPath = [path lastPathComponent];
269 withLanguage = [[tmpPath pathExtension] isEqualToString:@"lproj"];
272 TODO: can this code handle ".wo" templates without a wrapper? Eg if I place
273 Main.html and Main.wod directly into the bundle resources directory?
274 TODO: can this code handle static names for contained files? (eg
275 template.wod instead of Main.wod) This way we could avoid renaming
276 the individual files if the wrapper name changes.
279 /* eg /a/b/c/a.wo/English.lproj/a.html */
280 tmpPath = [path stringByDeletingLastPathComponent];
281 name = [[tmpPath lastPathComponent] stringByDeletingPathExtension];
283 else if ([path hasSuffix:@".html"]) {
285 If we pass in the .html, this basically means that the .html and .wod
286 are not inside a wrapper but in some arbitary directory.
287 This is used in OGo to support FHS location in a Unix-like way (the
288 templates live in /usr/share/ogo/templates/xyz.html and xyz.wod).
290 name = [[path lastPathComponent] stringByDeletingPathExtension];
291 path = [path stringByDeletingLastPathComponent];
292 if (debugOn) [self logWithFormat:@"CHECK: %@", path];
295 /* eg /a/b/c/a.wo/a.html */
296 name = [[path lastPathComponent] stringByDeletingPathExtension];
299 tmpPath = [name stringByAppendingPathExtension:@"wod"];
300 tmpPath = [path stringByAppendingPathComponent:tmpPath];
301 wodFile = [NSData dataWithContentsOfFile:tmpPath];
303 [self logWithFormat:@"CHECK: %@ + %@ => %@", path, name, tmpPath];
305 tmpPath = [name stringByAppendingPathExtension:@"html"];
306 tmpPath = [path stringByAppendingPathComponent:tmpPath];
307 htmlFile = [NSData dataWithContentsOfFile:tmpPath];
309 tmpPath = [name stringByAppendingPathExtension:@"woo"];
310 tmpPath = [path stringByAppendingPathComponent:tmpPath];
311 if ([fm fileExistsAtPath:tmpPath])
312 wooFile = [NSDictionary dictionaryWithContentsOfFile:tmpPath];
314 /* process language specific pathes */
317 if (wodFile == nil) { /* no .wod, no script (cannot be bound ...) */
319 tmpPath = [name stringByAppendingPathExtension:@"wod"];
320 tmpPath = [[path stringByDeletingLastPathComponent]
321 stringByAppendingPathComponent:tmpPath];
323 if ((wodFile = [NSData dataWithContentsOfFile:tmpPath]) == nil) {
327 @" could not load wod file of component '%@'\n"
331 __PRETTY_FUNCTION__, __LINE__,
332 name, [_url absoluteString], tmpPath, path];
338 @"%s:%i:\n could not load wod file of %@\n"
339 @" path=%@, not with lang.",
340 __PRETTY_FUNCTION__, __LINE__, name, path];
344 /* check for script */
345 NSFileManager *fm = [NSFileManager defaultManager];
347 tmpPath = [name stringByAppendingPathExtension:@"js"];
348 tmpPath = [path stringByAppendingPathComponent:tmpPath];
350 if ([fm fileExistsAtPath:tmpPath])
351 script = [[WOComponentScript alloc] initWithContentsOfFile:tmpPath];
353 if (htmlFile == nil) {
354 [self logWithFormat:@"%s:\n could not load html file of component %@.",
355 __PRETTY_FUNCTION__, name];
359 /* process string encoding */
361 encoding = parserEncoding;
362 if ((tmp = [wooFile objectForKey:@"encoding"]) != nil) {
363 // TODO: move to an NSString category, isn't there a method for this in
365 if ((encoding = [self encodingForString:tmp]) == 0) {
366 [self errorWithFormat:
367 @"(%s): cannot deal with template encoding: '%@'",
368 __PRETTY_FUNCTION__, tmp];
369 encoding = parserEncoding;
372 if (encoding != parserEncoding) {
373 // TODO: HACK and slow, the parsers should be able to deal with Unicode
374 // TODO: in case this works, remove the log
376 @"Note: rewriting template NSData for parser encoding (%@=>%@).",
377 [NSString localizedNameOfStringEncoding:encoding],
378 [NSString localizedNameOfStringEncoding:parserEncoding]];
380 htmlFile = [self rewriteData:htmlFile
381 fromEncoding:encoding toEncoding:parserEncoding];
382 wodFile = [self rewriteData:wodFile
383 fromEncoding:encoding toEncoding:parserEncoding];
387 /* instantiate template */
389 self->iTemplate = [[WOTemplate alloc] initWithURL:_url rootElement:nil];
392 rootElement = [self parseWithHTMLData:htmlFile declarationData:wodFile];
394 [[self _handleBuildException:localException atURL:_url] raise];
397 [self->iTemplate setRootElement:rootElement];
398 template = self->iTemplate;
399 self->iTemplate = nil;
403 if ((tmp = [wooFile objectForKey:@"variables"]))
404 [template setKeyValueArchivedTemplateVariables:tmp];
407 if (logScriptAdditions) {
408 [self logWithFormat:@"adding script %@ to template: '%@'",
411 [template setComponentScript:script];
415 return [template autorelease];
418 /* HTML parser callbacks */
420 - (NSString *)_uniqueComponentNameForDefinitionWithName:(NSString *)_element {
424 if (self->componentNames == nil)
425 self->componentNames = [[NSMutableSet alloc] init];
428 while ([self->componentNames containsObject:cname]) {
429 cname = [NSString stringWithFormat:@"%@%i", _element, i];
435 @"more than 200 components for definition named %@ "
436 @"(last name %@) (names=%@) ??",
437 _element, cname, self->componentNames);
439 [self->componentNames addObject:cname];
443 - (WOElement *)componentWithName:(NSString *)_element
444 attributes:(NSDictionary *)_attributes // not the associations !
445 contentElements:(NSArray *)_subElements
447 /* setup a new child component reference */
448 static Class ChildRefClass = Nil;
450 WOChildComponentReference *element = nil;
451 NSString *cname = nil;
453 if ((def = [self->definitions objectForKey:_element]) == nil)
456 if (ChildRefClass == Nil)
457 ChildRefClass = NSClassFromString(@"WOChildComponentReference");
459 cname = [self _uniqueComponentNameForDefinitionWithName:_element];
461 /* add subcomponent info */
463 addSubcomponentWithKey:cname
464 name:def->componentName
465 bindings:def->associations];
467 /* add subcomponent reference */
468 element = [[ChildRefClass alloc]
471 contentElements:_subElements];
472 if (element == nil) {
473 [self errorWithFormat:
474 @"could not instantiate child component reference."];
480 - (WOElement *)dynamicElementWithName:(NSString *)_element
481 attributes:(NSDictionary *)_attributes // not the associations !
482 contentElements:(NSArray *)_subElements
486 NSMutableDictionary *assoc = nil;
487 WODynamicElement *element;
489 if ((def = [self->definitions objectForKey:_element]) == nil) {
490 [self errorWithFormat:
491 @"did not find definition of dynamic element '%@'",
493 return [[NSClassFromString(@"WONoContentElement") alloc]
494 initWithElementName:_element
495 attributes:_attributes
496 contentElements:_subElements
497 componentDefinition:nil];
500 if (![def isDynamicElement]) {
501 /* definition describes a component */
502 return [self componentWithName:_element
503 attributes:_attributes
504 contentElements:_subElements];
507 elementClass = [def componentClass];
508 NSAssert1(elementClass, @"got no class for element %@", def);
510 assoc = [def->associations mutableCopy];
512 element = [[elementClass alloc]
513 initWithName:_element
515 contentElements:_subElements];
516 if (element == nil) {
517 [self errorWithFormat:@"could not instantiate dynamic element of class %@",
518 NSStringFromClass(elementClass)];
520 if ([assoc count] > 0) {
521 if (logExtraAssociations)
522 [self logWithFormat:@"remaining definition attributes: %@", assoc];
523 [element setExtraAttributes:assoc];
525 [assoc release]; assoc = nil;
530 /* WOTemplate(HTMLParser) */
532 - (BOOL)parser:(id)_parser willParseHTMLData:(NSData *)_data {
536 - (void)parser:(id)_parser finishedParsingHTMLData:(NSData *)_data
537 elements:(NSArray *)_elements
541 - (void)parser:(id)_parser failedParsingHTMLData:(NSData *)_data
542 exception:(NSException *)_exception
546 /* WOTemplate(WODParser) */
548 - (BOOL)parser:(id)_parser willParseDeclarationData:(NSData *)_data {
551 - (void)parser:(id)_parser finishedParsingDeclarationData:(NSData *)_data
552 declarations:(NSDictionary *)_decls
555 - (void)parser:(id)_parser failedParsingDeclarationData:(NSData *)_data
556 exception:(NSException *)_exception
561 - (id)parser:(id)_parser makeAssociationWithValue:(id)_value {
562 return [AssocClass associationWithValue:_value];
564 - (id)parser:(id)_parser makeAssociationWithKeyPath:(NSString *)_keyPath {
565 NSCAssert([_keyPath isKindOfClass:StrClass],
566 @"invalid keypath property (expected string)");
567 return [AssocClass associationWithKeyPath:_keyPath];
569 - (id)parser:(id)_parser makeDefinitionForComponentNamed:(NSString *)_cname
570 associations:(id)_entry
571 elementName:(NSString *)_elemName
575 def = [[[_WODFileEntry alloc] init] autorelease];
576 def->componentName = [_cname copy];
577 def->associations = [_entry retain];
579 [self->definitions setObject:def forKey:_elemName];
584 @end /* WOWrapperTemplateBuilder */
586 @implementation _WODFileEntry
589 self->isDynamicElement = -1;
594 [self->componentName release];
595 [self->associations release];
601 - (NSString *)componentName {
602 return self->componentName;
604 - (NSDictionary *)bindings {
605 return self->associations;
608 - (Class)componentClass {
609 if (self->componentClass == nil)
610 self->componentClass = NSClassFromString(self->componentName);
612 return self->componentClass;
615 - (BOOL)isDynamicElement {
616 if (self->isDynamicElement == -1) {
617 self->isDynamicElement =
618 [[self componentClass] isDynamicElement] ? 1 : 0;
620 return (self->isDynamicElement == 0) ? NO : YES;
623 @end /* _WODFileEntry */