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