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