changed <dom> and <input> parser such that the following conditions actually work:
[idzebra-moved-to-github.git] / index / mod_dom.c
1
2 /* $Id: mod_dom.c,v 1.31 2007-03-08 17:19:12 marc Exp $
3    Copyright (C) 1995-2007
4    Index Data ApS
5
6    This file is part of the Zebra server.
7
8    Zebra is free software; you can redistribute it and/or modify it under
9    the terms of the GNU General Public License as published by the Free
10    Software Foundation; either version 2, or (at your option) any later
11    version.
12
13    Zebra is distributed in the hope that it will be useful, but WITHOUT ANY
14    WARRANTY; without even the implied warranty of MERCHANTABILITY or
15    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16    for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21
22 */
23
24 #include <stdio.h>
25 #include <assert.h>
26 #include <ctype.h>
27 #include <stdarg.h>
28
29 #include <yaz/diagbib1.h>
30 #include <yaz/tpath.h>
31 #include <yaz/snprintf.h>
32
33 #include <libxml/xmlversion.h>
34 #include <libxml/parser.h>
35 #include <libxml/tree.h>
36 #include <libxml/xmlIO.h>
37 #include <libxml/xmlreader.h>
38 #include <libxslt/transform.h>
39 #include <libxslt/xsltutils.h>
40
41 #if YAZ_HAVE_EXSLT
42 #include <libexslt/exslt.h>
43 #endif
44
45 #include <idzebra/util.h>
46 #include <idzebra/recctrl.h>
47
48 /* DOM filter style indexing */
49 #define ZEBRA_DOM_NS "http://indexdata.com/zebra-2.0"
50 static const char *zebra_dom_ns = ZEBRA_DOM_NS;
51
52 /* DOM filter style indexing */
53 #define ZEBRA_PI_NAME "zebra-2.0"
54 static const char *zebra_pi_name = ZEBRA_PI_NAME;
55
56
57
58 struct convert_s {
59     const char *stylesheet;
60     xsltStylesheetPtr stylesheet_xsp;
61     struct convert_s *next;
62 };
63
64 struct filter_extract {
65     const char *name;
66     struct convert_s *convert;
67 };
68
69 struct filter_store {
70     struct convert_s *convert;
71 };
72
73 struct filter_retrieve {
74     const char *name;
75     const char *identifier;
76     struct convert_s *convert;
77     struct filter_retrieve *next;
78 };
79
80 #define DOM_INPUT_DOM 0
81 #define DOM_INPUT_XMLREADER 1
82 #define DOM_INPUT_MARC 2
83 struct filter_input {
84     const char *syntax;
85     const char *name;
86     struct convert_s *convert;
87     int type;
88     union {
89         struct {
90             int dummy;
91         } dom;
92         struct {
93             xmlTextReaderPtr reader;
94             int split_level;
95         } xmlreader;
96         struct {
97             const char *input_charset;
98             yaz_marc_t handle;
99             yaz_iconv_t iconv;
100         } marc;
101     } u;
102     struct filter_input *next;
103 };
104   
105 struct filter_info {
106     char *fname;
107     char *full_name;
108     const char *profile_path;
109     ODR odr_record;
110     ODR odr_config;
111     xmlDocPtr doc_config;
112     struct filter_extract *extract;
113     struct filter_retrieve *retrieve_list;
114     struct filter_input *input_list;
115     struct filter_store *store;
116     int record_info_invoked;
117 };
118
119
120
121 #define XML_STRCMP(a,b)   strcmp((char*)a, b)
122 #define XML_STRLEN(a) strlen((char*)a)
123
124
125 #define FOR_EACH_ELEMENT(ptr) for (; ptr; ptr = ptr->next) if (ptr->type == XML_ELEMENT_NODE)
126
127 static void dom_log(int level, struct filter_info *tinfo, xmlNodePtr ptr,
128                     const char *fmt, ...)
129 #ifdef __GNUC__
130     __attribute__ ((format (printf, 4, 5)))
131 #endif
132     ;
133
134 static void dom_log(int level, struct filter_info *tinfo, xmlNodePtr ptr,
135                     const char *fmt, ...)
136 {
137     va_list ap;
138     char buf[4096];
139
140     va_start(ap, fmt);
141     yaz_vsnprintf(buf, sizeof(buf)-1, fmt, ap);
142     if (ptr)
143     {
144         yaz_log(level, "%s:%ld: %s", tinfo->fname ? tinfo->fname : "none", 
145                 xmlGetLineNo(ptr), buf);
146     }
147     else
148     {
149         yaz_log(level, "%s: %s", tinfo->fname ? tinfo->fname : "none", buf);
150     }
151     va_end(ap);
152 }
153
154
155 static void set_param_str(const char **params, const char *name,
156                           const char *value, ODR odr)
157 {
158     char *quoted = odr_malloc(odr, 3 + strlen(value));
159     sprintf(quoted, "'%s'", value);
160     while (*params)
161         params++;
162     params[0] = name;
163     params[1] = quoted;
164     params[2] = 0;
165 }
166
167 static void set_param_int(const char **params, const char *name,
168                           zint value, ODR odr)
169 {
170     char *quoted = odr_malloc(odr, 30); /* 25 digits enough for 2^64 */
171     while (*params)
172         params++;
173     sprintf(quoted, "'" ZINT_FORMAT "'", value);
174     params[0] = name;
175     params[1] = quoted;
176     params[2] = 0;
177 }
178
179 static void *filter_init(Res res, RecType recType)
180 {
181     struct filter_info *tinfo = (struct filter_info *) xmalloc(sizeof(*tinfo));
182     tinfo->fname = 0;
183     tinfo->full_name = 0;
184     tinfo->profile_path = 0;
185     tinfo->odr_record = odr_createmem(ODR_ENCODE);
186     tinfo->odr_config = odr_createmem(ODR_ENCODE);
187     tinfo->extract = 0;
188     tinfo->retrieve_list = 0;
189     tinfo->input_list = 0;
190     tinfo->store = 0;
191     tinfo->doc_config = 0;
192     tinfo->record_info_invoked = 0;
193
194 #if YAZ_HAVE_EXSLT
195     exsltRegisterAll(); 
196 #endif
197
198     return tinfo;
199 }
200
201 static int attr_content(struct _xmlAttr *attr, const char *name,
202                         const char **dst_content)
203 {
204     if (!XML_STRCMP(attr->name, name) && attr->children 
205         && attr->children->type == XML_TEXT_NODE)
206     {
207         *dst_content = (const char *)(attr->children->content);
208         return 1;
209     }
210     return 0;
211 }
212
213 static void destroy_xsp(struct convert_s *c)
214 {
215     while(c)
216     {
217         if (c->stylesheet_xsp)
218             xsltFreeStylesheet(c->stylesheet_xsp);
219         c = c->next;
220     }
221 }
222
223 static void destroy_dom(struct filter_info *tinfo)
224 {
225     if (tinfo->extract)
226     {
227         destroy_xsp(tinfo->extract->convert);
228         tinfo->extract = 0;
229     }
230     if (tinfo->store)
231     {
232         destroy_xsp(tinfo->store->convert);
233         tinfo->store = 0;
234     }
235     if (tinfo->input_list)
236     {
237         struct filter_input *i_ptr;
238         for (i_ptr = tinfo->input_list; i_ptr; i_ptr = i_ptr->next)
239         {
240             switch(i_ptr->type)
241             {
242             case DOM_INPUT_DOM:
243                 break;
244             case DOM_INPUT_XMLREADER:
245                 if (i_ptr->u.xmlreader.reader)
246                     xmlFreeTextReader(i_ptr->u.xmlreader.reader);
247                 break;
248             case DOM_INPUT_MARC:
249                 yaz_iconv_close(i_ptr->u.marc.iconv);
250                 yaz_marc_destroy(i_ptr->u.marc.handle);
251                 break;
252             }
253             destroy_xsp(i_ptr->convert);
254         }
255         tinfo->input_list = 0;
256     }
257     if (tinfo->retrieve_list)
258     {
259         struct filter_retrieve *r_ptr;
260         for (r_ptr = tinfo->retrieve_list; r_ptr; r_ptr = r_ptr->next)
261             destroy_xsp(r_ptr->convert);
262         tinfo->retrieve_list = 0;
263     }
264
265     if (tinfo->doc_config)
266     {
267         xmlFreeDoc(tinfo->doc_config);
268         tinfo->doc_config = 0;
269     }
270     odr_reset(tinfo->odr_config);
271 }
272
273 static ZEBRA_RES parse_convert(struct filter_info *tinfo, xmlNodePtr ptr,
274                                struct convert_s **l)
275 {
276     *l = 0;
277     FOR_EACH_ELEMENT(ptr) {
278         if (!XML_STRCMP(ptr->name, "xslt"))
279         {
280             struct _xmlAttr *attr;
281             struct convert_s *p 
282                 = odr_malloc(tinfo->odr_config, sizeof(*p));
283             
284             p->next = 0;
285             p->stylesheet = 0;
286             p->stylesheet_xsp = 0;
287             
288             for (attr = ptr->properties; attr; attr = attr->next)
289                 if (attr_content(attr, "stylesheet", &p->stylesheet))
290                     ;
291                 else
292                 {
293                     dom_log(YLOG_WARN, tinfo, ptr,
294                             "bad attribute @%s", attr->name);
295                 }
296             if (p->stylesheet)
297             {
298                 char tmp_xslt_full_name[1024];
299                 if (!yaz_filepath_resolve(p->stylesheet, 
300                                           tinfo->profile_path,
301                                           NULL, 
302                                           tmp_xslt_full_name))
303                 {
304                     dom_log(YLOG_WARN, tinfo, 0,
305                             "stylesheet %s not found in "
306                             "path %s",
307                             p->stylesheet, 
308                             tinfo->profile_path);
309                     return ZEBRA_FAIL;
310                 }
311                 
312                 p->stylesheet_xsp
313                     = xsltParseStylesheetFile((const xmlChar*) 
314                                               tmp_xslt_full_name);
315                 if (!p->stylesheet_xsp)
316                 {
317                     dom_log(YLOG_WARN, tinfo, 0,
318                             "could not parse xslt stylesheet %s",
319                             tmp_xslt_full_name);
320                     return ZEBRA_FAIL;
321                 }
322                 }
323                 else
324                 {
325                     dom_log(YLOG_WARN, tinfo, ptr,
326                             "missing attribute 'stylesheet' ");
327                     return ZEBRA_FAIL;
328                 }
329                 *l = p;
330                 l = &p->next;
331         }
332         else
333         {
334             dom_log(YLOG_WARN, tinfo, ptr,
335                     "bad element '%s', expected <xslt>", ptr->name);
336             return ZEBRA_FAIL;
337         }
338     }
339     return ZEBRA_OK;
340 }
341
342 static ZEBRA_RES perform_convert(struct filter_info *tinfo, 
343                                  struct recExtractCtrl *extctr,
344                                  struct convert_s *convert,
345                                  const char **params,
346                                  xmlDocPtr *doc,
347                                  xsltStylesheetPtr *last_xsp)
348 {
349     for (; convert; convert = convert->next)
350     {
351         xmlChar *buf_out = 0;
352         int len_out = 0;
353         xmlDocPtr res_doc = xsltApplyStylesheet(convert->stylesheet_xsp,
354                                                 *doc, params);
355         if (last_xsp)
356             *last_xsp = convert->stylesheet_xsp;
357         
358         if (!res_doc)
359             break;
360
361         /* now saving into buffer and re-reading into DOM to avoid annoing
362            XSLT problem with thrown-out indentation text nodes */
363         xsltSaveResultToString(&buf_out, &len_out, res_doc,
364                                convert->stylesheet_xsp); 
365         xmlFreeDoc(res_doc);
366
367         xmlFreeDoc(*doc);
368
369         *doc = xmlParseMemory((const char *) buf_out, len_out);
370
371         /* writing debug info out */
372         if (extctr && extctr->flagShowRecords)
373             yaz_log(YLOG_LOG, "%s: XSLT %s\n %.*s", 
374                     tinfo->fname ? tinfo->fname : "(none)", 
375                     convert->stylesheet,
376                     len_out, buf_out);
377         
378         xmlFree(buf_out);
379     }
380     return ZEBRA_OK;
381 }
382
383 static struct filter_input *new_input(struct filter_info *tinfo, int type)
384 {
385     struct filter_input *p;
386     struct filter_input **np = &tinfo->input_list;
387     for (;*np; np = &(*np)->next)
388         ;
389     p = *np = odr_malloc(tinfo->odr_config, sizeof(*p));
390     p->next = 0;
391     p->syntax = 0;
392     p->name = 0;
393     p->convert = 0;
394     p->type = type;
395     return p;
396 }
397
398 static ZEBRA_RES parse_input(struct filter_info *tinfo, xmlNodePtr ptr,
399                              const char *syntax, const char *name)
400 {
401     FOR_EACH_ELEMENT(ptr) {
402         if (!XML_STRCMP(ptr->name, "marc"))
403         {
404             yaz_iconv_t iconv = 0;
405             const char *input_charset = "marc-8";
406             struct _xmlAttr *attr;
407             
408             for (attr = ptr->properties; attr; attr = attr->next)
409             {
410                 if (attr_content(attr, "inputcharset", &input_charset))
411                     ;
412                 else
413                 {
414                     dom_log(YLOG_WARN, tinfo, ptr,
415                             "bad attribute @%s, expected @inputcharset",
416                             attr->name);
417                 }
418             }
419             iconv = yaz_iconv_open("utf-8", input_charset);
420             if (!iconv)
421             {
422                 dom_log(YLOG_WARN, tinfo, ptr, 
423                         "unsupported @charset '%s'", input_charset);
424                 return ZEBRA_FAIL;
425             }
426             else
427             {
428                 struct filter_input *p 
429                     = new_input(tinfo, DOM_INPUT_MARC);
430                 p->u.marc.handle = yaz_marc_create();
431                 p->u.marc.iconv = iconv;
432                 
433                 yaz_marc_iconv(p->u.marc.handle, p->u.marc.iconv);
434                 
435                 ptr = ptr->next;
436                 
437                 parse_convert(tinfo, ptr, &p->convert);
438             }
439             break;
440
441         }
442         else if (!XML_STRCMP(ptr->name, "xmlreader"))
443         {
444             struct filter_input *p 
445                 = new_input(tinfo, DOM_INPUT_XMLREADER);
446             struct _xmlAttr *attr;
447             const char *level_str = 0;
448
449             p->u.xmlreader.split_level = 0;
450             p->u.xmlreader.reader = 0;
451
452             for (attr = ptr->properties; attr; attr = attr->next)
453             {
454                 if (attr_content(attr, "level", &level_str))
455                     ;
456                 else
457                 {
458                     dom_log(YLOG_WARN, tinfo, ptr,
459                             "bad attribute @%s, expected @level",
460                             attr->name);
461                 }
462             }
463             if (level_str)
464                 p->u.xmlreader.split_level = atoi(level_str);
465                 
466             ptr = ptr->next;
467
468             parse_convert(tinfo, ptr, &p->convert);
469             break;
470         }
471         else if (!XML_STRCMP(ptr->name, "xslt")){
472             struct filter_input *p 
473                 = new_input(tinfo, DOM_INPUT_DOM);
474             parse_convert(tinfo, ptr, &p->convert);
475             break;
476         }
477         else
478         {
479             dom_log(YLOG_WARN, tinfo, ptr,
480                     "bad element <%s>, expected <marc>|<xmlreader>|<xslt>",
481                     ptr->name);
482             return ZEBRA_FAIL;
483         }
484     }
485     return ZEBRA_OK;
486 }
487
488 static ZEBRA_RES parse_dom(struct filter_info *tinfo, const char *fname)
489 {
490     char tmp_full_name[1024];
491     xmlNodePtr ptr;
492     xmlDocPtr doc;
493
494     tinfo->fname = odr_strdup(tinfo->odr_config, fname);
495     
496     if (yaz_filepath_resolve(tinfo->fname, tinfo->profile_path, 
497                              NULL, tmp_full_name))
498         tinfo->full_name = odr_strdup(tinfo->odr_config, tmp_full_name);
499     else
500         tinfo->full_name = odr_strdup(tinfo->odr_config, tinfo->fname);
501     
502     yaz_log(YLOG_LOG, "%s dom filter: "
503             "loading config file %s", tinfo->fname, tinfo->full_name);
504
505     doc = xmlParseFile(tinfo->full_name);
506     if (!doc)
507     {
508         yaz_log(YLOG_WARN, "%s: dom filter: "
509                 "failed to parse config file %s",
510                 tinfo->fname, tinfo->full_name);
511         return ZEBRA_FAIL;
512     }
513     /* save because we store ptrs to the content */ 
514     tinfo->doc_config = doc;
515     
516     ptr = xmlDocGetRootElement(doc);
517     if (!ptr || ptr->type != XML_ELEMENT_NODE 
518         || XML_STRCMP(ptr->name, "dom"))
519     {
520         dom_log(YLOG_WARN, tinfo, ptr,
521                 "bad root element <%s>, expected root element <dom>", 
522                 ptr->name);  
523         return ZEBRA_FAIL;
524     }
525
526     ptr = ptr->children;
527     FOR_EACH_ELEMENT(ptr) {
528         if (!XML_STRCMP(ptr->name, "extract"))
529         {
530             /*
531               <extract name="index">
532               <xslt stylesheet="first.xsl"/>
533               <xslt stylesheet="second.xsl"/>
534               </extract>
535             */
536             struct _xmlAttr *attr;
537             struct filter_extract *f =
538                 odr_malloc(tinfo->odr_config, sizeof(*f));
539             
540             tinfo->extract = f;
541             f->name = 0;
542             f->convert = 0;
543             for (attr = ptr->properties; attr; attr = attr->next)
544             {
545                 if (attr_content(attr, "name", &f->name))
546                     ;
547                 else
548                 {
549                     dom_log(YLOG_WARN, tinfo, ptr,
550                             "bad attribute @%s, expected @name",
551                             attr->name);
552                 }
553             }
554             parse_convert(tinfo, ptr->children, &f->convert);
555         }
556         else if (!XML_STRCMP(ptr->name, "retrieve"))
557         {  
558             /* 
559                <retrieve name="F">
560                <xslt stylesheet="some.xsl"/>
561                <xslt stylesheet="some.xsl"/>
562                </retrieve>
563             */
564             struct _xmlAttr *attr;
565             struct filter_retrieve **fp = &tinfo->retrieve_list;
566             struct filter_retrieve *f =
567                 odr_malloc(tinfo->odr_config, sizeof(*f));
568             
569             while (*fp)
570                 fp = &(*fp)->next;
571
572             *fp = f;
573             f->name = 0;
574             f->identifier = 0;
575             f->convert = 0;
576             f->next = 0;
577
578             for (attr = ptr->properties; attr; attr = attr->next)
579             {
580                 if (attr_content(attr, "identifier", 
581                                  &f->identifier))
582                     ;
583                 else if (attr_content(attr, "name", &f->name))
584                     ;
585                 else
586                 {
587                     dom_log(YLOG_WARN, tinfo, ptr,
588                             "bad attribute @%s,  expected @identifier|@name",
589                             attr->name);
590                 }
591             }
592             parse_convert(tinfo, ptr->children, &f->convert);
593         }
594         else if (!XML_STRCMP(ptr->name, "store"))
595         {
596             /*
597               <store name="F">
598               <xslt stylesheet="some.xsl"/>
599               <xslt stylesheet="some.xsl"/>
600               </retrieve>
601             */
602             struct filter_store *f =
603                 odr_malloc(tinfo->odr_config, sizeof(*f));
604             
605             tinfo->store = f;
606             f->convert = 0;
607             parse_convert(tinfo, ptr->children, &f->convert);
608         }
609         else if (!XML_STRCMP(ptr->name, "input"))
610         {
611             /*
612               <input syntax="xml">
613               <xmlreader level="1"/>
614               </input>
615               <input syntax="usmarc">
616               <marc inputcharset="marc-8"/>
617               </input>
618             */
619             struct _xmlAttr *attr;
620             const char  *syntax = 0;
621             const char *name = 0;
622             for (attr = ptr->properties; attr; attr = attr->next)
623             {
624                 if (attr_content(attr, "syntax", &syntax))
625                     ;
626                 else if (attr_content(attr, "name", &name))
627                     ;
628                 else
629                 {
630                     dom_log(YLOG_WARN, tinfo, ptr,
631                             "bad attribute @%s,  expected @syntax|@name",
632                             attr->name);
633                 }
634             }
635             parse_input(tinfo, ptr->children, syntax, name);
636         }
637         else
638         {
639             dom_log(YLOG_WARN, tinfo, ptr,
640                     "bad element <%s>, "
641                     "expected <extract>|<input>|<retrieve>|<store>",
642                     ptr->name);
643             return ZEBRA_FAIL;
644         }
645     }
646
647     /* adding an empty DOM dummy type if no <input> list has been defined */
648     if (! tinfo->input_list){
649           struct filter_input *p 
650                 = new_input(tinfo, DOM_INPUT_DOM);
651     }
652     
653     return ZEBRA_OK;
654 }
655
656 static struct filter_retrieve *lookup_retrieve(struct filter_info *tinfo,
657                                                const char *est)
658 {
659     struct filter_retrieve *f = tinfo->retrieve_list;
660
661     /* return first schema if no est is provided */
662     if (!est)
663         return f;
664     for (; f; f = f->next)
665     { 
666         /* find requested schema */
667         if (est) 
668         {    
669             if (f->identifier && !strcmp(f->identifier, est))
670                 return f;
671             if (f->name && !strcmp(f->name, est))
672                 return f;
673         } 
674     }
675     return 0;
676 }
677
678 static ZEBRA_RES filter_config(void *clientData, Res res, const char *args)
679 {
680     struct filter_info *tinfo = clientData;
681     if (!args || !*args)
682     {
683         yaz_log(YLOG_WARN, "dom filter: need config file");
684         return ZEBRA_FAIL;
685     }
686
687     if (tinfo->fname && !strcmp(args, tinfo->fname))
688         return ZEBRA_OK;
689     
690     tinfo->profile_path = res_get(res, "profilePath");
691
692     destroy_dom(tinfo);
693     return parse_dom(tinfo, args);
694 }
695
696 static void filter_destroy(void *clientData)
697 {
698     struct filter_info *tinfo = clientData;
699     destroy_dom(tinfo);
700     odr_destroy(tinfo->odr_config);
701     odr_destroy(tinfo->odr_record);
702     xfree(tinfo);
703 }
704
705 static int ioread_ex(void *context, char *buffer, int len)
706 {
707     struct recExtractCtrl *p = context;
708     return p->stream->readf(p->stream, buffer, len);
709 }
710
711 static int ioclose_ex(void *context)
712 {
713     return 0;
714 }
715
716
717 /* DOM filter style indexing */
718 static int attr_content_xml(struct _xmlAttr *attr, const char *name,
719                             xmlChar **dst_content)
720 {
721     if (0 == XML_STRCMP(attr->name, name) && attr->children 
722         && attr->children->type == XML_TEXT_NODE)
723     {
724         *dst_content = (attr->children->content);
725         return 1;
726     }
727     return 0;
728 }
729
730
731 /* DOM filter style indexing */
732 static void index_value_of(struct filter_info *tinfo, 
733                            struct recExtractCtrl *extctr,
734                            RecWord* recword, 
735                            xmlNodePtr node, 
736                            xmlChar * index_p)
737 {
738     if (tinfo->record_info_invoked == 1)
739     {
740         xmlChar *text = xmlNodeGetContent(node);
741         size_t text_len = strlen((const char *)text);
742         
743         /* if there is no text, we do not need to proceed */
744         if (text_len)
745         {            
746             xmlChar *look = index_p;
747             xmlChar *bval;
748             xmlChar *eval;
749
750             xmlChar index[256];
751             xmlChar type[256];
752
753             /* assingning text to be indexed */
754             recword->term_buf = (const char *)text;
755             recword->term_len = text_len;
756
757             /* parsing all index name/type pairs */
758             /* may not start with ' ' or ':' */
759             while (*look && ' ' != *look && ':' != *look)
760             {
761                 /* setting name and type to zero */
762                 *index = '\0';
763                 *type = '\0';
764     
765                 /* parsing one index name */
766                 bval = look;
767                 while (*look && ':' != *look && ' ' != *look)
768                 {
769                     look++;
770                 }
771                 eval = look;
772                 strncpy((char *)index, (const char *)bval, eval - bval);
773                 index[eval - bval] = '\0';
774     
775     
776                 /* parsing one index type, if existing */
777                 if (':' == *look)
778                 {
779                     look++;
780       
781                     bval = look;
782                     while (*look && ' ' != *look)
783                     {
784                         look++;
785                     }
786                     eval = look;
787                     strncpy((char *)type, (const char *)bval, eval - bval);
788                     type[eval - bval] = '\0';
789                 }
790
791                 /* actually indexing the text given */
792                 dom_log(YLOG_DEBUG, tinfo, 0, 
793                         "INDEX '%s:%s' '%s'", 
794                         index ? (const char *) index : "null",
795                         type ? (const char *) type : "null", 
796                         text ? (const char *) text : "null");
797
798                 recword->index_name = (const char *)index;
799                 if (type && *type)
800                     recword->index_type = *type;
801
802                 /* writing debug out */
803                 if (extctr->flagShowRecords)
804                     dom_log(YLOG_LOG, tinfo, 0, 
805                             "INDEX '%s:%s' '%s'", 
806                             index ? (const char *) index : "null",
807                             type ? (const char *) type : "null", 
808                             text ? (const char *) text : "null");
809                 
810                 /* actually indexing the text given */
811                 recword->index_name = (const char *)index;
812                 if (type && *type)
813                     recword->index_type = *type;
814                 (extctr->tokenAdd)(recword);
815
816                 /* eat whitespaces */
817                 if (*look && ' ' == *look && *(look+1))
818                 {
819                     look++;
820                 } 
821             }
822         }
823         xmlFree(text); 
824     }
825 }
826
827
828 /* DOM filter style indexing */
829 static void set_record_info(struct filter_info *tinfo, 
830                             struct recExtractCtrl *extctr, 
831                             xmlNodePtr node, 
832                             xmlChar * id_p, 
833                             xmlChar * rank_p, 
834                             xmlChar * type_p)
835 {
836
837     /* writing debug info out */
838     if (extctr->flagShowRecords)
839         dom_log(YLOG_LOG, tinfo, 0,
840                 "RECORD id=%s rank=%s type=%s", 
841                 id_p ? (const char *) id_p : "(null)",
842                 rank_p ? (const char *) rank_p : "(null)",
843                 type_p ? (const char *) type_p : "(null)");
844     
845
846     if (id_p)
847         sscanf((const char *)id_p, "%255s", extctr->match_criteria);
848
849     if (rank_p)
850         extctr->staticrank = atozint((const char *)rank_p);
851
852     /*     if (!strcmp("update", type_str)) */
853     /*         index_node(tinfo, ctrl, ptr, recword); */
854     /*     else if (!strcmp("delete", type_str)) */
855     /*         dom_log(YLOG_WARN, tinfo, ptr, "dom filter delete: to be implemented"); */
856     /*     else */
857     /*         dom_log(YLOG_WARN, tinfo, ptr, "dom filter: unknown record type '%s'",  */
858     /*                 type_str); */
859     if (tinfo->record_info_invoked == 1)
860     {
861         /* warn about multiple only once */
862         dom_log(YLOG_WARN, tinfo, node, "multiple record elements");
863     }
864     tinfo->record_info_invoked++;
865
866 }
867
868
869 /* DOM filter style indexing */
870 static void process_xml_element_zebra_node(struct filter_info *tinfo, 
871                                            struct recExtractCtrl *extctr, 
872                                            RecWord* recword, 
873                                            xmlNodePtr node)
874 {
875     if (node->type == XML_ELEMENT_NODE && node->ns && node->ns->href
876         && 0 == XML_STRCMP(node->ns->href, zebra_dom_ns))
877     {
878          if (0 == XML_STRCMP(node->name, "index"))
879          {
880             xmlChar *index_p = 0;
881
882             struct _xmlAttr *attr;      
883             for (attr = node->properties; attr; attr = attr->next)
884             {
885                 if (attr_content_xml(attr, "name", &index_p))
886                 {
887                     index_value_of(tinfo, extctr, recword,node, index_p);
888                 }  
889                 else
890                 {
891                     dom_log(YLOG_WARN, tinfo, node,
892                             "bad attribute @%s, expected @name",
893                             attr->name);
894                 }
895             }
896         }
897         else if (0 == XML_STRCMP(node->name, "record"))
898         {
899             xmlChar *id_p = 0;
900             xmlChar *rank_p = 0;
901             xmlChar *type_p = 0;
902
903             struct _xmlAttr *attr;
904             for (attr = node->properties; attr; attr = attr->next)
905             {
906                 if (attr_content_xml(attr, "id", &id_p))
907                     ;
908                 else if (attr_content_xml(attr, "rank", &rank_p))
909                     ;
910                 else if (attr_content_xml(attr, "type", &type_p))
911                     ;
912                 else
913                 {
914                     dom_log(YLOG_WARN, tinfo, node,
915                             "bad attribute @%s, expected @id|@rank|@type",
916                             attr->name);
917                 }
918
919                 if (type_p && 0 != strcmp("update", (const char *)type_p))
920                 {
921                     dom_log(YLOG_WARN, tinfo, node,
922                             "attribute @%s, only implemented '@type='update'",
923                             attr->name);
924                 }
925             }
926             set_record_info(tinfo, extctr, node, id_p, rank_p, type_p);
927         } 
928         else
929         {
930             dom_log(YLOG_WARN, tinfo, node,
931                     "bad element <%s>,"
932                     " expected <record>|<index> in namespace '%s'",
933                     node->name, zebra_dom_ns);
934         }
935     }
936 }
937
938
939 /* DOM filter style indexing */
940 static void process_xml_pi_node(struct filter_info *tinfo, 
941                                 struct recExtractCtrl *extctr, 
942                                 xmlNodePtr node,
943                                 xmlChar **index_pp)
944 {
945     /* if right PI name, continue parsing PI */
946     if (0 == strcmp(zebra_pi_name, (const char *)node->name))
947     {
948         xmlChar *pi_p =  node->content;
949         xmlChar *look = pi_p;
950     
951         xmlChar *bval;
952         xmlChar *eval;
953
954         /* parsing PI record instructions */
955         if (0 == strncmp((const char *)look, "record", 6))
956         {
957             xmlChar id[256];
958             xmlChar rank[256];
959             xmlChar type[256];
960
961             *id = '\0';
962             *rank = '\0';
963             *type = '\0';
964       
965             look += 6;
966       
967             /* eat whitespace */
968             while (*look && ' ' == *look && *(look+1))
969                 look++;
970
971             /* parse possible id */
972             if (*look && 0 == strncmp((const char *)look, "id=", 3))
973             {
974                 look += 3;
975                 bval = look;
976                 while (*look && ' ' != *look)
977                     look++;
978                 eval = look;
979                 strncpy((char *)id, (const char *)bval, eval - bval);
980                 id[eval - bval] = '\0';
981             }
982       
983             /* eat whitespace */
984             while (*look && ' ' == *look && *(look+1))
985                 look++;
986       
987             /* parse possible rank */
988             if (*look && 0 == strncmp((const char *)look, "rank=", 5))
989             {
990                 look += 6;
991                 bval = look;
992                 while (*look && ' ' != *look)
993                     look++;
994                 eval = look;
995                 strncpy((char *)rank, (const char *)bval, eval - bval);
996                 rank[eval - bval] = '\0';
997             }
998
999             /* eat whitespace */
1000             while (*look && ' ' == *look && *(look+1))
1001                 look++;
1002
1003             if (look && '\0' != *look)
1004             {
1005                 dom_log(YLOG_WARN, tinfo, node,
1006                         "content '%s', can not parse '%s'",
1007                         pi_p, look);
1008             }
1009             else 
1010                 set_record_info(tinfo, extctr, node, id, rank, 0);
1011
1012         } 
1013         /* parsing index instruction */
1014         else if (0 == strncmp((const char *)look, "index", 5))
1015         {
1016             look += 5;
1017       
1018             /* eat whitespace */
1019             while (*look && ' ' == *look && *(look+1))
1020                 look++;
1021
1022             /* export index instructions to outside */
1023             *index_pp = look;
1024         } 
1025         else 
1026         {
1027             dom_log(YLOG_WARN, tinfo, node,
1028                     "content '%s', can not parse '%s'",
1029                     pi_p, look);
1030         }
1031     }
1032 }
1033
1034 /* DOM filter style indexing */
1035 static void process_xml_element_node(struct filter_info *tinfo, 
1036                                      struct recExtractCtrl *extctr, 
1037                                      RecWord* recword, 
1038                                      xmlNodePtr node)
1039 {
1040     /* remember indexing instruction from PI to next element node */
1041     xmlChar *index_p = 0;
1042
1043     /* check if we are an element node in the special zebra namespace 
1044        and either set record data or index value-of node content*/
1045     process_xml_element_zebra_node(tinfo, extctr, recword, node);
1046   
1047     /* loop through kid nodes */
1048     for (node = node->children; node; node = node->next)
1049     {
1050         /* check and set PI record and index index instructions */
1051         if (node->type == XML_PI_NODE)
1052         {
1053             process_xml_pi_node(tinfo, extctr, node, &index_p);
1054         }
1055         else if (node->type == XML_ELEMENT_NODE)
1056         {
1057             /* if there was a PI index instruction before this element */
1058             if (index_p)
1059             {
1060                 index_value_of(tinfo, extctr, recword, node, index_p);
1061                 index_p = 0;
1062             }
1063             process_xml_element_node(tinfo, extctr, recword,node);
1064         }
1065         else
1066             continue;
1067     }
1068 }
1069
1070
1071 /* DOM filter style indexing */
1072 static void extract_dom_doc_node(struct filter_info *tinfo, 
1073                                  struct recExtractCtrl *extctr, 
1074                                  xmlDocPtr doc)
1075 {
1076     /* only need to do the initialization once, reuse recword for all terms */
1077     RecWord recword;
1078     (*extctr->init)(extctr, &recword);
1079
1080     process_xml_element_node(tinfo, extctr, &recword, (xmlNodePtr)doc);
1081 }
1082
1083
1084
1085
1086 static int convert_extract_doc(struct filter_info *tinfo, 
1087                                struct filter_input *input,
1088                                struct recExtractCtrl *p, 
1089                                xmlDocPtr doc)
1090
1091 {
1092     xmlChar *buf_out;
1093     int len_out;
1094     const char *params[10];
1095     xsltStylesheetPtr last_xsp = 0;
1096     xmlDocPtr store_doc = 0;
1097
1098     /* per default do not ingest record */
1099     tinfo->record_info_invoked = 0;
1100
1101     /* exit if empty document given */
1102     if (!doc)
1103         return RECCTRL_EXTRACT_SKIP;
1104
1105     /* we actuallu have a document which needs to be processed further */
1106     params[0] = 0;
1107     set_param_str(params, "schema", zebra_dom_ns, tinfo->odr_record);
1108
1109     /* input conversion */
1110     perform_convert(tinfo, p, input->convert, params, &doc, 0);
1111
1112     if (tinfo->store)
1113     {
1114         /* store conversion */
1115         store_doc = xmlCopyDoc(doc, 1);
1116         perform_convert(tinfo, p, tinfo->store->convert,
1117                         params, &store_doc, &last_xsp);
1118     }
1119     
1120     /* saving either store doc or original doc in case no store doc exists */
1121     if (last_xsp)
1122         xsltSaveResultToString(&buf_out, &len_out, 
1123                                store_doc ? store_doc : doc, last_xsp);
1124     else
1125         xmlDocDumpMemory(store_doc ? store_doc : doc, &buf_out, &len_out);
1126
1127     (*p->setStoreData)(p, buf_out, len_out);
1128     xmlFree(buf_out);
1129
1130     if (store_doc)
1131         xmlFreeDoc(store_doc);
1132
1133     /* extract conversion */
1134     perform_convert(tinfo, p, tinfo->extract->convert, params, &doc, 0);
1135
1136
1137     /* finally, do the indexing */
1138     if (doc){
1139         extract_dom_doc_node(tinfo, p, doc);
1140         xmlFreeDoc(doc);
1141     }
1142     
1143     /* there was nothing to index, so there is no inserted/updated record */
1144     if (tinfo->record_info_invoked == 0)
1145         return RECCTRL_EXTRACT_SKIP;
1146
1147     return RECCTRL_EXTRACT_OK;
1148 }
1149
1150 static int extract_xml_split(struct filter_info *tinfo,
1151                              struct filter_input *input,
1152                              struct recExtractCtrl *p)
1153 {
1154     int ret;
1155
1156     if (p->first_record)
1157     {
1158         if (input->u.xmlreader.reader)
1159             xmlFreeTextReader(input->u.xmlreader.reader);
1160         input->u.xmlreader.reader = xmlReaderForIO(ioread_ex, ioclose_ex,
1161                                                    p /* I/O handler */,
1162                                                    0 /* URL */, 
1163                                                    0 /* encoding */,
1164                                                    XML_PARSE_XINCLUDE
1165                                                    | XML_PARSE_NOENT
1166                                                    | XML_PARSE_NONET);
1167     }
1168     if (!input->u.xmlreader.reader)
1169         return RECCTRL_EXTRACT_ERROR_GENERIC;
1170
1171     ret = xmlTextReaderRead(input->u.xmlreader.reader);
1172     while (ret == 1)
1173     {
1174         int type = xmlTextReaderNodeType(input->u.xmlreader.reader);
1175         int depth = xmlTextReaderDepth(input->u.xmlreader.reader);
1176
1177         if (type == XML_READER_TYPE_ELEMENT && 
1178             input->u.xmlreader.split_level == depth)
1179         {
1180             xmlNodePtr ptr;
1181
1182             /* per default do not ingest record */
1183             tinfo->record_info_invoked = 0;
1184             
1185             ptr = xmlTextReaderExpand(input->u.xmlreader.reader);
1186             if (ptr)
1187                 {
1188                 /* we have a new document */
1189
1190                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
1191                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
1192                 
1193                 xmlDocSetRootElement(doc, ptr2);
1194                 
1195                 /* writing debug info out */
1196                 if (p->flagShowRecords)
1197                 {
1198                     xmlChar *buf_out = 0;
1199                     int len_out = 0;
1200                     xmlDocDumpMemory(doc, &buf_out, &len_out);
1201                     yaz_log(YLOG_LOG, "%s: XMLREADER level: %i\n%.*s", 
1202                             tinfo->fname ? tinfo->fname : "(none)",
1203                             depth, len_out, buf_out); 
1204                     xmlFree(buf_out);
1205                 }
1206                 
1207                 return convert_extract_doc(tinfo, input, p, doc);
1208             }
1209             else
1210             {
1211                 xmlFreeTextReader(input->u.xmlreader.reader);
1212                 input->u.xmlreader.reader = 0;
1213                 return RECCTRL_EXTRACT_ERROR_GENERIC;
1214             }
1215         }
1216         ret = xmlTextReaderRead(input->u.xmlreader.reader);
1217     }
1218     xmlFreeTextReader(input->u.xmlreader.reader);
1219     input->u.xmlreader.reader = 0;
1220     return RECCTRL_EXTRACT_EOF;
1221 }
1222
1223 static int extract_xml_full(struct filter_info *tinfo, 
1224                             struct filter_input *input,
1225                             struct recExtractCtrl *p)
1226 {
1227     if (p->first_record) /* only one record per stream */
1228     {
1229         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, 
1230                                   p /* I/O handler */,
1231                                   0 /* URL */,
1232                                   0 /* encoding */,
1233                                   XML_PARSE_XINCLUDE
1234                                   | XML_PARSE_NOENT
1235                                   | XML_PARSE_NONET);
1236         if (!doc)
1237         {
1238             return RECCTRL_EXTRACT_ERROR_GENERIC;
1239         }
1240         return convert_extract_doc(tinfo, input, p, doc);
1241     }
1242     else
1243         return RECCTRL_EXTRACT_EOF;
1244 }
1245
1246 static int extract_iso2709(struct filter_info *tinfo,
1247                            struct filter_input *input,
1248                            struct recExtractCtrl *p)
1249 {
1250     char buf[100000];
1251     int record_length;
1252     int read_bytes, r;
1253
1254     if (p->stream->readf(p->stream, buf, 5) != 5)
1255         return RECCTRL_EXTRACT_EOF;
1256     while (*buf < '0' || *buf > '9')
1257     {
1258         int i;
1259
1260         dom_log(YLOG_WARN, tinfo, 0,
1261                 "MARC: Skipping bad byte %d (0x%02X)",
1262                 *buf & 0xff, *buf & 0xff);
1263         for (i = 0; i<4; i++)
1264             buf[i] = buf[i+1];
1265
1266         if (p->stream->readf(p->stream, buf+4, 1) != 1)
1267             return RECCTRL_EXTRACT_EOF;
1268     }
1269     record_length = atoi_n (buf, 5);
1270     if (record_length < 25)
1271     {
1272         dom_log(YLOG_WARN, tinfo, 0,
1273                 "MARC record length < 25, is %d",  record_length);
1274         return RECCTRL_EXTRACT_ERROR_GENERIC;
1275     }
1276     read_bytes = p->stream->readf(p->stream, buf+5, record_length-5);
1277     if (read_bytes < record_length-5)
1278     {
1279         dom_log(YLOG_WARN, tinfo, 0,
1280                 "couldn't read whole MARC record");
1281         return RECCTRL_EXTRACT_ERROR_GENERIC;
1282     }
1283     r = yaz_marc_read_iso2709(input->u.marc.handle,  buf, record_length);
1284     if (r < record_length)
1285     {
1286         dom_log (YLOG_WARN, tinfo, 0,
1287                  "parsing of MARC record failed r=%d length=%d",
1288                  r, record_length);
1289         return RECCTRL_EXTRACT_ERROR_GENERIC;
1290     }
1291     else
1292     {
1293         xmlDocPtr rdoc;
1294         xmlNode *root_ptr;
1295         yaz_marc_write_xml(input->u.marc.handle, &root_ptr, 0, 0, 0);
1296         rdoc = xmlNewDoc((const xmlChar*) "1.0");
1297         xmlDocSetRootElement(rdoc, root_ptr);
1298         return convert_extract_doc(tinfo, input, p, rdoc);        
1299     }
1300     return RECCTRL_EXTRACT_OK;
1301 }
1302
1303 static int filter_extract(void *clientData, struct recExtractCtrl *p)
1304 {
1305     struct filter_info *tinfo = clientData;
1306     struct filter_input *input = tinfo->input_list;
1307
1308
1309     if (!input)
1310         return RECCTRL_EXTRACT_ERROR_GENERIC; 
1311     
1312     odr_reset(tinfo->odr_record);
1313     switch(input->type)
1314     {
1315     case DOM_INPUT_DOM:
1316         return extract_xml_full(tinfo, input, p);
1317         break;
1318     case DOM_INPUT_XMLREADER:
1319         if (input->u.xmlreader.split_level == 0)
1320             return extract_xml_full(tinfo, input, p);
1321         else
1322             return extract_xml_split(tinfo, input, p);
1323         break;
1324     case DOM_INPUT_MARC:
1325         return extract_iso2709(tinfo, input, p);
1326     }
1327     return RECCTRL_EXTRACT_ERROR_GENERIC;
1328 }
1329
1330 static int ioread_ret(void *context, char *buffer, int len)
1331 {
1332     struct recRetrieveCtrl *p = context;
1333     return p->stream->readf(p->stream, buffer, len);
1334 }
1335
1336 static int ioclose_ret(void *context)
1337 {
1338     return 0;
1339 }
1340
1341 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
1342 {
1343     /* const char *esn = zebra_dom_ns; */
1344     const char *esn = 0;
1345     const char *params[32];
1346     struct filter_info *tinfo = clientData;
1347     xmlDocPtr doc;
1348     struct filter_retrieve *retrieve;
1349     xsltStylesheetPtr last_xsp = 0;
1350
1351     if (p->comp)
1352     {
1353         if (p->comp->which == Z_RecordComp_simple
1354             && p->comp->u.simple->which == Z_ElementSetNames_generic)
1355         {
1356             esn = p->comp->u.simple->u.generic;
1357         }
1358         else if (p->comp->which == Z_RecordComp_complex 
1359                  && p->comp->u.complex->generic->elementSpec
1360                  && p->comp->u.complex->generic->elementSpec->which ==
1361                  Z_ElementSpec_elementSetName)
1362         {
1363             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
1364         }
1365     }
1366     retrieve = lookup_retrieve(tinfo, esn);
1367     if (!retrieve)
1368     {
1369         p->diagnostic =
1370             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
1371         return 0;
1372     }
1373
1374     params[0] = 0;
1375     set_param_int(params, "id", p->localno, p->odr);
1376     if (p->fname)
1377         set_param_str(params, "filename", p->fname, p->odr);
1378     if (p->staticrank >= 0)
1379         set_param_int(params, "rank", p->staticrank, p->odr);
1380
1381     if (esn)
1382         set_param_str(params, "schema", esn, p->odr);
1383     else
1384         if (retrieve->name)
1385             set_param_str(params, "schema", retrieve->name, p->odr);
1386         else if (retrieve->identifier)
1387             set_param_str(params, "schema", retrieve->identifier, p->odr);
1388         else
1389             set_param_str(params, "schema", "", p->odr);
1390
1391     if (p->score >= 0)
1392         set_param_int(params, "score", p->score, p->odr);
1393     set_param_int(params, "size", p->recordSize, p->odr);
1394
1395     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
1396                     0 /* URL */,
1397                     0 /* encoding */,
1398                     XML_PARSE_XINCLUDE | XML_PARSE_NOENT | XML_PARSE_NONET);
1399     if (!doc)
1400     {
1401         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1402         return 0;
1403     }
1404
1405     /* retrieve conversion */
1406     perform_convert(tinfo, 0, retrieve->convert, params, &doc, &last_xsp);
1407     if (!doc)
1408     {
1409         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1410     }
1411     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
1412     {
1413         xmlChar *buf_out;
1414         int len_out;
1415
1416         if (last_xsp)
1417             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1418         else
1419             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1420
1421         p->output_format = VAL_TEXT_XML;
1422         p->rec_len = len_out;
1423         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1424         memcpy(p->rec_buf, buf_out, p->rec_len);
1425         xmlFree(buf_out);
1426     }
1427     else if (p->output_format == VAL_SUTRS)
1428     {
1429         xmlChar *buf_out;
1430         int len_out;
1431
1432         if (last_xsp)
1433             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1434         else
1435             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1436         
1437         p->output_format = VAL_SUTRS;
1438         p->rec_len = len_out;
1439         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1440         memcpy(p->rec_buf, buf_out, p->rec_len);
1441         
1442         xmlFree(buf_out);
1443     }
1444     else
1445     {
1446         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
1447     }
1448     xmlFreeDoc(doc);
1449     return 0;
1450 }
1451
1452 static struct recType filter_type = {
1453     0,
1454     "dom",
1455     filter_init,
1456     filter_config,
1457     filter_destroy,
1458     filter_extract,
1459     filter_retrieve
1460 };
1461
1462 RecType
1463 #ifdef IDZEBRA_STATIC_DOM
1464 idzebra_filter_dom
1465 #else
1466 idzebra_filter
1467 #endif
1468
1469 [] = {
1470     &filter_type,
1471     0,
1472 };
1473 /*
1474  * Local variables:
1475  * c-basic-offset: 4
1476  * indent-tabs-mode: nil
1477  * End:
1478  * vim: shiftwidth=4 tabstop=8 expandtab
1479  */
1480