4aeb4557be0d5fa17a3ce2fe35d12c77e08a298c
[idzebra-moved-to-github.git] / recctrl / xslt.c
1 /* $Id: xslt.c,v 1.4 2005-05-01 07:17:46 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/tree.h>
30 #ifdef LIBXML_READER_ENABLED
31 #include <libxml/xmlreader.h>
32 #endif
33 #include <libxslt/transform.h>
34
35 #include <idzebra/util.h>
36 #include <idzebra/recctrl.h>
37
38 struct filter_info {
39     xsltStylesheetPtr stylesheet_xsp;
40 #ifdef LIBXML_READER_ENABLED
41     xmlTextReaderPtr reader;
42 #endif
43     char *fname;
44     int split_depth;
45     ODR odr;
46 };
47
48 #define ZEBRA_INDEX_NS "http://indexdata.dk/zebra/indexing/1"
49 #define ZEBRA_SCHEMA_IDENTITY_NS "http://indexdata.dk/zebra/identity/1"
50 static const char *zebra_index_ns = ZEBRA_INDEX_NS;
51
52 static void set_param_str(const char **params, const char *name,
53                           const char *value, ODR odr)
54 {
55     char *quoted = odr_malloc(odr, 3 + strlen(value));
56     sprintf(quoted, "'%s'", value);
57     while (*params)
58         params++;
59     params[0] = name;
60     params[1] = quoted;
61     params[2] = 0;
62 }
63
64 static void set_param_int(const char **params, const char *name,
65                           zint value, ODR odr)
66 {
67     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
68     while (*params)
69         params++;
70     sprintf(quoted, "'" ZINT_FORMAT "'", value);
71     params[0] = name;
72     params[1] = quoted;
73     params[2] = 0;
74 }
75
76
77 static void *filter_init_xslt(Res res, RecType recType)
78 {
79     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
80     tinfo->stylesheet_xsp = 0;
81 #ifdef LIBXML_READER_ENABLED
82     tinfo->reader = 0;
83 #endif
84     tinfo->fname = 0;
85     tinfo->split_depth = 0;
86     tinfo->odr = odr_createmem(ODR_ENCODE);
87     return tinfo;
88 }
89
90 static void *filter_init_xslt1(Res res, RecType recType)
91 {
92     struct filter_info *tinfo = (struct filter_info *)
93         filter_init_xslt(res, recType);
94     tinfo->split_depth = 1;
95     return tinfo;
96 }
97
98 static void filter_config(void *clientData, Res res, const char *args)
99 {
100     struct filter_info *tinfo = clientData;
101     if (!args || !*args)
102         args = "default.xsl";
103     if (!tinfo->fname || strcmp(args, tinfo->fname))
104     {
105         /* different filename so must reread stylesheet */
106         xfree(tinfo->fname);
107         tinfo->fname = xstrdup(args);
108         if (tinfo->stylesheet_xsp)
109             xsltFreeStylesheet(tinfo->stylesheet_xsp);
110         tinfo->stylesheet_xsp =
111             xsltParseStylesheetFile((const xmlChar*) tinfo->fname);
112     }
113 }
114
115 static void filter_destroy(void *clientData)
116 {
117     struct filter_info *tinfo = clientData;
118     if (tinfo->stylesheet_xsp)
119         xsltFreeStylesheet(tinfo->stylesheet_xsp);
120 #ifdef LIBXML_READER_ENABLED
121     if (tinfo->reader)
122         xmlFreeTextReader(tinfo->reader);
123 #endif
124     xfree(tinfo->fname);
125     odr_destroy(tinfo->odr);
126     xfree(tinfo);
127 }
128
129 static int ioread_ex(void *context, char *buffer, int len)
130 {
131     struct recExtractCtrl *p = context;
132     return (*p->readf)(p->fh, buffer, len);
133 }
134
135 static int ioclose_ex(void *context)
136 {
137     return 0;
138 }
139
140 static void index_field(struct filter_info *tinfo, struct recExtractCtrl *ctrl,
141                         xmlNodePtr ptr, RecWord *recWord)
142 {
143     for(; ptr; ptr = ptr->next)
144     {
145         index_field(tinfo, ctrl, ptr->children, recWord);
146         if (ptr->type != XML_TEXT_NODE)
147             continue;
148         recWord->term_buf = ptr->content;
149         recWord->term_len = strlen(ptr->content);
150         (*ctrl->tokenAdd)(recWord);
151     }
152 }
153
154 static void index_node(struct filter_info *tinfo,  struct recExtractCtrl *ctrl,
155                        xmlNodePtr ptr, RecWord *recWord)
156 {
157     for(; ptr; ptr = ptr->next)
158     {
159         index_node(tinfo, ctrl, ptr->children, recWord);
160         if (ptr->type != XML_ELEMENT_NODE || !ptr->ns ||
161             strcmp(ptr->ns->href, zebra_index_ns))
162             continue;
163         if (!strcmp(ptr->name, "index"))
164         {
165             char *field_str = 0;
166             const char *xpath_str = 0;
167             struct _xmlAttr *attr;
168             for (attr = ptr->properties; attr; attr = attr->next)
169             {
170                 if (!strcmp(attr->name, "field") 
171                     && attr->children && attr->children->type == XML_TEXT_NODE)
172                     field_str = attr->children->content;
173                 if (!strcmp(attr->name, "xpath") 
174                     && attr->children && attr->children->type == XML_TEXT_NODE)
175                     xpath_str = attr->children->content;
176             }
177             if (field_str)
178             {
179                 recWord->attrStr = field_str;
180                 index_field(tinfo, ctrl, ptr->children, recWord);
181             }
182         }
183     }
184 }
185
186 static int extract_doc(struct filter_info *tinfo, struct recExtractCtrl *p,
187                        xmlDocPtr doc)
188 {
189     RecWord recWord;
190     const char *params[10];
191     params[0] = 0;
192     xmlChar *buf_out;
193     int len_out;
194
195     set_param_str(params, "schema", ZEBRA_INDEX_NS, tinfo->odr);
196
197     (*p->init)(p, &recWord);
198     recWord.reg_type = 'w';
199
200     if (tinfo->stylesheet_xsp)
201     {
202         xmlDocPtr resDoc = 
203             xsltApplyStylesheet(tinfo->stylesheet_xsp,
204                                 doc, params);
205         if (p->flagShowRecords)
206         {
207             xmlDocDumpMemory(resDoc, &buf_out, &len_out);
208             fwrite(buf_out, len_out, 1, stdout);
209             xmlFree(buf_out);
210         }
211         index_node(tinfo, p, xmlDocGetRootElement(resDoc), &recWord);
212         xmlFreeDoc(resDoc);
213     }
214     xmlDocDumpMemory(doc, &buf_out, &len_out);
215     if (p->flagShowRecords)
216         fwrite(buf_out, len_out, 1, stdout);
217     (*p->setStoreData)(p, buf_out, len_out);
218     xmlFree(buf_out);
219     
220     xmlFreeDoc(doc);
221     return RECCTRL_EXTRACT_OK;
222 }
223
224 #ifdef LIBXML_READER_ENABLED
225 static int extract_split(struct filter_info *tinfo, struct recExtractCtrl *p)
226 {
227     int ret;
228     if (p->first_record)
229     {
230         if (tinfo->reader)
231             xmlFreeTextReader(tinfo->reader);
232         tinfo->reader = xmlReaderForIO(ioread_ex, ioclose_ex,
233                                        p /* I/O handler */,
234                                        0 /* URL */, 
235                                        0 /* encoding */,
236                                        XML_PARSE_XINCLUDE);
237     }
238     if (!tinfo->reader)
239         return RECCTRL_EXTRACT_ERROR_GENERIC;
240
241     if (!tinfo->stylesheet_xsp)
242         return RECCTRL_EXTRACT_ERROR_GENERIC;
243
244     ret = xmlTextReaderRead(tinfo->reader);
245     while (ret == 1) {
246         int type = xmlTextReaderNodeType(tinfo->reader);
247         int depth = xmlTextReaderDepth(tinfo->reader);
248         if (tinfo->split_depth == 0 ||
249             (type == XML_READER_TYPE_ELEMENT && tinfo->split_depth == depth))
250         {
251             xmlNodePtr ptr = xmlTextReaderExpand(tinfo->reader);
252             xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
253             xmlDocPtr doc = xmlNewDoc("1.0");
254
255             xmlDocSetRootElement(doc, ptr2);
256
257             return extract_doc(tinfo, p, doc);      
258         }
259         ret = xmlTextReaderRead(tinfo->reader);
260     }
261     xmlFreeTextReader(tinfo->reader);
262     tinfo->reader = 0;
263     return RECCTRL_EXTRACT_EOF;
264 }
265 #endif
266
267 static int extract_full(struct filter_info *tinfo, struct recExtractCtrl *p)
268 {
269     if (p->first_record) /* only one record per stream */
270     {
271         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
272                                   0 /* URL */,
273                                   0 /* encoding */,
274                                   XML_PARSE_XINCLUDE);
275         if (!doc)
276         {
277             return RECCTRL_EXTRACT_ERROR_GENERIC;
278         }
279         return extract_doc(tinfo, p, doc);
280     }
281     else
282         return RECCTRL_EXTRACT_EOF;
283 }
284
285 static int filter_extract(void *clientData, struct recExtractCtrl *p)
286 {
287     struct filter_info *tinfo = clientData;
288
289     odr_reset(tinfo->odr);
290
291     if (tinfo->split_depth == 0)
292         return extract_full(tinfo, p);
293     else
294     {
295 #ifdef LIBXML_READER_ENABLED
296         return extract_split(tinfo, p);
297 #else
298         /* no xmlreader so we can't split it */
299         return RECCTRL_EXTRACT_ERROR_GENERIC;
300 #endif
301     }
302 }
303
304 static int ioread_ret(void *context, char *buffer, int len)
305 {
306     struct recRetrieveCtrl *p = context;
307     return (*p->readf)(p->fh, buffer, len);
308 }
309
310 static int ioclose_ret(void *context)
311 {
312     return 0;
313 }
314
315 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
316 {
317     const char *esn = ZEBRA_SCHEMA_IDENTITY_NS;
318     const char *params[10];
319     struct filter_info *tinfo = clientData;
320     xmlDocPtr resDoc;
321     xmlDocPtr doc;
322
323     if (p->comp)
324     {
325         if (p->comp->which != Z_RecordComp_simple
326             || p->comp->u.simple->which != Z_ElementSetNames_generic)
327         {
328             p->diagnostic = YAZ_BIB1_PRESENT_COMP_SPEC_PARAMETER_UNSUPP;
329             return 0;
330         }
331         esn = p->comp->u.simple->u.generic;
332     }
333     
334     params[0] = 0;
335     set_param_str(params, "schema", esn, p->odr);
336     if (p->fname)
337         set_param_str(params, "filename", p->fname, p->odr);
338     if (p->score >= 0)
339         set_param_int(params, "score", p->score, p->odr);
340     set_param_int(params, "size", p->recordSize, p->odr);
341     
342     if (!tinfo->stylesheet_xsp)
343     {
344         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
345         return 0;
346     }
347     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
348                     0 /* URL */,
349                     0 /* encoding */,
350                     XML_PARSE_XINCLUDE);
351     if (!doc)
352     {
353         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
354         return 0;
355     }
356
357     if (!strcmp(esn, ZEBRA_SCHEMA_IDENTITY_NS))
358         resDoc = doc;
359     else
360     {
361         resDoc = xsltApplyStylesheet(tinfo->stylesheet_xsp,
362                                      doc, params);
363         xmlFreeDoc(doc);
364     }
365     if (!resDoc)
366     {
367         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
368     }
369     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
370     {
371         xmlChar *buf_out;
372         int len_out;
373         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
374
375         p->output_format = VAL_TEXT_XML;
376         p->rec_len = len_out;
377         p->rec_buf = odr_malloc(p->odr, p->rec_len);
378         memcpy(p->rec_buf, buf_out, p->rec_len);
379         
380         xmlFree(buf_out);
381     }
382     else if (p->output_format == VAL_SUTRS)
383     {
384         xmlChar *buf_out;
385         int len_out;
386         xmlDocDumpMemory(resDoc, &buf_out, &len_out);
387
388         p->output_format = VAL_SUTRS;
389         p->rec_len = len_out;
390         p->rec_buf = odr_malloc(p->odr, p->rec_len);
391         memcpy(p->rec_buf, buf_out, p->rec_len);
392         
393         xmlFree(buf_out);
394     }
395     else
396     {
397         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
398     }
399     xmlFreeDoc(resDoc);
400     return 0;
401 }
402
403 static struct recType filter_type_xslt = {
404     0,
405     "xslt",
406     filter_init_xslt,
407     filter_config,
408     filter_destroy,
409     filter_extract,
410     filter_retrieve
411 };
412
413 static struct recType filter_type_xslt1 = {
414     0,
415     "xslt1",
416     filter_init_xslt1,
417     filter_config,
418     filter_destroy,
419     filter_extract,
420     filter_retrieve
421 };
422
423 RecType
424 #ifdef IDZEBRA_STATIC_XSLT
425 idzebra_filter_xslt
426 #else
427 idzebra_filter
428 #endif
429
430 [] = {
431     &filter_type_xslt,
432 #ifdef LIBXML_READER_ENABLED
433     &filter_type_xslt1,
434 #endif
435     0,
436 };