]> err.no Git - sope/blob - sope-appserver/NGObjWeb/Templates/WOxElemBuilder.m
moved WOxTagClassElemBuilder to own file
[sope] / sope-appserver / NGObjWeb / Templates / WOxElemBuilder.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
22 #include <NGObjWeb/WOxElemBuilder.h>
23 #include <DOM/EDOM.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 "WOComponentFault.h"
30 #include "common.h"
31
32 @interface WOElement(UsedPrivates)
33 - (id)initWithValue:(id)_value escapeHTML:(BOOL)_flag;
34 @end
35
36 @interface WOAssociation(misc)
37 - (id)initWithScript:(NSString *)_script language:(NSString *)_lang;
38 @end
39
40 @implementation WOxElemBuilderComponentInfo
41
42 - (id)initWithComponentId:(NSString *)_cid
43   componentName:(NSString *)_name
44   bindings:(NSMutableDictionary *)_bindings
45 {
46   self->cid      = [_cid copy];
47   self->pageName = [_name copy];
48   self->bindings = [_bindings retain];
49   return self;
50 }
51 - (void)dealloc {
52   [self->cid      release];
53   [self->pageName release];
54   [self->bindings release];
55   [super dealloc];
56 }
57
58 /* accessors */
59
60 - (NSString *)componentId {
61   return self->cid;
62 }
63
64 - (NSString *)pageName {
65   return self->pageName;
66 }
67
68 - (NSMutableDictionary *)bindings {
69   return self->bindings;
70 }
71
72 /* operations */
73
74 - (WOComponent *)instantiateWithResourceManager:(WOResourceManager *)_rm
75   languages:(NSArray *)_languages
76 {
77   static Class FaultClass = Nil;
78   WOComponentFault *fault;
79   
80   if (FaultClass == Nil)
81     FaultClass = [WOComponentFault class];
82   
83   fault = [FaultClass alloc];
84   NSAssert1(fault, @"couldn't allocated object of class '%@' ..", FaultClass);
85   
86   fault = [fault initWithResourceManager:_rm
87                  pageName:self->pageName
88                  languages:_languages
89                  bindings:self->bindings];
90   return (id)fault;
91 }
92
93 @end /* SxElementBuilderComponentInfo */
94
95 @implementation WOxElemBuilder
96
97 static Class        StrClass  = Nil;
98 static Class        AStrClass = Nil;
99 static NSDictionary *defaultAssocMap = nil;
100 static Class        ValAssoc = Nil;
101 static BOOL         logAssocMap      = NO;
102 static BOOL         logAssocCreation = NO;
103 static BOOL         debugOn          = NO;
104
105 + (int)version {
106   return 1;
107 }
108 + (void)initialize {
109   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
110   static BOOL didInit = NO;
111   if (didInit) return;
112   didInit = YES;
113   
114   StrClass = NSClassFromString(@"_WOSimpleStaticString");
115   if (StrClass == Nil)
116     NSLog(@"ERROR: missing class _WOSimpleStaticString !");
117   AStrClass = NSClassFromString(@"_WOSimpleStaticASCIIString");
118   if (AStrClass == Nil)
119     NSLog(@"ERROR: missing class _WOSimpleStaticASCIIString !");
120
121   logAssocMap = [ud boolForKey:@"WOxElemBuilder_LogAssociationMapping"];
122   logAssocCreation = 
123     [ud boolForKey:@"WOxElemBuilder_LogAssociationCreation"];
124   if (logAssocMap)      NSLog(@"Note: association mapping is logged!");
125   if (logAssocCreation) NSLog(@"Note: association creation is logged!");
126   
127   defaultAssocMap = [[ud dictionaryForKey:@"WOxAssociationClassMapping"] copy];
128   if (defaultAssocMap == nil)
129     NSLog(@"WARNING: WOxAssociationClassMapping default is not set!");
130   
131   if (ValAssoc == Nil)
132     ValAssoc = NSClassFromString(@"WOValueAssociation");
133 }
134
135 + (WOxElemBuilder *)createBuilderQueue:(NSArray *)_classNames {
136   unsigned       i, count;
137   WOxElemBuilder *first, *current = nil;
138   NSMutableArray *missingBuilders = nil;
139   
140   if ((count = [_classNames count]) == 0)
141     return nil;
142   
143   for (first = nil, i = 0; i < count; i++) {
144     WOxElemBuilder *nx;
145     NSString *cn;
146     Class    clazz;
147     
148     cn = [_classNames objectAtIndex:i];
149 #if 0
150     NSLog(@"builder class: %@", cn);
151 #endif
152     
153     if ((clazz = NSClassFromString(cn)) == Nil) {
154       if (missingBuilders == nil) 
155         missingBuilders = [NSMutableArray arrayWithCapacity:16];
156       [missingBuilders addObject:cn];
157       continue;
158     }
159     
160     if ((nx = [[clazz alloc] init])) {
161       if (first == nil) {
162         first = current = nx;
163         [nx autorelease];
164       }
165       else {
166         [current setNextBuilder:nx];
167         current = [nx autorelease];
168       }
169     }
170     else {
171       NSLog(@"%s: couldn't allocate builder (class=%@)", cn);
172       continue;
173     }
174   }
175   
176   if (missingBuilders) {
177     NSLog(@"WOxElemBuilder: could not locate builders: %@", 
178           [missingBuilders componentsJoinedByString:@","]);
179   }
180   return first;
181 }
182
183 + (WOxElemBuilder *)createBuilderQueueV:(NSString *)_className, ... {
184   // TODO: reimplement using createBuilderQueue:
185   va_list       ap;
186   NSString      *cn;
187   WOxElemBuilder *first, *current;
188   
189   if (_className == nil)
190     return [[[self alloc] init] autorelease];
191     
192   first = [[[NSClassFromString(_className) alloc] init] autorelease];
193     
194   va_start(ap, _className);
195   for (current = first; (cn = va_arg(ap, id)); ) {
196     WOxElemBuilder *nx;
197
198     nx = [[NSClassFromString(cn) alloc] init];
199     [current setNextBuilder:nx];
200     current = [nx autorelease];
201   }
202   va_end(ap);
203     
204   return first;
205 }
206
207 - (void)dealloc {
208   [self->script            release];
209   [self->subcomponentInfos release];
210   [self->nsToAssoc         release];
211   [self->nextBuilder       release];
212   [super dealloc];
213 }
214
215 /* building an element (returns a retained object !!!) */
216
217 - (WOElement *)buildNode:(id<DOMNode>)_node templateBuilder:(id)_builder {
218   if (_node == nil)
219     return nil;
220
221   switch ([_node nodeType]) {
222     case DOM_ELEMENT_NODE:
223       return [self buildElement:(id<DOMElement>)_node
224                    templateBuilder:_builder];
225     case DOM_TEXT_NODE:
226       return [self buildText:(id<DOMText>)_node
227                    templateBuilder:_builder];
228     case DOM_CDATA_SECTION_NODE:
229       return [self buildCDATASection:(id<DOMCDATASection>)_node
230                    templateBuilder:_builder];
231     case DOM_COMMENT_NODE:
232       return [self buildComment:(id<DOMComment>)_node
233                    templateBuilder:_builder];
234     case DOM_DOCUMENT_NODE:
235       return [self buildDocument:(id<DOMDocument>)_node
236                    templateBuilder:_builder];
237       
238     default:
239       if (self->nextBuilder)
240         return [self->nextBuilder buildNode:_node templateBuilder:_builder];
241       else {
242         NSLog(@"unknown node type %i, node %@", [_node nodeType], _node);
243         return nil;
244       }
245   }
246 }
247
248 - (NSArray *)buildNodes:(id<DOMNodeList>)_nodes templateBuilder:(id)_bld {
249   // Note: returns a regular autoreleased array
250   NSMutableArray *children;
251   unsigned       i, count;
252   
253   if ((count = [_nodes length]) == 0)
254     return nil;
255   
256   children = [NSMutableArray arrayWithCapacity:(count + 1)];
257   
258   for (i = 0; i < count; i++) {
259     WOElement *e;
260
261     e = [_bld buildNode:[_nodes objectAtIndex:i] templateBuilder:_bld];
262     if (e) {
263       [children addObject:e];
264       [e release];
265     }
266   }
267   return children;
268 }
269
270 /* building methods specialized on type (return retained objects !!!) */
271
272 - (WOElement *)buildDocument:(id<DOMDocument>)_node templateBuilder:(id)_bld {
273   return [self buildElement:[_node documentElement] templateBuilder:_bld];
274 }
275
276 - (WOElement *)buildElement:(id<DOMElement>)_node templateBuilder:(id)_bld {
277   if (self->nextBuilder)
278     return [self->nextBuilder buildElement:_node templateBuilder:_bld];
279
280   [self logWithFormat:@"cannot build node %@ (template builder %@)",
281           _node, _bld];
282   return nil;
283 }
284
285 - (WOElement *)buildCharacterData:(id<DOMCharacterData>)_text
286   templateBuilder:(id)_builder
287 {
288   static Class ValClass = Nil;
289   WOElement *textElement;
290   unsigned len;
291   BOOL     isASCII = NO;
292   id       str;
293   
294   str = [_text data];
295   if ((len = [str length]) == 0) return nil;
296   
297   /* 
298      we use WOValueAssociation directly, because WOAssociation caches all
299      values
300   */
301   if (ValClass == Nil)
302     ValClass = NSClassFromString(@"WOValueAssociation");
303
304 #if 0
305 #  warning not using ASCII string !
306   isASCII = NO;
307 #else
308   if (len > 1) {
309     // TODO(perf): improve on that
310     /* not very efficient, but only used during template parsing ... */
311     if ([str dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:NO])
312       isASCII = YES;
313     else
314       isASCII = NO;
315   }
316   else {
317     isASCII = ([str characterAtIndex:0] < 128) ? YES : NO;
318   }
319 #endif
320   
321   str = [[ValClass alloc] initWithString:str];
322   textElement = 
323     [[(isASCII?AStrClass:StrClass) alloc] initWithValue:str escapeHTML:YES];
324   [str release];
325   return textElement;
326 }
327 - (WOElement *)buildText:(id<DOMText>)_node
328   templateBuilder:(id)_builder
329 {
330   return [self buildCharacterData:_node templateBuilder:_builder];
331 }
332 - (WOElement *)buildCDATASection:(id<DOMCDATASection>)_node
333   templateBuilder:(id)_builder
334 {
335   return [self buildCharacterData:_node templateBuilder:_builder];
336 }
337
338 - (WOElement *)buildComment:(id<DOMComment>)_node
339   templateBuilder:(id)_builder
340 {
341   /* comments aren't delivered ... */
342   return nil;
343 }
344
345 /* building the whole template */
346
347 - (WOElement *)buildTemplateFromDocument:(id<DOMDocument>)_document {
348   NSAutoreleasePool *pool;
349   WOElement *result;
350   
351   pool   = [[NSAutoreleasePool alloc] init];
352   result = [[self buildNode:_document templateBuilder:self] retain];
353   [pool release];
354   return [result autorelease];
355 }
356
357 /* association callbacks */
358
359 - (WOAssociation *)associationForValue:(id)_value {
360   return [WOAssociation associationWithValue:_value];
361 }
362
363 - (WOAssociation *)associationForKeyPath:(NSString *)_path {
364   return [WOAssociation associationWithKeyPath:_path];
365 }
366
367 - (WOAssociation *)associationForJavaScript:(NSString *)_js {
368   WOAssociation *assoc;
369   
370   assoc = [NSClassFromString(@"WOScriptAssociation") alloc];
371   assoc = [(id)assoc initWithScript:_js language:@"javascript"];
372   return [assoc autorelease];
373 }
374
375 - (WOAssociation *)associationForAttribute:(id<DOMAttr>)_attribute {
376   NSString      *nsuri;
377   NSString      *value;
378   WOAssociation *assoc;
379   Class c;
380   
381   nsuri = [_attribute namespaceURI];
382   value = [_attribute nodeValue];
383   
384   c = [self associationClassForNamespaceURI:[_attribute namespaceURI]];
385   if (c == Nil) {
386     [self logWithFormat:
387             @"WARNING, found no association class for "
388             @"attribute %@ (namespace=%@)",
389             _attribute, [_attribute namespaceURI]];
390     return nil;
391   }
392   if (logAssocMap) {
393     [self logWithFormat:@"use class %@ for namespaceURI %@ (attribute %@)",
394             c, [_attribute namespaceURI], [_attribute name]];
395   }
396   
397   assoc = [[c alloc] initWithString:value];
398   if (logAssocCreation) {
399     [self logWithFormat:@"created assoc %@ for attribute %@", 
400             assoc, [_attribute name]];
401   }
402   
403   return [assoc autorelease];
404 }
405
406 - (NSMutableDictionary *)associationsForAttributes:(id<DOMNamedNodeMap>)_attrs{
407   NSMutableDictionary *assocs;
408   int i, count;
409   
410   if ((count = [_attrs length]) == 0)
411     return nil;
412   
413   assocs = [NSMutableDictionary dictionaryWithCapacity:(count + 1)];
414
415   for (i = 0; i < count; i++) {
416     id<DOMAttr>   attr;
417     WOAssociation *assoc;
418
419     attr = [_attrs objectAtIndex:i];
420     
421     if ((assoc = [self associationForAttribute:attr])) {
422       NSString *key;
423       
424       key = [attr name];
425       if ([key characterAtIndex:0] == '_')
426         key = [@"?" stringByAppendingString:[key substringFromIndex:1]];
427       
428       [assocs setObject:assoc forKey:key];
429     }
430   }
431   return assocs;
432 }
433
434 - (void)_ensureDefaultAssocMappings {
435   NSEnumerator *e;
436   NSString     *ns;
437   
438   if (self->nsToAssoc) 
439     return;
440   
441   self->nsToAssoc = [[NSMutableDictionary alloc] initWithCapacity:8];
442   e = [defaultAssocMap keyEnumerator];
443   while ((ns = [e nextObject])) {
444     NSString *className;
445     Class    clazz;
446     
447     className = [defaultAssocMap objectForKey:ns];
448     clazz = NSClassFromString(className);
449     
450     if (clazz == Nil) {
451       [self logWithFormat:@"WARNING: did not find association class: '%@'",
452               className];
453       continue;
454     }
455     
456     /* register */
457     [self->nsToAssoc setObject:clazz forKey:ns];
458   }
459 }
460 - (void)registerAssociationClass:(Class)_class forNamespaceURI:(NSString *)_ns{
461   if (_ns    == nil) return;
462   if (_class == Nil) return;
463   
464   [self _ensureDefaultAssocMappings];
465   [self->nsToAssoc setObject:_class forKey:_ns];
466 }
467 - (Class)associationClassForNamespaceURI:(NSString *)_ns {
468   Class c;
469   
470   [self _ensureDefaultAssocMappings];
471   
472   if ((c = [self->nsToAssoc objectForKey:_ns]) == nil)
473     /* if we have no class mapped for a namespace, we treat it as a value */
474     c = ValAssoc;
475   
476   if (debugOn)
477     [self debugWithFormat:@"using class %@ for namespace %@", c, _ns];
478   return c;
479 }
480
481 /* creating unique IDs */
482
483 - (NSString *)uniqueIDForNode:(id)_node {
484   NSMutableArray  *nodePath;
485   NSMutableString *uid;
486   NSEnumerator    *topDown;
487   id   node, parent;
488   BOOL isFirst;
489
490   if (_node == nil) return nil;
491
492   nodePath = [NSMutableArray arrayWithCapacity:16];
493
494   /* collect all parent nodes in bottom-up form */
495
496   for (node = _node; node; node = [node parentNode])
497     [nodePath addObject:node];
498
499   /* generate ID */
500
501   uid     = [NSMutableString stringWithCapacity:64];
502   topDown = [nodePath reverseObjectEnumerator];
503   isFirst = YES;
504   parent  = nil;
505
506   for (isFirst = YES; (node = [topDown nextObject]); parent = node) {
507     if (!isFirst) {
508       NSArray  *children;
509       unsigned i, count;
510
511       [uid appendString:@"."];
512
513       /* determine index of _node */
514
515       children = (NSArray *)[parent childNodes];
516       for (i = 0, count = [children count]; i < count; i++) {
517         if ([children objectAtIndex:i] == node)
518           break;
519       }
520       [uid appendFormat:@"%d", i];
521     }
522     else {
523       [uid appendString:@"R"];
524       isFirst = NO;
525     }
526   }
527
528   return [[uid copy] autorelease];
529 }
530
531 /* logging */
532
533 - (void)logWithFormat:(NSString *)_format, ... {
534   NSString *value = nil;
535   va_list  ap;
536
537   va_start(ap, _format);
538   value = [[NSString alloc] initWithFormat:_format arguments:ap];
539   va_end(ap);
540
541   NSLog(@"|%@| %@", self, value);
542   [value release];
543 }
544 - (void)debugWithFormat:(NSString *)_format, ... {
545   static char showDebug = 2;
546   NSString *value = nil;
547   va_list  ap;
548   
549   if (showDebug == 2) {
550     showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
551   }
552   
553   if (showDebug) {
554     va_start(ap, _format);
555     value = [[NSString alloc] initWithFormat:_format arguments:ap];
556     va_end(ap);
557     
558     NSLog(@"|%@|D %@", self, value);
559     [value release];
560   }
561 }
562
563 /* managing builder queues */
564
565 - (void)setNextBuilder:(WOxElemBuilder *)_builder {
566   ASSIGN(self->nextBuilder, _builder);
567 }
568 - (WOxElemBuilder *)nextBuilder {
569   return self->nextBuilder;
570 }
571
572 /* component script parts */
573
574 - (void)addComponentScriptPart:(WOComponentScriptPart *)_part {
575   if (self->script == nil)
576     self->script = [[WOComponentScript alloc] init];
577   
578   [self->script addScriptPart:_part];
579 }
580 - (void)addComponentScript:(NSString *)_script line:(unsigned)_line {
581   WOComponentScriptPart *part;
582   
583   part = [[WOComponentScriptPart alloc] initWithURL:nil startLine:_line
584                                         script:_script];
585   [self addComponentScriptPart:part];
586   RELEASE(part);
587 }
588
589 - (WOComponentScript *)componentScript {
590   return self->script;
591 }
592
593 /* subcomponent registry, created during parsing ... */
594
595 - (void)registerSubComponentWithId:(NSString *)_cid
596   componentName:(NSString *)_name
597   bindings:(NSMutableDictionary *)_bindings
598 {
599   WOxElemBuilderComponentInfo *info;
600   
601   info = [[WOxElemBuilderComponentInfo alloc] initWithComponentId:_cid
602     componentName:_name
603     bindings:_bindings];
604     
605   if (self->subcomponentInfos == nil)
606     self->subcomponentInfos = [[NSMutableArray alloc] initWithCapacity:16];
607   [self->subcomponentInfos addObject:info];
608   RELEASE(info);
609 }
610
611 - (NSArray *)subcomponentInfos {
612   return self->subcomponentInfos;
613 }
614
615 - (void)reset {
616   [self->subcomponentInfos removeAllObjects];
617   ASSIGN(self->script, (id)nil);
618 }
619
620 @end /* WOxElemBuilder */