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