optimize for C source code
[idzebra-moved-to-github.git] / recctrl / xslt.c
1 /* $Id: xslt.c,v 1.17 2005-08-24 08:30:37 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 void filter_config(void *clientData, Res res, const char *args)
253 {
254     struct filter_info *tinfo = clientData;
255     if (!args || !*args)
256         args = "xsltfilter.xml";
257     if (tinfo->fname && !strcmp(args, tinfo->fname))
258         return;
259     destroy_schemas(tinfo);
260     create_schemas(tinfo, args);
261 }
262
263 static void filter_destroy(void *clientData)
264 {
265     struct filter_info *tinfo = clientData;
266     destroy_schemas(tinfo);
267     if (tinfo->reader)
268         xmlFreeTextReader(tinfo->reader);
269     odr_destroy(tinfo->odr);
270     xfree(tinfo);
271 }
272
273 static int ioread_ex(void *context, char *buffer, int len)
274 {
275     struct recExtractCtrl *p = context;
276     return (*p->readf)(p->fh, buffer, len);
277 }
278
279 static int ioclose_ex(void *context)
280 {
281     return 0;
282 }
283
284 static void index_cdata(struct filter_info *tinfo, struct recExtractCtrl *ctrl,
285                         xmlNodePtr ptr, RecWord *recWord)
286 {
287     for(; ptr; ptr = ptr->next)
288     {
289         index_cdata(tinfo, ctrl, ptr->children, recWord);
290         if (ptr->type != XML_TEXT_NODE)
291             continue;
292         recWord->term_buf = (const char *)ptr->content;
293         recWord->term_len = XML_STRLEN(ptr->content);
294         (*ctrl->tokenAdd)(recWord);
295     }
296 }
297
298 static void index_node(struct filter_info *tinfo,  struct recExtractCtrl *ctrl,
299                        xmlNodePtr ptr, RecWord *recWord)
300 {
301     for(; ptr; ptr = ptr->next)
302     {
303         index_node(tinfo, ctrl, ptr->children, recWord);
304         if (ptr->type != XML_ELEMENT_NODE || !ptr->ns ||
305             XML_STRCMP(ptr->ns->href, zebra_xslt_ns))
306             continue;
307         if (!XML_STRCMP(ptr->name, "index"))
308         {
309             const char *name_str = 0;
310             const char *type_str = 0;
311             const char *xpath_str = 0;
312             struct _xmlAttr *attr;
313             for (attr = ptr->properties; attr; attr = attr->next)
314             {
315                 attr_content(attr, "name", &name_str);
316                 attr_content(attr, "xpath", &xpath_str);
317                 attr_content(attr, "type", &type_str);
318             }
319             if (name_str)
320             {
321                 int prev_type = recWord->index_type; /* save default type */
322
323                 if (type_str && *type_str)
324                     recWord->index_type = *type_str; /* type was given */
325                 recWord->index_name = name_str;
326                 index_cdata(tinfo, ctrl, ptr->children, recWord);
327
328                 recWord->index_type = prev_type;     /* restore it again */
329             }
330         }
331     }
332 }
333
334 static void index_record(struct filter_info *tinfo,struct recExtractCtrl *ctrl,
335                          xmlNodePtr ptr, RecWord *recWord)
336 {
337     if (ptr && ptr->type == XML_ELEMENT_NODE && ptr->ns &&
338         !XML_STRCMP(ptr->ns->href, zebra_xslt_ns)
339         && !XML_STRCMP(ptr->name, "record"))
340     {
341         const char *type_str = "update";
342         const char *id_str = 0;
343         const char *rank_str = 0;
344         struct _xmlAttr *attr;
345         for (attr = ptr->properties; attr; attr = attr->next)
346         {
347             attr_content(attr, "type", &type_str);
348             attr_content(attr, "id", &id_str);
349             attr_content(attr, "rank", &rank_str);
350         }
351         if (id_str)
352             sscanf(id_str, "%255s", ctrl->match_criteria);
353         if (rank_str)
354         {
355             ctrl->staticrank = atoi(rank_str);
356             yaz_log(YLOG_LOG, "rank=%d",ctrl->staticrank);
357         }
358         else
359             yaz_log(YLOG_LOG, "no rank");
360         
361         ptr = ptr->children;
362     }
363     index_node(tinfo, ctrl, ptr, recWord);
364 }
365     
366 static int extract_doc(struct filter_info *tinfo, struct recExtractCtrl *p,
367                        xmlDocPtr doc)
368 {
369     RecWord recWord;
370     const char *params[10];
371     xmlChar *buf_out;
372     int len_out;
373
374     struct filter_schema *schema = lookup_schema(tinfo, zebra_xslt_ns);
375
376     params[0] = 0;
377     set_param_str(params, "schema", zebra_xslt_ns, tinfo->odr);
378
379     (*p->init)(p, &recWord);
380
381     if (schema && schema->stylesheet_xsp)
382     {
383         xmlNodePtr root_ptr;
384         xmlDocPtr resDoc = 
385             xsltApplyStylesheet(schema->stylesheet_xsp,
386                                 doc, params);
387         if (p->flagShowRecords)
388         {
389             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
390             fwrite(buf_out, len_out, 1, stdout);
391             xmlFree(buf_out);
392         }
393         root_ptr = xmlDocGetRootElement(resDoc);
394         if (root_ptr)
395             index_record(tinfo, p, root_ptr, &recWord);
396         else
397         {
398             yaz_log(YLOG_WARN, "No root for index XML record."
399                     " split_level=%s stylesheet=%s",
400                     tinfo->split_level, schema->stylesheet);
401         }
402         xmlFreeDoc(resDoc);
403     }
404     xmlDocDumpMemory(doc, &buf_out, &len_out);
405     if (p->flagShowRecords)
406         fwrite(buf_out, len_out, 1, stdout);
407     (*p->setStoreData)(p, buf_out, len_out);
408     xmlFree(buf_out);
409     
410     xmlFreeDoc(doc);
411     return RECCTRL_EXTRACT_OK;
412 }
413
414 static int extract_split(struct filter_info *tinfo, struct recExtractCtrl *p)
415 {
416     int ret;
417     int split_depth = 0;
418     if (p->first_record)
419     {
420         if (tinfo->reader)
421             xmlFreeTextReader(tinfo->reader);
422         tinfo->reader = xmlReaderForIO(ioread_ex, ioclose_ex,
423                                        p /* I/O handler */,
424                                        0 /* URL */, 
425                                        0 /* encoding */,
426                                        XML_PARSE_XINCLUDE);
427     }
428     if (!tinfo->reader)
429         return RECCTRL_EXTRACT_ERROR_GENERIC;
430
431     if (tinfo->split_level)
432         split_depth = atoi(tinfo->split_level);
433     ret = xmlTextReaderRead(tinfo->reader);
434     while (ret == 1) {
435         int type = xmlTextReaderNodeType(tinfo->reader);
436         int depth = xmlTextReaderDepth(tinfo->reader);
437         if (split_depth == 0 ||
438             (split_depth > 0 &&
439              type == XML_READER_TYPE_ELEMENT && split_depth == depth))
440         {
441             xmlNodePtr ptr = xmlTextReaderExpand(tinfo->reader);
442             xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
443             xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
444
445             xmlDocSetRootElement(doc, ptr2);
446
447             return extract_doc(tinfo, p, doc);   
448         }
449         ret = xmlTextReaderRead(tinfo->reader);
450     }
451     xmlFreeTextReader(tinfo->reader);
452     tinfo->reader = 0;
453     return RECCTRL_EXTRACT_EOF;
454 }
455
456 static int extract_full(struct filter_info *tinfo, struct recExtractCtrl *p)
457 {
458     if (p->first_record) /* only one record per stream */
459     {
460         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
461                                   0 /* URL */,
462                                   0 /* encoding */,
463                                   XML_PARSE_XINCLUDE);
464         if (!doc)
465         {
466             return RECCTRL_EXTRACT_ERROR_GENERIC;
467         }
468         return extract_doc(tinfo, p, doc);
469     }
470     else
471         return RECCTRL_EXTRACT_EOF;
472 }
473
474 static int filter_extract(void *clientData, struct recExtractCtrl *p)
475 {
476     struct filter_info *tinfo = clientData;
477
478     odr_reset(tinfo->odr);
479
480     if (tinfo->split_level == 0 && tinfo->split_path == 0)
481         return extract_full(tinfo, p);
482     else
483     {
484         return extract_split(tinfo, p);
485     }
486 }
487
488 static int ioread_ret(void *context, char *buffer, int len)
489 {
490     struct recRetrieveCtrl *p = context;
491     return (*p->readf)(p->fh, buffer, len);
492 }
493
494 static int ioclose_ret(void *context)
495 {
496     return 0;
497 }
498
499
500 static const char *snippet_doc(struct recRetrieveCtrl *p, int text_mode,
501                                int window_size)
502 {
503     const char *xml_doc_str;
504     int ord = 0;
505     WRBUF wrbuf = wrbuf_alloc();
506     zebra_snippets *res = 
507         zebra_snippets_window(p->doc_snippet, p->hit_snippet, window_size);
508     zebra_snippet_word *w = zebra_snippets_list(res);
509
510     if (text_mode)
511         wrbuf_printf(wrbuf, "\'");
512     else
513         wrbuf_printf(wrbuf, "<snippet xmlns='%s'>\n", zebra_xslt_ns);
514     for (; w; w = w->next)
515     {
516         if (ord == 0)
517             ord = w->ord;
518         else if (ord != w->ord)
519
520             break;
521         if (text_mode)
522             wrbuf_printf(wrbuf, "%s%s%s ", 
523                          w->match ? "*" : "",
524                          w->term,
525                          w->match ? "*" : "");
526         else
527         {
528             wrbuf_printf(wrbuf, " <term ord='%d' seqno='" ZINT_FORMAT "' %s>", 
529                          w->ord, w->seqno,
530                          (w->match ? "match='1'" : ""));
531             wrbuf_xmlputs(wrbuf, w->term);
532             wrbuf_printf(wrbuf, "</term>\n");
533         }
534     }
535     if (text_mode)
536         wrbuf_printf(wrbuf, "\'");
537     else
538         wrbuf_printf(wrbuf, "</snippet>\n");
539
540     xml_doc_str = odr_strdup(p->odr, wrbuf_buf(wrbuf));
541
542     zebra_snippets_destroy(res);
543     wrbuf_free(wrbuf, 1);
544     return xml_doc_str;
545 }
546
547 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
548 {
549     const char *esn = zebra_xslt_ns;
550     const char *params[20];
551     struct filter_info *tinfo = clientData;
552     xmlDocPtr resDoc;
553     xmlDocPtr doc;
554     struct filter_schema *schema;
555     int window_size = -1;
556
557     if (p->comp)
558     {
559         if (p->comp->which == Z_RecordComp_simple
560             && p->comp->u.simple->which == Z_ElementSetNames_generic)
561         {
562             esn = p->comp->u.simple->u.generic;
563         }
564         else if (p->comp->which == Z_RecordComp_complex 
565                  && p->comp->u.complex->generic->elementSpec
566                  && p->comp->u.complex->generic->elementSpec->which ==
567                  Z_ElementSpec_elementSetName)
568         {
569             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
570         }
571     }
572     schema = lookup_schema(tinfo, esn);
573     if (!schema)
574     {
575         p->diagnostic =
576             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
577         return 0;
578     }
579
580     if (schema->include_snippet)
581         window_size = atoi(schema->include_snippet);
582
583     params[0] = 0;
584     set_param_str(params, "schema", esn, p->odr);
585     if (p->fname)
586         set_param_str(params, "filename", p->fname, p->odr);
587     if (p->score >= 0)
588         set_param_int(params, "score", p->score, p->odr);
589     set_param_int(params, "size", p->recordSize, p->odr);
590     set_param_int(params, "id", p->localno, p->odr);
591
592     if (window_size >= 0)
593         set_param_xml(params, "snippet", snippet_doc(p, 1, window_size),
594                       p->odr);
595     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
596                     0 /* URL */,
597                     0 /* encoding */,
598                     XML_PARSE_XINCLUDE);
599     if (!doc)
600     {
601         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
602         return 0;
603     }
604
605     if (window_size >= 0)
606     {
607         xmlNodePtr node = xmlDocGetRootElement(doc);
608         const char *snippet_str = snippet_doc(p, 0, window_size);
609         xmlDocPtr snippet_doc = xmlParseMemory(snippet_str, strlen(snippet_str));
610         xmlAddChild(node, xmlDocGetRootElement(snippet_doc));
611     }
612     if (!schema->stylesheet_xsp)
613         resDoc = doc;
614     else
615     {
616         resDoc = xsltApplyStylesheet(schema->stylesheet_xsp,
617                                      doc, params);
618         xmlFreeDoc(doc);
619     }
620     if (!resDoc)
621     {
622         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
623     }
624     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
625     {
626         xmlChar *buf_out;
627         int len_out;
628         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
629
630         p->output_format = VAL_TEXT_XML;
631         p->rec_len = len_out;
632         p->rec_buf = odr_malloc(p->odr, p->rec_len);
633         memcpy(p->rec_buf, buf_out, p->rec_len);
634         
635         xmlFree(buf_out);
636     }
637     else if (p->output_format == VAL_SUTRS)
638     {
639         xmlChar *buf_out;
640         int len_out;
641         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
642
643         p->output_format = VAL_SUTRS;
644         p->rec_len = len_out;
645         p->rec_buf = odr_malloc(p->odr, p->rec_len);
646         memcpy(p->rec_buf, buf_out, p->rec_len);
647         
648         xmlFree(buf_out);
649     }
650     else
651     {
652         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
653     }
654     xmlFreeDoc(resDoc);
655     return 0;
656 }
657
658 static struct recType filter_type = {
659     0,
660     "xslt",
661     filter_init,
662     filter_config,
663     filter_destroy,
664     filter_extract,
665     filter_retrieve
666 };
667
668 RecType
669 #ifdef IDZEBRA_STATIC_XSLT
670 idzebra_filter_xslt
671 #else
672 idzebra_filter
673 #endif
674
675 [] = {
676     &filter_type,
677     0,
678 };