removed the crappy PI and <z:index> parsing code comitted yesterday
[idzebra-moved-to-github.git] / index / mod_dom.c
1 /* $Id: mod_dom.c,v 1.6 2007-02-14 15:23:33 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 == 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   const char *look = index_p;
782   const char *bval;
783   const char *eval;
784
785   char index[256];
786   char 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(index, 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(type, 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   xmlChar *err_p = 0;
846
847   if (node->type == XML_ELEMENT_NODE 
848       && node->ns && 0 == strcmp(node->ns->href, zebra_xslt_ns)){
849     
850     if (0 == strcmp(node->name, "index")){
851       xmlChar *index_p = 0;
852
853       struct _xmlAttr *attr;      
854       for (attr = node->properties; attr; attr = attr->next){
855         if (attr_content_xml(attr, "name", &index_p)){
856           index_value_of(node, index_p);        
857         }  
858         else
859           //   printf("%s: dom filter: s% bad attribute %s",
860           //        tinfo->fname, xmlGetNodePath(node)), nodeattr->name);
861           printf("dom filter: %s bad attribute @%s, expected @name\n",
862                   xmlGetNodePath(node), attr->name);
863       }
864     }
865     else if (0 == strcmp(node->name, "record")){
866       xmlChar *id_p = 0;
867       xmlChar *rank_p = 0;
868       xmlChar *action_p = 0;
869
870       struct _xmlAttr *attr;
871       for (attr = node->properties; attr; attr = attr->next){
872         if (attr_content_xml(attr, "id", &id_p))
873           ;
874         else if (attr_content_xml(attr, "rank", &rank_p))
875           ;
876         else if (attr_content_xml(attr, "acton", &action_p))
877           ;
878         else
879           //   printf("%s: dom filter: s% bad attribute %s",
880           //        tinfo->fname, xmlGetNodePath(node)), nodeattr->name);
881           printf("dom filter: %s bad attribute @%s,"
882                  " expected @id|@rank|@action\n",
883                  xmlGetNodePath(node), attr->name);
884
885         if (action_p && 0 != strcmp("update", action_p))
886           printf("dom filter: %s attribute @%s,"
887                  " only implemented '@action=\"update\"\n",
888                  xmlGetNodePath(node), attr->name);
889           
890
891       }
892       set_record_info(id_p, rank_p, action_p);
893     } else {
894       //   printf("%s: dom filter: s% bad attribute %s",
895       //        tinfo->fname, xmlGetNodePath(node)), nodeattr->name);
896       printf("dom filter: %s bad element <%s>,"
897              " expected <record>|<index> in namespace '%s'\n",
898              xmlGetNodePath(node), node->name, zebra_xslt_ns);
899       
900     }
901   }
902 }
903
904
905 /* DOM filter style indexing */
906 void process_xml_element_node(xmlNodePtr node, xmlChar **record_pp)
907 {
908   /* remember indexing instruction from PI to next element node */
909   xmlChar *index_p = 0;
910
911   printf("ELEM   %s\n", xmlGetNodePath(node));
912
913   /* check if we are an element node in the special zebra namespace 
914      and either set record data or index value-of node content*/
915   process_xml_element_zebra_node(node, record_pp);
916   
917   /* loop through kid nodes */
918   for (node = node->children; node; node = node->next)
919     {
920       /* check and set PI record and index index instructions */
921       if (node->type == XML_PI_NODE){
922         process_xml_pi_node(node, record_pp, &index_p);
923       }
924       else if (node->type == XML_ELEMENT_NODE){
925         /* if there was a PI index instruction before this element node */
926         if (index_p){
927           index_value_of(node, index_p);            
928           index_p = 0;
929         }
930         process_xml_element_node(node, record_pp);
931       }
932       else
933         continue;
934     }
935 }
936
937
938 /* DOM filter style indexing */
939 int process_xml_pi_node(xmlNodePtr node, xmlChar **record_pp, 
940                         xmlChar **index_pp)
941 {
942   printf("PI     %s\n", xmlGetNodePath(node));  
943
944   /* if right PI name, continue parsing PI */
945   if (0 == strcmp("zebra-2.0", node->name)){
946     xmlChar *pi_p =  node->content;
947     xmlChar *look = pi_p;
948     
949     xmlChar *bval;
950     xmlChar *eval;
951
952     xmlChar *index_p = 0;
953
954
955     /* parsing PI record instructions */
956     if (0 == strncmp(look, "record", 6)){
957       xmlChar id[256];
958       xmlChar rank[256];
959       xmlChar action[256];
960
961       *id = '\0';
962       *rank = '\0';
963       *action = '\0';
964       
965       look += 6;
966       
967       /* eat whitespace */
968       while (*look && ' ' == *look && *(look+1))
969         look++;
970
971       /* parse possible id */
972       if (*look && 0 == strncmp(look, "id=", 3)){
973         look += 3;
974         bval = look;
975         while (*look && ' ' != *look)
976           look++;
977         eval = look;
978         strncpy(id, bval, eval - bval);
979         id[eval - bval] = '\0';
980       }
981       
982       /* eat whitespace */
983       while (*look && ' ' == *look && *(look+1))
984         look++;
985       
986       /* parse possible rank */
987       if (*look && 0 == strncmp(look, "rank=", 5)){
988         look += 6;
989         bval = look;
990         while (*look && ' ' != *look)
991           look++;
992         eval = look;
993         strncpy(rank, bval, eval - bval);
994         rank[eval - bval] = '\0';
995       }
996
997       /* eat whitespace */
998       while (*look && ' ' == *look && *(look+1))
999         look++;
1000
1001       if (look && '\0' != *look){
1002         printf ("ERROR %s: content '%s'; can not parse '%s'\n", 
1003                 xmlGetNodePath(node), pi_p, look);
1004       } else {
1005         /* set_record_info(id, rank, action); */
1006         set_record_info(id, rank, 0);
1007       }
1008
1009     } 
1010    
1011     /* parsing index instruction */
1012     else   if (0 == strncmp(look, "index", 5)){
1013       look += 5;
1014       
1015       /* eat whitespace */
1016       while (*look && ' ' == *look && *(look+1))
1017         look++;
1018
1019       /* export index instructions to outside */
1020       *index_pp = look;
1021
1022       /* nor record, neither index */ 
1023     } else {
1024     
1025       printf ("ERROR %s: content '%s'; can not parse '%s'\n", 
1026               xmlGetNodePath(node), pi_p, look);
1027     }  
1028   }
1029 }
1030
1031
1032 /* DOM filter style indexing */
1033 void process_xml_doc_node(xmlDocPtr doc)
1034 {
1035     xmlChar *record_pp;
1036     
1037     printf("DOC    %s\n", xmlGetNodePath(doc));
1038
1039     process_xml_element_node((xmlNodePtr)doc, &record_pp);
1040 }
1041
1042
1043
1044
1045 static int convert_extract_doc(struct filter_info *tinfo, 
1046                                struct filter_input *input,
1047                                struct recExtractCtrl *p, 
1048                                xmlDocPtr doc)
1049
1050 {
1051     /* RecWord recWord; */
1052     xmlChar *buf_out;
1053     int len_out;
1054     const char *params[10];
1055     xsltStylesheetPtr last_xsp = 0;
1056     xmlDocPtr store_doc = 0;
1057
1058     params[0] = 0;
1059     set_param_str(params, "schema", zebra_xslt_ns, tinfo->odr_record);
1060
1061     /* input conversion */
1062     perform_convert(tinfo, input->convert, params, &doc, 0);
1063
1064     if (tinfo->store)
1065     {
1066         /* store conversion */
1067         store_doc = xmlCopyDoc(doc, 1);
1068         perform_convert(tinfo, tinfo->store->convert,
1069                         params, &store_doc, &last_xsp);
1070     }
1071     
1072     if (last_xsp)
1073         xsltSaveResultToString(&buf_out, &len_out, 
1074                                store_doc ? store_doc : doc, last_xsp);
1075     else
1076         xmlDocDumpMemory(store_doc ? store_doc : doc, &buf_out, &len_out);
1077     if (p->flagShowRecords)
1078         fwrite(buf_out, len_out, 1, stdout);
1079     (*p->setStoreData)(p, buf_out, len_out);
1080     xmlFree(buf_out);
1081
1082     if (store_doc)
1083         xmlFreeDoc(store_doc);
1084
1085     /* extract conversion */
1086     perform_convert(tinfo, tinfo->extract->convert, params, &doc, 0);
1087
1088     if (doc){
1089         extract_doc_alvis(tinfo, p, doc);
1090         xmlFreeDoc(doc);
1091     }
1092
1093     return RECCTRL_EXTRACT_OK;
1094 }
1095
1096 static int extract_xml_split(struct filter_info *tinfo,
1097                              struct filter_input *input,
1098                              struct recExtractCtrl *p)
1099 {
1100     int ret;
1101
1102     if (p->first_record)
1103     {
1104         if (input->u.xmlreader.reader)
1105             xmlFreeTextReader(input->u.xmlreader.reader);
1106         input->u.xmlreader.reader = xmlReaderForIO(ioread_ex, ioclose_ex,
1107                                                    p /* I/O handler */,
1108                                                    0 /* URL */, 
1109                                                    0 /* encoding */,
1110                                                    XML_PARSE_XINCLUDE|
1111                                                    XML_PARSE_NOENT);
1112     }
1113     if (!input->u.xmlreader.reader)
1114         return RECCTRL_EXTRACT_ERROR_GENERIC;
1115
1116     ret = xmlTextReaderRead(input->u.xmlreader.reader);
1117     while (ret == 1)
1118     {
1119         int type = xmlTextReaderNodeType(input->u.xmlreader.reader);
1120         int depth = xmlTextReaderDepth(input->u.xmlreader.reader);
1121         if (type == XML_READER_TYPE_ELEMENT && 
1122             input->u.xmlreader.split_level == depth)
1123         {
1124             xmlNodePtr ptr = xmlTextReaderExpand(input->u.xmlreader.reader);
1125             if (ptr)
1126             {
1127                 xmlNodePtr ptr2 = xmlCopyNode(ptr, 1);
1128                 xmlDocPtr doc = xmlNewDoc((const xmlChar*) "1.0");
1129                 
1130                 xmlDocSetRootElement(doc, ptr2);
1131                 
1132                 return convert_extract_doc(tinfo, input, p, doc);
1133             }
1134             else
1135             {
1136                 xmlFreeTextReader(input->u.xmlreader.reader);
1137                 input->u.xmlreader.reader = 0;
1138                 return RECCTRL_EXTRACT_ERROR_GENERIC;
1139             }
1140         }
1141         ret = xmlTextReaderRead(input->u.xmlreader.reader);
1142     }
1143     xmlFreeTextReader(input->u.xmlreader.reader);
1144     input->u.xmlreader.reader = 0;
1145     return RECCTRL_EXTRACT_EOF;
1146 }
1147
1148 static int extract_xml_full(struct filter_info *tinfo, 
1149                             struct filter_input *input,
1150                             struct recExtractCtrl *p)
1151 {
1152     if (p->first_record) /* only one record per stream */
1153     {
1154         xmlDocPtr doc = xmlReadIO(ioread_ex, ioclose_ex, p /* I/O handler */,
1155                                   0 /* URL */,
1156                                   0 /* encoding */,
1157                                   XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1158         if (!doc)
1159         {
1160             return RECCTRL_EXTRACT_ERROR_GENERIC;
1161         }
1162         return convert_extract_doc(tinfo, input, p, doc);
1163     }
1164     else
1165         return RECCTRL_EXTRACT_EOF;
1166 }
1167
1168 static int extract_iso2709(struct filter_info *tinfo,
1169                            struct filter_input *input,
1170                            struct recExtractCtrl *p)
1171 {
1172     char buf[100000];
1173     int record_length;
1174     int read_bytes, r;
1175
1176     if (p->stream->readf(p->stream, buf, 5) != 5)
1177         return RECCTRL_EXTRACT_EOF;
1178     while (*buf < '0' || *buf > '9')
1179     {
1180         int i;
1181
1182         yaz_log(YLOG_WARN, "MARC: Skipping bad byte %d (0x%02X)",
1183                 *buf & 0xff, *buf & 0xff);
1184         for (i = 0; i<4; i++)
1185             buf[i] = buf[i+1];
1186
1187         if (p->stream->readf(p->stream, buf+4, 1) != 1)
1188             return RECCTRL_EXTRACT_EOF;
1189     }
1190     record_length = atoi_n (buf, 5);
1191     if (record_length < 25)
1192     {
1193         yaz_log (YLOG_WARN, "MARC record length < 25, is %d", record_length);
1194         return RECCTRL_EXTRACT_ERROR_GENERIC;
1195     }
1196     read_bytes = p->stream->readf(p->stream, buf+5, record_length-5);
1197     if (read_bytes < record_length-5)
1198     {
1199         yaz_log (YLOG_WARN, "Couldn't read whole MARC record");
1200         return RECCTRL_EXTRACT_ERROR_GENERIC;
1201     }
1202     r = yaz_marc_read_iso2709(input->u.marc.handle,  buf, record_length);
1203     if (r < record_length)
1204     {
1205         yaz_log (YLOG_WARN, "Parsing of MARC record failed r=%d length=%d",
1206                  r, record_length);
1207         return RECCTRL_EXTRACT_ERROR_GENERIC;
1208     }
1209     else
1210     {
1211         xmlDocPtr rdoc;
1212         xmlNode *root_ptr;
1213         yaz_marc_write_xml(input->u.marc.handle, &root_ptr, 0, 0, 0);
1214         rdoc = xmlNewDoc((const xmlChar*) "1.0");
1215         xmlDocSetRootElement(rdoc, root_ptr);
1216         return convert_extract_doc(tinfo, input, p, rdoc);        
1217     }
1218     return RECCTRL_EXTRACT_OK;
1219 }
1220
1221 static int filter_extract(void *clientData, struct recExtractCtrl *p)
1222 {
1223     struct filter_info *tinfo = clientData;
1224     struct filter_input *input = tinfo->input_list;
1225
1226     if (!input)
1227         return RECCTRL_EXTRACT_ERROR_GENERIC;
1228
1229     odr_reset(tinfo->odr_record);
1230     switch(input->type)
1231     {
1232     case DOM_INPUT_XMLREADER:
1233         if (input->u.xmlreader.split_level == 0)
1234             return extract_xml_full(tinfo, input, p);
1235         else
1236             return extract_xml_split(tinfo, input, p);
1237         break;
1238     case DOM_INPUT_MARC:
1239         return extract_iso2709(tinfo, input, p);
1240     }
1241     return RECCTRL_EXTRACT_ERROR_GENERIC;
1242 }
1243
1244 static int ioread_ret(void *context, char *buffer, int len)
1245 {
1246     struct recRetrieveCtrl *p = context;
1247     return p->stream->readf(p->stream, buffer, len);
1248 }
1249
1250 static int ioclose_ret(void *context)
1251 {
1252     return 0;
1253 }
1254
1255 static int filter_retrieve (void *clientData, struct recRetrieveCtrl *p)
1256 {
1257     /* const char *esn = zebra_xslt_ns; */
1258     const char *esn = 0;
1259     const char *params[32];
1260     struct filter_info *tinfo = clientData;
1261     xmlDocPtr doc;
1262     struct filter_retrieve *retrieve;
1263     xsltStylesheetPtr last_xsp = 0;
1264
1265     if (p->comp)
1266     {
1267         if (p->comp->which == Z_RecordComp_simple
1268             && p->comp->u.simple->which == Z_ElementSetNames_generic)
1269         {
1270             esn = p->comp->u.simple->u.generic;
1271         }
1272         else if (p->comp->which == Z_RecordComp_complex 
1273                  && p->comp->u.complex->generic->elementSpec
1274                  && p->comp->u.complex->generic->elementSpec->which ==
1275                  Z_ElementSpec_elementSetName)
1276         {
1277             esn = p->comp->u.complex->generic->elementSpec->u.elementSetName;
1278         }
1279     }
1280     retrieve = lookup_retrieve(tinfo, esn);
1281     if (!retrieve)
1282     {
1283         p->diagnostic =
1284             YAZ_BIB1_SPECIFIED_ELEMENT_SET_NAME_NOT_VALID_FOR_SPECIFIED_;
1285         return 0;
1286     }
1287
1288     params[0] = 0;
1289     set_param_int(params, "id", p->localno, p->odr);
1290     if (p->fname)
1291         set_param_str(params, "filename", p->fname, p->odr);
1292     if (p->staticrank >= 0)
1293         set_param_int(params, "rank", p->staticrank, p->odr);
1294
1295     if (esn)
1296         set_param_str(params, "schema", esn, p->odr);
1297     else
1298         if (retrieve->name)
1299             set_param_str(params, "schema", retrieve->name, p->odr);
1300         else if (retrieve->identifier)
1301             set_param_str(params, "schema", retrieve->identifier, p->odr);
1302         else
1303             set_param_str(params, "schema", "", p->odr);
1304
1305     if (p->score >= 0)
1306         set_param_int(params, "score", p->score, p->odr);
1307     set_param_int(params, "size", p->recordSize, p->odr);
1308
1309     doc = xmlReadIO(ioread_ret, ioclose_ret, p /* I/O handler */,
1310                     0 /* URL */,
1311                     0 /* encoding */,
1312                     XML_PARSE_XINCLUDE|XML_PARSE_NOENT);
1313     if (!doc)
1314     {
1315         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1316         return 0;
1317     }
1318
1319     /* retrieve conversion */
1320     perform_convert(tinfo, retrieve->convert, params, &doc, &last_xsp);
1321     if (!doc)
1322     {
1323         p->diagnostic = YAZ_BIB1_SYSTEM_ERROR_IN_PRESENTING_RECORDS;
1324     }
1325     else if (p->input_format == VAL_NONE || p->input_format == VAL_TEXT_XML)
1326     {
1327         xmlChar *buf_out;
1328         int len_out;
1329
1330         if (last_xsp)
1331             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1332         else
1333             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1334
1335         p->output_format = VAL_TEXT_XML;
1336         p->rec_len = len_out;
1337         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1338         memcpy(p->rec_buf, buf_out, p->rec_len);
1339         xmlFree(buf_out);
1340     }
1341     else if (p->output_format == VAL_SUTRS)
1342     {
1343         xmlChar *buf_out;
1344         int len_out;
1345
1346         if (last_xsp)
1347             xsltSaveResultToString(&buf_out, &len_out, doc, last_xsp);
1348         else
1349             xmlDocDumpMemory(doc, &buf_out, &len_out);            
1350         
1351         p->output_format = VAL_SUTRS;
1352         p->rec_len = len_out;
1353         p->rec_buf = odr_malloc(p->odr, p->rec_len);
1354         memcpy(p->rec_buf, buf_out, p->rec_len);
1355         
1356         xmlFree(buf_out);
1357     }
1358     else
1359     {
1360         p->diagnostic = YAZ_BIB1_RECORD_SYNTAX_UNSUPP;
1361     }
1362     xmlFreeDoc(doc);
1363     return 0;
1364 }
1365
1366 static struct recType filter_type = {
1367     0,
1368     "dom",
1369     filter_init,
1370     filter_config,
1371     filter_destroy,
1372     filter_extract,
1373     filter_retrieve
1374 };
1375
1376 RecType
1377 #ifdef IDZEBRA_STATIC_DOM
1378 idzebra_filter_dom
1379 #else
1380 idzebra_filter
1381 #endif
1382
1383 [] = {
1384     &filter_type,
1385     0,
1386 };
1387 /*
1388  * Local variables:
1389  * c-basic-offset: 4
1390  * indent-tabs-mode: nil
1391  * End:
1392  * vim: shiftwidth=4 tabstop=8 expandtab
1393  */
1394