Fixed bug #884: Entity declarations in input are lost at retrieval time.
[idzebra-moved-to-github.git] / index / mod_dom.c
1 /* $Id: mod_dom.c,v 1.2 2007-02-12 10:33:51 adam Exp $
2    Copyright (C) 1995-2007
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 this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
21 */
22
23 #include <stdio.h>
24 #include <assert.h>
25 #include <ctype.h>
26
27 #include <yaz/diagbib1.h>
28 #include <yaz/tpath.h>
29
30 #include <libxml/xmlversion.h>
31 #include <libxml/parser.h>
32 #include <libxml/tree.h>
33 #include <libxml/xmlIO.h>
34 #include <libxml/xmlreader.h>
35 #include <libxslt/transform.h>
36 #include <libxslt/xsltutils.h>
37
38 #if YAZ_HAVE_EXSLT
39 #include <libexslt/exslt.h>
40 #endif
41
42 #include <idzebra/util.h>
43 #include <idzebra/recctrl.h>
44
45 struct convert_s {
46     const char *stylesheet;
47     xsltStylesheetPtr stylesheet_xsp;
48     struct convert_s *next;
49 };
50
51 struct filter_extract {
52     const char *name;
53     struct convert_s *convert;
54 };
55
56 struct filter_store {
57     struct convert_s *convert;
58 };
59
60 struct filter_retrieve {
61     const char *name;
62     const char *identifier;
63     struct convert_s *convert;
64     struct filter_retrieve *next;
65 };
66
67 #define DOM_INPUT_XMLREADER 1
68 #define DOM_INPUT_MARC 2
69 struct filter_input {
70     const char *syntax;
71     const char *name;
72     struct convert_s *convert;
73     int type;
74     union {
75         struct {
76             const char *input_charset;
77             yaz_marc_t handle;
78             yaz_iconv_t iconv;
79         } marc;
80         struct {
81             xmlTextReaderPtr reader;
82             int split_level;
83         } xmlreader;
84     } u;
85     struct filter_input *next;
86 };
87   
88 struct filter_info {
89     char *fname;
90     char *full_name;
91     const char *profile_path;
92     ODR odr_record;
93     ODR odr_config;
94     xmlDocPtr doc_config;
95     struct filter_extract *extract;
96     struct filter_retrieve *retrieve_list;
97     struct filter_input *input_list;
98     struct filter_store *store;
99 };
100
101 #define XML_STRCMP(a,b)   strcmp((char*)a, b)
102 #define XML_STRLEN(a) strlen((char*)a)
103
104 static void set_param_str(const char **params, const char *name,
105                           const char *value, ODR odr)
106 {
107     char *quoted = odr_malloc(odr, 3 + strlen(value));
108     sprintf(quoted, "'%s'", value);
109     while (*params)
110         params++;
111     params[0] = name;
112     params[1] = quoted;
113     params[2] = 0;
114 }
115
116 static void set_param_int(const char **params, const char *name,
117                           zint value, ODR odr)
118 {
119     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
120     while (*params)
121         params++;
122     sprintf(quoted, "'" ZINT_FORMAT "'", value);
123     params[0] = name;
124     params[1] = quoted;
125     params[2] = 0;
126 }
127
128 static void *filter_init(Res res, RecType recType)
129 {
130     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
131     tinfo->fname = 0;
132     tinfo->full_name = 0;
133     tinfo->profile_path = 0;
134     tinfo->odr_record = odr_createmem(ODR_ENCODE);
135     tinfo->odr_config = odr_createmem(ODR_ENCODE);
136     tinfo->extract = 0;
137     tinfo->retrieve_list = 0;
138     tinfo->input_list = 0;
139     tinfo->store = 0;
140     tinfo->doc_config = 0;
141
142 #if YAZ_HAVE_EXSLT
143     exsltRegisterAll(); 
144 #endif
145
146     return tinfo;
147 }
148
149 static int attr_content(struct _xmlAttr *attr, const char *name,
150                         const char **dst_content)
151 {
152     if (!XML_STRCMP(attr->name, name) && attr->children 
153         && attr->children->type == XML_TEXT_NODE)
154     {
155         *dst_content = (const char *)(attr->children->content);
156         return 1;
157     }
158     return 0;
159 }
160
161 static void destroy_xsp(struct convert_s *c)
162 {
163     while(c)
164     {
165         if (c->stylesheet_xsp)
166             xsltFreeStylesheet(c->stylesheet_xsp);
167         c = c->next;
168     }
169 }
170
171 static void destroy_dom(struct filter_info *tinfo)
172 {
173     if (tinfo->extract)
174     {
175         destroy_xsp(tinfo->extract->convert);
176         tinfo->extract = 0;
177     }
178     if (tinfo->store)
179     {
180         destroy_xsp(tinfo->store->convert);
181         tinfo->store = 0;
182     }
183     if (tinfo->input_list)
184     {
185         struct filter_input *i_ptr;
186         for (i_ptr = tinfo->input_list; i_ptr; i_ptr = i_ptr->next)
187         {
188             switch(i_ptr->type)
189             {
190             case DOM_INPUT_XMLREADER:
191                 if (i_ptr->u.xmlreader.reader)
192                     xmlFreeTextReader(i_ptr->u.xmlreader.reader);
193                 break;
194             case DOM_INPUT_MARC:
195                 yaz_iconv_close(i_ptr->u.marc.iconv);
196                 yaz_marc_destroy(i_ptr->u.marc.handle);
197                 break;
198             }
199             destroy_xsp(i_ptr->convert);
200         }
201         tinfo->input_list = 0;
202     }
203     if (tinfo->retrieve_list)
204     {
205         struct filter_retrieve *r_ptr;
206         for (r_ptr = tinfo->retrieve_list; r_ptr; r_ptr = r_ptr->next)
207             destroy_xsp(r_ptr->convert);
208         tinfo->retrieve_list = 0;
209     }
210
211     if (tinfo->doc_config)
212     {
213         xmlFreeDoc(tinfo->doc_config);
214         tinfo->doc_config = 0;
215     }
216     odr_reset(tinfo->odr_config);
217 }
218
219 static ZEBRA_RES parse_convert(struct filter_info *tinfo, xmlNodePtr ptr,
220                                struct convert_s **l)
221 {
222     *l = 0;
223     for(; ptr; ptr = ptr->next)
224     {
225         if (ptr->type != XML_ELEMENT_NODE)
226             continue;
227         if (!XML_STRCMP(ptr->name, "xslt"))
228         {
229             struct _xmlAttr *attr;
230             struct convert_s *p = odr_malloc(tinfo->odr_config, sizeof(*p));
231
232             p->next = 0;
233             p->stylesheet = 0;
234             p->stylesheet_xsp = 0;
235
236             for (attr = ptr->properties; attr; attr = attr->next)
237                 if (attr_content(attr, "stylesheet", &p->stylesheet))
238                     ;
239                 else
240                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
241                             " for <xslt>",
242                             tinfo->fname, attr->name);
243             if (p->stylesheet)
244             {
245                 char tmp_xslt_full_name[1024];
246                 if (!yaz_filepath_resolve(p->stylesheet, tinfo->profile_path,
247                                           NULL, tmp_xslt_full_name))
248                 {
249                     yaz_log(YLOG_WARN,
250                             "%s: dom filter: stylesheet %s not found in "
251                             "path %s",
252                             tinfo->fname,
253                             p->stylesheet, tinfo->profile_path);
254                     return ZEBRA_FAIL;
255                 }
256                 
257                 p->stylesheet_xsp
258                     = xsltParseStylesheetFile((const xmlChar*) tmp_xslt_full_name);
259                 if (!p->stylesheet_xsp)
260                 {
261                     yaz_log(YLOG_WARN,
262                             "%s: dom filter: could not parse xslt "
263                             "stylesheet %s",
264                             tinfo->fname, tmp_xslt_full_name);
265                     return ZEBRA_FAIL;
266                 }
267             }
268             else
269             {
270                 yaz_log(YLOG_WARN,
271                         "%s: dom filter: missing attribute 'stylesheet' "
272                         "for element 'xslt'", tinfo->fname);
273                 return ZEBRA_FAIL;
274             }
275             *l = p;
276             l = &p->next;
277         }
278         else
279         {
280             yaz_log(YLOG_LOG, "%s: dom filter: bad node '%s' for <conv>",
281                     tinfo->fname, ptr->name);
282             return ZEBRA_FAIL;
283         }
284         
285     }
286     return ZEBRA_OK;
287 }
288
289 static ZEBRA_RES perform_convert(struct filter_info *tinfo, 
290                                  struct convert_s *convert,
291                                  const char **params,
292                                  xmlDocPtr *doc,
293                                  xsltStylesheetPtr *last_xsp)
294 {
295     for (; convert; convert = convert->next)
296     {
297         xmlDocPtr res_doc = xsltApplyStylesheet(convert->stylesheet_xsp,
298                                                *doc, params);
299         if (last_xsp)
300             *last_xsp = convert->stylesheet_xsp;
301         xmlFreeDoc(*doc);
302         *doc = res_doc;
303     }
304     return ZEBRA_OK;
305 }
306
307 static struct filter_input *new_input(struct filter_info *tinfo, int type)
308 {
309     struct filter_input *p;
310     struct filter_input **np = &tinfo->input_list;
311     for (;*np; np = &(*np)->next)
312         ;
313     p = *np = odr_malloc(tinfo->odr_config, sizeof(*p));
314     p->next = 0;
315     p->syntax = 0;
316     p->name = 0;
317     p->convert = 0;
318     p->type = type;
319     return p;
320 }
321
322 static ZEBRA_RES parse_input(struct filter_info *tinfo, xmlNodePtr ptr,
323                              const char *syntax,
324                              const char *name)
325 {
326     for (; ptr; ptr = ptr->next)
327     {
328         if (ptr->type != XML_ELEMENT_NODE)
329             continue;
330         if (!XML_STRCMP(ptr->name, "marc"))
331         {
332             yaz_iconv_t iconv = 0;
333             const char *input_charset = "marc-8";
334             struct _xmlAttr *attr;
335             
336             for (attr = ptr->properties; attr; attr = attr->next)
337             {
338                 if (attr_content(attr, "charset", &input_charset))
339                     ;
340                 else
341                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
342                             " for <marc>",
343                             tinfo->fname, attr->name);
344             }
345             iconv = yaz_iconv_open("utf-8", input_charset);
346             if (!iconv)
347             {
348                 yaz_log(YLOG_WARN, "%s: dom filter: unsupported charset "
349                         "'%s' for <marc>", 
350                         tinfo->fname,  input_charset);
351                 return ZEBRA_FAIL;
352             }
353             else
354             {
355                 struct filter_input *p = new_input(tinfo, DOM_INPUT_MARC);
356                 p->u.marc.handle = yaz_marc_create();
357                 p->u.marc.iconv = iconv;
358                 
359                 yaz_marc_iconv(p->u.marc.handle, p->u.marc.iconv);
360                 
361                 ptr = ptr->next;
362                 
363                 parse_convert(tinfo, ptr, &p->convert);
364             }
365             break;
366
367         }
368         else if (!XML_STRCMP(ptr->name, "xmlreader"))
369         {
370             struct filter_input *p = new_input(tinfo, DOM_INPUT_XMLREADER);
371             struct _xmlAttr *attr;
372             const char *level_str = 0;
373
374             p->u.xmlreader.split_level = 0;
375             p->u.xmlreader.reader = 0;
376
377             for (attr = ptr->properties; attr; attr = attr->next)
378             {
379                 if (attr_content(attr, "level", &level_str))
380                     ;
381                 else
382                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
383                             " for <xmlreader>",
384                             tinfo->fname, attr->name);
385             }
386             if (level_str)
387                 p->u.xmlreader.split_level = atoi(level_str);
388                 
389             ptr = ptr->next;
390
391             parse_convert(tinfo, ptr, &p->convert);
392             break;
393         }
394         else
395         {
396             yaz_log(YLOG_WARN, "%s: dom filter: bad input type %s",
397                     tinfo->fname, ptr->name);
398             return ZEBRA_FAIL;
399         }
400     }
401     return ZEBRA_OK;
402 }
403
404 static ZEBRA_RES parse_dom(struct filter_info *tinfo, const char *fname)
405 {
406     char tmp_full_name[1024];
407     xmlNodePtr ptr;
408     xmlDocPtr doc;
409
410     tinfo->fname = odr_strdup(tinfo->odr_config, fname);
411     
412     if (yaz_filepath_resolve(tinfo->fname, tinfo->profile_path, 
413                              NULL, tmp_full_name))
414         tinfo->full_name = odr_strdup(tinfo->odr_config, tmp_full_name);
415     else
416         tinfo->full_name = odr_strdup(tinfo->odr_config, tinfo->fname);
417     
418     yaz_log(YLOG_LOG, "dom filter: loading config file %s", tinfo->full_name);
419     
420     doc = xmlParseFile(tinfo->full_name);
421     if (!doc)
422     {
423         yaz_log(YLOG_WARN, "%s: dom filter: failed to parse config file %s",
424                 tinfo->fname, tinfo->full_name);
425         return ZEBRA_FAIL;
426     }
427     /* save because we store ptrs to the content */ 
428     tinfo->doc_config = doc;
429     
430     ptr = xmlDocGetRootElement(doc);
431     if (!ptr || ptr->type != XML_ELEMENT_NODE 
432         || XML_STRCMP(ptr->name, "dom"))
433     {
434         yaz_log(YLOG_WARN, 
435                 "%s: dom filter: expected root element <dom>", 
436                 tinfo->fname);  
437         return ZEBRA_FAIL;
438     }
439
440     for (ptr = ptr->children; ptr; ptr = ptr->next)
441     {
442         if (ptr->type != XML_ELEMENT_NODE)
443             continue;
444         if (!XML_STRCMP(ptr->name, "extract"))
445         {
446             /*
447               <extract name="index">
448               <xslt stylesheet="first.xsl"/>
449               <xslt stylesheet="second.xsl"/>
450               </extract>
451             */
452             struct _xmlAttr *attr;
453             struct filter_extract *f =
454                 odr_malloc(tinfo->odr_config, sizeof(*f));
455             
456             tinfo->extract = f;
457             f->name = 0;
458             f->convert = 0;
459             for (attr = ptr->properties; attr; attr = attr->next)
460             {
461                 if (attr_content(attr, "name", &f->name))
462                     ;
463                 else
464                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
465                             " for <extract>",
466                             tinfo->fname, attr->name);
467
468             }
469             parse_convert(tinfo, ptr->children, &f->convert);
470         }
471         else if (!XML_STRCMP(ptr->name, "retrieve"))
472         {  
473             /* 
474                <retrieve name="F">
475                <xslt stylesheet="some.xsl"/>
476                <xslt stylesheet="some.xsl"/>
477                </retrieve>
478             */
479             struct _xmlAttr *attr;
480             struct filter_retrieve **fp = &tinfo->retrieve_list;
481             struct filter_retrieve *f =
482                 odr_malloc(tinfo->odr_config, sizeof(*f));
483             
484             while (*fp)
485                 fp = &(*fp)->next;
486
487             *fp = f;
488             f->name = 0;
489             f->identifier = 0;
490             f->convert = 0;
491             f->next = 0;
492
493             for (attr = ptr->properties; attr; attr = attr->next)
494             {
495                 if (attr_content(attr, "identifier", &f->identifier))
496                     ;
497                 else if (attr_content(attr, "name", &f->name))
498                     ;
499                 else
500                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
501                             " for <retrieve>",
502                             tinfo->fname, attr->name);
503             }
504             parse_convert(tinfo, ptr->children, &f->convert);
505         }
506         else if (!XML_STRCMP(ptr->name, "store"))
507         {
508             /*
509                <retrieve name="F">
510                <xslt stylesheet="some.xsl"/>
511                <xslt stylesheet="some.xsl"/>
512                </retrieve>
513             */
514             struct filter_store *f =
515                 odr_malloc(tinfo->odr_config, sizeof(*f));
516             
517             tinfo->store = f;
518             f->convert = 0;
519             parse_convert(tinfo, ptr->children, &f->convert);
520         }
521         else if (!XML_STRCMP(ptr->name, "input"))
522         {
523             /*
524               <input syntax="xml">
525               <xmlreader level="1"/>
526               </input>
527               <input syntax="usmarc">
528               <marc inputcharset="marc-8"/>
529               </input>
530             */
531             struct _xmlAttr *attr;
532             const char  *syntax = 0;
533             const char *name = 0;
534             for (attr = ptr->properties; attr; attr = attr->next)
535             {
536                 if (attr_content(attr, "syntax", &syntax))
537                     ;
538                 else if (attr_content(attr, "name", &name))
539                     ;
540                 else
541                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
542                             " for <input>",
543                             tinfo->fname, attr->name);
544             }
545             parse_input(tinfo, ptr->children, syntax, name);
546         }
547         else
548         {
549             yaz_log(YLOG_WARN, "%s: dom filter: bad element %s",
550                     tinfo->fname, ptr->name);
551             return ZEBRA_FAIL;
552         }
553     }
554     return ZEBRA_OK;
555 }
556
557 static struct filter_retrieve *lookup_retrieve(struct filter_info *tinfo,
558                                                const char *est)
559 {
560     struct filter_retrieve *f = tinfo->retrieve_list;
561
562     /* return first schema if no est is provided */
563     if (!est)
564         return f;
565     for (; f; f = f->next)
566     { 
567         /* find requested schema */
568         if (est) 
569         {    
570             if (f->identifier && !strcmp(f->identifier, est))
571                 return f;
572             if (f->name && !strcmp(f->name, est))
573                 return f;
574         } 
575     }
576     return 0;
577 }
578
579 static ZEBRA_RES filter_config(void *clientData, Res res, const char *args)
580 {
581     struct filter_info *tinfo = clientData;
582     if (!args || !*args)
583     {
584         yaz_log(YLOG_WARN, "dom filter: need config file");
585         return ZEBRA_FAIL;
586     }
587
588     if (tinfo->fname && !strcmp(args, tinfo->fname))
589         return ZEBRA_OK;
590     
591     tinfo->profile_path = res_get(res, "profilePath");
592
593     destroy_dom(tinfo);
594     return parse_dom(tinfo, args);
595 }
596
597 static void filter_destroy(void *clientData)
598 {
599     struct filter_info *tinfo = clientData;
600     destroy_dom(tinfo);
601     odr_destroy(tinfo->odr_config);
602     odr_destroy(tinfo->odr_record);
603     xfree(tinfo);
604 }
605
606 static int ioread_ex(void *context, char *buffer, int len)
607 {
608     struct recExtractCtrl *p = context;
609     return p->stream->readf(p->stream, buffer, len);
610 }
611
612 static int ioclose_ex(void *context)
613 {
614     return 0;
615 }
616
617 static void index_cdata(struct filter_info *tinfo, struct recExtractCtrl *ctrl,
618                         xmlNodePtr ptr, RecWord *recWord)
619 {
620     for(; ptr; ptr = ptr->next)
621     {
622         index_cdata(tinfo, ctrl, ptr->children, recWord);
623         if (ptr->type != XML_TEXT_NODE)
624             continue;
625         recWord->term_buf = (const char *)ptr->content;
626         recWord->term_len = XML_STRLEN(ptr->content);
627         (*ctrl->tokenAdd)(recWord);
628     }
629 }
630
631 #define ZEBRA_SCHEMA_XSLT_NS "http://indexdata.dk/zebra/xslt/1"
632
633
634 static const char *zebra_xslt_ns = ZEBRA_SCHEMA_XSLT_NS;
635
636 static void index_node(struct filter_info *tinfo,  struct recExtractCtrl *ctrl,
637                        xmlNodePtr ptr, RecWord *recWord)
638 {
639     for(; ptr; ptr = ptr->next)
640     {
641         index_node(tinfo, ctrl, ptr->children, recWord);
642         if (ptr->type != XML_ELEMENT_NODE || !ptr->ns ||
643             XML_STRCMP(ptr->ns->href, zebra_xslt_ns))
644             continue;
645         if (!XML_STRCMP(ptr->name, "index"))
646         {
647             const char *name_str = 0;
648             const char *type_str = 0;
649             const char *xpath_str = 0;
650             struct _xmlAttr *attr;
651             for (attr = ptr->properties; attr; attr = attr->next)
652             {
653                 if (attr_content(attr, "name", &name_str))
654                     ;
655                 else if (attr_content(attr, "xpath", &xpath_str))
656                     ;
657                 else if (attr_content(attr, "type", &type_str))
658                     ;
659                 else
660                     yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
661                             " for <index>",
662                             tinfo->fname, attr->name);
663             }
664             if (name_str)
665             {
666                 int prev_type = recWord->index_type; /* save default type */
667
668                 if (type_str && *type_str)
669                     recWord->index_type = *type_str; /* type was given */
670                 recWord->index_name = name_str;
671                 index_cdata(tinfo, ctrl, ptr->children, recWord);
672
673                 recWord->index_type = prev_type;     /* restore it again */
674             }
675         }
676     }
677 }
678
679 static void index_record(struct filter_info *tinfo,struct recExtractCtrl *ctrl,
680                          xmlNodePtr ptr, RecWord *recWord)
681 {
682     const char *type_str = "update";
683
684     if (ptr && ptr->type == XML_ELEMENT_NODE && ptr->ns &&
685         !XML_STRCMP(ptr->ns->href, zebra_xslt_ns)
686         && !XML_STRCMP(ptr->name, "record"))
687     {
688         const char *id_str = 0;
689         const char *rank_str = 0;
690         struct _xmlAttr *attr;
691         for (attr = ptr->properties; attr; attr = attr->next)
692         {
693             if (attr_content(attr, "type", &type_str))
694                 ;
695             else if (attr_content(attr, "id", &id_str))
696                 ;
697             else if (attr_content(attr, "rank", &rank_str))
698                 ;
699             else
700                 yaz_log(YLOG_WARN, "%s: dom filter: bad attribute %s"
701                         " for <record>",
702                         tinfo->fname, attr->name);
703         }
704         if (id_str)
705             sscanf(id_str, "%255s", ctrl->match_criteria);
706
707         if (rank_str)
708             ctrl->staticrank = atozint(rank_str);
709         ptr = ptr->children;
710     }
711
712     if (!strcmp("update", type_str))
713         index_node(tinfo, ctrl, ptr, recWord);
714     else if (!strcmp("delete", type_str))
715          yaz_log(YLOG_WARN, "dom filter delete: to be implemented");
716     else
717          yaz_log(YLOG_WARN, "dom filter: unknown record type '%s'", 
718                  type_str);
719 }
720     
721 static int extract_doc(struct filter_info *tinfo, struct filter_input *input,
722                        struct recExtractCtrl *p, xmlDocPtr doc)
723 {
724     RecWord recWord;
725     const char *params[10];
726     xmlChar *buf_out;
727     int len_out;
728     xsltStylesheetPtr last_xsp = 0;
729     xmlDocPtr store_doc = 0;
730
731     params[0] = 0;
732     set_param_str(params, "schema", zebra_xslt_ns, tinfo->odr_record);
733
734     /* input conversion */
735     perform_convert(tinfo, input->convert, params, &doc, 0);
736
737     (*p->init)(p, &recWord);
738
739     if (tinfo->store)
740     {
741         /* store conversion */
742         store_doc = xmlCopyDoc(doc, 1);
743         perform_convert(tinfo, tinfo->store->convert,
744                         params, &store_doc, &last_xsp);
745     }
746     
747     if (last_xsp)
748         xsltSaveResultToString(&buf_out, &len_out, 
749                                store_doc ? store_doc : doc, last_xsp);
750     else
751         xmlDocDumpMemory(store_doc ? store_doc : doc, &buf_out, &len_out);
752     if (p->flagShowRecords)
753         fwrite(buf_out, len_out, 1, stdout);
754     (*p->setStoreData)(p, buf_out, len_out);
755     xmlFree(buf_out);
756
757     if (store_doc)
758         xmlFreeDoc(store_doc);
759
760     /* extract conversion */
761     perform_convert(tinfo, tinfo->extract->convert, params, &doc, 0);
762     if (doc)
763     {
764         xmlNodePtr root_ptr;
765         if (p->flagShowRecords)
766         {
767             xmlDocDumpMemory(doc, &buf_out, &len_out);
768             fwrite(buf_out, len_out, 1, stdout);
769             xmlFree(buf_out);
770         }
771         root_ptr = xmlDocGetRootElement(doc);
772         if (root_ptr)
773             index_record(tinfo, p, root_ptr, &recWord);
774         else
775         {
776             yaz_log(YLOG_WARN, "No root for index XML record");
777         }
778         xmlFreeDoc(doc);
779     }    
780     return RECCTRL_EXTRACT_OK;
781 }
782
783 static int extract_xml_split(struct filter_info *tinfo,
784                              struct filter_input *input,
785                              struct recExtractCtrl *p)
786 {
787     int ret;
788
789     if (p->first_record)
790     {
791         if (input->u.xmlreader.reader)
792             xmlFreeTextReader(input->u.xmlreader.reader);
793         input->u.xmlreader.reader = xmlReaderForIO(ioread_ex, ioclose_ex,
794                                                    p /* I/O handler */,
795                                                    0 /* URL */, 
796                                                    0 /* encoding */,
797                                                    XML_PARSE_XINCLUDE|
798                                                    XML_PARSE_NOENT);
799     }
800     if (!input->u.xmlreader.reader)
801         return RECCTRL_EXTRACT_ERROR_GENERIC;
802
803     ret = xmlTextReaderRead(input->u.xmlreader.reader);
804     while (ret == 1)
805     {
806         int type = xmlTextReaderNodeType(input->u.xmlreader.reader);
807         int depth = xmlTextReaderDepth(input->u.xmlreader.reader);
808         if (type == XML_READER_TYPE_ELEMENT && 
809             input->u.xmlreader.split_level == depth)
810         {
811             xmlNodePtr ptr = xmlTextReaderExpand(input->u.xmlreader.reader);
812             if (ptr)
813             {
814                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
815                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
816                 
817                 xmlDocSetRootElement(doc, ptr2);
818                 
819                 return extract_doc(tinfo, input, p, doc);
820             }
821             else
822             {
823                 xmlFreeTextReader(input->u.xmlreader.reader);
824                 input->u.xmlreader.reader = 0;
825                 return RECCTRL_EXTRACT_ERROR_GENERIC;
826             }
827         }
828         ret = xmlTextReaderRead(input->u.xmlreader.reader);
829     }
830     xmlFreeTextReader(input->u.xmlreader.reader);
831     input->u.xmlreader.reader = 0;
832     return RECCTRL_EXTRACT_EOF;
833 }
834
835 static int extract_xml_full(struct filter_info *tinfo, 
836                             struct filter_input *input,
837                             struct recExtractCtrl *p)
838 {
839     if (p->first_record) /* only one record per stream */
840     {
841         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
842                                   0 /* URL */,
843                                   0 /* encoding */,
844                                   XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
845         if (!doc)
846         {
847             return RECCTRL_EXTRACT_ERROR_GENERIC;
848         }
849         return extract_doc(tinfo, input, p, doc);
850     }
851     else
852         return RECCTRL_EXTRACT_EOF;
853 }
854
855 static int extract_iso2709(struct filter_info *tinfo,
856                            struct filter_input *input,
857                            struct recExtractCtrl *p)
858 {
859     char buf[100000];
860     int record_length;
861     int read_bytes, r;
862
863     if (p->stream->readf(p->stream, buf, 5) != 5)
864         return RECCTRL_EXTRACT_EOF;
865     while (*buf < '0' || *buf > '9')
866     {
867         int i;
868
869         yaz_log(YLOG_WARN, "MARC: Skipping bad byte %d (0x%02X)",
870                 *buf & 0xff, *buf & 0xff);
871         for (i = 0; i<4; i++)
872             buf[i] = buf[i+1];
873
874         if (p->stream->readf(p->stream, buf+4, 1) != 1)
875             return RECCTRL_EXTRACT_EOF;
876     }
877     record_length = atoi_n (buf, 5);
878     if (record_length < 25)
879     {
880         yaz_log (YLOG_WARN, "MARC record length < 25, is %d", record_length);
881         return RECCTRL_EXTRACT_ERROR_GENERIC;
882     }
883     read_bytes = p->stream->readf(p->stream, buf+5, record_length-5);
884     if (read_bytes < record_length-5)
885     {
886         yaz_log (YLOG_WARN, "Couldn't read whole MARC record");
887         return RECCTRL_EXTRACT_ERROR_GENERIC;
888     }
889     r = yaz_marc_read_iso2709(input->u.marc.handle,  buf, record_length);
890     if (r < record_length)
891     {
892         yaz_log (YLOG_WARN, "Parsing of MARC record failed r=%d length=%d",
893                  r, record_length);
894         return RECCTRL_EXTRACT_ERROR_GENERIC;
895     }
896     else
897     {
898         xmlDocPtr rdoc;
899         xmlNode *root_ptr;
900         yaz_marc_write_xml(input->u.marc.handle, &root_ptr, 0, 0, 0);
901         rdoc = xmlNewDoc((const xmlChar*) "1.0");
902         xmlDocSetRootElement(rdoc, root_ptr);
903         return extract_doc(tinfo, input, p, rdoc);        
904     }
905     return RECCTRL_EXTRACT_OK;
906 }
907
908 static int filter_extract(void *clientData, struct recExtractCtrl *p)
909 {
910     struct filter_info *tinfo = clientData;
911     struct filter_input *input = tinfo->input_list;
912
913     if (!input)
914         return RECCTRL_EXTRACT_ERROR_GENERIC;
915
916     odr_reset(tinfo->odr_record);
917     switch(input->type)
918     {
919     case DOM_INPUT_XMLREADER:
920         if (input->u.xmlreader.split_level == 0)
921             return extract_xml_full(tinfo, input, p);
922         else
923             return extract_xml_split(tinfo, input, p);
924         break;
925     case DOM_INPUT_MARC:
926         return extract_iso2709(tinfo, input, p);
927     }
928     return RECCTRL_EXTRACT_ERROR_GENERIC;
929 }
930
931 static int ioread_ret(void *context, char *buffer, int len)
932 {
933     struct recRetrieveCtrl *p = context;
934     return p->stream->readf(p->stream, buffer, len);
935 }
936
937 static int ioclose_ret(void *context)
938 {
939     return 0;
940 }
941
942 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
943 {
944     /* const char *esn = zebra_xslt_ns; */
945     const char *esn = 0;
946     const char *params[32];
947     struct filter_info *tinfo = clientData;
948     xmlDocPtr doc;
949     struct filter_retrieve *retrieve;
950     xsltStylesheetPtr last_xsp = 0;
951
952     if (p->comp)
953     {
954         if (p->comp->which == Z_RecordComp_simple
955             && p->comp->u.simple->which == Z_ElementSetNames_generic)
956         {
957             esn = p->comp->u.simple->u.generic;
958         }
959         else if (p->comp->which == Z_RecordComp_complex 
960                  && p->comp->u.complex->generic->elementSpec
961                  && p->comp->u.complex->generic->elementSpec->which ==
962                  Z_ElementSpec_elementSetName)
963         {
964             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
965         }
966     }
967     retrieve = lookup_retrieve(tinfo, esn);
968     if (!retrieve)
969     {
970         p->diagnostic =
971             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
972         return 0;
973     }
974
975     params[0] = 0;
976     set_param_int(params, "id", p->localno, p->odr);
977     if (p->fname)
978         set_param_str(params, "filename", p->fname, p->odr);
979     if (p->staticrank >= 0)
980         set_param_int(params, "rank", p->staticrank, p->odr);
981
982     if (esn)
983         set_param_str(params, "schema", esn, p->odr);
984     else
985         if (retrieve->name)
986             set_param_str(params, "schema", retrieve->name, p->odr);
987         else if (retrieve->identifier)
988             set_param_str(params, "schema", retrieve->identifier, p->odr);
989         else
990             set_param_str(params, "schema", "", p->odr);
991
992     if (p->score >= 0)
993         set_param_int(params, "score", p->score, p->odr);
994     set_param_int(params, "size", p->recordSize, p->odr);
995
996     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
997                     0 /* URL */,
998                     0 /* encoding */,
999                     XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1000     if (!doc)
1001     {
1002         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1003         return 0;
1004     }
1005
1006     /* retrieve conversion */
1007     perform_convert(tinfo, retrieve->convert, params, &doc, &last_xsp);
1008     if (!doc)
1009     {
1010         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1011     }
1012     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
1013     {
1014         xmlChar *buf_out;
1015         int len_out;
1016
1017         if (last_xsp)
1018             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1019         else
1020             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1021
1022         p->output_format = VAL_TEXT_XML;
1023         p->rec_len = len_out;
1024         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1025         memcpy(p->rec_buf, buf_out, p->rec_len);
1026         xmlFree(buf_out);
1027     }
1028     else if (p->output_format == VAL_SUTRS)
1029     {
1030         xmlChar *buf_out;
1031         int len_out;
1032
1033         if (last_xsp)
1034             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1035         else
1036             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1037         
1038         p->output_format = VAL_SUTRS;
1039         p->rec_len = len_out;
1040         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1041         memcpy(p->rec_buf, buf_out, p->rec_len);
1042         
1043         xmlFree(buf_out);
1044     }
1045     else
1046     {
1047         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
1048     }
1049     xmlFreeDoc(doc);
1050     return 0;
1051 }
1052
1053 static struct recType filter_type = {
1054     0,
1055     "dom",
1056     filter_init,
1057     filter_config,
1058     filter_destroy,
1059     filter_extract,
1060     filter_retrieve
1061 };
1062
1063 RecType
1064 #ifdef IDZEBRA_STATIC_DOM
1065 idzebra_filter_dom
1066 #else
1067 idzebra_filter
1068 #endif
1069
1070 [] = {
1071     &filter_type,
1072     0,
1073 };
1074 /*
1075  * Local variables:
1076  * c-basic-offset: 4
1077  * indent-tabs-mode: nil
1078  * End:
1079  * vim: shiftwidth=4 tabstop=8 expandtab
1080  */
1081