xmlbuilder copies comments over now.
[Evergreen.git] / Open-ILS / src / apachemods / mod_xmlbuilder.c
1 #include "mod_xmlbuilder.h"
2
3 char* __xmlBuilderDynamicLocale = NULL;
4 request_rec* currentRec = NULL;
5
6
7 /* set the base DTD directory */
8 static const char* xmlBuilderSetBaseDir(cmd_parms *params, void *cfg, const char *arg) {
9         xmlBuilderConfig* config = ap_get_module_config(
10                 params->server->module_config, &xmlbuilder_module );
11         config->baseDir = (char*) arg;
12         return NULL;
13 }
14
15 static const char* xmlBuilderSetDefaultLocale(
16                                          cmd_parms* params, void* cfg, const char* arg ) {
17         xmlBuilderConfig* config = ap_get_module_config(
18                 params->server->module_config, &xmlbuilder_module );
19         config->defaultLocale = (char*) arg;
20         return NULL;
21 }
22
23 static const char* xmlBuilderSetDefaultDtd(
24                                          cmd_parms* params, void* cfg, const char* arg ) {
25         xmlBuilderConfig* config = ap_get_module_config(
26                 params->server->module_config, &xmlbuilder_module );
27         config->defaultDtd = (char*) arg;
28         if( config->defaultDtd ) {
29                 if(!strcmp(config->defaultDtd,"NULL"))
30                         config->defaultDtd = NULL;
31         }
32         return NULL;
33 }
34
35
36 static const char* xmlBuilderSetLocaleParam(
37                                          cmd_parms* params, void* cfg, const char* arg ) {
38         xmlBuilderConfig* config = ap_get_module_config(
39                 params->server->module_config, &xmlbuilder_module );
40         config->localeParam = (char*) arg;
41         return NULL;
42 }
43
44
45 static const char* xmlBuilderSetPostXSL(
46                                          cmd_parms* params, void* cfg, const char* arg ) {
47         xmlBuilderConfig* config = ap_get_module_config(
48                 params->server->module_config, &xmlbuilder_module );
49         config->postXSL = xsltParseStylesheetFile((xmlChar*) arg);
50         if( config->postXSL == NULL ) 
51                 apacheDebug("Unable to parse postXSL stylesheet: %s.  No postXSL will be performed", arg);      
52         return NULL;
53 }
54
55 static const char* xmlBuilderSetContentType(
56                                          cmd_parms* params, void* cfg, const char* arg ) {
57         xmlBuilderConfig* config = ap_get_module_config(
58                 params->server->module_config, &xmlbuilder_module );
59         config->contentType = (char*) arg;
60         return NULL;
61 }
62
63 // ACCESS_CONF - OR_ALL ?
64 static const command_rec xmlBuilderCommands[] = {
65         AP_INIT_TAKE1( MODXMLB_CONFIG_LOCALE, 
66                         xmlBuilderSetDefaultLocale, NULL, RSRC_CONF, "Default Locale"),
67         AP_INIT_TAKE1( MODXMLB_CONFIG_BASE_DIR, 
68                         xmlBuilderSetBaseDir, NULL, RSRC_CONF, "Base Directory"),
69         AP_INIT_TAKE1( MODXMLB_CONFIG_POST_XSL, 
70                         xmlBuilderSetPostXSL, NULL, RSRC_CONF, "Post XSL"),
71         AP_INIT_TAKE1( MODXMLB_CONFIG_DEFAULT_DTD, 
72                         xmlBuilderSetDefaultDtd, NULL, RSRC_CONF, "Default DTD"),
73         AP_INIT_TAKE1( MODXMLB_CONFIG_LOCALE_PARAM,
74                         xmlBuilderSetLocaleParam, NULL, RSRC_CONF, "Locale URL param name"),
75         AP_INIT_TAKE1( MODXMLB_CONFIG_CONTENT_TYPE,
76                         xmlBuilderSetContentType, NULL, RSRC_CONF, "Content Type"),
77         {NULL}
78 };
79
80 static void* xmlBuilderCreateConfig( apr_pool_t* p, server_rec* s ) {
81         xmlBuilderConfig* config = 
82                 (xmlBuilderConfig*) apr_palloc( p, sizeof(xmlBuilderConfig) );
83         config->baseDir                 = MODXMLB_DEFAULT_BASE_DIR;
84         config->defaultLocale   = MODXMLB_DEFAULT_LOCALE;
85         config->defaultDtd              = NULL;
86         config->postXSL                 = NULL;
87         config->contentType             = NULL;
88         config->localeParam             = MODXMLB_DEFAULT_LOCALE_PARAM;
89         return (void*) config;
90 }
91
92
93 /* Child Init handler  ----------------------------------------------------------- */
94 static void xmlBuilderChildInit( apr_pool_t *p, server_rec *s ) {
95 }
96
97 static int xmlBuilderHandler( request_rec* r ) {
98
99         if( strcmp(r->handler, MODULE_NAME ) ) return DECLINED;
100         currentRec = r;
101
102         xmlBuilderConfig* config = ap_get_module_config( 
103                         r->server->module_config, &xmlbuilder_module );
104
105         /*
106         xmlBuilderConfig* config = ap_get_module_config( 
107                         r->per_dir_config, &xmlbuilder_module );
108                         */
109
110         if(config == NULL) {
111                 ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, currentRec,
112                                 "config is nulll...");
113                 return HTTP_INTERNAL_SERVER_ERROR; 
114         }
115         
116         r->allowed |= (AP_METHOD_BIT << M_GET);
117         r->allowed |= (AP_METHOD_BIT << M_POST);
118         char* ct = config->contentType;
119
120
121         if(!config->contentType) ct = "text/html; charset=utf-8";
122         /* ---------------------------------- */
123         // Hack to force XUL mime type (until config is fixed)
124         char* f = r->filename;
125         if(f) {
126                 int l = strlen(f);
127                 if(l > 4) {
128                         if( !strcmp( f + (l - 4), ".xul"))
129                                 ct = "application/vnd.mozilla.xul+xml";
130                 }
131         }
132         /* ---------------------------------- */
133
134         ap_set_content_type(r, ct);
135
136         //ap_table_set(r->headers_out, "Cache-Control", "max-age=15552000");
137
138         char* dates = apr_pcalloc(r->pool, MAX_STRING_LEN);
139         apr_rfc822_date(dates, apr_time_now() + 604800000000); /* cache for one week */
140         ap_table_set(r->headers_out, "Expires", dates);
141
142         apr_rfc822_date(dates, apr_time_now()); /* cache for one week */
143         ap_table_set(r->headers_out, "Date", dates);
144
145
146         /*
147         char expire_hdr[APR_RFC822_DATE_LEN];
148          apr_rfc822_date(expire_hdr, (apr_time_t)(time(NULL) + 15552000));
149         ap_table_set(r->headers_out, "Expires", expire_hdr);
150          */
151
152         string_array* params = apacheParseParms(r);
153         char* locale = apacheGetFirstParamValue(params, config->localeParam);
154         if(locale) __xmlBuilderDynamicLocale = locale;
155         char* XMLFile = r->filename;
156
157         xmlDocPtr doc = xmlBuilderProcessFile( XMLFile, config );
158         if(!doc) return apacheError( "Unable to parse XML file %s", XMLFile );
159
160         /* apply the post XSL */
161         if(config->postXSL) {
162                 xmlDocPtr newdoc;
163                 newdoc = xsltApplyStylesheet(config->postXSL, doc, NULL );
164
165                 if(newdoc == NULL) {
166                         apacheDebug("Error applying postXSL... skipping.");
167                 } else {
168                         xmlFreeDoc(doc);
169                         doc = newdoc;
170                 }
171         }
172
173         char* docXML = xmlDocToString( doc, 1 );
174         //apacheDebug("DOC:\n%s\n%s", docXML);
175         ap_rputs(docXML, r);
176         free(docXML);
177         xmlFreeDoc( doc );
178         doc = NULL;
179         //xmlCleanupCharEncodingHandlers();
180         //xmlCleanupParser();
181
182         return OK;
183 }
184
185
186 /* frees the collected DTD's */
187 static void __xmlBuilderFreeDtdHash( char* key, void* item ) {
188         if(!item) return;
189         xmlFreeDtd( item );
190 }
191
192
193 xmlDocPtr xmlBuilderProcessFile( char* filename, xmlBuilderConfig* config ) {
194         if(!filename) { 
195                 apacheError( "No XML file provided" ); return NULL; }
196
197         xmlBuilderContext context;
198         context.config          = config;
199         context.doc                     = xmlNewDoc( BAD_CAST "1.0" );
200         context.dtdHash = osrfNewHash();
201         context.entHash = osrfNewHash();
202         context.nodeList        = osrfNewList();
203         context.xmlError        = 0;
204         context.xmlFile = filename;
205         context.dtdHash->freeItem = &__xmlBuilderFreeDtdHash;
206
207                 
208         /*
209         ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 0, currentRec,
210                         "xmlBuilderProcessFile() Options: "
211                         "XMLBuilderDefaultLocale : %s |  XMLBuilderBaseDir : %s | "
212          "XMLBuilderDefaultDTD : %s | XMLBuilderLocaleParam : %s",
213                         config->defaultLocale, config->baseDir, config->defaultDtd, config->localeParam );
214                         */
215
216         /* pre-parse the default dtd if defined */
217         if( config->defaultDtd ) 
218                 xmlBuilderAddDtd( config->defaultDtd, &context );
219
220         xmlParserCtxtPtr parserCtx;
221
222         parserCtx = xmlCreatePushParserCtxt(xmlBuilderSaxHandler, &context, "", 0, NULL);
223         xmlCtxtReadFile( parserCtx, filename, NULL, XML_PARSE_RECOVER );
224
225         xmlFreeParserCtxt( parserCtx );
226         osrfListFree(context.nodeList);
227         osrfHashFree(context.entHash);
228         osrfHashFree(context.dtdHash);
229         return context.doc;
230 }
231
232
233 void xmlBuilderStartElement( void* context, const xmlChar *name, const xmlChar **atts ) {
234         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
235
236         xmlNodePtr node = NULL;
237
238         /* process xincludes as a sub-doc */
239         if( !strcmp( name, "xi:include" ) ) { 
240
241                 char* href = strdup(xmlSaxAttr( atts, "href" ));
242                 if(href) {
243
244                         /* find the relative path for the xinclude */
245                         if(href[0] != '/') {
246                                 int len = strlen(ctx->xmlFile) + strlen(href) + 1;
247                                 char buf[len];
248                                 bzero(buf, len);
249                                 strcpy( buf, ctx->xmlFile );
250                                 int i;
251                                 for( i = strlen(buf); i != 0; i-- ) {
252                                         if( buf[i] == '/' ) break;
253                                         buf[i] = '\0';
254                                 }
255                                 strcat( buf, href );
256                                 free(href);
257                                 href = strdup(buf);
258                         }
259
260
261                         xmlDocPtr subDoc = xmlBuilderProcessFile( href, ctx->config );
262                         node = xmlDocGetRootElement( subDoc );
263                 }
264
265                 if(!node) {
266                         apacheError("Unable to parse xinclude: %s", href );
267                         free(href);
268                         return;
269                 }
270                 free(href);
271
272         } else {
273                 node = xmlNewNode(NULL, name);
274                 xmlBuilderAddAtts( ctx, node, atts );
275         }
276
277
278         xmlNodePtr parent = osrfListGetIndex( 
279                         ctx->nodeList, ctx->nodeList->size - 1 );
280
281         if( parent ) xmlAddChild( parent, node );
282         else xmlDocSetRootElement(ctx->doc, node);
283         
284         osrfListPush( ctx->nodeList, node );
285 }
286
287
288 void xmlBuilderAddAtts( xmlBuilderContext* ctx, xmlNodePtr node, const xmlChar** atts ) {
289         if(!(ctx && node && atts)) return;
290         int i;
291
292         for(i = 0; (atts[i] != NULL); i++) {
293
294                 if(atts[i+1]) {
295
296                         const xmlChar* name = atts[i];
297                         const xmlChar* prop = atts[i+1];
298                         int nl = strlen(prop);
299                         char* _prop = NULL;
300
301                         if( prop[0] == '&' && prop[nl-1] == ';' ) { /* replace the entity if we are one */
302                                 char buf[nl+1];
303                                 bzero(buf, nl+1);
304                                 strncat(buf, prop + 1, nl - 2);
305                                 xmlEntityPtr ent = osrfHashGet( ctx->entHash, buf );
306                                 if(ent && ent->content) _prop = ent->content;
307                         } else { _prop = (char*) prop; }
308
309                         xmlSetProp( node, name, _prop );
310                         i++;
311                 }
312         }
313 }
314
315 void xmlBuilderEndElement( void* context, const xmlChar* name ) {
316         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
317         osrfListPop( ctx->nodeList );
318 }
319
320
321 void xmlBuilderHandleCharacter(void* context, const xmlChar *ch, int len) {
322         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
323         xmlNodePtr node = osrfListGetIndex( 
324                         ctx->nodeList, ctx->nodeList->size - 1 );
325
326         if(node) {
327                 xmlNodePtr txt = xmlNewTextLen(ch, len);
328                 xmlAddChild( node, txt );
329         }
330
331 }
332
333
334 void xmlBuilderParseError( void* context, const char* msg, ... ) {
335         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
336         VA_LIST_TO_STRING(msg);
337         apacheDebug( "Parser Error Occurred: %s", VA_BUF);
338         ctx->xmlError = 1;
339 }
340
341
342 xmlEntityPtr xmlBuilderGetEntity( void* context, const xmlChar* name ) {
343         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
344         xmlEntityPtr ent = osrfHashGet( ctx->entHash, name );
345         return ent;
346 }
347
348
349 void xmlBuilderExtSubset( void* blob, 
350                 const xmlChar* name, const xmlChar* extId, const xmlChar* sysId ) {
351
352         xmlBuilderContext* context = (xmlBuilderContext*) blob;
353         if( context->config->defaultDtd ) {
354                 apacheDebug("Ignoring DTD [%s] because default DTD is set...", sysId);
355                 return; 
356         }
357
358         xmlBuilderAddDtd( sysId, context );
359 }
360
361 void xmlBuilderComment( void* blob, const xmlChar* data ) {
362         xmlBuilderContext* ctx = (xmlBuilderContext*) blob;
363         xmlNodePtr comment = xmlNewComment( data );
364         xmlNodePtr parent = osrfListGetIndex( 
365                         ctx->nodeList, ctx->nodeList->size - 1 );
366         if( parent ) xmlAddChild( parent, comment );
367 }
368
369
370
371 void xmlBuilderProcInstruction( 
372                         void* blob, const xmlChar* name, const xmlChar* data ) {
373         xmlBuilderContext* ctx = (xmlBuilderContext*) blob;
374         //xmlNodePtr node = xmlNewDocPI( ctx->doc, name, data );
375         xmlNodePtr pi = xmlNewDocPI( ctx->doc, name, data );
376         xmlAddChild( (xmlNodePtr) ctx->doc, pi );
377 }
378
379
380
381 void xmlBuilderAddDtd( const char* sysId, xmlBuilderContext* context ) {
382
383         if(!sysId) return;
384         if( osrfHashGet( context->dtdHash, sysId ) ) return; /* already parsed this hash */
385
386         //apacheDebug("Adding new DTD file to the entity hash: %s", sysId);
387
388         /* use the dynamic locale if defined... default locale instead */
389         char* locale;
390         if(__xmlBuilderDynamicLocale) locale = __xmlBuilderDynamicLocale;
391         else locale = context->config->defaultLocale;
392
393         /* determine the path to the DTD file and load it */
394         int len = strlen(context->config->baseDir) + strlen(locale) + strlen(sysId) + 4;
395         char buf[len]; bzero(buf,len);
396         snprintf( buf, len, "%s/%s/%s", context->config->baseDir, locale, sysId );
397
398         xmlDtdPtr dtd = xmlParseDTD(NULL, buf);
399         if(!dtd) return;
400
401         /* cycle through entities and push them into the entity hash */
402         xmlNodePtr node = dtd->children;
403         while( node ) {
404                 if( node->type == XML_ENTITY_DECL ) { /* shove the entities into the hash */
405                         xmlEntityPtr ent = (xmlEntityPtr) node;
406                         osrfHashSet( context->entHash, ent, (char*) ent->name );
407                 }
408                 node = node->next;
409         }
410
411         /* cache the DTD so we can free it later */
412         osrfHashSet( context->dtdHash, dtd, sysId );
413 }
414
415
416 /* ------------------------------------------------------------------------ */
417
418 /* register callbacks */
419 static void xmlBuilderRegisterHooks (apr_pool_t *p) {
420         ap_hook_handler(xmlBuilderHandler, NULL, NULL, APR_HOOK_MIDDLE);
421         ap_hook_child_init(xmlBuilderChildInit,NULL,NULL,APR_HOOK_MIDDLE);
422 }
423
424
425 /* finally, flesh the module */
426 module AP_MODULE_DECLARE_DATA xmlbuilder_module = {
427         STANDARD20_MODULE_STUFF,
428         NULL,
429         NULL,
430         xmlBuilderCreateConfig,
431         NULL,
432         xmlBuilderCommands,
433         xmlBuilderRegisterHooks,
434 };
435
436
437
438
439