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