added parsing function 'parse_pi_zebra_20' for processing-instruction parsing and...
[idzebra-moved-to-github.git] / index / mod_dom.c
1 /* $Id: mod_dom.c,v 1.3 2007-02-12 13:24:31 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 static int extract_doc(struct filter_info *tinfo, struct filter_input *input,
852                        struct recExtractCtrl *p, xmlDocPtr doc)
853 {
854     RecWord recWord;
855     const char *params[10];
856     xmlChar *buf_out;
857     int len_out;
858     xsltStylesheetPtr last_xsp = 0;
859     xmlDocPtr store_doc = 0;
860
861     params[0] = 0;
862     set_param_str(params, "schema", zebra_xslt_ns, tinfo->odr_record);
863
864     /* input conversion */
865     perform_convert(tinfo, input->convert, params, &doc, 0);
866
867     (*p->init)(p, &recWord);
868
869     if (tinfo->store)
870     {
871         /* store conversion */
872         store_doc = xmlCopyDoc(doc, 1);
873         perform_convert(tinfo, tinfo->store->convert,
874                         params, &store_doc, &last_xsp);
875     }
876     
877     if (last_xsp)
878         xsltSaveResultToString(&buf_out, &len_out, 
879                                store_doc ? store_doc : doc, last_xsp);
880     else
881         xmlDocDumpMemory(store_doc ? store_doc : doc, &buf_out, &len_out);
882     if (p->flagShowRecords)
883         fwrite(buf_out, len_out, 1, stdout);
884     (*p->setStoreData)(p, buf_out, len_out);
885     xmlFree(buf_out);
886
887     if (store_doc)
888         xmlFreeDoc(store_doc);
889
890     /* extract conversion */
891     perform_convert(tinfo, tinfo->extract->convert, params, &doc, 0);
892     if (doc)
893     {
894         xmlNodePtr root_ptr;
895         if (p->flagShowRecords)
896         {
897             xmlDocDumpMemory(doc, &buf_out, &len_out);
898             fwrite(buf_out, len_out, 1, stdout);
899             xmlFree(buf_out);
900         }
901         root_ptr = xmlDocGetRootElement(doc);
902         if (root_ptr)
903             index_record(tinfo, p, root_ptr, &recWord);
904         else
905         {
906             yaz_log(YLOG_WARN, "No root for index XML record");
907         }
908         xmlFreeDoc(doc);
909     }    
910     return RECCTRL_EXTRACT_OK;
911 }
912
913 static int extract_xml_split(struct filter_info *tinfo,
914                              struct filter_input *input,
915                              struct recExtractCtrl *p)
916 {
917     int ret;
918
919     if (p->first_record)
920     {
921         if (input->u.xmlreader.reader)
922             xmlFreeTextReader(input->u.xmlreader.reader);
923         input->u.xmlreader.reader = xmlReaderForIO(ioread_ex, ioclose_ex,
924                                                    p /* I/O handler */,
925                                                    0 /* URL */, 
926                                                    0 /* encoding */,
927                                                    XML_PARSE_XINCLUDE|
928                                                    XML_PARSE_NOENT);
929     }
930     if (!input->u.xmlreader.reader)
931         return RECCTRL_EXTRACT_ERROR_GENERIC;
932
933     ret = xmlTextReaderRead(input->u.xmlreader.reader);
934     while (ret == 1)
935     {
936         int type = xmlTextReaderNodeType(input->u.xmlreader.reader);
937         int depth = xmlTextReaderDepth(input->u.xmlreader.reader);
938         if (type == XML_READER_TYPE_ELEMENT && 
939             input->u.xmlreader.split_level == depth)
940         {
941             xmlNodePtr ptr = xmlTextReaderExpand(input->u.xmlreader.reader);
942             if (ptr)
943             {
944                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
945                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
946                 
947                 xmlDocSetRootElement(doc, ptr2);
948                 
949                 return extract_doc(tinfo, input, p, doc);
950             }
951             else
952             {
953                 xmlFreeTextReader(input->u.xmlreader.reader);
954                 input->u.xmlreader.reader = 0;
955                 return RECCTRL_EXTRACT_ERROR_GENERIC;
956             }
957         }
958         ret = xmlTextReaderRead(input->u.xmlreader.reader);
959     }
960     xmlFreeTextReader(input->u.xmlreader.reader);
961     input->u.xmlreader.reader = 0;
962     return RECCTRL_EXTRACT_EOF;
963 }
964
965 static int extract_xml_full(struct filter_info *tinfo, 
966                             struct filter_input *input,
967                             struct recExtractCtrl *p)
968 {
969     if (p->first_record) /* only one record per stream */
970     {
971         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
972                                   0 /* URL */,
973                                   0 /* encoding */,
974                                   XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
975         if (!doc)
976         {
977             return RECCTRL_EXTRACT_ERROR_GENERIC;
978         }
979         return extract_doc(tinfo, input, p, doc);
980     }
981     else
982         return RECCTRL_EXTRACT_EOF;
983 }
984
985 static int extract_iso2709(struct filter_info *tinfo,
986                            struct filter_input *input,
987                            struct recExtractCtrl *p)
988 {
989     char buf[100000];
990     int record_length;
991     int read_bytes, r;
992
993     if (p->stream->readf(p->stream, buf, 5) != 5)
994         return RECCTRL_EXTRACT_EOF;
995     while (*buf < '0' || *buf > '9')
996     {
997         int i;
998
999         yaz_log(YLOG_WARN, "MARC: Skipping bad byte %d (0x%02X)",
1000                 *buf & 0xff, *buf & 0xff);
1001         for (i = 0; i<4; i++)
1002             buf[i] = buf[i+1];
1003
1004         if (p->stream->readf(p->stream, buf+4, 1) != 1)
1005             return RECCTRL_EXTRACT_EOF;
1006     }
1007     record_length = atoi_n (buf, 5);
1008     if (record_length < 25)
1009     {
1010         yaz_log (YLOG_WARN, "MARC record length < 25, is %d", record_length);
1011         return RECCTRL_EXTRACT_ERROR_GENERIC;
1012     }
1013     read_bytes = p->stream->readf(p->stream, buf+5, record_length-5);
1014     if (read_bytes < record_length-5)
1015     {
1016         yaz_log (YLOG_WARN, "Couldn't read whole MARC record");
1017         return RECCTRL_EXTRACT_ERROR_GENERIC;
1018     }
1019     r = yaz_marc_read_iso2709(input->u.marc.handle,  buf, record_length);
1020     if (r < record_length)
1021     {
1022         yaz_log (YLOG_WARN, "Parsing of MARC record failed r=%d length=%d",
1023                  r, record_length);
1024         return RECCTRL_EXTRACT_ERROR_GENERIC;
1025     }
1026     else
1027     {
1028         xmlDocPtr rdoc;
1029         xmlNode *root_ptr;
1030         yaz_marc_write_xml(input->u.marc.handle, &root_ptr, 0, 0, 0);
1031         rdoc = xmlNewDoc((const xmlChar*) "1.0");
1032         xmlDocSetRootElement(rdoc, root_ptr);
1033         return extract_doc(tinfo, input, p, rdoc);        
1034     }
1035     return RECCTRL_EXTRACT_OK;
1036 }
1037
1038 static int filter_extract(void *clientData, struct recExtractCtrl *p)
1039 {
1040     struct filter_info *tinfo = clientData;
1041     struct filter_input *input = tinfo->input_list;
1042
1043     if (!input)
1044         return RECCTRL_EXTRACT_ERROR_GENERIC;
1045
1046     odr_reset(tinfo->odr_record);
1047     switch(input->type)
1048     {
1049     case DOM_INPUT_XMLREADER:
1050         if (input->u.xmlreader.split_level == 0)
1051             return extract_xml_full(tinfo, input, p);
1052         else
1053             return extract_xml_split(tinfo, input, p);
1054         break;
1055     case DOM_INPUT_MARC:
1056         return extract_iso2709(tinfo, input, p);
1057     }
1058     return RECCTRL_EXTRACT_ERROR_GENERIC;
1059 }
1060
1061 static int ioread_ret(void *context, char *buffer, int len)
1062 {
1063     struct recRetrieveCtrl *p = context;
1064     return p->stream->readf(p->stream, buffer, len);
1065 }
1066
1067 static int ioclose_ret(void *context)
1068 {
1069     return 0;
1070 }
1071
1072 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
1073 {
1074     /* const char *esn = zebra_xslt_ns; */
1075     const char *esn = 0;
1076     const char *params[32];
1077     struct filter_info *tinfo = clientData;
1078     xmlDocPtr doc;
1079     struct filter_retrieve *retrieve;
1080     xsltStylesheetPtr last_xsp = 0;
1081
1082     if (p->comp)
1083     {
1084         if (p->comp->which == Z_RecordComp_simple
1085             && p->comp->u.simple->which == Z_ElementSetNames_generic)
1086         {
1087             esn = p->comp->u.simple->u.generic;
1088         }
1089         else if (p->comp->which == Z_RecordComp_complex 
1090                  && p->comp->u.complex->generic->elementSpec
1091                  && p->comp->u.complex->generic->elementSpec->which ==
1092                  Z_ElementSpec_elementSetName)
1093         {
1094             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
1095         }
1096     }
1097     retrieve = lookup_retrieve(tinfo, esn);
1098     if (!retrieve)
1099     {
1100         p->diagnostic =
1101             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
1102         return 0;
1103     }
1104
1105     params[0] = 0;
1106     set_param_int(params, "id", p->localno, p->odr);
1107     if (p->fname)
1108         set_param_str(params, "filename", p->fname, p->odr);
1109     if (p->staticrank >= 0)
1110         set_param_int(params, "rank", p->staticrank, p->odr);
1111
1112     if (esn)
1113         set_param_str(params, "schema", esn, p->odr);
1114     else
1115         if (retrieve->name)
1116             set_param_str(params, "schema", retrieve->name, p->odr);
1117         else if (retrieve->identifier)
1118             set_param_str(params, "schema", retrieve->identifier, p->odr);
1119         else
1120             set_param_str(params, "schema", "", p->odr);
1121
1122     if (p->score >= 0)
1123         set_param_int(params, "score", p->score, p->odr);
1124     set_param_int(params, "size", p->recordSize, p->odr);
1125
1126     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
1127                     0 /* URL */,
1128                     0 /* encoding */,
1129                     XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1130     if (!doc)
1131     {
1132         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1133         return 0;
1134     }
1135
1136     /* retrieve conversion */
1137     perform_convert(tinfo, retrieve->convert, params, &doc, &last_xsp);
1138     if (!doc)
1139     {
1140         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1141     }
1142     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
1143     {
1144         xmlChar *buf_out;
1145         int len_out;
1146
1147         if (last_xsp)
1148             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1149         else
1150             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1151
1152         p->output_format = VAL_TEXT_XML;
1153         p->rec_len = len_out;
1154         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1155         memcpy(p->rec_buf, buf_out, p->rec_len);
1156         xmlFree(buf_out);
1157     }
1158     else if (p->output_format == VAL_SUTRS)
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_SUTRS;
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         
1173         xmlFree(buf_out);
1174     }
1175     else
1176     {
1177         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
1178     }
1179     xmlFreeDoc(doc);
1180     return 0;
1181 }
1182
1183 static struct recType filter_type = {
1184     0,
1185     "dom",
1186     filter_init,
1187     filter_config,
1188     filter_destroy,
1189     filter_extract,
1190     filter_retrieve
1191 };
1192
1193 RecType
1194 #ifdef IDZEBRA_STATIC_DOM
1195 idzebra_filter_dom
1196 #else
1197 idzebra_filter
1198 #endif
1199
1200 [] = {
1201     &filter_type,
1202     0,
1203 };
1204 /*
1205  * Local variables:
1206  * c-basic-offset: 4
1207  * indent-tabs-mode: nil
1208  * End:
1209  * vim: shiftwidth=4 tabstop=8 expandtab
1210  */
1211