XSLT filter uses XML config for to list each supported recordSchema.
[idzebra-moved-to-github.git] / recctrl / xslt.c
1 /* $Id: xslt.c,v 1.6 2005-05-31 17:36:16 adam Exp $
2    Copyright (C) 1995-2005
3    Index Data ApS
4
5 This file is part of the Zebra server.
6
7 Zebra is free software; you can redistribute it and/or modify it under
8 the terms of the GNU General Public License as published by the Free
9 Software Foundation; either version 2, or (at your option) any later
10 version.
11
12 Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
13 WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Zebra; see the file LICENSE.zebra.  If not, write to the
19 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20 02111-1307, USA.
21 */
22
23 #include <stdio.h>
24 #include <assert.h>
25 #include <ctype.h>
26
27 #include <yaz/diagbib1.h>
28 #include <libxml/xmlversion.h>
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
31 #include <libxml/xmlreader.h>
32 #include <libxslt/transform.h>
33
34 #include <idzebra/util.h>
35 #include <idzebra/recctrl.h>
36
37 struct filter_schema {
38     const char *name;
39     const char *identifier;
40     const char *stylesheet;
41     struct filter_schema *next;
42     const char *default_schema;
43     xsltStylesheetPtr stylesheet_xsp;
44 };
45
46 struct filter_info {
47     xmlDocPtr doc;
48     char *fname;
49     int split_depth;
50     ODR odr;
51     struct filter_schema *schemas;
52     xmlTextReaderPtr reader;
53 };
54
55 #define ZEBRA_INDEX_NS "http://indexdata.dk/zebra/indexing/1"
56 #define ZEBRA_SCHEMA_IDENTITY_NS "http://indexdata.dk/zebra/identity/1"
57 static const char *zebra_index_ns = ZEBRA_INDEX_NS;
58
59 static void set_param_str(const char **params, const char *name,
60                           const char *value, ODR odr)
61 {
62     char *quoted = odr_malloc(odr, 3 + strlen(value));
63     sprintf(quoted, "'%s'", value);
64     while (*params)
65         params++;
66     params[0] = name;
67     params[1] = quoted;
68     params[2] = 0;
69 }
70
71 static void set_param_int(const char **params, const char *name,
72                           zint value, ODR odr)
73 {
74     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
75     while (*params)
76         params++;
77     sprintf(quoted, "'" ZINT_FORMAT "'", value);
78     params[0] = name;
79     params[1] = quoted;
80     params[2] = 0;
81 }
82
83
84 static void *filter_init_xslt(Res res, RecType recType)
85 {
86     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
87     tinfo->reader = 0;
88     tinfo->fname = 0;
89     tinfo->split_depth = 0;
90     tinfo->odr = odr_createmem(ODR_ENCODE);
91     tinfo->doc = 0;
92     tinfo->schemas = 0;
93     return tinfo;
94 }
95
96 static void *filter_init_xslt1(Res res, RecType recType)
97 {
98     struct filter_info *tinfo = (struct filter_info *)
99         filter_init_xslt(res, recType);
100     tinfo->split_depth = 1;
101     return tinfo;
102 }
103
104 static int attr_content(struct _xmlAttr *attr, const char *name,
105                         const char **dst_content)
106 {
107     if (!strcmp(attr->name, name) && attr->children &&
108         attr->children->type == XML_TEXT_NODE)
109     {
110         *dst_content = attr->children->content;
111         return 1;
112     }
113     return 0;
114 }
115
116 static void destroy_schemas(struct filter_info *tinfo)
117 {
118     struct filter_schema *schema = tinfo->schemas;
119     while (schema)
120     {
121         struct filter_schema *schema_next = schema->next;
122         if (schema->stylesheet_xsp)
123             xsltFreeStylesheet(schema->stylesheet_xsp);
124         xfree(schema);
125         schema = schema_next;
126     }
127     tinfo->schemas = 0;
128     xfree(tinfo->fname);
129     if (tinfo->doc)
130         xmlFreeDoc(tinfo->doc);    
131     tinfo->doc = 0;
132 }
133
134 static ZEBRA_RES create_schemas(struct filter_info *tinfo, const char *fname)
135 {
136     xmlNodePtr ptr;
137     tinfo->fname = xstrdup(fname);
138     tinfo->doc = xmlParseFile(tinfo->fname);
139     if (!tinfo->doc)
140         return ZEBRA_FAIL;
141     ptr = xmlDocGetRootElement(tinfo->doc);
142     if (!ptr || ptr->type != XML_ELEMENT_NODE ||
143         strcmp(ptr->name, "schemaInfo"))
144         return ZEBRA_FAIL;
145     for (ptr = ptr->children; ptr; ptr = ptr->next)
146     {
147         if (ptr->type == XML_ELEMENT_NODE &&
148             !strcmp(ptr->name, "schema"))
149         {
150             struct _xmlAttr *attr;
151             struct filter_schema *schema = xmalloc(sizeof(*schema));
152             schema->name = 0;
153             schema->identifier = 0;
154             schema->stylesheet = 0;
155             schema->default_schema = 0;
156             schema->next = tinfo->schemas;
157             schema->stylesheet_xsp = 0;
158             tinfo->schemas = schema;
159             for (attr = ptr->properties; attr; attr = attr->next)
160             {
161                 attr_content(attr, "identifier", &schema->identifier);
162                 attr_content(attr, "name", &schema->name);
163                 attr_content(attr, "stylesheet", &schema->stylesheet);
164                 attr_content(attr, "default", &schema->default_schema);
165             }
166             if (schema->stylesheet)
167                 schema->stylesheet_xsp =
168                     xsltParseStylesheetFile(
169                         (const xmlChar*) schema->stylesheet);
170         }
171     }
172     return ZEBRA_OK;
173 }
174
175 static struct filter_schema *lookup_schema(struct filter_info *tinfo,
176                                            const char *est)
177 {
178     struct filter_schema *schema;
179     for (schema = tinfo->schemas; schema; schema = schema->next)
180     {
181         if (est)
182         {
183             if (schema->identifier && !strcmp(schema->identifier, est))
184                 return schema;
185             if (schema->name && !strcmp(schema->name, est))
186                 return schema;
187         }
188         if (schema->default_schema)
189             return schema;
190     }
191     return 0;
192 }
193
194 static void filter_config(void *clientData, Res res, const char *args)
195 {
196     struct filter_info *tinfo = clientData;
197     if (!args || !*args)
198         args = "xsltfilter.xml";
199     if (tinfo->fname && !strcmp(args, tinfo->fname))
200         return;
201     destroy_schemas(tinfo);
202     create_schemas(tinfo, args);
203 }
204
205 static void filter_destroy(void *clientData)
206 {
207     struct filter_info *tinfo = clientData;
208     destroy_schemas(tinfo);
209     if (tinfo->reader)
210         xmlFreeTextReader(tinfo->reader);
211     odr_destroy(tinfo->odr);
212     xfree(tinfo);
213 }
214
215 static int ioread_ex(void *context, char *buffer, int len)
216 {
217     struct recExtractCtrl *p = context;
218     return (*p->readf)(p->fh, buffer, len);
219 }
220
221 static int ioclose_ex(void *context)
222 {
223     return 0;
224 }
225
226 static void index_field(struct filter_info *tinfo, struct recExtractCtrl *ctrl,
227                         xmlNodePtr ptr, RecWord *recWord)
228 {
229     for(; ptr; ptr = ptr->next)
230     {
231         index_field(tinfo, ctrl, ptr->children, recWord);
232         if (ptr->type != XML_TEXT_NODE)
233             continue;
234         recWord->term_buf = ptr->content;
235         recWord->term_len = strlen(ptr->content);
236         (*ctrl->tokenAdd)(recWord);
237     }
238 }
239
240 static void index_node(struct filter_info *tinfo,  struct recExtractCtrl *ctrl,
241                        xmlNodePtr ptr, RecWord *recWord)
242 {
243     for(; ptr; ptr = ptr->next)
244     {
245         index_node(tinfo, ctrl, ptr->children, recWord);
246         if (ptr->type != XML_ELEMENT_NODE || !ptr->ns ||
247             strcmp(ptr->ns->href, zebra_index_ns))
248             continue;
249         if (!strcmp(ptr->name, "index"))
250         {
251             char *field_str = 0;
252             const char *xpath_str = 0;
253             struct _xmlAttr *attr;
254             for (attr = ptr->properties; attr; attr = attr->next)
255             {
256                 if (!strcmp(attr->name, "field") 
257                     && attr->children && attr->children->type == XML_TEXT_NODE)
258                     field_str = attr->children->content;
259                 if (!strcmp(attr->name, "xpath") 
260                     && attr->children && attr->children->type == XML_TEXT_NODE)
261                     xpath_str = attr->children->content;
262             }
263             if (field_str)
264             {
265                 recWord->attrStr = field_str;
266                 index_field(tinfo, ctrl, ptr->children, recWord);
267             }
268         }
269     }
270 }
271
272 static int extract_doc(struct filter_info *tinfo, struct recExtractCtrl *p,
273                        xmlDocPtr doc)
274 {
275     RecWord recWord;
276     const char *params[10];
277     xmlChar *buf_out;
278     int len_out;
279
280     struct filter_schema *schema = lookup_schema(tinfo, ZEBRA_INDEX_NS);
281
282     params[0] = 0;
283     set_param_str(params, "schema", ZEBRA_INDEX_NS, tinfo->odr);
284
285     (*p->init)(p, &recWord);
286     recWord.reg_type = 'w';
287
288     if (schema && schema->stylesheet_xsp)
289     {
290         xmlDocPtr resDoc = 
291             xsltApplyStylesheet(schema->stylesheet_xsp,
292                                 doc, params);
293         if (p->flagShowRecords)
294         {
295             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
296             fwrite(buf_out, len_out, 1, stdout);
297             xmlFree(buf_out);
298         }
299         index_node(tinfo, p, xmlDocGetRootElement(resDoc), &recWord);
300         xmlFreeDoc(resDoc);
301     }
302     xmlDocDumpMemory(doc, &buf_out, &len_out);
303     if (p->flagShowRecords)
304         fwrite(buf_out, len_out, 1, stdout);
305     (*p->setStoreData)(p, buf_out, len_out);
306     xmlFree(buf_out);
307     
308     xmlFreeDoc(doc);
309     return RECCTRL_EXTRACT_OK;
310 }
311
312 static int extract_split(struct filter_info *tinfo, struct recExtractCtrl *p)
313 {
314     int ret;
315     if (p->first_record)
316     {
317         if (tinfo->reader)
318             xmlFreeTextReader(tinfo->reader);
319         tinfo->reader = xmlReaderForIO(ioread_ex, ioclose_ex,
320                                        p /* I/O handler */,
321                                        0 /* URL */, 
322                                        0 /* encoding */,
323                                        XML_PARSE_XINCLUDE);
324     }
325     if (!tinfo->reader)
326         return RECCTRL_EXTRACT_ERROR_GENERIC;
327
328     ret = xmlTextReaderRead(tinfo->reader);
329     while (ret == 1) {
330         int type = xmlTextReaderNodeType(tinfo->reader);
331         int depth = xmlTextReaderDepth(tinfo->reader);
332         if (tinfo->split_depth == 0 ||
333             (type == XML_READER_TYPE_ELEMENT && tinfo->split_depth == depth))
334         {
335             xmlNodePtr ptr = xmlTextReaderExpand(tinfo->reader);
336             xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
337             xmlDocPtr doc = xmlNewDoc("1.0");
338
339             xmlDocSetRootElement(doc, ptr2);
340
341             return extract_doc(tinfo, p, doc);      
342         }
343         ret = xmlTextReaderRead(tinfo->reader);
344     }
345     xmlFreeTextReader(tinfo->reader);
346     tinfo->reader = 0;
347     return RECCTRL_EXTRACT_EOF;
348 }
349
350 static int extract_full(struct filter_info *tinfo, struct recExtractCtrl *p)
351 {
352     if (p->first_record) /* only one record per stream */
353     {
354         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
355                                   0 /* URL */,
356                                   0 /* encoding */,
357                                   XML_PARSE_XINCLUDE);
358         if (!doc)
359         {
360             return RECCTRL_EXTRACT_ERROR_GENERIC;
361         }
362         return extract_doc(tinfo, p, doc);
363     }
364     else
365         return RECCTRL_EXTRACT_EOF;
366 }
367
368 static int filter_extract(void *clientData, struct recExtractCtrl *p)
369 {
370     struct filter_info *tinfo = clientData;
371
372     odr_reset(tinfo->odr);
373
374     if (tinfo->split_depth == 0)
375         return extract_full(tinfo, p);
376     else
377     {
378         return extract_split(tinfo, p);
379     }
380 }
381
382 static int ioread_ret(void *context, char *buffer, int len)
383 {
384     struct recRetrieveCtrl *p = context;
385     return (*p->readf)(p->fh, buffer, len);
386 }
387
388 static int ioclose_ret(void *context)
389 {
390     return 0;
391 }
392
393
394 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
395 {
396     const char *esn = ZEBRA_SCHEMA_IDENTITY_NS;
397     const char *params[10];
398     struct filter_info *tinfo = clientData;
399     xmlDocPtr resDoc;
400     xmlDocPtr doc;
401     struct filter_schema *schema;
402
403     if (p->comp)
404     {
405         if (p->comp->which != Z_RecordComp_simple
406             || p->comp->u.simple->which != Z_ElementSetNames_generic)
407         {
408             p->diagnostic = YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP;
409             return 0;
410         }
411         esn = p->comp->u.simple->u.generic;
412     }
413     schema = lookup_schema(tinfo, esn);
414     if (!schema)
415     {
416         p->diagnostic =
417             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
418         return 0;
419     }
420
421     params[0] = 0;
422     set_param_str(params, "schema", esn, p->odr);
423     if (p->fname)
424         set_param_str(params, "filename", p->fname, p->odr);
425     if (p->score >= 0)
426         set_param_int(params, "score", p->score, p->odr);
427     set_param_int(params, "size", p->recordSize, p->odr);
428     
429     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
430                     0 /* URL */,
431                     0 /* encoding */,
432                     XML_PARSE_XINCLUDE);
433     if (!doc)
434     {
435         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
436         return 0;
437     }
438
439     if (!schema->stylesheet_xsp)
440         resDoc = doc;
441     else
442     {
443         resDoc = xsltApplyStylesheet(schema->stylesheet_xsp,
444                                      doc, params);
445         xmlFreeDoc(doc);
446     }
447     if (!resDoc)
448     {
449         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
450     }
451     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
452     {
453         xmlChar *buf_out;
454         int len_out;
455         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
456
457         p->output_format = VAL_TEXT_XML;
458         p->rec_len = len_out;
459         p->rec_buf = odr_malloc(p->odr, p->rec_len);
460         memcpy(p->rec_buf, buf_out, p->rec_len);
461         
462         xmlFree(buf_out);
463     }
464     else if (p->output_format == VAL_SUTRS)
465     {
466         xmlChar *buf_out;
467         int len_out;
468         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
469
470         p->output_format = VAL_SUTRS;
471         p->rec_len = len_out;
472         p->rec_buf = odr_malloc(p->odr, p->rec_len);
473         memcpy(p->rec_buf, buf_out, p->rec_len);
474         
475         xmlFree(buf_out);
476     }
477     else
478     {
479         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
480     }
481     xmlFreeDoc(resDoc);
482     return 0;
483 }
484
485 static struct recType filter_type_xslt = {
486     0,
487     "xslt",
488     filter_init_xslt,
489     filter_config,
490     filter_destroy,
491     filter_extract,
492     filter_retrieve
493 };
494
495 static struct recType filter_type_xslt1 = {
496     0,
497     "xslt1",
498     filter_init_xslt1,
499     filter_config,
500     filter_destroy,
501     filter_extract,
502     filter_retrieve
503 };
504
505 RecType
506 #ifdef IDZEBRA_STATIC_XSLT
507 idzebra_filter_xslt
508 #else
509 idzebra_filter
510 #endif
511
512 [] = {
513     &filter_type_xslt,
514 #ifdef LIBXML_READER_ENABLED
515     &filter_type_xslt1,
516 #endif
517     0,
518 };