Make a few functions static to avoid nameclash with xslt filter
[idzebra-moved-to-github.git] / recctrl / alvis.c
1 /* $Id: alvis.c,v 1.5 2005-08-19 21:40:17 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/xmlIO.h>
32 #include <libxml/xmlreader.h>
33 #include <libxslt/transform.h>
34
35 #include <idzebra/util.h>
36 #include <idzebra/recctrl.h>
37
38 struct filter_schema {
39     const char *name;
40     const char *identifier;
41     const char *stylesheet;
42     struct filter_schema *next;
43     const char *default_schema;
44     const char *include_snippet;
45     xsltStylesheetPtr stylesheet_xsp;
46 };
47
48 struct filter_info {
49     xmlDocPtr doc;
50     char *fname;
51     const char *split_level;
52     const char *split_path;
53     ODR odr;
54     struct filter_schema *schemas;
55     xmlTextReaderPtr reader;
56 };
57
58 #define ZEBRA_SCHEMA_XSLT_NS "http://indexdata.dk/zebra/xslt/1"
59
60 static const char *zebra_xslt_ns = ZEBRA_SCHEMA_XSLT_NS;
61
62 static void set_param_xml(const char **params, const char *name,
63                           const char *value, ODR odr)
64 {
65     while (*params)
66         params++;
67     params[0] = name;
68     params[1] = value;
69     params[2] = 0;
70 }
71
72 static void set_param_str(const char **params, const char *name,
73                           const char *value, ODR odr)
74 {
75     char *quoted = odr_malloc(odr, 3 + strlen(value));
76     sprintf(quoted, "'%s'", value);
77     while (*params)
78         params++;
79     params[0] = name;
80     params[1] = quoted;
81     params[2] = 0;
82 }
83
84 static void set_param_int(const char **params, const char *name,
85                           zint value, ODR odr)
86 {
87     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
88     while (*params)
89         params++;
90     sprintf(quoted, "'" ZINT_FORMAT "'", value);
91     params[0] = name;
92     params[1] = quoted;
93     params[2] = 0;
94 }
95
96 static int zebra_xmlInputMatchCallback (char const *filename)
97 {
98   /* yaz_log(YLOG_LOG, "match %s", filename); */
99     return 0;
100 }
101
102 static void * zebra_xmlInputOpenCallback (char const *filename)
103 {
104     return 0;
105 }
106
107 static int zebra_xmlInputReadCallback (void * context, char * buffer, int len)
108 {
109     return 0;
110 }
111
112 static int zebra_xmlInputCloseCallback (void * context)
113 {
114     return 0;
115 }
116
117 static void *filter_init_xslt(Res res, RecType recType)
118 {
119     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
120     tinfo->reader = 0;
121     tinfo->fname = 0;
122     tinfo->split_level = 0;
123     tinfo->split_path = 0;
124     tinfo->odr = odr_createmem(ODR_ENCODE);
125     tinfo->doc = 0;
126     tinfo->schemas = 0;
127
128 #if 0
129     xmlRegisterDefaultInputCallbacks();
130     xmlRegisterInputCallbacks(zebra_xmlInputMatchCallback,
131                               zebra_xmlInputOpenCallback,
132                               zebra_xmlInputReadCallback,
133                               zebra_xmlInputCloseCallback);
134 #endif
135     return tinfo;
136 }
137
138 static int attr_content(struct _xmlAttr *attr, const char *name,
139                         const char **dst_content)
140 {
141     if (!strcmp(attr->name, name) && attr->children &&
142         attr->children->type == XML_TEXT_NODE)
143     {
144         *dst_content = attr->children->content;
145         return 1;
146     }
147     return 0;
148 }
149
150 static void destroy_schemas(struct filter_info *tinfo)
151 {
152     struct filter_schema *schema = tinfo->schemas;
153     while (schema)
154     {
155         struct filter_schema *schema_next = schema->next;
156         if (schema->stylesheet_xsp)
157             xsltFreeStylesheet(schema->stylesheet_xsp);
158         xfree(schema);
159         schema = schema_next;
160     }
161     tinfo->schemas = 0;
162     xfree(tinfo->fname);
163     if (tinfo->doc)
164         xmlFreeDoc(tinfo->doc);    
165     tinfo->doc = 0;
166 }
167
168 static ZEBRA_RES create_schemas(struct filter_info *tinfo, const char *fname)
169 {
170     xmlNodePtr ptr;
171     tinfo->fname = xstrdup(fname);
172     tinfo->doc = xmlParseFile(tinfo->fname);
173     if (!tinfo->doc)
174         return ZEBRA_FAIL;
175     ptr = xmlDocGetRootElement(tinfo->doc);
176     if (!ptr || ptr->type != XML_ELEMENT_NODE ||
177         strcmp(ptr->name, "schemaInfo"))
178         return ZEBRA_FAIL;
179     for (ptr = ptr->children; ptr; ptr = ptr->next)
180     {
181         if (ptr->type != XML_ELEMENT_NODE)
182             continue;
183         if (!strcmp(ptr->name, "schema"))
184         {
185             struct _xmlAttr *attr;
186             struct filter_schema *schema = xmalloc(sizeof(*schema));
187             schema->name = 0;
188             schema->identifier = 0;
189             schema->stylesheet = 0;
190             schema->default_schema = 0;
191             schema->next = tinfo->schemas;
192             schema->stylesheet_xsp = 0;
193             schema->include_snippet = 0;
194             tinfo->schemas = schema;
195             for (attr = ptr->properties; attr; attr = attr->next)
196             {
197                 attr_content(attr, "identifier", &schema->identifier);
198                 attr_content(attr, "name", &schema->name);
199                 attr_content(attr, "stylesheet", &schema->stylesheet);
200                 attr_content(attr, "default", &schema->default_schema);
201                 attr_content(attr, "snippet", &schema->include_snippet);
202             }
203             if (schema->stylesheet)
204                 schema->stylesheet_xsp =
205                     xsltParseStylesheetFile(
206                         (const xmlChar*) schema->stylesheet);
207         }
208         else if (!strcmp(ptr->name, "split"))
209         {
210             struct _xmlAttr *attr;
211             for (attr = ptr->properties; attr; attr = attr->next)
212             {
213                 attr_content(attr, "level", &tinfo->split_level);
214                 attr_content(attr, "path", &tinfo->split_path);
215             }
216         }
217         else
218         {
219             yaz_log(YLOG_WARN, "Bad element %s in %s", ptr->name, fname);
220             return ZEBRA_FAIL;
221         }
222     }
223     return ZEBRA_OK;
224 }
225
226 static struct filter_schema *lookup_schema(struct filter_info *tinfo,
227                                            const char *est)
228 {
229     struct filter_schema *schema;
230     for (schema = tinfo->schemas; schema; schema = schema->next)
231     {
232         if (est)
233         {
234             if (schema->identifier && !strcmp(schema->identifier, est))
235                 return schema;
236             if (schema->name && !strcmp(schema->name, est))
237                 return schema;
238         }
239         if (schema->default_schema)
240             return schema;
241     }
242     return 0;
243 }
244
245 static void filter_config(void *clientData, Res res, const char *args)
246 {
247     struct filter_info *tinfo = clientData;
248     if (!args || !*args)
249         args = "xsltfilter.xml";
250     if (tinfo->fname && !strcmp(args, tinfo->fname))
251         return;
252     destroy_schemas(tinfo);
253     create_schemas(tinfo, args);
254 }
255
256 static void filter_destroy(void *clientData)
257 {
258     struct filter_info *tinfo = clientData;
259     destroy_schemas(tinfo);
260     if (tinfo->reader)
261         xmlFreeTextReader(tinfo->reader);
262     odr_destroy(tinfo->odr);
263     xfree(tinfo);
264 }
265
266 static int ioread_ex(void *context, char *buffer, int len)
267 {
268     struct recExtractCtrl *p = context;
269     return (*p->readf)(p->fh, buffer, len);
270 }
271
272 static int ioclose_ex(void *context)
273 {
274     return 0;
275 }
276
277 static void index_cdata(struct filter_info *tinfo, struct recExtractCtrl *ctrl,
278                         xmlNodePtr ptr, RecWord *recWord)
279 {
280     for(; ptr; ptr = ptr->next)
281     {
282         index_cdata(tinfo, ctrl, ptr->children, recWord);
283         if (ptr->type != XML_TEXT_NODE)
284             continue;
285         recWord->term_buf = ptr->content;
286         recWord->term_len = strlen(ptr->content);
287         (*ctrl->tokenAdd)(recWord);
288     }
289 }
290
291 static void index_node(struct filter_info *tinfo,  struct recExtractCtrl *ctrl,
292                        xmlNodePtr ptr, RecWord *recWord)
293 {
294     for(; ptr; ptr = ptr->next)
295     {
296         index_node(tinfo, ctrl, ptr->children, recWord);
297         if (ptr->type != XML_ELEMENT_NODE || !ptr->ns ||
298             strcmp(ptr->ns->href, zebra_xslt_ns))
299             continue;
300         if (!strcmp(ptr->name, "index"))
301         {
302             const char *name_str = 0;
303             const char *type_str = 0;
304             const char *xpath_str = 0;
305             struct _xmlAttr *attr;
306             for (attr = ptr->properties; attr; attr = attr->next)
307             {
308                 attr_content(attr, "name", &name_str);
309                 attr_content(attr, "xpath", &xpath_str);
310                 attr_content(attr, "type", &type_str);
311             }
312             if (name_str)
313             {
314                 int prev_type = recWord->index_type; /* save default type */
315
316                 if (type_str && *type_str)
317                     recWord->index_type = *type_str; /* type was given */
318                 recWord->index_name = name_str;
319                 index_cdata(tinfo, ctrl, ptr->children, recWord);
320
321                 recWord->index_type = prev_type;     /* restore it again */
322             }
323         }
324     }
325 }
326
327 static void index_record(struct filter_info *tinfo,struct recExtractCtrl *ctrl,
328                          xmlNodePtr ptr, RecWord *recWord)
329 {
330     if (ptr && ptr->type == XML_ELEMENT_NODE && ptr->ns &&
331         !strcmp(ptr->ns->href, zebra_xslt_ns)
332         && !strcmp(ptr->name, "record"))
333     {
334         const char *type_str = "update";
335         const char *id_str = 0;
336         const char *rank_str = 0;
337         struct _xmlAttr *attr;
338         for (attr = ptr->properties; attr; attr = attr->next)
339         {
340             attr_content(attr, "type", &type_str);
341             attr_content(attr, "id", &id_str);
342             attr_content(attr, "rank", &rank_str);
343         }
344         if (id_str)
345             sscanf(id_str, "%255s", ctrl->match_criteria);
346         if (rank_str)
347         {
348             ctrl->staticrank = atoi(rank_str);
349             yaz_log(YLOG_LOG, "rank=%d",ctrl->staticrank);
350         }
351         else
352             yaz_log(YLOG_LOG, "no rank");
353         
354         ptr = ptr->children;
355     }
356     index_node(tinfo, ctrl, ptr, recWord);
357 }
358     
359 static int extract_doc(struct filter_info *tinfo, struct recExtractCtrl *p,
360                        xmlDocPtr doc)
361 {
362     RecWord recWord;
363     const char *params[10];
364     xmlChar *buf_out;
365     int len_out;
366
367     struct filter_schema *schema = lookup_schema(tinfo, zebra_xslt_ns);
368
369     params[0] = 0;
370     set_param_str(params, "schema", zebra_xslt_ns, tinfo->odr);
371
372     (*p->init)(p, &recWord);
373
374     if (schema && schema->stylesheet_xsp)
375     {
376         xmlNodePtr root_ptr;
377         xmlDocPtr resDoc = 
378             xsltApplyStylesheet(schema->stylesheet_xsp,
379                                 doc, params);
380         if (p->flagShowRecords)
381         {
382             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
383             fwrite(buf_out, len_out, 1, stdout);
384             xmlFree(buf_out);
385         }
386         root_ptr = xmlDocGetRootElement(resDoc);
387         if (root_ptr)
388             index_record(tinfo, p, root_ptr, &recWord);
389         else
390         {
391             yaz_log(YLOG_WARN, "No root for index XML record."
392                     " split_level=%s stylesheet=%s",
393                     tinfo->split_level, schema->stylesheet);
394         }
395         xmlFreeDoc(resDoc);
396     }
397     xmlDocDumpMemory(doc, &buf_out, &len_out);
398     if (p->flagShowRecords)
399         fwrite(buf_out, len_out, 1, stdout);
400     (*p->setStoreData)(p, buf_out, len_out);
401     xmlFree(buf_out);
402     
403     xmlFreeDoc(doc);
404     return RECCTRL_EXTRACT_OK;
405 }
406
407 static int extract_split(struct filter_info *tinfo, struct recExtractCtrl *p)
408 {
409     int ret;
410     int split_depth = 0;
411     if (p->first_record)
412     {
413         if (tinfo->reader)
414             xmlFreeTextReader(tinfo->reader);
415         tinfo->reader = xmlReaderForIO(ioread_ex, ioclose_ex,
416                                        p /* I/O handler */,
417                                        0 /* URL */, 
418                                        0 /* encoding */,
419                                        XML_PARSE_XINCLUDE);
420     }
421     if (!tinfo->reader)
422         return RECCTRL_EXTRACT_ERROR_GENERIC;
423
424     if (tinfo->split_level)
425         split_depth = atoi(tinfo->split_level);
426     ret = xmlTextReaderRead(tinfo->reader);
427     while (ret == 1) {
428         int type = xmlTextReaderNodeType(tinfo->reader);
429         int depth = xmlTextReaderDepth(tinfo->reader);
430         if (split_depth == 0 ||
431             (split_depth > 0 &&
432              type == XML_READER_TYPE_ELEMENT && split_depth == depth))
433         {
434             xmlNodePtr ptr = xmlTextReaderExpand(tinfo->reader);
435             xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
436             xmlDocPtr doc = xmlNewDoc("1.0");
437
438             xmlDocSetRootElement(doc, ptr2);
439
440             return extract_doc(tinfo, p, doc);      
441         }
442         ret = xmlTextReaderRead(tinfo->reader);
443     }
444     xmlFreeTextReader(tinfo->reader);
445     tinfo->reader = 0;
446     return RECCTRL_EXTRACT_EOF;
447 }
448
449 static int extract_full(struct filter_info *tinfo, struct recExtractCtrl *p)
450 {
451     if (p->first_record) /* only one record per stream */
452     {
453         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
454                                   0 /* URL */,
455                                   0 /* encoding */,
456                                   XML_PARSE_XINCLUDE);
457         if (!doc)
458         {
459             return RECCTRL_EXTRACT_ERROR_GENERIC;
460         }
461         return extract_doc(tinfo, p, doc);
462     }
463     else
464         return RECCTRL_EXTRACT_EOF;
465 }
466
467 static int filter_extract(void *clientData, struct recExtractCtrl *p)
468 {
469     struct filter_info *tinfo = clientData;
470
471     odr_reset(tinfo->odr);
472
473     if (tinfo->split_level == 0 && tinfo->split_path == 0)
474         return extract_full(tinfo, p);
475     else
476     {
477         return extract_split(tinfo, p);
478     }
479 }
480
481 static int ioread_ret(void *context, char *buffer, int len)
482 {
483     struct recRetrieveCtrl *p = context;
484     return (*p->readf)(p->fh, buffer, len);
485 }
486
487 static int ioclose_ret(void *context)
488 {
489     return 0;
490 }
491
492
493 static const char *snippet_doc(struct recRetrieveCtrl *p, int text_mode,
494                                int window_size)
495 {
496     const char *xml_doc_str;
497     int ord = 0;
498     WRBUF wrbuf = wrbuf_alloc();
499     zebra_snippets *res = 
500         zebra_snippets_window(p->doc_snippet, p->hit_snippet, window_size);
501     zebra_snippet_word *w = zebra_snippets_list(res);
502
503     if (text_mode)
504         wrbuf_printf(wrbuf, "\'");
505     else
506         wrbuf_printf(wrbuf, "<snippet xmlns='%s'>\n", zebra_xslt_ns);
507     for (; w; w = w->next)
508     {
509         if (ord == 0)
510             ord = w->ord;
511         else if (ord != w->ord)
512
513             break;
514         if (text_mode)
515             wrbuf_printf(wrbuf, "%s%s%s ", 
516                          w->match ? "*" : "",
517                          w->term,
518                          w->match ? "*" : "");
519         else
520         {
521             wrbuf_printf(wrbuf, " <term ord='%d' seqno='" ZINT_FORMAT "' %s>", 
522                          w->ord, w->seqno,
523                          (w->match ? "match='1'" : ""));
524             wrbuf_xmlputs(wrbuf, w->term);
525             wrbuf_printf(wrbuf, "</term>\n");
526         }
527     }
528     if (text_mode)
529         wrbuf_printf(wrbuf, "\'");
530     else
531         wrbuf_printf(wrbuf, "</snippet>\n");
532
533     xml_doc_str = odr_strdup(p->odr, wrbuf_buf(wrbuf));
534
535     zebra_snippets_destroy(res);
536     wrbuf_free(wrbuf, 1);
537     return xml_doc_str;
538 }
539
540 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
541 {
542     const char *esn = zebra_xslt_ns;
543     const char *params[10];
544     struct filter_info *tinfo = clientData;
545     xmlDocPtr resDoc;
546     xmlDocPtr doc;
547     struct filter_schema *schema;
548     int window_size = -1;
549
550     if (p->comp)
551     {
552         if (p->comp->which != Z_RecordComp_simple
553             || p->comp->u.simple->which != Z_ElementSetNames_generic)
554         {
555             p->diagnostic = YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP;
556             return 0;
557         }
558         esn = p->comp->u.simple->u.generic;
559     }
560     schema = lookup_schema(tinfo, esn);
561     if (!schema)
562     {
563         p->diagnostic =
564             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
565         return 0;
566     }
567
568     if (schema->include_snippet)
569         window_size = atoi(schema->include_snippet);
570
571     params[0] = 0;
572     set_param_str(params, "schema", esn, p->odr);
573     if (p->fname)
574         set_param_str(params, "filename", p->fname, p->odr);
575     if (p->score >= 0)
576         set_param_int(params, "score", p->score, p->odr);
577     set_param_int(params, "size", p->recordSize, p->odr);
578     set_param_int(params, "id", p->localno, p->odr);
579
580     if (window_size >= 0)
581         set_param_xml(params, "snippet", snippet_doc(p, 1, window_size),
582                       p->odr);
583     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
584                     0 /* URL */,
585                     0 /* encoding */,
586                     XML_PARSE_XINCLUDE);
587     if (!doc)
588     {
589         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
590         return 0;
591     }
592
593     if (window_size >= 0)
594     {
595         xmlNodePtr node = xmlDocGetRootElement(doc);
596         const char *snippet_str = snippet_doc(p, 0, window_size);
597         xmlDocPtr snippet_doc = xmlParseMemory(snippet_str, strlen(snippet_str));
598         xmlAddChild(node, xmlDocGetRootElement(snippet_doc));
599     }
600     if (!schema->stylesheet_xsp)
601         resDoc = doc;
602     else
603     {
604         resDoc = xsltApplyStylesheet(schema->stylesheet_xsp,
605                                      doc, params);
606         xmlFreeDoc(doc);
607     }
608     if (!resDoc)
609     {
610         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
611     }
612     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
613     {
614         xmlChar *buf_out;
615         int len_out;
616         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
617
618         p->output_format = VAL_TEXT_XML;
619         p->rec_len = len_out;
620         p->rec_buf = odr_malloc(p->odr, p->rec_len);
621         memcpy(p->rec_buf, buf_out, p->rec_len);
622         
623         xmlFree(buf_out);
624     }
625     else if (p->output_format == VAL_SUTRS)
626     {
627         xmlChar *buf_out;
628         int len_out;
629         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
630
631         p->output_format = VAL_SUTRS;
632         p->rec_len = len_out;
633         p->rec_buf = odr_malloc(p->odr, p->rec_len);
634         memcpy(p->rec_buf, buf_out, p->rec_len);
635         
636         xmlFree(buf_out);
637     }
638     else
639     {
640         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
641     }
642     xmlFreeDoc(resDoc);
643     return 0;
644 }
645
646 static struct recType filter_type_alvis = {
647     0,
648     "alvis",
649     filter_init_xslt,
650     filter_config,
651     filter_destroy,
652     filter_extract,
653     filter_retrieve
654 };
655
656 RecType
657 #ifdef IDZEBRA_STATIC_ALVIS
658 idzebra_filter_alvis
659 #else
660 idzebra_filter
661 #endif
662
663 [] = {
664     &filter_type_alvis,
665     0,
666 };