recover from non-existant DTD's
[Evergreen.git] / Open-ILS / src / apachemods / mod_xmlbuilder.c
1 #include "mod_xmlbuilder.h"
2
3 char* __xmlBuilderDynamicLocale = NULL;
4
5
6 /* set the base DTD directory */
7 static const char* xmlBuilderSetBaseDir(cmd_parms *params, void *cfg, const char *arg) {
8         xmlBuilderConfig* config = ap_get_module_config(
9                 params->server->module_config, &xmlbuilder_module );
10         config->baseDir = (char*) arg;
11         return NULL;
12 }
13
14 static const char* xmlBuilderSetDefaultLocale(
15                                          cmd_parms* params, void* cfg, const char* arg ) {
16         xmlBuilderConfig* config = ap_get_module_config(
17                 params->server->module_config, &xmlbuilder_module );
18         config->defaultLocale = (char*) arg;
19         return NULL;
20 }
21
22 static const char* xmlBuilderSetDefaultDtd(
23                                          cmd_parms* params, void* cfg, const char* arg ) {
24         xmlBuilderConfig* config = ap_get_module_config(
25                 params->server->module_config, &xmlbuilder_module );
26         config->defaultDtd = (char*) arg;
27         return NULL;
28 }
29
30
31 static const char* xmlBuilderSetLocaleParam(
32                                          cmd_parms* params, void* cfg, const char* arg ) {
33         xmlBuilderConfig* config = ap_get_module_config(
34                 params->server->module_config, &xmlbuilder_module );
35         config->localeParam = (char*) arg;
36         return NULL;
37 }
38
39
40 static const char* xmlBuilderSetPostXSL(
41                                          cmd_parms* params, void* cfg, const char* arg ) {
42         xmlBuilderConfig* config = ap_get_module_config(
43                 params->server->module_config, &xmlbuilder_module );
44         config->postXSL = xsltParseStylesheetFile((xmlChar*) arg);
45         if( config->postXSL == NULL ) 
46                 apacheDebug("Unable to parse postXSL stylesheet: %s.  No postXSL will be performed", arg);      
47         return NULL;
48 }
49
50 static const command_rec xmlBuilderCommands[] = {
51         AP_INIT_TAKE1( MODXMLB_CONFIG_LOCALE, 
52                         xmlBuilderSetDefaultLocale, NULL, ACCESS_CONF, "Default Locale"),
53         AP_INIT_TAKE1( MODXMLB_CONFIG_BASE_DIR, 
54                         xmlBuilderSetBaseDir, NULL, ACCESS_CONF, "Base Directory"),
55         AP_INIT_TAKE1( MODXMLB_CONFIG_POST_XSL, 
56                         xmlBuilderSetPostXSL, NULL, ACCESS_CONF, "Post XSL"),
57         AP_INIT_TAKE1( MODXMLB_CONFIG_DEFAULT_DTD, 
58                         xmlBuilderSetDefaultDtd, NULL, ACCESS_CONF, "Default DTD"),
59         AP_INIT_TAKE1( MODXMLB_CONFIG_LOCALE_PARAM,
60                         xmlBuilderSetLocaleParam, NULL, ACCESS_CONF, "Default DTD"),
61         {NULL}
62 };
63
64 static void* xmlBuilderCreateConfig( apr_pool_t* p, server_rec* s ) {
65         xmlBuilderConfig* config = 
66                 (xmlBuilderConfig*) apr_palloc( p, sizeof(xmlBuilderConfig) );
67         config->baseDir                 = MODXMLB_DEFAULT_BASE_DIR;
68         config->defaultLocale   = MODXMLB_DEFAULT_LOCALE;
69         config->defaultDtd              = NULL;
70         config->postXSL                 = NULL;
71         config->localeParam             = MODXMLB_DEFAULT_LOCALE_PARAM;
72         return (void*) config;
73 }
74
75
76 /* Child Init handler  ----------------------------------------------------------- */
77 static void xmlBuilderChildInit( apr_pool_t *p, server_rec *s ) {
78 }
79
80 static int xmlBuilderHandler( request_rec* r ) {
81
82         if( strcmp(r->handler, MODULE_NAME ) ) return DECLINED;
83
84         xmlBuilderConfig* config = ap_get_module_config( 
85                         r->server->module_config, &xmlbuilder_module );
86         
87         r->allowed |= (AP_METHOD_BIT << M_GET);
88         r->allowed |= (AP_METHOD_BIT << M_POST);
89         ap_set_content_type(r, "text/html; charset=utf-8");
90
91         string_array* params = apacheParseParms(r);
92         char* locale = apacheGetFirstParamValue(params, config->localeParam);
93         if(locale) __xmlBuilderDynamicLocale = locale;
94         char* XMLFile = r->filename;
95
96         xmlDocPtr doc = xmlBuilderProcessFile( XMLFile, config );
97         if(!doc) return apacheError( "Unable to parse XML file %s", XMLFile );
98
99         /* apply the post XSL */
100         if(config->postXSL) {
101                 xmlDocPtr newdoc;
102                 newdoc = xsltApplyStylesheet(config->postXSL, doc, NULL );
103
104                 if(newdoc == NULL) {
105                         apacheDebug("Error applying postXSL... skipping.");
106                 } else {
107                         xmlFreeDoc(doc);
108                         doc = newdoc;
109                 }
110         }
111
112         char* docXML = xmlDocToString( doc, 1 );
113         ap_rputs(docXML, r);
114         free(docXML);
115         xmlFreeDoc( doc );
116         doc = NULL;
117         xmlCleanupCharEncodingHandlers();
118         xmlCleanupParser();
119
120         return OK;
121 }
122
123
124 /* frees the collected DTD's */
125 static void __xmlBuilderFreeDtdHash( char* key, void* item ) {
126         if(!item) return;
127         xmlFreeDtd( item );
128 }
129
130
131 xmlDocPtr xmlBuilderProcessFile( char* filename, xmlBuilderConfig* config ) {
132         if(!filename) { 
133                 apacheError( "No XML file provided" ); return NULL; }
134
135         xmlBuilderContext context;
136         context.config          = config;
137         context.doc                     = xmlNewDoc( BAD_CAST "1.0" );
138         context.dtdHash = osrfNewHash();
139         context.entHash = osrfNewHash();
140         context.nodeList        = osrfNewList();
141         context.xmlError        = 0;
142         context.xmlFile = filename;
143         context.dtdHash->freeItem = &__xmlBuilderFreeDtdHash;
144
145         /* pre-parse the default dtd if defined */
146         if( config->defaultDtd ) 
147                 xmlBuilderAddDtd( config->defaultDtd, &context );
148
149         xmlParserCtxtPtr parserCtx;
150
151         parserCtx = xmlCreatePushParserCtxt(xmlBuilderSaxHandler, &context, "", 0, NULL);
152         xmlCtxtReadFile( parserCtx, filename, NULL, XML_PARSE_RECOVER );
153
154         xmlFreeParserCtxt( parserCtx );
155         osrfListFree(context.nodeList);
156         osrfHashFree(context.entHash);
157         osrfHashFree(context.dtdHash);
158         return context.doc;
159 }
160
161
162 void xmlBuilderStartElement( void* context, const xmlChar *name, const xmlChar **atts ) {
163         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
164
165         xmlNodePtr node = NULL;
166
167         /* process xincludes as a sub-doc */
168         if( !strcmp( name, "xi:include" ) ) { 
169
170                 char* href = strdup(xmlSaxAttr( atts, "href" ));
171                 if(href) {
172
173                         /* find the relative path for the xinclude */
174                         if(href[0] != '/') {
175                                 int len = strlen(ctx->xmlFile) + strlen(href) + 1;
176                                 char buf[len];
177                                 bzero(buf, len);
178                                 strcpy( buf, ctx->xmlFile );
179                                 int i;
180                                 for( i = strlen(buf); i != 0; i-- ) {
181                                         if( buf[i] == '/' ) break;
182                                         buf[i] = '\0';
183                                 }
184                                 strcat( buf, href );
185                                 free(href);
186                                 href = strdup(buf);
187                         }
188
189
190                         xmlDocPtr subDoc = xmlBuilderProcessFile( href, ctx->config );
191                         node = xmlDocGetRootElement( subDoc );
192                 }
193
194                 if(!node) {
195                         apacheError("Unable to parse xinclude: %s", href );
196                         free(href);
197                         return;
198                 }
199                 free(href);
200
201         } else {
202                 node = xmlNewNode(NULL, name);
203                 xmlAddAttrs( node, atts );
204         }
205
206
207         xmlNodePtr parent = osrfListGetIndex( 
208                         ctx->nodeList, ctx->nodeList->size - 1 );
209
210         if( parent ) xmlAddChild( parent, node );
211         else xmlDocSetRootElement(ctx->doc, node);
212         
213         osrfListPush( ctx->nodeList, node );
214 }
215
216 void xmlBuilderEndElement( void* context, const xmlChar* name ) {
217         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
218         osrfListPop( ctx->nodeList );
219 }
220
221
222 void xmlBuilderHandleCharacter(void* context, const xmlChar *ch, int len) {
223         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
224         xmlNodePtr node = osrfListGetIndex( 
225                         ctx->nodeList, ctx->nodeList->size - 1 );
226
227         if(node) {
228                 xmlNodePtr txt = xmlNewTextLen(ch, len);
229                 xmlAddChild( node, txt );
230         }
231
232 }
233
234
235 void xmlBuilderParseError( void* context, const char* msg, ... ) {
236         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
237         VA_LIST_TO_STRING(msg);
238         apacheDebug( "Parser Error Occurred: %s", VA_BUF);
239         ctx->xmlError = 1;
240 }
241
242
243 xmlEntityPtr xmlBuilderGetEntity( void* context, const xmlChar* name ) {
244         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
245         return osrfHashGet( ctx->entHash, name );
246 }
247
248
249 void xmlBuilderExtSubset( void* blob, 
250                 const xmlChar* name, const xmlChar* extId, const xmlChar* sysId ) {
251
252         xmlBuilderContext* context = (xmlBuilderContext*) blob;
253         if( context->config->defaultDtd ) return; /* only use the default if defined */
254         xmlBuilderAddDtd( sysId, context );
255 }
256
257
258
259 void xmlBuilderAddDtd( const char* sysId, xmlBuilderContext* context ) {
260
261         if(!sysId) return;
262         if( osrfHashGet( context->dtdHash, sysId ) ) return; /* already parsed this hash */
263
264         /* use the dynamic locale if defined... default locale instead */
265         char* locale;
266         if(__xmlBuilderDynamicLocale) locale = __xmlBuilderDynamicLocale;
267         else locale = context->config->defaultLocale;
268
269         /* determine the path to the DTD file and load it */
270         int len = strlen(context->config->baseDir) + strlen(locale) + strlen(sysId) + 4;
271         char buf[len]; bzero(buf,len);
272         snprintf( buf, len, "%s/%s/%s", context->config->baseDir, locale, sysId );
273
274         xmlDtdPtr dtd = xmlParseDTD(NULL, buf);
275         if(!dtd) return;
276
277         /* cycle through entities and push them into the entity hash */
278         xmlNodePtr node = dtd->children;
279         while( node ) {
280                 if( node->type == XML_ENTITY_DECL ) { /* shove the entities into the hash */
281                         xmlEntityPtr ent = (xmlEntityPtr) node;
282                         osrfHashSet( context->entHash, ent, (char*) ent->name );
283                 }
284                 node = node->next;
285         }
286
287         /* cache the DTD so we can free it later */
288         osrfHashSet( context->dtdHash, dtd, sysId );
289 }
290
291
292 /* ------------------------------------------------------------------------ */
293
294 /* register callbacks */
295 static void xmlBuilderRegisterHooks (apr_pool_t *p) {
296         ap_hook_handler(xmlBuilderHandler, NULL, NULL, APR_HOOK_MIDDLE);
297         ap_hook_child_init(xmlBuilderChildInit,NULL,NULL,APR_HOOK_MIDDLE);
298 }
299
300
301 /* finally, flesh the module */
302 module AP_MODULE_DECLARE_DATA xmlbuilder_module = {
303         STANDARD20_MODULE_STUFF,
304         NULL,
305         NULL,
306         xmlBuilderCreateConfig,
307         NULL,
308         xmlBuilderCommands,
309         xmlBuilderRegisterHooks,
310 };
311
312
313
314
315