Allow record filters to return 'skip' this record (RECCTRL_EXTRACT_SKIP).
[idzebra-moved-to-github.git] / index / mod_dom.c
1 /* $Id: mod_dom.c,v 1.25 2007-03-01 10:35: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             const char *input_charset;
89             yaz_marc_t handle;
90             yaz_iconv_t iconv;
91         } marc;
92         struct {
93             xmlTextReaderPtr reader;
94             int split_level;
95         } xmlreader;
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         xmlFreeDoc(*doc);
352
353         /* now saving into buffer and re-reading into DOM to avoid annoing
354            XSLT problem with thrown-out indentation text nodes */
355         if (res_doc){
356             xsltSaveResultToString(&buf_out, &len_out, res_doc,
357                                    convert->stylesheet_xsp); 
358             xmlFreeDoc(res_doc);
359         }
360
361
362         *doc =  xmlParseDoc(buf_out);
363
364         /* writing debug info out */
365         if (extctr->flagShowRecords)
366             yaz_log(YLOG_LOG, "%s: XSLT %s \n %s", 
367                     tinfo->fname ? tinfo->fname : "(none)", 
368                     convert->stylesheet,
369                     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     return ZEBRA_OK;
634 }
635
636 static struct filter_retrieve *lookup_retrieve(struct filter_info *tinfo,
637                                                const char *est)
638 {
639     struct filter_retrieve *f = tinfo->retrieve_list;
640
641     /* return first schema if no est is provided */
642     if (!est)
643         return f;
644     for (; f; f = f->next)
645     { 
646         /* find requested schema */
647         if (est) 
648         {    
649             if (f->identifier && !strcmp(f->identifier, est))
650                 return f;
651             if (f->name && !strcmp(f->name, est))
652                 return f;
653         } 
654     }
655     return 0;
656 }
657
658 static ZEBRA_RES filter_config(void *clientData, Res res, const char *args)
659 {
660     struct filter_info *tinfo = clientData;
661     if (!args || !*args)
662     {
663         yaz_log(YLOG_WARN, "dom filter: need config file");
664         return ZEBRA_FAIL;
665     }
666
667     if (tinfo->fname && !strcmp(args, tinfo->fname))
668         return ZEBRA_OK;
669     
670     tinfo->profile_path = res_get(res, "profilePath");
671
672     destroy_dom(tinfo);
673     return parse_dom(tinfo, args);
674 }
675
676 static void filter_destroy(void *clientData)
677 {
678     struct filter_info *tinfo = clientData;
679     destroy_dom(tinfo);
680     odr_destroy(tinfo->odr_config);
681     odr_destroy(tinfo->odr_record);
682     xfree(tinfo);
683 }
684
685 static int ioread_ex(void *context, char *buffer, int len)
686 {
687     struct recExtractCtrl *p = context;
688     return p->stream->readf(p->stream, buffer, len);
689 }
690
691 static int ioclose_ex(void *context)
692 {
693     return 0;
694 }
695
696
697 /* DOM filter style indexing */
698 static int attr_content_xml(struct _xmlAttr *attr, const char *name,
699                             xmlChar **dst_content)
700 {
701     if (0 == XML_STRCMP(attr->name, name) && attr->children 
702         && attr->children->type == XML_TEXT_NODE)
703     {
704         *dst_content = (attr->children->content);
705         return 1;
706     }
707     return 0;
708 }
709
710
711 /* DOM filter style indexing */
712 static void index_value_of(struct filter_info *tinfo, 
713                            struct recExtractCtrl *extctr,
714                            RecWord* recword, 
715                            xmlNodePtr node, 
716                            xmlChar * index_p)
717 {
718     if (tinfo->record_info_invoked == 1)
719     {
720         xmlChar *text = xmlNodeGetContent(node);
721         size_t text_len = strlen((const char *)text);
722         
723         yaz_log(YLOG_LOG, "Indexing :%.*s:", text_len, text);
724         
725         /* if there is no text, we do not need to proceed */
726         if (text_len)
727         {            
728             xmlChar *look = index_p;
729             xmlChar *bval;
730             xmlChar *eval;
731
732             xmlChar index[256];
733             xmlChar type[256];
734
735             /* assingning text to be indexed */
736             recword->term_buf = (const char *)text;
737             recword->term_len = text_len;
738
739             /* parsing all index name/type pairs */
740             /* may not start with ' ' or ':' */
741             while (*look && ' ' != *look && ':' != *look)
742             {
743                 /* setting name and type to zero */
744                 *index = '\0';
745                 *type = '\0';
746     
747                 /* parsing one index name */
748                 bval = look;
749                 while (*look && ':' != *look && ' ' != *look)
750                 {
751                     look++;
752                 }
753                 eval = look;
754                 strncpy((char *)index, (const char *)bval, eval - bval);
755                 index[eval - bval] = '\0';
756     
757     
758                 /* parsing one index type, if existing */
759                 if (':' == *look)
760                 {
761                     look++;
762       
763                     bval = look;
764                     while (*look && ' ' != *look)
765                     {
766                         look++;
767                     }
768                     eval = look;
769                     strncpy((char *)type, (const char *)bval, eval - bval);
770                     type[eval - bval] = '\0';
771                 }
772
773                 /* actually indexing the text given */
774                 dom_log(YLOG_DEBUG, tinfo, 0, 
775                         "INDEX '%s:%s' '%s'", 
776                         index ? (const char *) index : "null",
777                         type ? (const char *) type : "null", 
778                         text ? (const char *) text : "null");
779
780                 recword->index_name = (const char *)index;
781                 if (type && *type)
782                     recword->index_type = *type;
783
784                 /* writing debug out */
785                 if (extctr->flagShowRecords)
786                     dom_log(YLOG_LOG, tinfo, 0, 
787                             "INDEX '%s:%s' '%s'", 
788                             index ? (const char *) index : "null",
789                             type ? (const char *) type : "null", 
790                             text ? (const char *) text : "null");
791                 
792                 /* actually indexing the text given */
793                 recword->index_name = (const char *)index;
794                 if (type && *type)
795                     recword->index_type = *type;
796                 (extctr->tokenAdd)(recword);
797
798                 /* eat whitespaces */
799                 if (*look && ' ' == *look && *(look+1))
800                 {
801                     look++;
802                 } 
803             }
804         }
805         xmlFree(text); 
806     }
807 }
808
809
810 /* DOM filter style indexing */
811 static void set_record_info(struct filter_info *tinfo, 
812                             struct recExtractCtrl *extctr, 
813                             xmlNodePtr node, 
814                             xmlChar * id_p, 
815                             xmlChar * rank_p, 
816                             xmlChar * type_p)
817 {
818
819     /* writing debug info out */
820     if (extctr->flagShowRecords)
821         dom_log(YLOG_LOG, tinfo, 0,
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)
829         sscanf((const char *)id_p, "%255s", extctr->match_criteria);
830
831     if (rank_p)
832         extctr->staticrank = atozint((const char *)rank_p);
833
834     /*     if (!strcmp("update", type_str)) */
835     /*         index_node(tinfo, ctrl, ptr, recword); */
836     /*     else if (!strcmp("delete", type_str)) */
837     /*         dom_log(YLOG_WARN, tinfo, ptr, "dom filter delete: to be implemented"); */
838     /*     else */
839     /*         dom_log(YLOG_WARN, tinfo, ptr, "dom filter: unknown record type '%s'",  */
840     /*                 type_str); */
841     if (tinfo->record_info_invoked == 1)
842     {
843         /* warn about multiple only once */
844         dom_log(YLOG_WARN, tinfo, node, "multiple record elements");
845     }
846     tinfo->record_info_invoked++;
847
848 }
849
850
851 /* DOM filter style indexing */
852 static void process_xml_element_zebra_node(struct filter_info *tinfo, 
853                                            struct recExtractCtrl *extctr, 
854                                            RecWord* recword, 
855                                            xmlNodePtr node)
856 {
857     if (node->type == XML_ELEMENT_NODE && node->ns && node->ns->href
858         && 0 == XML_STRCMP(node->ns->href, zebra_dom_ns))
859     {
860          if (0 == XML_STRCMP(node->name, "index"))
861          {
862             xmlChar *index_p = 0;
863
864             struct _xmlAttr *attr;      
865             for (attr = node->properties; attr; attr = attr->next)
866             {
867                 if (attr_content_xml(attr, "name", &index_p))
868                 {
869                     index_value_of(tinfo, extctr, recword,node, index_p);
870                 }  
871                 else
872                 {
873                     dom_log(YLOG_WARN, tinfo, node,
874                             "bad attribute @%s, expected @name",
875                             attr->name);
876                 }
877             }
878         }
879         else if (0 == XML_STRCMP(node->name, "record"))
880         {
881             xmlChar *id_p = 0;
882             xmlChar *rank_p = 0;
883             xmlChar *type_p = 0;
884
885             struct _xmlAttr *attr;
886             for (attr = node->properties; attr; attr = attr->next)
887             {
888                 if (attr_content_xml(attr, "id", &id_p))
889                     ;
890                 else if (attr_content_xml(attr, "rank", &rank_p))
891                     ;
892                 else if (attr_content_xml(attr, "type", &type_p))
893                     ;
894                 else
895                 {
896                     dom_log(YLOG_WARN, tinfo, node,
897                             "bad attribute @%s, expected @id|@rank|@type",
898                             attr->name);
899                 }
900
901                 if (type_p && 0 != strcmp("update", (const char *)type_p))
902                 {
903                     dom_log(YLOG_WARN, tinfo, node,
904                             "attribute @%s, only implemented '@type='update'",
905                             attr->name);
906                 }
907             }
908             set_record_info(tinfo, extctr, node, id_p, rank_p, type_p);
909         } 
910         else
911         {
912             dom_log(YLOG_WARN, tinfo, node,
913                     "bad element <%s>,"
914                     " expected <record>|<index> in namespace '%s'",
915                     node->name, zebra_dom_ns);
916         }
917     }
918 }
919
920
921 /* DOM filter style indexing */
922 static void process_xml_pi_node(struct filter_info *tinfo, 
923                                 struct recExtractCtrl *extctr, 
924                                 xmlNodePtr node,
925                                 xmlChar **index_pp)
926 {
927     /* if right PI name, continue parsing PI */
928     if (0 == strcmp(zebra_pi_name, (const char *)node->name))
929     {
930         xmlChar *pi_p =  node->content;
931         xmlChar *look = pi_p;
932     
933         xmlChar *bval;
934         xmlChar *eval;
935
936         /* parsing PI record instructions */
937         if (0 == strncmp((const char *)look, "record", 6))
938         {
939             xmlChar id[256];
940             xmlChar rank[256];
941             xmlChar type[256];
942
943             *id = '\0';
944             *rank = '\0';
945             *type = '\0';
946       
947             look += 6;
948       
949             /* eat whitespace */
950             while (*look && ' ' == *look && *(look+1))
951                 look++;
952
953             /* parse possible id */
954             if (*look && 0 == strncmp((const char *)look, "id=", 3))
955             {
956                 look += 3;
957                 bval = look;
958                 while (*look && ' ' != *look)
959                     look++;
960                 eval = look;
961                 strncpy((char *)id, (const char *)bval, eval - bval);
962                 id[eval - bval] = '\0';
963             }
964       
965             /* eat whitespace */
966             while (*look && ' ' == *look && *(look+1))
967                 look++;
968       
969             /* parse possible rank */
970             if (*look && 0 == strncmp((const char *)look, "rank=", 5))
971             {
972                 look += 6;
973                 bval = look;
974                 while (*look && ' ' != *look)
975                     look++;
976                 eval = look;
977                 strncpy((char *)rank, (const char *)bval, eval - bval);
978                 rank[eval - bval] = '\0';
979             }
980
981             /* eat whitespace */
982             while (*look && ' ' == *look && *(look+1))
983                 look++;
984
985             if (look && '\0' != *look)
986             {
987                 dom_log(YLOG_WARN, tinfo, node,
988                         "content '%s', can not parse '%s'",
989                         pi_p, look);
990             }
991             else 
992                 set_record_info(tinfo, extctr, node, id, rank, 0);
993
994         } 
995         /* parsing index instruction */
996         else if (0 == strncmp((const char *)look, "index", 5))
997         {
998             look += 5;
999       
1000             /* eat whitespace */
1001             while (*look && ' ' == *look && *(look+1))
1002                 look++;
1003
1004             /* export index instructions to outside */
1005             *index_pp = look;
1006         } 
1007         else 
1008         {
1009             dom_log(YLOG_WARN, tinfo, node,
1010                     "content '%s', can not parse '%s'",
1011                     pi_p, look);
1012         }
1013     }
1014 }
1015
1016 /* DOM filter style indexing */
1017 static void process_xml_element_node(struct filter_info *tinfo, 
1018                                      struct recExtractCtrl *extctr, 
1019                                      RecWord* recword, 
1020                                      xmlNodePtr node)
1021 {
1022     /* remember indexing instruction from PI to next element node */
1023     xmlChar *index_p = 0;
1024
1025     /* check if we are an element node in the special zebra namespace 
1026        and either set record data or index value-of node content*/
1027     process_xml_element_zebra_node(tinfo, extctr, recword, node);
1028   
1029     /* loop through kid nodes */
1030     for (node = node->children; node; node = node->next)
1031     {
1032         /* check and set PI record and index index instructions */
1033         if (node->type == XML_PI_NODE)
1034         {
1035             process_xml_pi_node(tinfo, extctr, node, &index_p);
1036         }
1037         else if (node->type == XML_ELEMENT_NODE)
1038         {
1039             /* if there was a PI index instruction before this element */
1040             if (index_p)
1041             {
1042                 index_value_of(tinfo, extctr, recword, node, index_p);
1043                 index_p = 0;
1044             }
1045             process_xml_element_node(tinfo, extctr, recword,node);
1046         }
1047         else
1048             continue;
1049     }
1050 }
1051
1052
1053 /* DOM filter style indexing */
1054 static void extract_dom_doc_node(struct filter_info *tinfo, 
1055                                  struct recExtractCtrl *extctr, 
1056                                  xmlDocPtr doc)
1057 {
1058     /* only need to do the initialization once, reuse recword for all terms */
1059     RecWord recword;
1060     (*extctr->init)(extctr, &recword);
1061
1062     /*
1063     if (extctr->flagShowRecords)
1064     {
1065         xmlDocDumpMemory(doc, &buf_out, &len_out);
1066         fwrite(buf_out, len_out, 1, stdout);
1067         xmlFree(buf_out);
1068     }
1069     */
1070     tinfo->record_info_invoked = 0;
1071     process_xml_element_node(tinfo, extctr, &recword, (xmlNodePtr)doc);
1072 }
1073
1074
1075
1076
1077 static int convert_extract_doc(struct filter_info *tinfo, 
1078                                struct filter_input *input,
1079                                struct recExtractCtrl *p, 
1080                                xmlDocPtr doc)
1081
1082 {
1083     xmlChar *buf_out;
1084     int len_out;
1085     const char *params[10];
1086     xsltStylesheetPtr last_xsp = 0;
1087     xmlDocPtr store_doc = 0;
1088
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     if (last_xsp)
1104         xsltSaveResultToString(&buf_out, &len_out, 
1105                                store_doc ? store_doc : doc, last_xsp);
1106     else
1107         xmlDocDumpMemory(store_doc ? store_doc : doc, &buf_out, &len_out);
1108   
1109     /* if (p->flagShowRecords)
1110        fwrite(buf_out, len_out, 1, stdout); */
1111
1112     (*p->setStoreData)(p, buf_out, len_out);
1113     xmlFree(buf_out);
1114
1115     if (store_doc)
1116         xmlFreeDoc(store_doc);
1117
1118     /* extract conversion */
1119     perform_convert(tinfo, p, tinfo->extract->convert, params, &doc, 0);
1120
1121     /* finally, do the indexing */
1122     if (doc)
1123         extract_dom_doc_node(tinfo, p, doc);
1124
1125     if (doc)
1126         xmlFreeDoc(doc);
1127
1128     if (tinfo->record_info_invoked == 0)
1129         return RECCTRL_EXTRACT_SKIP;
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     }
1150     if (!input->u.xmlreader.reader)
1151         return RECCTRL_EXTRACT_ERROR_GENERIC;
1152
1153     ret = xmlTextReaderRead(input->u.xmlreader.reader);
1154     while (ret == 1)
1155     {
1156         int type = xmlTextReaderNodeType(input->u.xmlreader.reader);
1157         int depth = xmlTextReaderDepth(input->u.xmlreader.reader);
1158         if (type == XML_READER_TYPE_ELEMENT && 
1159             input->u.xmlreader.split_level == depth)
1160         {
1161             xmlNodePtr ptr
1162                 = xmlTextReaderExpand(input->u.xmlreader.reader);
1163             if (ptr)
1164             {
1165                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
1166                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
1167                 
1168                 xmlDocSetRootElement(doc, ptr2);
1169                 
1170                 /* writing debug info out */
1171                 if (p->flagShowRecords){
1172                     xmlChar *buf_out = 0;
1173                     int len_out = 0;
1174                     xmlDocDumpMemory(doc, &buf_out, &len_out);
1175                     yaz_log(YLOG_LOG, "%s: XMLREADER depth: %i\n%s", 
1176                             tinfo->fname ? tinfo->fname : "(none)",
1177                             depth,
1178                             buf_out); 
1179                     xmlFree(buf_out);
1180                 }
1181                 
1182                 return convert_extract_doc(tinfo, input, p, doc);
1183             }
1184             else
1185             {
1186                 xmlFreeTextReader(input->u.xmlreader.reader);
1187                 input->u.xmlreader.reader = 0;
1188                 return RECCTRL_EXTRACT_ERROR_GENERIC;
1189             }
1190         }
1191         ret = xmlTextReaderRead(input->u.xmlreader.reader);
1192     }
1193     xmlFreeTextReader(input->u.xmlreader.reader);
1194     input->u.xmlreader.reader = 0;
1195     return RECCTRL_EXTRACT_EOF;
1196 }
1197
1198 static int extract_xml_full(struct filter_info *tinfo, 
1199                             struct filter_input *input,
1200                             struct recExtractCtrl *p)
1201 {
1202     if (p->first_record) /* only one record per stream */
1203     {
1204         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, 
1205                                   p /* I/O handler */,
1206                                   0 /* URL */,
1207                                   0 /* encoding */,
1208                                   XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1209         if (!doc)
1210         {
1211             return RECCTRL_EXTRACT_ERROR_GENERIC;
1212         }
1213         return convert_extract_doc(tinfo, input, p, doc);
1214     }
1215     else
1216         return RECCTRL_EXTRACT_EOF;
1217 }
1218
1219 static int extract_iso2709(struct filter_info *tinfo,
1220                            struct filter_input *input,
1221                            struct recExtractCtrl *p)
1222 {
1223     char buf[100000];
1224     int record_length;
1225     int read_bytes, r;
1226
1227     if (p->stream->readf(p->stream, buf, 5) != 5)
1228         return RECCTRL_EXTRACT_EOF;
1229     while (*buf < '0' || *buf > '9')
1230     {
1231         int i;
1232
1233         dom_log(YLOG_WARN, tinfo, 0,
1234                 "MARC: Skipping bad byte %d (0x%02X)",
1235                 *buf & 0xff, *buf & 0xff);
1236         for (i = 0; i<4; i++)
1237             buf[i] = buf[i+1];
1238
1239         if (p->stream->readf(p->stream, buf+4, 1) != 1)
1240             return RECCTRL_EXTRACT_EOF;
1241     }
1242     record_length = atoi_n (buf, 5);
1243     if (record_length < 25)
1244     {
1245         dom_log(YLOG_WARN, tinfo, 0,
1246                 "MARC record length < 25, is %d",  record_length);
1247         return RECCTRL_EXTRACT_ERROR_GENERIC;
1248     }
1249     read_bytes = p->stream->readf(p->stream, buf+5, record_length-5);
1250     if (read_bytes < record_length-5)
1251     {
1252         dom_log(YLOG_WARN, tinfo, 0,
1253                 "couldn't read whole MARC record");
1254         return RECCTRL_EXTRACT_ERROR_GENERIC;
1255     }
1256     r = yaz_marc_read_iso2709(input->u.marc.handle,  buf, record_length);
1257     if (r < record_length)
1258     {
1259         dom_log (YLOG_WARN, tinfo, 0,
1260                  "parsing of MARC record failed r=%d length=%d",
1261                  r, record_length);
1262         return RECCTRL_EXTRACT_ERROR_GENERIC;
1263     }
1264     else
1265     {
1266         xmlDocPtr rdoc;
1267         xmlNode *root_ptr;
1268         yaz_marc_write_xml(input->u.marc.handle, &root_ptr, 0, 0, 0);
1269         rdoc = xmlNewDoc((const xmlChar*) "1.0");
1270         xmlDocSetRootElement(rdoc, root_ptr);
1271         return convert_extract_doc(tinfo, input, p, rdoc);        
1272     }
1273     return RECCTRL_EXTRACT_OK;
1274 }
1275
1276 static int filter_extract(void *clientData, struct recExtractCtrl *p)
1277 {
1278     struct filter_info *tinfo = clientData;
1279     struct filter_input *input = tinfo->input_list;
1280
1281     if (!input)
1282         return RECCTRL_EXTRACT_ERROR_GENERIC;
1283
1284     odr_reset(tinfo->odr_record);
1285     switch(input->type)
1286     {
1287     case DOM_INPUT_XMLREADER:
1288         if (input->u.xmlreader.split_level == 0)
1289             return extract_xml_full(tinfo, input, p);
1290         else
1291             return extract_xml_split(tinfo, input, p);
1292         break;
1293     case DOM_INPUT_MARC:
1294         return extract_iso2709(tinfo, input, p);
1295     }
1296     return RECCTRL_EXTRACT_ERROR_GENERIC;
1297 }
1298
1299 static int ioread_ret(void *context, char *buffer, int len)
1300 {
1301     struct recRetrieveCtrl *p = context;
1302     return p->stream->readf(p->stream, buffer, len);
1303 }
1304
1305 static int ioclose_ret(void *context)
1306 {
1307     return 0;
1308 }
1309
1310 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
1311 {
1312     /* const char *esn = zebra_dom_ns; */
1313     const char *esn = 0;
1314     const char *params[32];
1315     struct filter_info *tinfo = clientData;
1316     xmlDocPtr doc;
1317     struct filter_retrieve *retrieve;
1318     xsltStylesheetPtr last_xsp = 0;
1319
1320     if (p->comp)
1321     {
1322         if (p->comp->which == Z_RecordComp_simple
1323             && p->comp->u.simple->which == Z_ElementSetNames_generic)
1324         {
1325             esn = p->comp->u.simple->u.generic;
1326         }
1327         else if (p->comp->which == Z_RecordComp_complex 
1328                  && p->comp->u.complex->generic->elementSpec
1329                  && p->comp->u.complex->generic->elementSpec->which ==
1330                  Z_ElementSpec_elementSetName)
1331         {
1332             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
1333         }
1334     }
1335     retrieve = lookup_retrieve(tinfo, esn);
1336     if (!retrieve)
1337     {
1338         p->diagnostic =
1339             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
1340         return 0;
1341     }
1342
1343     params[0] = 0;
1344     set_param_int(params, "id", p->localno, p->odr);
1345     if (p->fname)
1346         set_param_str(params, "filename", p->fname, p->odr);
1347     if (p->staticrank >= 0)
1348         set_param_int(params, "rank", p->staticrank, p->odr);
1349
1350     if (esn)
1351         set_param_str(params, "schema", esn, p->odr);
1352     else
1353         if (retrieve->name)
1354             set_param_str(params, "schema", retrieve->name, p->odr);
1355         else if (retrieve->identifier)
1356             set_param_str(params, "schema", retrieve->identifier, p->odr);
1357         else
1358             set_param_str(params, "schema", "", p->odr);
1359
1360     if (p->score >= 0)
1361         set_param_int(params, "score", p->score, p->odr);
1362     set_param_int(params, "size", p->recordSize, p->odr);
1363
1364     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
1365                     0 /* URL */,
1366                     0 /* encoding */,
1367                     XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1368     if (!doc)
1369     {
1370         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1371         return 0;
1372     }
1373
1374     /* retrieve conversion */
1375     perform_convert(tinfo, 0, retrieve->convert, params, &doc, &last_xsp);
1376     if (!doc)
1377     {
1378         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1379     }
1380     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
1381     {
1382         xmlChar *buf_out;
1383         int len_out;
1384
1385         if (last_xsp)
1386             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1387         else
1388             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1389
1390         p->output_format = VAL_TEXT_XML;
1391         p->rec_len = len_out;
1392         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1393         memcpy(p->rec_buf, buf_out, p->rec_len);
1394         xmlFree(buf_out);
1395     }
1396     else if (p->output_format == VAL_SUTRS)
1397     {
1398         xmlChar *buf_out;
1399         int len_out;
1400
1401         if (last_xsp)
1402             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1403         else
1404             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1405         
1406         p->output_format = VAL_SUTRS;
1407         p->rec_len = len_out;
1408         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1409         memcpy(p->rec_buf, buf_out, p->rec_len);
1410         
1411         xmlFree(buf_out);
1412     }
1413     else
1414     {
1415         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
1416     }
1417     xmlFreeDoc(doc);
1418     return 0;
1419 }
1420
1421 static struct recType filter_type = {
1422     0,
1423     "dom",
1424     filter_init,
1425     filter_config,
1426     filter_destroy,
1427     filter_extract,
1428     filter_retrieve
1429 };
1430
1431 RecType
1432 #ifdef IDZEBRA_STATIC_DOM
1433 idzebra_filter_dom
1434 #else
1435 idzebra_filter
1436 #endif
1437
1438 [] = {
1439     &filter_type,
1440     0,
1441 };
1442 /*
1443  * Local variables:
1444  * c-basic-offset: 4
1445  * indent-tabs-mode: nil
1446  * End:
1447  * vim: shiftwidth=4 tabstop=8 expandtab
1448  */
1449