bd03c00eecede142622468f900b5fb2b3a40c197
[yaz-moved-to-github.git] / src / marcdisp.c
1 /* This file is part of the YAZ toolkit.
2  * Copyright (C) 1995-2010 Index Data
3  * See the file LICENSE for details.
4  */
5
6 /**
7  * \file marcdisp.c
8  * \brief Implements MARC conversion utilities
9  */
10
11 #if HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 #ifdef WIN32
16 #include <windows.h>
17 #endif
18
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <yaz/marcdisp.h>
25 #include <yaz/wrbuf.h>
26 #include <yaz/yaz-util.h>
27 #include <yaz/nmem_xml.h>
28 #include <yaz/snprintf.h>
29
30 #if YAZ_HAVE_XML2
31 #include <libxml/parser.h>
32 #include <libxml/tree.h>
33 #endif
34
35 enum yaz_collection_state {
36     no_collection,
37     collection_first,
38     collection_second
39 };
40    
41 /** \brief node types for yaz_marc_node */
42 enum YAZ_MARC_NODE_TYPE
43
44     YAZ_MARC_DATAFIELD,
45     YAZ_MARC_CONTROLFIELD,
46     YAZ_MARC_COMMENT,
47     YAZ_MARC_LEADER
48 };
49
50 /** \brief represets a data field */
51 struct yaz_marc_datafield {
52     char *tag;
53     char *indicator;
54     struct yaz_marc_subfield *subfields;
55 };
56
57 /** \brief represents a control field */
58 struct yaz_marc_controlfield {
59     char *tag;
60     char *data;
61 };
62
63 /** \brief a comment node */
64 struct yaz_marc_comment {
65     char *comment;
66 };
67
68 /** \brief MARC node */
69 struct yaz_marc_node {
70     enum YAZ_MARC_NODE_TYPE which;
71     union {
72         struct yaz_marc_datafield datafield;
73         struct yaz_marc_controlfield controlfield;
74         char *comment;
75         char *leader;
76     } u;
77     struct yaz_marc_node *next;
78 };
79
80 /** \brief represents a subfield */
81 struct yaz_marc_subfield {
82     char *code_data;
83     struct yaz_marc_subfield *next;
84 };
85
86 /** \brief the internals of a yaz_marc_t handle */
87 struct yaz_marc_t_ {
88     WRBUF m_wr;
89     NMEM nmem;
90     int input_format;
91     int output_format;
92     int debug;
93     int write_using_libxml2;
94     int turbo_format;
95     enum yaz_collection_state enable_collection;
96     yaz_iconv_t iconv_cd;
97     char subfield_str[8];
98     char endline_str[8];
99     char *leader_spec;
100     struct yaz_marc_node *nodes;
101     struct yaz_marc_node **nodes_pp;
102     struct yaz_marc_subfield **subfield_pp;
103 };
104
105 yaz_marc_t yaz_marc_create(void)
106 {
107     yaz_marc_t mt = (yaz_marc_t) xmalloc(sizeof(*mt));
108     mt->output_format = YAZ_MARC_LINE;
109     mt->debug = 0;
110     mt->write_using_libxml2 = 0;
111     mt->enable_collection = no_collection;
112     mt->m_wr = wrbuf_alloc();
113     mt->iconv_cd = 0;
114     mt->leader_spec = 0;
115     strcpy(mt->subfield_str, " $");
116     strcpy(mt->endline_str, "\n");
117
118     mt->nmem = nmem_create();
119     yaz_marc_reset(mt);
120     return mt;
121 }
122
123 void yaz_marc_destroy(yaz_marc_t mt)
124 {
125     if (!mt)
126         return ;
127     nmem_destroy(mt->nmem);
128     wrbuf_destroy(mt->m_wr);
129     xfree(mt->leader_spec);
130     xfree(mt);
131 }
132
133 NMEM yaz_marc_get_nmem(yaz_marc_t mt)
134 {
135     return mt->nmem;
136 }
137
138 static void marc_iconv_reset(yaz_marc_t mt, WRBUF wr)
139 {
140     wrbuf_iconv_reset(wr, mt->iconv_cd);
141 }
142
143 static int marc_exec_leader(const char *leader_spec, char *leader,
144                             size_t size);
145
146
147 static struct yaz_marc_node *yaz_marc_add_node(yaz_marc_t mt)
148 {
149     struct yaz_marc_node *n = (struct yaz_marc_node *)
150         nmem_malloc(mt->nmem, sizeof(*n));
151     n->next = 0;
152     *mt->nodes_pp = n;
153     mt->nodes_pp = &n->next;
154     return n;
155 }
156
157 #if YAZ_HAVE_XML2
158 void yaz_marc_add_controlfield_xml(yaz_marc_t mt, const xmlNode *ptr_tag,
159                                    const xmlNode *ptr_data)
160 {
161     struct yaz_marc_node *n = yaz_marc_add_node(mt);
162     n->which = YAZ_MARC_CONTROLFIELD;
163     n->u.controlfield.tag = nmem_text_node_cdata(ptr_tag, mt->nmem);
164     n->u.controlfield.data = nmem_text_node_cdata(ptr_data, mt->nmem);
165 }
166 #endif
167
168
169 void yaz_marc_add_comment(yaz_marc_t mt, char *comment)
170 {
171     struct yaz_marc_node *n = yaz_marc_add_node(mt);
172     n->which = YAZ_MARC_COMMENT;
173     n->u.comment = nmem_strdup(mt->nmem, comment);
174 }
175
176 void yaz_marc_cprintf(yaz_marc_t mt, const char *fmt, ...)
177 {
178     va_list ap;
179     char buf[200];
180
181     va_start(ap, fmt);
182     yaz_vsnprintf(buf, sizeof(buf)-1, fmt, ap);
183     yaz_marc_add_comment(mt, buf);
184     va_end (ap);
185 }
186
187 int yaz_marc_get_debug(yaz_marc_t mt)
188 {
189     return mt->debug;
190 }
191
192 void yaz_marc_add_leader(yaz_marc_t mt, const char *leader, size_t leader_len)
193 {
194     struct yaz_marc_node *n = yaz_marc_add_node(mt);
195     n->which = YAZ_MARC_LEADER;
196     n->u.leader = nmem_strdupn(mt->nmem, leader, leader_len);
197     marc_exec_leader(mt->leader_spec, n->u.leader, leader_len);
198 }
199
200 void yaz_marc_add_controlfield(yaz_marc_t mt, const char *tag,
201                                const char *data, size_t data_len)
202 {
203     struct yaz_marc_node *n = yaz_marc_add_node(mt);
204     n->which = YAZ_MARC_CONTROLFIELD;
205     n->u.controlfield.tag = nmem_strdup(mt->nmem, tag);
206     n->u.controlfield.data = nmem_strdupn(mt->nmem, data, data_len);
207     if (mt->debug)
208     {
209         size_t i;
210         char msg[80];
211
212         sprintf(msg, "controlfield:");
213         for (i = 0; i < 16 && i < data_len; i++)
214             sprintf(msg + strlen(msg), " %02X", data[i] & 0xff);
215         if (i < data_len)
216             sprintf(msg + strlen(msg), " ..");
217         yaz_marc_add_comment(mt, msg);
218     }
219 }
220
221 void yaz_marc_add_datafield(yaz_marc_t mt, const char *tag,
222                             const char *indicator, size_t indicator_len)
223 {
224     struct yaz_marc_node *n = yaz_marc_add_node(mt);
225     n->which = YAZ_MARC_DATAFIELD;
226     n->u.datafield.tag = nmem_strdup(mt->nmem, tag);
227     n->u.datafield.indicator =
228         nmem_strdupn(mt->nmem, indicator, indicator_len);
229     n->u.datafield.subfields = 0;
230
231     /* make subfield_pp the current (last one) */
232     mt->subfield_pp = &n->u.datafield.subfields;
233 }
234
235 #if YAZ_HAVE_XML2
236 void yaz_marc_add_datafield_xml(yaz_marc_t mt, const xmlNode *ptr_tag,
237                                 const char *indicator, size_t indicator_len)
238 {
239     struct yaz_marc_node *n = yaz_marc_add_node(mt);
240     n->which = YAZ_MARC_DATAFIELD;
241     n->u.datafield.tag = nmem_text_node_cdata(ptr_tag, mt->nmem);
242     n->u.datafield.indicator =
243         nmem_strdupn(mt->nmem, indicator, indicator_len);
244     n->u.datafield.subfields = 0;
245
246     /* make subfield_pp the current (last one) */
247     mt->subfield_pp = &n->u.datafield.subfields;
248 }
249
250 struct yaz_marc_node* yaz_marc_add_datafield_turbo_xml(yaz_marc_t mt, char *tag_value)
251 {
252     struct yaz_marc_node *n = yaz_marc_add_node(mt);
253     n->which = YAZ_MARC_DATAFIELD;
254     n->u.datafield.tag = tag_value;
255     n->u.datafield.indicator = 0;
256     n->u.datafield.subfields = 0;
257
258     /* make subfield_pp the current (last one) */
259     mt->subfield_pp = &n->u.datafield.subfields;
260     return n;
261 }
262
263 void yaz_marc_datafield_set_indicators(struct yaz_marc_node *n, char *indicator)
264 {
265     n->u.datafield.indicator = indicator;
266 }
267
268 #endif
269
270 void yaz_marc_add_subfield(yaz_marc_t mt,
271                            const char *code_data, size_t code_data_len)
272 {
273     if (mt->debug)
274     {
275         size_t i;
276         char msg[80];
277
278         sprintf(msg, "subfield:");
279         for (i = 0; i < 16 && i < code_data_len; i++)
280             sprintf(msg + strlen(msg), " %02X", code_data[i] & 0xff);
281         if (i < code_data_len)
282             sprintf(msg + strlen(msg), " ..");
283         yaz_marc_add_comment(mt, msg);
284     }
285
286     if (mt->subfield_pp)
287     {
288         struct yaz_marc_subfield *n = (struct yaz_marc_subfield *)
289             nmem_malloc(mt->nmem, sizeof(*n));
290         n->code_data = nmem_strdupn(mt->nmem, code_data, code_data_len);
291         n->next = 0;
292         /* mark subfield_pp to point to this one, so we append here next */
293         *mt->subfield_pp = n;
294         mt->subfield_pp = &n->next;
295     }
296 }
297
298 void yaz_marc_set_leader(yaz_marc_t mt, const char *leader_c,
299                          int *indicator_length,
300                          int *identifier_length,
301                          int *base_address,
302                          int *length_data_entry,
303                          int *length_starting,
304                          int *length_implementation)
305 {
306     char leader[24];
307
308     memcpy(leader, leader_c, 24);
309
310     if (!atoi_n_check(leader+10, 1, indicator_length))
311     {
312         yaz_marc_cprintf(mt, 
313                          "Indicator length at offset 10 should hold a digit."
314                          " Assuming 2");
315         leader[10] = '2';
316         *indicator_length = 2;
317     }
318     if (!atoi_n_check(leader+11, 1, identifier_length))
319     {
320         yaz_marc_cprintf(mt, 
321                          "Identifier length at offset 11 should hold a digit."
322                          " Assuming 2");
323         leader[11] = '2';
324         *identifier_length = 2;
325     }
326     if (!atoi_n_check(leader+12, 5, base_address))
327     {
328         yaz_marc_cprintf(mt, 
329                          "Base address at offsets 12..16 should hold a number."
330                          " Assuming 0");
331         *base_address = 0;
332     }
333     if (!atoi_n_check(leader+20, 1, length_data_entry))
334     {
335         yaz_marc_cprintf(mt, 
336                          "Length data entry at offset 20 should hold a digit."
337                          " Assuming 4");
338         *length_data_entry = 4;
339         leader[20] = '4';
340     }
341     if (!atoi_n_check(leader+21, 1, length_starting))
342     {
343         yaz_marc_cprintf(mt,
344                          "Length starting at offset 21 should hold a digit."
345                          " Assuming 5");
346         *length_starting = 5;
347         leader[21] = '5';
348     }
349     if (!atoi_n_check(leader+22, 1, length_implementation))
350     {
351         yaz_marc_cprintf(mt, 
352                          "Length implementation at offset 22 should hold a digit."
353                          " Assuming 0");
354         *length_implementation = 0;
355         leader[22] = '0';
356     }
357
358     if (mt->debug)
359     {
360         yaz_marc_cprintf(mt, "Indicator length      %5d", *indicator_length);
361         yaz_marc_cprintf(mt, "Identifier length     %5d", *identifier_length);
362         yaz_marc_cprintf(mt, "Base address          %5d", *base_address);
363         yaz_marc_cprintf(mt, "Length data entry     %5d", *length_data_entry);
364         yaz_marc_cprintf(mt, "Length starting       %5d", *length_starting);
365         yaz_marc_cprintf(mt, "Length implementation %5d", *length_implementation);
366     }
367     yaz_marc_add_leader(mt, leader, 24);
368 }
369
370 void yaz_marc_subfield_str(yaz_marc_t mt, const char *s)
371 {
372     strncpy(mt->subfield_str, s, sizeof(mt->subfield_str)-1);
373     mt->subfield_str[sizeof(mt->subfield_str)-1] = '\0';
374 }
375
376 void yaz_marc_endline_str(yaz_marc_t mt, const char *s)
377 {
378     strncpy(mt->endline_str, s, sizeof(mt->endline_str)-1);
379     mt->endline_str[sizeof(mt->endline_str)-1] = '\0';
380 }
381
382 /* try to guess how many bytes the identifier really is! */
383 static size_t cdata_one_character(yaz_marc_t mt, const char *buf)
384 {
385     if (mt->iconv_cd)
386     {
387         size_t i;
388         for (i = 1; i<5; i++)
389         {
390             char outbuf[12];
391             size_t outbytesleft = sizeof(outbuf);
392             char *outp = outbuf;
393             const char *inp = buf;
394
395             size_t inbytesleft = i;
396             size_t r = yaz_iconv(mt->iconv_cd, (char**) &inp, &inbytesleft,
397                                  &outp, &outbytesleft);
398             if (r != (size_t) (-1))
399                 return i;  /* got a complete sequence */
400         }
401         return 1; /* giving up */
402     }
403     return 1; /* we don't know */
404 }
405                               
406 void yaz_marc_reset(yaz_marc_t mt)
407 {
408     nmem_reset(mt->nmem);
409     mt->nodes = 0;
410     mt->nodes_pp = &mt->nodes;
411     mt->subfield_pp = 0;
412 }
413
414 int yaz_marc_write_check(yaz_marc_t mt, WRBUF wr)
415 {
416     struct yaz_marc_node *n;
417     int identifier_length;
418     const char *leader = 0;
419
420     for (n = mt->nodes; n; n = n->next)
421         if (n->which == YAZ_MARC_LEADER)
422         {
423             leader = n->u.leader;
424             break;
425         }
426     
427     if (!leader)
428         return -1;
429     if (!atoi_n_check(leader+11, 1, &identifier_length))
430         return -1;
431
432     for (n = mt->nodes; n; n = n->next)
433     {
434         switch(n->which)
435         {
436         case YAZ_MARC_COMMENT:
437             wrbuf_iconv_write(wr, mt->iconv_cd, 
438                               n->u.comment, strlen(n->u.comment));
439             wrbuf_puts(wr, "\n");
440             break;
441         default:
442             break;
443         }
444     }
445     return 0;
446 }
447
448 static size_t get_subfield_len(yaz_marc_t mt, const char *data,
449                                int identifier_length)
450 {
451     /* if identifier length is 2 (most MARCs) or less (probably an error),
452        the code is a single character .. However we've
453        seen multibyte codes, so see how big it really is */
454     if (identifier_length > 2)
455         return identifier_length - 1;
456     else
457         return cdata_one_character(mt, data);
458 }
459
460 int yaz_marc_write_line(yaz_marc_t mt, WRBUF wr)
461 {
462     struct yaz_marc_node *n;
463     int identifier_length;
464     const char *leader = 0;
465
466     for (n = mt->nodes; n; n = n->next)
467         if (n->which == YAZ_MARC_LEADER)
468         {
469             leader = n->u.leader;
470             break;
471         }
472     
473     if (!leader)
474         return -1;
475     if (!atoi_n_check(leader+11, 1, &identifier_length))
476         return -1;
477
478     for (n = mt->nodes; n; n = n->next)
479     {
480         struct yaz_marc_subfield *s;
481         switch(n->which)
482         {
483         case YAZ_MARC_DATAFIELD:
484             wrbuf_printf(wr, "%s %s", n->u.datafield.tag,
485                          n->u.datafield.indicator);
486             for (s = n->u.datafield.subfields; s; s = s->next)
487             {
488                 size_t using_code_len = get_subfield_len(mt, s->code_data,
489                                                          identifier_length);
490                 
491                 wrbuf_puts (wr, mt->subfield_str); 
492                 wrbuf_iconv_write(wr, mt->iconv_cd, s->code_data, 
493                                   using_code_len);
494                 wrbuf_iconv_puts(wr, mt->iconv_cd, " ");
495                 wrbuf_iconv_puts(wr, mt->iconv_cd, 
496                                  s->code_data + using_code_len);
497                 marc_iconv_reset(mt, wr);
498             }
499             wrbuf_puts (wr, mt->endline_str);
500             break;
501         case YAZ_MARC_CONTROLFIELD:
502             wrbuf_printf(wr, "%s", n->u.controlfield.tag);
503             wrbuf_iconv_puts(wr, mt->iconv_cd, " ");
504             wrbuf_iconv_puts(wr, mt->iconv_cd, n->u.controlfield.data);
505             marc_iconv_reset(mt, wr);
506             wrbuf_puts (wr, mt->endline_str);
507             break;
508         case YAZ_MARC_COMMENT:
509             wrbuf_puts(wr, "(");
510             wrbuf_iconv_write(wr, mt->iconv_cd, 
511                               n->u.comment, strlen(n->u.comment));
512             marc_iconv_reset(mt, wr);
513             wrbuf_puts(wr, ")\n");
514             break;
515         case YAZ_MARC_LEADER:
516             wrbuf_printf(wr, "%s\n", n->u.leader);
517         }
518     }
519     wrbuf_puts(wr, "\n");
520     return 0;
521 }
522
523 int yaz_marc_write_trailer(yaz_marc_t mt, WRBUF wr)
524 {
525     if (mt->enable_collection == collection_second)
526     {
527         switch(mt->output_format)
528         {
529         case YAZ_MARC_MARCXML:
530             wrbuf_printf(wr, "</collection>\n");
531             break;
532         case YAZ_MARC_XCHANGE:
533             wrbuf_printf(wr, "</collection>\n");
534             break;
535         }
536     }
537     return 0;
538 }
539
540 void yaz_marc_enable_collection(yaz_marc_t mt)
541 {
542     mt->enable_collection = collection_first;
543 }
544
545 int yaz_marc_write_mode(yaz_marc_t mt, WRBUF wr)
546 {
547     switch(mt->output_format)
548     {
549     case YAZ_MARC_LINE:
550         return yaz_marc_write_line(mt, wr);
551     case YAZ_MARC_MARCXML:
552     case YAZ_MARC_TMARCXML:
553         return yaz_marc_write_marcxml(mt, wr);
554     case YAZ_MARC_XCHANGE:
555         return yaz_marc_write_marcxchange(mt, wr, 0, 0); /* no format, type */
556     case YAZ_MARC_ISO2709:
557         return yaz_marc_write_iso2709(mt, wr);
558     case YAZ_MARC_CHECK:
559         return yaz_marc_write_check(mt, wr);
560     }
561     return -1;
562 }
563
564 /** \brief common MARC XML/Xchange writer
565     \param mt handle
566     \param wr WRBUF output
567     \param ns XMLNS for the elements
568     \param format record format (e.g. "MARC21")
569     \param type record type (e.g. "Bibliographic")
570 */
571 static int yaz_marc_write_marcxml_ns1(yaz_marc_t mt, WRBUF wr,
572                                       const char *ns, 
573                                       const char *format,
574                                       const char *type)
575 {
576     struct yaz_marc_node *n;
577     int identifier_length;
578     const char *leader = 0;
579
580     for (n = mt->nodes; n; n = n->next)
581         if (n->which == YAZ_MARC_LEADER)
582         {
583             leader = n->u.leader;
584             break;
585         }
586     
587     if (!leader)
588         return -1;
589     if (!atoi_n_check(leader+11, 1, &identifier_length))
590         return -1;
591     
592     if (mt->enable_collection != no_collection)
593     {
594         if (mt->enable_collection == collection_first)
595             wrbuf_printf(wr, "<collection xmlns=\"%s\">\n", ns);
596         mt->enable_collection = collection_second;
597         wrbuf_printf(wr, "<record");
598     }
599     else
600     {
601         wrbuf_printf(wr, "<record xmlns=\"%s\"", ns);
602     }
603     if (format)
604         wrbuf_printf(wr, " format=\"%.80s\"", format);
605     if (type)
606         wrbuf_printf(wr, " type=\"%.80s\"", type);
607     wrbuf_printf(wr, ">\n");
608     for (n = mt->nodes; n; n = n->next)
609     {
610         struct yaz_marc_subfield *s;
611
612         switch(n->which)
613         {
614         case YAZ_MARC_DATAFIELD:
615             wrbuf_printf(wr, "  <datafield tag=\"");
616             wrbuf_iconv_write_cdata(wr, mt->iconv_cd, n->u.datafield.tag,
617                                     strlen(n->u.datafield.tag));
618             wrbuf_printf(wr, "\"");
619             if (n->u.datafield.indicator)
620             {
621                 int i;
622                 for (i = 0; n->u.datafield.indicator[i]; i++)
623                 {
624                     wrbuf_printf(wr, " ind%d=\"", i+1);
625                     wrbuf_iconv_write_cdata(wr, mt->iconv_cd,
626                                           n->u.datafield.indicator+i, 1);
627                     wrbuf_iconv_puts(wr, mt->iconv_cd, "\"");
628                 }
629             }
630             wrbuf_printf(wr, ">\n");
631             for (s = n->u.datafield.subfields; s; s = s->next)
632             {
633                 size_t using_code_len = get_subfield_len(mt, s->code_data,
634                                                          identifier_length);
635                 wrbuf_iconv_puts(wr, mt->iconv_cd, "    <subfield code=\"");
636                 wrbuf_iconv_write_cdata(wr, mt->iconv_cd,
637                                         s->code_data, using_code_len);
638                 wrbuf_iconv_puts(wr, mt->iconv_cd, "\">");
639                 wrbuf_iconv_write_cdata(wr, mt->iconv_cd,
640                                         s->code_data + using_code_len,
641                                         strlen(s->code_data + using_code_len));
642                 marc_iconv_reset(mt, wr);
643                 wrbuf_iconv_puts(wr, mt->iconv_cd, "</subfield>");
644                 wrbuf_puts(wr, "\n");
645             }
646             wrbuf_printf(wr, "  </datafield>\n");
647             break;
648         case YAZ_MARC_CONTROLFIELD:
649             wrbuf_printf(wr, "  <controlfield tag=\"");
650             wrbuf_iconv_write_cdata(wr, mt->iconv_cd, n->u.controlfield.tag,
651                                     strlen(n->u.controlfield.tag));
652             wrbuf_iconv_puts(wr, mt->iconv_cd, "\">");
653             wrbuf_iconv_write_cdata(wr, mt->iconv_cd,
654                                     n->u.controlfield.data,
655                                     strlen(n->u.controlfield.data));
656
657             marc_iconv_reset(mt, wr);
658             wrbuf_iconv_puts(wr, mt->iconv_cd, "</controlfield>");
659             wrbuf_puts(wr, "\n");
660             break;
661         case YAZ_MARC_COMMENT:
662             wrbuf_printf(wr, "<!-- ");
663             wrbuf_puts(wr, n->u.comment);
664             wrbuf_printf(wr, " -->\n");
665             break;
666         case YAZ_MARC_LEADER:
667             wrbuf_printf(wr, "  <leader>");
668             wrbuf_iconv_write_cdata(wr, 
669                                     0 /* no charset conversion for leader */,
670                                     n->u.leader, strlen(n->u.leader));
671             wrbuf_printf(wr, "</leader>\n");
672         }
673     }
674     wrbuf_puts(wr, "</record>\n");
675     return 0;
676 }
677
678 static int yaz_marc_write_marcxml_ns(yaz_marc_t mt, WRBUF wr,
679                                      const char *ns, 
680                                      const char *format,
681                                      const char *type)
682 {
683     if (mt->write_using_libxml2)
684     {
685 #if YAZ_HAVE_XML2
686         int ret;
687         xmlNode *root_ptr;
688
689         if (!mt->turbo_format)
690                 ret = yaz_marc_write_xml(mt, &root_ptr, ns, format, type);
691         else
692                 ret = yaz_marc_write_turbo_xml(mt, &root_ptr, ns, format, type);
693         if (ret == 0)
694         {
695             xmlChar *buf_out;
696             xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
697             int len_out;
698
699             xmlDocSetRootElement(doc, root_ptr);
700             xmlDocDumpMemory(doc, &buf_out, &len_out);
701
702             wrbuf_write(wr, (const char *) buf_out, len_out);
703             wrbuf_puts(wr, "");
704             xmlFree(buf_out);
705             xmlFreeDoc(doc);
706         }
707         return ret;
708 #else
709         return -1;
710 #endif
711     }
712     else
713         return yaz_marc_write_marcxml_ns1(mt, wr, ns, format, type);
714 }
715
716 int yaz_marc_write_marcxml(yaz_marc_t mt, WRBUF wr)
717 {
718     /* set leader 09 to 'a' for UNICODE */
719     /* http://www.loc.gov/marc/bibliographic/ecbdldrd.html#mrcblea */
720     if (!mt->leader_spec)
721         yaz_marc_modify_leader(mt, 9, "a");
722     char *name_space = "http://www.loc.gov/MARC21/slim";
723     if (mt->output_format == YAZ_MARC_TMARCXML)
724         name_space = "http://www.indexdata.com/MARC21/turboxml";
725     return yaz_marc_write_marcxml_ns(mt, wr, name_space,
726                                      0, 0);
727 }
728
729 int yaz_marc_write_marcxchange(yaz_marc_t mt, WRBUF wr,
730                                const char *format,
731                                const char *type)
732 {
733     return yaz_marc_write_marcxml_ns(mt, wr,
734                                      "info:lc/xmlns/marcxchange-v1",
735                                      0, 0);
736 }
737
738 #if YAZ_HAVE_XML2
739
740 void add_marc_datafield_turbo_xml(yaz_marc_t mt, struct yaz_marc_node *n, xmlNode *record_ptr, xmlNsPtr ns_record, WRBUF wr_cdata, int identifier_length)
741 {
742     xmlNode *ptr;
743     struct yaz_marc_subfield *s;
744     int turbo = mt->turbo_format;
745     if (!turbo) {
746         ptr = xmlNewChild(record_ptr, ns_record, BAD_CAST "datafield", 0);
747         xmlNewProp(ptr, BAD_CAST "tag", BAD_CAST n->u.datafield.tag);
748     }
749     else {
750         //TODO consider if safe
751         char field[10];
752         field[0] = 'd';
753         strncpy(field + 1, n->u.datafield.tag, 3);
754         ptr = xmlNewChild(record_ptr, ns_record, BAD_CAST field, 0);
755     }
756     if (n->u.datafield.indicator)
757     {
758         int i;
759         for (i = 0; n->u.datafield.indicator[i]; i++)
760         {
761             char ind_str[6];
762             char ind_val[2];
763             
764             ind_val[0] = n->u.datafield.indicator[i];
765             ind_val[1] = '\0';
766             if (!turbo) {
767                 sprintf(ind_str, "ind%d", i+1);
768                 xmlNewProp(ptr, BAD_CAST ind_str, BAD_CAST ind_val);
769             }
770             else {
771                 sprintf(ind_str, "i%d", i+1);
772                 xmlNewTextChild(ptr, ns_record, BAD_CAST ind_str, BAD_CAST ind_val);
773             }
774         }
775     }
776         WRBUF subfield_name = wrbuf_alloc();
777     for (s = n->u.datafield.subfields; s; s = s->next)
778     {
779         xmlNode *ptr_subfield;
780         size_t using_code_len = get_subfield_len(mt, s->code_data,
781                                                  identifier_length);
782         wrbuf_rewind(wr_cdata);
783         wrbuf_iconv_puts(wr_cdata, mt->iconv_cd, s->code_data + using_code_len);
784         marc_iconv_reset(mt, wr_cdata);
785         
786         if (!turbo) {
787                 ptr_subfield = xmlNewTextChild(
788                                 ptr, ns_record,
789                                 BAD_CAST "subfield",  BAD_CAST wrbuf_cstr(wr_cdata));
790                 wrbuf_rewind(wr_cdata);
791                 wrbuf_iconv_write(wr_cdata, mt->iconv_cd,s->code_data, using_code_len);
792                 xmlNewProp(ptr_subfield, BAD_CAST "code",
793                                 BAD_CAST wrbuf_cstr(wr_cdata));
794         }
795         else { // Turbo format
796                 wrbuf_rewind(subfield_name);
797                 wrbuf_puts(subfield_name, "s");
798                 // TODO Map special codes to something possible for XML ELEMENT names
799                 if ((s->code_data[0] >= '0' && s->code_data[0] <= '9') ||
800                     (s->code_data[0] >= 'a' && s->code_data[0] <= 'z') ||
801                                 (s->code_data[0] >= 'A' && s->code_data[0] <= 'Z'))
802                 {
803                         wrbuf_iconv_write(subfield_name, mt->iconv_cd,s->code_data, using_code_len);
804                 }
805                 else {
806                                 char buffer[2*using_code_len + 1];
807                                 int index;
808                                 for (index = 0; index < using_code_len; index++) {
809                                         sprintf(buffer + 2*index, "%02X", (unsigned char) s->code_data[index] & 0xFF);
810                                 };
811                                 buffer[2*(index+1)] = 0;
812                                 wrbuf_puts(subfield_name, "-");
813                                 wrbuf_puts(subfield_name, buffer);
814                         yaz_log(YLOG_WARN, "Using numeric value in element name: %s", buffer);
815                 }
816                 ptr_subfield = xmlNewTextChild(ptr, ns_record,
817                                 BAD_CAST wrbuf_cstr(subfield_name),
818                                 BAD_CAST wrbuf_cstr(wr_cdata));
819         }
820     }
821         wrbuf_destroy(subfield_name);
822 }
823
824 int yaz_marc_write_turbo_xml(yaz_marc_t mt, xmlNode **root_ptr,
825                        const char *ns, 
826                        const char *format,
827                        const char *type)
828 {
829     struct yaz_marc_node *n;
830     int identifier_length;
831     const char *leader = 0;
832     xmlNode *record_ptr;
833     xmlNsPtr ns_record;
834     WRBUF wr_cdata = 0;
835     int turbo = mt->turbo_format;
836     for (n = mt->nodes; n; n = n->next)
837         if (n->which == YAZ_MARC_LEADER)
838         {
839             leader = n->u.leader;
840             break;
841         }
842     
843     if (!leader)
844         return -1;
845     if (!atoi_n_check(leader+11, 1, &identifier_length))
846         return -1;
847
848     wr_cdata = wrbuf_alloc();
849
850     record_ptr = xmlNewNode(0, BAD_CAST "record");
851     *root_ptr = record_ptr;
852
853     ns_record = xmlNewNs(record_ptr, BAD_CAST ns, 0);
854     xmlSetNs(record_ptr, ns_record);
855
856     if (format)
857         xmlNewProp(record_ptr, BAD_CAST "format", BAD_CAST format);
858     if (type)
859         xmlNewProp(record_ptr, BAD_CAST "type", BAD_CAST type);
860     for (n = mt->nodes; n; n = n->next)
861     {
862         struct yaz_marc_subfield *s;
863         xmlNode *ptr;
864
865         switch(n->which)
866         {
867         case YAZ_MARC_DATAFIELD:
868                 add_marc_datafield_turbo_xml(mt, n, record_ptr, ns_record, wr_cdata, identifier_length);
869             break;
870         case YAZ_MARC_CONTROLFIELD:
871             wrbuf_rewind(wr_cdata);
872             wrbuf_iconv_puts(wr_cdata, mt->iconv_cd, n->u.controlfield.data);
873             marc_iconv_reset(mt, wr_cdata);
874             
875             if (!turbo) {
876                                 ptr = xmlNewTextChild(record_ptr, ns_record,
877                                                                           BAD_CAST "controlfield",
878                                                                           BAD_CAST wrbuf_cstr(wr_cdata));
879                                 xmlNewProp(ptr, BAD_CAST "tag", BAD_CAST n->u.controlfield.tag);
880             }
881             else {
882                 // TODO required iconv?
883                 char field[10];
884                                 field[0] = 'c';
885                 strncpy(field + 1, n->u.controlfield.tag, 3);
886                 ptr = xmlNewTextChild(record_ptr, ns_record,
887                                                                           BAD_CAST field,
888                                                                           BAD_CAST wrbuf_cstr(wr_cdata));
889             }
890
891             break;
892         case YAZ_MARC_COMMENT:
893             ptr = xmlNewComment(BAD_CAST n->u.comment);
894             xmlAddChild(record_ptr, ptr);
895             break;
896         case YAZ_MARC_LEADER:
897                         {
898                                 char *field = "leader";
899                                 if (turbo)
900                                         field = "l";
901                                 xmlNewTextChild(record_ptr, ns_record, BAD_CAST field,
902                                                                 BAD_CAST n->u.leader);
903                         }
904             break;
905         }
906     }
907     wrbuf_destroy(wr_cdata);
908     return 0;
909 }
910
911
912 int yaz_marc_write_xml(yaz_marc_t mt, xmlNode **root_ptr,
913                        const char *ns, 
914                        const char *format,
915                        const char *type)
916 {
917     struct yaz_marc_node *n;
918     int identifier_length;
919     const char *leader = 0;
920     xmlNode *record_ptr;
921     xmlNsPtr ns_record;
922     WRBUF wr_cdata = 0;
923
924     for (n = mt->nodes; n; n = n->next)
925         if (n->which == YAZ_MARC_LEADER)
926         {
927             leader = n->u.leader;
928             break;
929         }
930     
931     if (!leader)
932         return -1;
933     if (!atoi_n_check(leader+11, 1, &identifier_length))
934         return -1;
935
936     wr_cdata = wrbuf_alloc();
937
938     record_ptr = xmlNewNode(0, BAD_CAST "record");
939     *root_ptr = record_ptr;
940
941     ns_record = xmlNewNs(record_ptr, BAD_CAST ns, 0);
942     xmlSetNs(record_ptr, ns_record);
943
944     if (format)
945         xmlNewProp(record_ptr, BAD_CAST "format", BAD_CAST format);
946     if (type)
947         xmlNewProp(record_ptr, BAD_CAST "type", BAD_CAST type);
948     for (n = mt->nodes; n; n = n->next)
949     {
950         struct yaz_marc_subfield *s;
951         xmlNode *ptr;
952
953         switch(n->which)
954         {
955         case YAZ_MARC_DATAFIELD:
956             ptr = xmlNewChild(record_ptr, ns_record, BAD_CAST "datafield", 0);
957             xmlNewProp(ptr, BAD_CAST "tag", BAD_CAST n->u.datafield.tag);
958             if (n->u.datafield.indicator)
959             {
960                 int i;
961                 for (i = 0; n->u.datafield.indicator[i]; i++)
962                 {
963                     char ind_str[6];
964                     char ind_val[2];
965
966                     sprintf(ind_str, "ind%d", i+1);
967                     ind_val[0] = n->u.datafield.indicator[i];
968                     ind_val[1] = '\0';
969                     xmlNewProp(ptr, BAD_CAST ind_str, BAD_CAST ind_val);
970                 }
971             }
972             for (s = n->u.datafield.subfields; s; s = s->next)
973             {
974                 xmlNode *ptr_subfield;
975                 size_t using_code_len = get_subfield_len(mt, s->code_data,
976                                                          identifier_length);
977                 wrbuf_rewind(wr_cdata);
978                 wrbuf_iconv_puts(wr_cdata, mt->iconv_cd,
979                                  s->code_data + using_code_len);
980                 marc_iconv_reset(mt, wr_cdata);
981                 ptr_subfield = xmlNewTextChild(
982                     ptr, ns_record,
983                     BAD_CAST "subfield",  BAD_CAST wrbuf_cstr(wr_cdata));
984
985                 wrbuf_rewind(wr_cdata);
986                 wrbuf_iconv_write(wr_cdata, mt->iconv_cd,
987                                   s->code_data, using_code_len);
988                 xmlNewProp(ptr_subfield, BAD_CAST "code",
989                            BAD_CAST wrbuf_cstr(wr_cdata));
990             }
991             break;
992         case YAZ_MARC_CONTROLFIELD:
993             wrbuf_rewind(wr_cdata);
994             wrbuf_iconv_puts(wr_cdata, mt->iconv_cd, n->u.controlfield.data);
995             marc_iconv_reset(mt, wr_cdata);
996             
997             ptr = xmlNewTextChild(record_ptr, ns_record,
998                                   BAD_CAST "controlfield",
999                                   BAD_CAST wrbuf_cstr(wr_cdata));
1000             
1001             xmlNewProp(ptr, BAD_CAST "tag", BAD_CAST n->u.controlfield.tag);
1002             break;
1003         case YAZ_MARC_COMMENT:
1004             ptr = xmlNewComment(BAD_CAST n->u.comment);
1005             xmlAddChild(record_ptr, ptr);
1006             break;
1007         case YAZ_MARC_LEADER:
1008             xmlNewTextChild(record_ptr, ns_record, BAD_CAST "leader",
1009                             BAD_CAST n->u.leader);
1010             break;
1011         }
1012     }
1013     wrbuf_destroy(wr_cdata);
1014     return 0;
1015 }
1016
1017
1018
1019
1020 #endif
1021
1022 int yaz_marc_write_iso2709(yaz_marc_t mt, WRBUF wr)
1023 {
1024     struct yaz_marc_node *n;
1025     int indicator_length;
1026     int identifier_length;
1027     int length_data_entry;
1028     int length_starting;
1029     int length_implementation;
1030     int data_offset = 0;
1031     const char *leader = 0;
1032     WRBUF wr_dir, wr_head, wr_data_tmp;
1033     int base_address;
1034     
1035     for (n = mt->nodes; n; n = n->next)
1036         if (n->which == YAZ_MARC_LEADER)
1037             leader = n->u.leader;
1038     
1039     if (!leader)
1040         return -1;
1041     if (!atoi_n_check(leader+10, 1, &indicator_length))
1042         return -1;
1043     if (!atoi_n_check(leader+11, 1, &identifier_length))
1044         return -1;
1045     if (!atoi_n_check(leader+20, 1, &length_data_entry))
1046         return -1;
1047     if (!atoi_n_check(leader+21, 1, &length_starting))
1048         return -1;
1049     if (!atoi_n_check(leader+22, 1, &length_implementation))
1050         return -1;
1051
1052     wr_data_tmp = wrbuf_alloc();
1053     wr_dir = wrbuf_alloc();
1054     for (n = mt->nodes; n; n = n->next)
1055     {
1056         int data_length = 0;
1057         struct yaz_marc_subfield *s;
1058
1059         switch(n->which)
1060         {
1061         case YAZ_MARC_DATAFIELD:
1062             wrbuf_printf(wr_dir, "%.3s", n->u.datafield.tag);
1063             data_length += indicator_length;
1064             wrbuf_rewind(wr_data_tmp);
1065             for (s = n->u.datafield.subfields; s; s = s->next)
1066             {
1067                 /* write dummy IDFS + content */
1068                 wrbuf_iconv_putchar(wr_data_tmp, mt->iconv_cd, ' ');
1069                 wrbuf_iconv_puts(wr_data_tmp, mt->iconv_cd, s->code_data);
1070                 marc_iconv_reset(mt, wr_data_tmp);
1071             }
1072             /* write dummy FS (makes MARC-8 to become ASCII) */
1073             wrbuf_iconv_putchar(wr_data_tmp, mt->iconv_cd, ' ');
1074             marc_iconv_reset(mt, wr_data_tmp);
1075             data_length += wrbuf_len(wr_data_tmp);
1076             break;
1077         case YAZ_MARC_CONTROLFIELD:
1078             wrbuf_printf(wr_dir, "%.3s", n->u.controlfield.tag);
1079
1080             wrbuf_rewind(wr_data_tmp);
1081             wrbuf_iconv_puts(wr_data_tmp, mt->iconv_cd, 
1082                              n->u.controlfield.data);
1083             marc_iconv_reset(mt, wr_data_tmp);
1084             wrbuf_iconv_putchar(wr_data_tmp, mt->iconv_cd, ' ');/* field sep */
1085             marc_iconv_reset(mt, wr_data_tmp);
1086             data_length += wrbuf_len(wr_data_tmp);
1087             break;
1088         case YAZ_MARC_COMMENT:
1089             break;
1090         case YAZ_MARC_LEADER:
1091             break;
1092         }
1093         if (data_length)
1094         {
1095             wrbuf_printf(wr_dir, "%0*d", length_data_entry, data_length);
1096             wrbuf_printf(wr_dir, "%0*d", length_starting, data_offset);
1097             data_offset += data_length;
1098         }
1099     }
1100     /* mark end of directory */
1101     wrbuf_putc(wr_dir, ISO2709_FS);
1102
1103     /* base address of data (comes after leader+directory) */
1104     base_address = 24 + wrbuf_len(wr_dir);
1105
1106     wr_head = wrbuf_alloc();
1107
1108     /* write record length */
1109     wrbuf_printf(wr_head, "%05d", base_address + data_offset + 1);
1110     /* from "original" leader */
1111     wrbuf_write(wr_head, leader+5, 7);
1112     /* base address of data */
1113     wrbuf_printf(wr_head, "%05d", base_address);
1114     /* from "original" leader */
1115     wrbuf_write(wr_head, leader+17, 7);
1116     
1117     wrbuf_write(wr, wrbuf_buf(wr_head), 24);
1118     wrbuf_write(wr, wrbuf_buf(wr_dir), wrbuf_len(wr_dir));
1119     wrbuf_destroy(wr_head);
1120     wrbuf_destroy(wr_dir);
1121     wrbuf_destroy(wr_data_tmp);
1122
1123     for (n = mt->nodes; n; n = n->next)
1124     {
1125         struct yaz_marc_subfield *s;
1126
1127         switch(n->which)
1128         {
1129         case YAZ_MARC_DATAFIELD:
1130             wrbuf_printf(wr, "%.*s", indicator_length,
1131                          n->u.datafield.indicator);
1132             for (s = n->u.datafield.subfields; s; s = s->next)
1133             {
1134                 wrbuf_putc(wr, ISO2709_IDFS);
1135                 wrbuf_iconv_puts(wr, mt->iconv_cd, s->code_data);
1136                 marc_iconv_reset(mt, wr);
1137             }
1138             wrbuf_putc(wr, ISO2709_FS);
1139             break;
1140         case YAZ_MARC_CONTROLFIELD:
1141             wrbuf_iconv_puts(wr, mt->iconv_cd, n->u.controlfield.data);
1142             marc_iconv_reset(mt, wr);
1143             wrbuf_putc(wr, ISO2709_FS);
1144             break;
1145         case YAZ_MARC_COMMENT:
1146             break;
1147         case YAZ_MARC_LEADER:
1148             break;
1149         }
1150     }
1151     wrbuf_printf(wr, "%c", ISO2709_RS);
1152     return 0;
1153 }
1154
1155
1156 int yaz_marc_decode_wrbuf(yaz_marc_t mt, const char *buf, int bsize, WRBUF wr)
1157 {
1158     int s, r = yaz_marc_read_iso2709(mt, buf, bsize);
1159     if (r <= 0)
1160         return r;
1161     s = yaz_marc_write_mode(mt, wr); /* returns 0 for OK, -1 otherwise */
1162     if (s != 0)
1163         return -1; /* error */
1164     return r; /* OK, return length > 0 */
1165 }
1166
1167 int yaz_marc_decode_buf (yaz_marc_t mt, const char *buf, int bsize,
1168                          const char **result, size_t *rsize)
1169 {
1170     int r;
1171
1172     wrbuf_rewind(mt->m_wr);
1173     r = yaz_marc_decode_wrbuf(mt, buf, bsize, mt->m_wr);
1174     if (result)
1175         *result = wrbuf_cstr(mt->m_wr);
1176     if (rsize)
1177         *rsize = wrbuf_len(mt->m_wr);
1178     return r;
1179 }
1180
1181 void yaz_marc_set_read_format(yaz_marc_t mt, int format)
1182 {
1183     if (mt)
1184         mt->input_format = format;
1185 }
1186
1187 int yaz_marc_get_read_format(yaz_marc_t mt)
1188 {
1189     if (mt)
1190         return mt->input_format;
1191     return -1;
1192 }
1193
1194
1195 void yaz_marc_set_write_format(yaz_marc_t mt, int format)
1196 {
1197     if (mt)
1198         mt->output_format = format;
1199 }
1200
1201 int yaz_marc_get_write_format(yaz_marc_t mt)
1202 {
1203     if (mt)
1204         return mt->output_format;
1205     return -1;
1206 }
1207
1208
1209 /**
1210  * Deprecated, use yaz_marc_set_write_format
1211  */
1212 void yaz_marc_xml(yaz_marc_t mt, int xmlmode)
1213 {
1214     if (mt)
1215         mt->output_format = xmlmode;
1216 }
1217
1218
1219
1220 void yaz_marc_debug(yaz_marc_t mt, int level)
1221 {
1222     if (mt)
1223         mt->debug = level;
1224 }
1225
1226 void yaz_marc_iconv(yaz_marc_t mt, yaz_iconv_t cd)
1227 {
1228     mt->iconv_cd = cd;
1229 }
1230
1231 yaz_iconv_t yaz_marc_get_iconv(yaz_marc_t mt)
1232 {
1233     return mt->iconv_cd;
1234 }
1235
1236 void yaz_marc_modify_leader(yaz_marc_t mt, size_t off, const char *str)
1237 {
1238     struct yaz_marc_node *n;
1239     char *leader = 0;
1240     for (n = mt->nodes; n; n = n->next)
1241         if (n->which == YAZ_MARC_LEADER)
1242         {
1243             leader = n->u.leader;
1244             memcpy(leader+off, str, strlen(str));
1245             break;
1246         }
1247 }
1248
1249 int yaz_marc_leader_spec(yaz_marc_t mt, const char *leader_spec)
1250 {
1251     xfree(mt->leader_spec);
1252     mt->leader_spec = 0;
1253     if (leader_spec)
1254     {
1255         char dummy_leader[24];
1256         if (marc_exec_leader(leader_spec, dummy_leader, 24))
1257             return -1;
1258         mt->leader_spec = xstrdup(leader_spec);
1259     }
1260     return 0;
1261 }
1262
1263 static int marc_exec_leader(const char *leader_spec, char *leader, size_t size)
1264 {
1265     const char *cp = leader_spec;
1266     while (cp)
1267     {
1268         char val[21];
1269         int pos;
1270         int no_read = 0, no = 0;
1271
1272         no = sscanf(cp, "%d=%20[^,]%n", &pos, val, &no_read);
1273         if (no < 2 || no_read < 3)
1274             return -1;
1275         if (pos < 0 || (size_t) pos >= size)
1276             return -1;
1277
1278         if (*val == '\'')
1279         {
1280             const char *vp = strchr(val+1, '\'');
1281             size_t len;
1282             
1283             if (!vp)
1284                 return -1;
1285             len = vp-val-1;
1286             if (len + pos > size)
1287                 return -1;
1288             memcpy(leader + pos, val+1, len);
1289         }
1290         else if (*val >= '0' && *val <= '9')
1291         {
1292             int ch = atoi(val);
1293             leader[pos] = ch;
1294         }
1295         else
1296             return -1;
1297         cp += no_read;
1298         if (*cp != ',')
1299             break;
1300
1301         cp++;
1302     }
1303     return 0;
1304 }
1305
1306 int yaz_marc_decode_formatstr(const char *arg)
1307 {
1308     int mode = -1; 
1309     if (!strcmp(arg, "marc"))
1310         mode = YAZ_MARC_ISO2709;
1311     if (!strcmp(arg, "marcxml"))
1312         mode = YAZ_MARC_MARCXML;
1313     if (!strcmp(arg, "tmarcxml"))
1314         mode = YAZ_MARC_TMARCXML;
1315     if (!strcmp(arg, "marcxchange"))
1316         mode = YAZ_MARC_XCHANGE;
1317     if (!strcmp(arg, "line"))
1318         mode = YAZ_MARC_LINE;
1319     return mode;
1320 }
1321
1322 void yaz_marc_write_using_libxml2(yaz_marc_t mt, int enable)
1323 {
1324     mt->write_using_libxml2 = enable;
1325 }
1326
1327 void yaz_marc_write_turbo_format(yaz_marc_t mt, int enable)
1328 {
1329     mt->turbo_format = enable;
1330 }
1331
1332 int yaz_marc_is_turbo_format(yaz_marc_t mt)
1333 {
1334     return mt->turbo_format;
1335 }
1336
1337
1338 /*
1339  * Local variables:
1340  * c-basic-offset: 4
1341  * c-file-style: "Stroustrup"
1342  * indent-tabs-mode: nil
1343  * End:
1344  * vim: shiftwidth=4 tabstop=8 expandtab
1345  */
1346