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