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