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