1 /*
2 * modsout.c
3 *
4 * Copyright (c) Chris Putnam 2003-2021
5 *
6 * Source code released under the GPL version 2
7 *
8 */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdarg.h>
12 #include <string.h>
13 #include "is_ws.h"
14 #include "str.h"
15 #include "charsets.h"
16 #include "str_conv.h"
17 #include "fields.h"
18 #include "iso639_2.h"
19 #include "utf8.h"
20 #include "modstypes.h"
21 #include "bu_auth.h"
22 #include "marc_auth.h"
23 #include "bibformats.h"
24
25 /*****************************************************
26 PUBLIC: int modsout_initparams()
27 *****************************************************/
28
29 static void modsout_writeheader( FILE *outptr, param *p );
30 static void modsout_writefooter( FILE *outptr );
31 static int modsout_write( fields *info, FILE *outptr, param *p, unsigned long numrefs );
32
33 int
modsout_initparams(param * pm,const char * progname)34 modsout_initparams( param *pm, const char *progname )
35 {
36 pm->writeformat = BIBL_MODSOUT;
37 pm->format_opts = 0;
38 pm->charsetout = BIBL_CHARSET_UNICODE;
39 pm->charsetout_src = BIBL_SRC_DEFAULT;
40 pm->latexout = 0;
41 pm->utf8out = 1;
42 pm->utf8bom = 1;
43 pm->xmlout = BIBL_XMLOUT_TRUE;
44 pm->nosplittitle = 0;
45 pm->verbose = 0;
46 pm->addcount = 0;
47 pm->singlerefperfile = 0;
48
49 pm->headerf = modsout_writeheader;
50 pm->footerf = modsout_writefooter;
51 pm->assemblef = NULL;
52 pm->writef = modsout_write;
53
54 if ( !pm->progname ) {
55 if ( !progname ) pm->progname = NULL;
56 else {
57 pm->progname = strdup( progname );
58 if ( !pm->progname ) return BIBL_ERR_MEMERR;
59 }
60 }
61
62 return BIBL_OK;
63 }
64
65 /*****************************************************
66 PUBLIC: int modsout_write()
67 *****************************************************/
68
69 /* output_tag()
70 *
71 * mode & TAG_OPEN, "<tag>"
72 * mode & TAG_CLOSE, "</tag>"
73 * mode & TAG_OPENCLOSE, "<tag>data</tag>"
74 * mode & TAG_SELFCLOSE, "<tag/>"
75 *
76 * mode & TAG_NEWLINE, "<tag>\n"
77 *
78 */
79 #define TAG_NONEWLINE (0)
80 #define TAG_OPEN (1)
81 #define TAG_CLOSE (2)
82 #define TAG_OPENCLOSE (4)
83 #define TAG_SELFCLOSE (8)
84 #define TAG_NEWLINE (16)
85
86 static void
output_tag_core(FILE * outptr,int nindents,const char * tag,const char * data,unsigned char mode,va_list * attrs)87 output_tag_core( FILE *outptr, int nindents, const char *tag, const char *data, unsigned char mode, va_list *attrs )
88 {
89 const char *attr, *val;
90 int i;
91
92 for ( i=0; i<nindents; ++i ) fprintf( outptr, " " );
93
94 if ( mode & TAG_CLOSE ) fprintf( outptr, "</%s", tag );
95 else fprintf( outptr, "<%s", tag );
96
97 do {
98 attr = va_arg( *attrs, const char * );
99 if ( attr ) val = va_arg( *attrs, const char * );
100 if ( attr && val ) fprintf( outptr, " %s=\"%s\"", attr, val );
101 } while ( attr && val );
102
103 if ( mode & TAG_SELFCLOSE ) fprintf( outptr, "/>" );
104 else fprintf( outptr, ">" );
105
106 if ( mode & TAG_OPENCLOSE ) fprintf( outptr, "%s</%s>", data, tag );
107
108 if ( mode & TAG_NEWLINE ) fprintf( outptr, "\n" );
109 }
110
111 /* output_tag()
112 *
113 * output XML tag
114 *
115 * mode = [ TAG_OPEN | TAG_CLOSE | TAG_OPENCLOSE | TAG_SELFCLOSE | TAG_NEWLINE ]
116 *
117 * for mode TAG_OPENCLOSE, ensure that value is non-NULL, as string pointed to by value
118 * will be output in the tag
119 */
120 static void
output_tag(FILE * outptr,int nindents,const char * tag,const char * value,unsigned char mode,...)121 output_tag( FILE *outptr, int nindents, const char *tag, const char *value, unsigned char mode, ... )
122 {
123 va_list attrs;
124
125 va_start( attrs, mode );
126 output_tag_core( outptr, nindents, tag, value, mode, &attrs );
127 va_end( attrs );
128 }
129
130 /* output_fil()
131 *
132 * output XML tag, but lookup data in fields struct
133 *
134 * mode = [ TAG_OPEN | TAG_CLOSE | TAG_OPENCLOSE | TAG_SELFCLOSE | TAG_NEWLINE ]
135 */
136 static void
output_fil(FILE * outptr,int nindents,const char * tag,fields * f,int n,unsigned char mode,...)137 output_fil( FILE *outptr, int nindents, const char *tag, fields *f, int n, unsigned char mode, ... )
138 {
139 va_list attrs;
140 char *value;
141
142 if ( n==FIELDS_NOTFOUND ) return;
143
144 value = (char *) fields_value( f, n, FIELDS_CHRP );
145 va_start( attrs, mode );
146 output_tag_core( outptr, nindents, tag, value, mode, &attrs );
147 va_end( attrs );
148 }
149
150 /* output_vpl()
151 *
152 * output XML tag for each element in the vplist
153 *
154 * mode = [ TAG_OPEN | TAG_CLOSE | TAG_OPENCLOSE | TAG_SELFCLOSE | TAG_NEWLINE ]
155 */
156
157 static void
output_vpl(FILE * outptr,int nindents,const char * tag,vplist * values,unsigned char mode,...)158 output_vpl( FILE *outptr, int nindents, const char *tag, vplist *values, unsigned char mode, ... )
159 {
160 vplist_index i;
161 va_list attrs;
162 char *value;
163
164 /* need to reinitialize attrs for each loop */
165 for ( i=0; i<values->n; ++i ) {
166 va_start( attrs, mode );
167 value = vplist_get( values, i );
168 output_tag_core( outptr, nindents, tag, value, mode, &attrs );
169 va_end( attrs );
170 }
171 }
172
173 /*
174 * lvl2indent()
175 *
176 * Since levels can be negative (source items), need to do a simple
177 * calculation to determine the number of "tabs" to put before xml tag
178 */
179 static inline int
lvl2indent(int level)180 lvl2indent( int level )
181 {
182 if ( level < -1 ) return -level + 1;
183 else return level + 1;
184 }
185
186 /*
187 * incr_level()
188 *
189 * Increment positive levels (normal) or decrement negative levels (souce items)
190 */
191 static inline int
incr_level(int level,int amount)192 incr_level( int level, int amount )
193 {
194 if ( level > -1 ) return level + amount;
195 else return level - amount;
196 }
197
198 static void
output_title(FILE * outptr,fields * f,int level)199 output_title( FILE *outptr, fields *f, int level )
200 {
201 int ttl = fields_find( f, "TITLE", level );
202 int subttl = fields_find( f, "SUBTITLE", level );
203 int shrttl = fields_find( f, "SHORTTITLE", level );
204 int parttl = fields_find( f, "PARTTITLE", level );
205 int indent1, indent2;
206 char *val;
207
208
209 indent1 = lvl2indent( level );
210 indent2 = lvl2indent( incr_level( level, 1 ) );
211
212
213 /* output main title */
214 output_tag( outptr, indent1, "titleInfo", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
215 output_fil( outptr, indent2, "title", f, ttl, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
216 output_fil( outptr, indent2, "subTitle", f, subttl, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
217 output_fil( outptr, indent2, "partName", f, parttl, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
218
219 /* MODS output doesn't verify if we don't at least have a <title/> element */
220 if ( ttl==FIELDS_NOTFOUND && subttl==FIELDS_NOTFOUND && parttl==FIELDS_NOTFOUND )
221 output_tag( outptr, indent2, "title", NULL, TAG_SELFCLOSE | TAG_NEWLINE, NULL );
222
223 output_tag( outptr, indent1, "titleInfo", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
224
225
226 /* output shorttitle if it's different from normal title */
227 if ( shrttl==FIELDS_NOTFOUND ) return;
228
229 val = (char *) fields_value( f, shrttl, FIELDS_CHRP );
230 if ( ttl==FIELDS_NOTFOUND || subttl!=FIELDS_NOTFOUND || strcmp(fields_value(f,ttl,FIELDS_CHRP),val) ) {
231 output_tag( outptr, indent1, "titleInfo", NULL, TAG_OPEN | TAG_NEWLINE, "type", "abbreviated", NULL );
232 output_tag( outptr, indent2, "title", val, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
233 output_tag( outptr, indent1, "titleInfo", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
234 }
235 }
236
237 static void
output_name(FILE * outptr,char * p,int level)238 output_name( FILE *outptr, char *p, int level )
239 {
240 str family, part, suffix;
241 int n=0;
242
243 strs_init( &family, &part, &suffix, NULL );
244
245 while ( *p && *p!='|' ) str_addchar( &family, *p++ );
246 if ( *p=='|' ) p++;
247
248 while ( *p ) {
249 while ( *p && *p!='|' ) str_addchar( &part, *p++ );
250 /* truncate periods from "A. B. Jones" names */
251 if ( part.len ) {
252 if ( part.len==2 && part.data[1]=='.' ) {
253 part.len=1;
254 part.data[1]='\0';
255 }
256 if ( n==0 )
257 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_OPEN | TAG_NEWLINE, "type", "personal", NULL );
258 output_tag( outptr, lvl2indent(incr_level(level,1)), "namePart", part.data, TAG_OPENCLOSE | TAG_NEWLINE, "type", "given", NULL );
259 n++;
260 }
261 if ( *p=='|' ) {
262 p++;
263 if ( *p=='|' ) {
264 p++;
265 while ( *p && *p!='|' ) str_addchar( &suffix, *p++ );
266 }
267 str_empty( &part );
268 }
269 }
270
271 if ( family.len ) {
272 if ( n==0 )
273 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_OPEN | TAG_NEWLINE, "type", "personal", NULL );
274 output_tag( outptr, lvl2indent(incr_level(level,1)), "namePart", family.data, TAG_OPENCLOSE | TAG_NEWLINE, "type", "family", NULL );
275 n++;
276 }
277
278 if ( suffix.len ) {
279 if ( n==0 )
280 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_OPEN | TAG_NEWLINE, "type", "personal", NULL );
281 output_tag( outptr, lvl2indent(incr_level(level,1)), "namePart", suffix.data, TAG_OPENCLOSE | TAG_NEWLINE, "type", "suffix", NULL );
282 }
283
284 strs_free( &part, &family, &suffix, NULL );
285 }
286
287
288 /* MODS v 3.4
289 *
290 * <name [type="corporation"/type="conference"]>
291 * <namePart></namePart>
292 * <displayForm></displayForm>
293 * <affiliation></affiliation>
294 * <role>
295 * <roleTerm [authority="marcrealtor"] type="text"></roleTerm>
296 * </role>
297 * <description></description>
298 * </name>
299 */
300
301 #define NO_AUTHORITY (0)
302 #define MARC_AUTHORITY (1)
303
304 static void
output_names(FILE * outptr,fields * f,int level)305 output_names( FILE *outptr, fields *f, int level )
306 {
307 convert2 names[] = {
308 { "author", "AUTHOR", 0, MARC_AUTHORITY },
309 { "editor", "EDITOR", 0, MARC_AUTHORITY },
310 { "annotator", "ANNOTATOR", 0, MARC_AUTHORITY },
311 { "artist", "ARTIST", 0, MARC_AUTHORITY },
312 { "author", "2ND_AUTHOR", 0, MARC_AUTHORITY },
313 { "author", "3RD_AUTHOR", 0, MARC_AUTHORITY },
314 { "author", "SUB_AUTHOR", 0, MARC_AUTHORITY },
315 { "author", "COMMITTEE", 0, MARC_AUTHORITY },
316 { "author", "COURT", 0, MARC_AUTHORITY },
317 { "author", "LEGISLATIVEBODY", 0, MARC_AUTHORITY },
318 { "author of afterword, colophon, etc.", "AFTERAUTHOR", 0, MARC_AUTHORITY },
319 { "author of introduction, etc.", "INTROAUTHOR", 0, MARC_AUTHORITY },
320 { "cartographer", "CARTOGRAPHER", 0, MARC_AUTHORITY },
321 { "collaborator", "COLLABORATOR", 0, MARC_AUTHORITY },
322 { "commentator", "COMMENTATOR", 0, MARC_AUTHORITY },
323 { "compiler", "COMPILER", 0, MARC_AUTHORITY },
324 { "degree grantor", "DEGREEGRANTOR", 0, MARC_AUTHORITY },
325 { "director", "DIRECTOR", 0, MARC_AUTHORITY },
326 { "event", "EVENT", 0, NO_AUTHORITY },
327 { "inventor", "INVENTOR", 0, MARC_AUTHORITY },
328 { "organizer of meeting", "ORGANIZER", 0, MARC_AUTHORITY },
329 { "patent holder", "ASSIGNEE", 0, MARC_AUTHORITY },
330 { "performer", "PERFORMER", 0, MARC_AUTHORITY },
331 { "producer", "PRODUCER", 0, MARC_AUTHORITY },
332 { "addressee", "ADDRESSEE", 0, MARC_AUTHORITY },
333 { "redactor", "REDACTOR", 0, MARC_AUTHORITY },
334 { "reporter", "REPORTER", 0, MARC_AUTHORITY },
335 { "sponsor", "SPONSOR", 0, MARC_AUTHORITY },
336 { "translator", "TRANSLATOR", 0, MARC_AUTHORITY },
337 { "writer", "WRITER", 0, MARC_AUTHORITY },
338 };
339 int ntypes = sizeof( names ) / sizeof( names[0] );
340
341 int f_asis, f_corp, f_conf;
342 int i, n, nfields;
343 str role;
344
345 str_init( &role );
346 nfields = fields_num( f );
347 for ( n=0; n<ntypes; ++n ) {
348 for ( i=0; i<nfields; ++i ) {
349 if ( fields_level( f, i )!=level ) continue;
350 if ( fields_no_value( f, i ) ) continue;
351 f_asis = f_corp = f_conf = 0;
352 str_strcpy( &role, fields_tag( f, i, FIELDS_STRP ) );
353 if ( str_findreplace( &role, ":ASIS", "" )) f_asis=1;
354 if ( str_findreplace( &role, ":CORP", "" )) f_corp=1;
355 if ( str_findreplace( &role, ":CONF", "" )) f_conf=1;
356 if ( strcasecmp( role.data, names[n].internal ) )
357 continue;
358 if ( f_asis ) {
359 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
360 output_fil( outptr, lvl2indent(incr_level(level,1)), "namePart", f, i, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
361 } else if ( f_corp ) {
362 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_OPEN | TAG_NEWLINE, "type", "corporate", NULL );
363 output_fil( outptr, lvl2indent(incr_level(level,1)), "namePart", f, i, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
364 } else if ( f_conf ) {
365 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_OPEN | TAG_NEWLINE, "type", "conference", NULL );
366 output_fil( outptr, lvl2indent(incr_level(level,1)), "namePart", f, i, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
367 } else {
368 output_name(outptr, fields_value( f, i, FIELDS_CHRP ), level);
369 }
370 output_tag( outptr, lvl2indent(incr_level(level,1)), "role", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
371 if ( names[n].code & MARC_AUTHORITY )
372 output_tag( outptr, lvl2indent(incr_level(level,2)), "roleTerm", names[n].mods, TAG_OPENCLOSE | TAG_NEWLINE, "authority", "marcrelator", "type", "text", NULL );
373 else
374 output_tag( outptr, lvl2indent(incr_level(level,2)), "roleTerm", names[n].mods, TAG_OPENCLOSE | TAG_NEWLINE, "type", "text", NULL );
375 output_tag( outptr, lvl2indent(incr_level(level,1)), "role", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
376 output_tag( outptr, lvl2indent(level), "name", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
377 fields_set_used( f, i );
378 }
379 }
380 str_free( &role );
381 }
382
383 /* datepos[ NUM_DATE_TYPES ]
384 * use define to ensure that the array and loops don't get out of sync
385 * datepos[0] -> DATE:YEAR/PARTDATE:YEAR
386 * datepos[1] -> DATE:MONTH/PARTDATE:MONTH
387 * datepos[2] -> DATE:DAY/PARTDATE:DAY
388 * datepos[3] -> DATE/PARTDATE
389 */
390 #define DATE_YEAR (0)
391 #define DATE_MONTH (1)
392 #define DATE_DAY (2)
393 #define DATE_ALL (3)
394 #define NUM_DATE_TYPES (4)
395
396 static int
find_datepos(fields * f,int level,unsigned char use_altnames,int datepos[NUM_DATE_TYPES])397 find_datepos( fields *f, int level, unsigned char use_altnames, int datepos[NUM_DATE_TYPES] )
398 {
399 char *src_names[] = { "DATE:YEAR", "DATE:MONTH", "DATE:DAY", "DATE" };
400 char *alt_names[] = { "PARTDATE:YEAR", "PARTDATE:MONTH", "PARTDATE:DAY", "PARTDATE" };
401 int found = 0;
402 int i;
403
404 for ( i=0; i<NUM_DATE_TYPES; ++i ) {
405 if ( !use_altnames )
406 datepos[i] = fields_find( f, src_names[i], level );
407 else
408 datepos[i] = fields_find( f, alt_names[i], level );
409 if ( datepos[i]!=FIELDS_NOTFOUND ) found = 1;
410 }
411
412 return found;
413 }
414
415 /* find_dateinfo()
416 *
417 * fill datepos[] array with position indexes to date information in fields *f
418 *
419 * when generating dates for LEVEL_MAIN, first look at level=LEVEL_MAIN, but if that
420 * fails, use LEVEL_ANY (-1)
421 *
422 * returns 1 if date information found, 0 otherwise
423 */
424 static int
find_dateinfo(fields * f,int level,int datepos[NUM_DATE_TYPES])425 find_dateinfo( fields *f, int level, int datepos[ NUM_DATE_TYPES ] )
426 {
427 int found;
428
429 /* default to finding date information for the current level */
430 found = find_datepos( f, level, 0, datepos );
431
432 /* for LEVEL_MAIN, do whatever it takes to find a date */
433 if ( !found && level == LEVEL_MAIN ) {
434 found = find_datepos( f, -1, 0, datepos );
435 }
436 if ( !found && level == LEVEL_MAIN ) {
437 found = find_datepos( f, -1, 1, datepos );
438 }
439
440 return found;
441 }
442
443 static void
output_datepieces(fields * f,FILE * outptr,int pos[NUM_DATE_TYPES])444 output_datepieces( fields *f, FILE *outptr, int pos[ NUM_DATE_TYPES ] )
445 {
446 str *s;
447 int i;
448
449 for ( i=0; i<3 && pos[i]!=-1; ++i ) {
450 if ( i>0 ) fprintf( outptr, "-" );
451 /* zero pad month or days written as "1", "2", "3" ... */
452 if ( i==DATE_MONTH || i==DATE_DAY ) {
453 s = fields_value( f, pos[i], FIELDS_STRP_NOUSE );
454 if ( s->len==1 ) {
455 fprintf( outptr, "0" );
456 }
457 }
458 fprintf( outptr, "%s", (char *) fields_value( f, pos[i], FIELDS_CHRP ) );
459 }
460 }
461
462 /*
463 * <dateIssued>YYYY-MM-DD</dateIssued>
464 * or
465 * <dateIssued>DateAll</dateIssued>
466 */
467 static void
output_dateissued(fields * f,FILE * outptr,int level,int pos[NUM_DATE_TYPES])468 output_dateissued( fields *f, FILE *outptr, int level, int pos[ NUM_DATE_TYPES ] )
469 {
470 output_tag( outptr, lvl2indent(incr_level(level,1)), "dateIssued", NULL, TAG_OPEN, NULL );
471 if ( pos[ DATE_YEAR ]!=-1 || pos[ DATE_MONTH ]!=-1 || pos[ DATE_DAY ]!=-1 ) {
472 output_datepieces( f, outptr, pos );
473 } else {
474 fprintf( outptr, "%s", (char *) fields_value( f, pos[ DATE_ALL ], FIELDS_CHRP ) );
475 }
476 output_tag( outptr, 0, "dateIssued", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
477 }
478
479 static void
output_origin(FILE * outptr,fields * f,int level)480 output_origin( FILE *outptr, fields *f, int level )
481 {
482 convert2 parts[] = {
483 { "issuance", "ISSUANCE", 0, 0 },
484 { "publisher", "PUBLISHER", 0, 0 },
485 { "place", "ADDRESS", 0, 1 },
486 { "place", "ADDRESS:PUBLISHER", 0, 1 },
487 { "place", "ADDRESS:AUTHOR", 0, 1 },
488 { "edition", "EDITION", 0, 0 },
489 { "dateCaptured", "URLDATE", 0, 0 }
490 };
491 int nparts = sizeof( parts ) / sizeof( parts[0] );
492
493 int i, found, datefound, datepos[ NUM_DATE_TYPES ];
494 int indent0, indent1, indent2;
495
496 found = convert2_findallfields( f, parts, nparts, level );
497 datefound = find_dateinfo( f, level, datepos );
498 if ( !found && !datefound ) return;
499
500 indent0 = lvl2indent( level );
501 indent1 = lvl2indent( incr_level( level, 1 ) );
502 indent2 = lvl2indent( incr_level( level, 2 ) );
503
504 output_tag( outptr, indent0, "originInfo", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
505
506 /* issuance must precede date */
507 if ( parts[0].pos!=-1 )
508 output_fil( outptr, indent1, "issuance", f, parts[0].pos, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
509
510 /* date */
511 if ( datefound )
512 output_dateissued( f, outptr, level, datepos );
513
514 /* rest of the originInfo elements */
515 for ( i=1; i<nparts; i++ ) {
516
517 /* skip missing originInfo elements */
518 if ( parts[i].pos==-1 ) continue;
519
520 /* normal originInfo element */
521 if ( parts[i].code==0 ) {
522 output_fil( outptr, indent1, parts[i].mods, f, parts[i].pos, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
523 }
524
525 /* originInfo with placeTerm info */
526 else {
527 output_tag( outptr, indent1, parts[i].mods, NULL, TAG_OPEN | TAG_NEWLINE, NULL );
528 output_fil( outptr, indent2, "placeTerm", f, parts[i].pos, TAG_OPENCLOSE | TAG_NEWLINE, "type", "text", NULL );
529 output_tag( outptr, indent1, parts[i].mods, NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
530 }
531 }
532
533 output_tag( outptr, indent0, "originInfo", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
534 }
535
536 /* output_language_core()
537 *
538 * generates language output for tag="langauge" or tag="languageOfCataloging"
539 * if possible, outputs iso639-2b code for the language
540 *
541 * <language>
542 * <languageTerm type="text">xxx</languageTerm>
543 * </language>
544 *
545 * <languageOfCataloging>
546 * <languageTerm type="text">xxx</languageTerm>
547 * </languageOfCataloging>
548 *
549 * <language>
550 * <languageTerm type="text">xxx</languageTerm>
551 * <languageTerm type="code" authority="iso639-2b">xxx</languageTerm>
552 * </language>
553 *
554 */
555 static void
output_language_core(fields * f,int n,FILE * outptr,const char * tag,int level)556 output_language_core( fields *f, int n, FILE *outptr, const char *tag, int level )
557 {
558 const char *term = "languageTerm";
559 const char *lang, *code;
560 int indent1, indent2;
561
562 lang = (const char *) fields_value( f, n, FIELDS_CHRP );
563 code = iso639_2_from_language( lang );
564
565 indent1 = lvl2indent( level );
566 indent2 = lvl2indent( incr_level( level, 1 ) );
567
568 output_tag( outptr, indent1, tag, NULL, TAG_OPEN | TAG_NEWLINE, NULL );
569 output_tag( outptr, indent2, term, lang, TAG_OPENCLOSE | TAG_NEWLINE, "type", "text", NULL );
570
571 if ( code )
572 output_tag( outptr, indent2, term, code, TAG_OPENCLOSE | TAG_NEWLINE, "type", "code", "authority", "iso639-2b", NULL );
573
574 output_tag( outptr, indent1, tag, NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
575 }
576
577 /*
578 * output_language()
579 *
580 * <language>
581 * <languageTerm type="text">xxx</languageTerm>
582 * <languageTerm type="code" authority="iso639-2b">xxx</languageTerm>
583 * </language>
584 */
585 static inline void
output_language(FILE * outptr,fields * f,int level)586 output_language( FILE *outptr, fields *f, int level )
587 {
588 int n;
589
590 n = fields_find( f, "LANGUAGE", level );
591 if ( n==FIELDS_NOTFOUND ) return;
592
593 output_language_core( f, n, outptr, "language", level );
594 }
595
596 /* output_description()
597 *
598 * <physicalDescription>
599 * <note>XXXX</note>
600 * </physicalDescription>
601 */
602 static void
output_description(FILE * outptr,fields * f,int level)603 output_description( FILE *outptr, fields *f, int level )
604 {
605 int n, indent1, indent2;
606 const char *val;
607
608 n = fields_find( f, "DESCRIPTION", level );
609 if ( n==FIELDS_NOTFOUND ) return;
610
611 val = ( const char * ) fields_value( f, n, FIELDS_CHRP );
612
613 indent1 = lvl2indent( level );
614 indent2 = lvl2indent( incr_level( level, 1 ) );
615
616 output_tag( outptr, indent1, "physicalDescription", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
617 output_tag( outptr, indent2, "note", val, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
618 output_tag( outptr, indent1, "physicalDescription", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
619 }
620
621 /* output_toc()
622 *
623 * <tableOfContents>XXXX</tableOfContents>
624 */
625 static void
output_toc(FILE * outptr,fields * f,int level)626 output_toc( FILE *outptr, fields *f, int level )
627 {
628 int n, indent;
629 char *val;
630
631 n = fields_find( f, "CONTENTS", level );
632 if ( n==FIELDS_NOTFOUND ) return;
633
634 val = (char *) fields_value( f, n, FIELDS_CHRP );
635
636 indent = lvl2indent( level );
637
638 output_tag( outptr, indent, "tableOfContents", val, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
639 }
640
641 /* detail output
642 *
643 * for example:
644 *
645 * <detail type="volume"><number>xxx</number></detail>
646 */
647 static void
output_detail(FILE * outptr,fields * f,int n,char * item_name,int level)648 output_detail( FILE *outptr, fields *f, int n, char *item_name, int level )
649 {
650 int indent;
651
652 if ( n==FIELDS_NOTFOUND ) return;
653
654 indent = lvl2indent( incr_level( level, 1 ) );
655
656 output_tag( outptr, indent, "detail", NULL, TAG_OPEN, "type", item_name, NULL );
657 output_fil( outptr, 0, "number", f, n, TAG_OPENCLOSE, NULL );
658 output_tag( outptr, 0, "detail", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
659 }
660
661 /* extents output
662 *
663 * <extent unit="page">
664 * <start>xxx</start>
665 * <end>xxx</end>
666 * </extent>
667 */
668 static void
output_extents(FILE * outptr,fields * f,int start,int end,int total,const char * type,int level)669 output_extents( FILE *outptr, fields *f, int start, int end, int total, const char *type, int level )
670 {
671 int indent1, indent2;
672 char *val;
673
674 indent1 = lvl2indent( incr_level( level, 1 ) );
675 indent2 = lvl2indent( incr_level( level, 2 ) );
676
677 output_tag( outptr, indent1, "extent", NULL, TAG_OPEN | TAG_NEWLINE, "unit", type, NULL );
678 if ( start!=FIELDS_NOTFOUND ) {
679 val = (char *) fields_value( f, start, FIELDS_CHRP );
680 output_tag( outptr, indent2, "start", val, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
681 }
682 if ( end!=FIELDS_NOTFOUND ) {
683 val = (char *) fields_value( f, end, FIELDS_CHRP );
684 output_tag( outptr, indent2, "end", val, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
685 }
686 if ( total!=FIELDS_NOTFOUND ) {
687 val = (char *) fields_value( f, total, FIELDS_CHRP );
688 output_tag( outptr, indent2, "total", val, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
689 }
690 output_tag( outptr, indent1, "extent", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
691 }
692
693 static void
try_output_partheader(FILE * outptr,int wrote_header,int level)694 try_output_partheader( FILE *outptr, int wrote_header, int level )
695 {
696 if ( !wrote_header )
697 output_tag( outptr, lvl2indent(level), "part", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
698 }
699
700 static void
try_output_partfooter(FILE * outptr,int wrote_header,int level)701 try_output_partfooter( FILE *outptr, int wrote_header, int level )
702 {
703 if ( wrote_header )
704 output_tag( outptr, lvl2indent(level), "part", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
705 }
706
707 /* part date output
708 *
709 * <date>xxxx-xx-xx</date>
710 *
711 */
712 static int
output_partdate(FILE * outptr,fields * f,int level,int wrote_header)713 output_partdate( FILE *outptr, fields *f, int level, int wrote_header )
714 {
715 convert2 parts[] = {
716 { "", "PARTDATE:YEAR", 0, 0 },
717 { "", "PARTDATE:MONTH", 0, 0 },
718 { "", "PARTDATE:DAY", 0, 0 },
719 };
720 int nparts = sizeof(parts)/sizeof(parts[0]);
721
722 if ( !convert2_findallfields( f, parts, nparts, level ) ) return 0;
723
724 try_output_partheader( outptr, wrote_header, level );
725
726 output_tag( outptr, lvl2indent(incr_level(level,1)), "date", NULL, TAG_OPEN, NULL );
727
728 if ( parts[0].pos!=-1 ) {
729 fprintf( outptr, "%s", (char *) fields_value( f, parts[0].pos, FIELDS_CHRP ) );
730 } else fprintf( outptr, "XXXX" );
731
732 if ( parts[1].pos!=-1 ) {
733 fprintf( outptr, "-%s", (char *) fields_value( f, parts[1].pos, FIELDS_CHRP ) );
734 }
735
736 if ( parts[2].pos!=-1 ) {
737 if ( parts[1].pos==-1 )
738 fprintf( outptr, "-XX" );
739 fprintf( outptr, "-%s", (char *) fields_value( f, parts[2].pos, FIELDS_CHRP ) );
740 }
741
742 fprintf( outptr,"</date>\n");
743
744 return 1;
745 }
746
747 static int
output_partpages(FILE * outptr,fields * f,int level,int wrote_header)748 output_partpages( FILE *outptr, fields *f, int level, int wrote_header )
749 {
750 convert2 parts[] = {
751 { "", "PAGES:START", 0, 0 },
752 { "", "PAGES:STOP", 0, 0 },
753 { "", "PAGES", 0, 0 },
754 { "", "PAGES:TOTAL", 0, 0 }
755 };
756 int nparts = sizeof(parts)/sizeof(parts[0]);
757
758 if ( !convert2_findallfields( f, parts, nparts, level ) ) return 0;
759
760 try_output_partheader( outptr, wrote_header, level );
761
762 /* If PAGES:START or PAGES:STOP are undefined */
763 if ( parts[0].pos==-1 || parts[1].pos==-1 ) {
764 if ( parts[0].pos!=-1 )
765 output_detail ( outptr, f, parts[0].pos, "page", level );
766 if ( parts[1].pos!=-1 )
767 output_detail ( outptr, f, parts[1].pos, "page", level );
768 if ( parts[2].pos!=-1 )
769 output_detail ( outptr, f, parts[2].pos, "page", level );
770 if ( parts[3].pos!=-1 )
771 output_extents( outptr, f, FIELDS_NOTFOUND, FIELDS_NOTFOUND, parts[3].pos, "page", level );
772 }
773 /* If both PAGES:START and PAGES:STOP are defined */
774 else {
775 output_extents( outptr, f, parts[0].pos, parts[1].pos, parts[3].pos, "page", level );
776 }
777
778 return 1;
779 }
780
781 static int
output_partelement(FILE * outptr,fields * f,int level,int wrote_header)782 output_partelement( FILE *outptr, fields *f, int level, int wrote_header )
783 {
784 convert2 parts[] = {
785 { "", "NUMVOLUMES", 0, 0 },
786 { "volume", "VOLUME", 0, 0 },
787 { "section", "SECTION", 0, 0 },
788 { "issue", "ISSUE", 0, 0 },
789 { "number", "NUMBER", 0, 0 },
790 { "publiclawnumber", "PUBLICLAWNUMBER", 0, 0 },
791 { "session", "SESSION", 0, 0 },
792 { "articlenumber", "ARTICLENUMBER", 0, 0 },
793 { "part", "PART", 0, 0 },
794 { "chapter", "CHAPTER", 0, 0 },
795 { "report number", "REPORTNUMBER", 0, 0 },
796 };
797 int i, nparts = sizeof( parts ) / sizeof( parts[0] );
798
799 if ( !convert2_findallfields( f, parts, nparts, level ) ) return 0;
800
801 try_output_partheader( outptr, wrote_header, level );
802
803 /* start loop at 1 to skip NUMVOLUMES */
804 for ( i=1; i<nparts; ++i ) {
805 if ( parts[i].pos==FIELDS_NOTFOUND ) continue;
806 output_detail( outptr, f, parts[i].pos, parts[i].mods, level );
807 }
808
809 if ( parts[0].pos!=FIELDS_NOTFOUND )
810 output_extents( outptr, f, FIELDS_NOTFOUND, FIELDS_NOTFOUND, parts[0].pos, "volumes", level );
811
812 return 1;
813 }
814
815 static void
output_part(FILE * outptr,fields * f,int level)816 output_part( FILE *outptr, fields *f, int level )
817 {
818 int wrote_hdr;
819 wrote_hdr = output_partdate( outptr, f, level, 0 );
820 wrote_hdr += output_partelement( outptr, f, level, wrote_hdr );
821 wrote_hdr += output_partpages( outptr, f, level, wrote_hdr );
822 try_output_partfooter( outptr, wrote_hdr, level );
823 }
824
825 /* output_recordInfo()
826 *
827 * <recordInfo>
828 * <languageOfCataloging>
829 * <languageTerm type="text">xxx</languageTerm>
830 * <languageTerm type="code" authority="iso639-2b">xxx</languageTerm>
831 * </languageOfCataloging>
832 * </recordInfo>
833 */
834 static void
output_recordInfo(FILE * outptr,fields * f,int level)835 output_recordInfo( FILE *outptr, fields *f, int level )
836 {
837 int n, indent;
838
839 n = fields_find( f, "LANGCATALOG", level );
840 if ( n==FIELDS_NOTFOUND ) return;
841
842 indent = lvl2indent( level );
843
844 output_tag( outptr, indent, "recordInfo", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
845 output_language_core( f, n, outptr, "languageOfCataloging", incr_level(level,1) );
846 output_tag( outptr, indent, "recordInfo", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
847 }
848
849 /* output_genre()
850 *
851 * <genre authority="marcgt">thesis</genre>
852 * <genre authority="bibutilsgt">Diploma thesis</genre>
853 */
854 static void
output_genre(FILE * outptr,fields * f,int level)855 output_genre( FILE *outptr, fields *f, int level )
856 {
857 const char *authority="authority", *marcauth="marcgt", *buauth="bibutilsgt";
858 const char *attr = NULL, *attrvalue = NULL;
859 const char *tag, *value;
860 int i, n;
861
862 n = fields_num( f );
863
864 for ( i=0; i<n; ++i ) {
865
866 if ( fields_level( f, i ) != level ) continue;
867
868 tag = ( const char * ) fields_tag( f, i, FIELDS_CHRP );
869
870 if ( !strcmp( tag, "GENRE:MARC" ) ) {
871 attr = authority;
872 attrvalue = marcauth;
873 }
874 else if ( !strcmp( tag, "GENRE:BIBUTILS" ) ) {
875 attr = authority;
876 attrvalue = buauth;
877 }
878 else if ( !strcmp( tag, "GENRE:UNKNOWN" ) || !strcmp( tag, "GENRE" ) ) {
879 /* do nothing */
880 }
881 else continue;
882
883 value = ( const char * ) fields_value( f, i, FIELDS_CHRP );
884
885 /* if the internal tag hasn't told us, try to look up genre tag */
886 if ( !attr ) {
887 if ( is_marc_genre( value ) ) {
888 attr = authority;
889 attrvalue = marcauth;
890 }
891 else if ( is_bu_genre( value ) ) {
892 attr = authority;
893 attrvalue = buauth;
894 }
895 }
896
897 output_tag( outptr, lvl2indent(level), "genre", value, TAG_OPENCLOSE | TAG_NEWLINE, attr, attrvalue, NULL );
898
899 }
900 }
901
902 /* output_resource()
903 *
904 * <typeOfResource>text</typeOfResource>
905 *
906 * Only output typeOfResources defined by MARC authority
907 */
908 static void
output_resource(FILE * outptr,fields * f,int level)909 output_resource( FILE *outptr, fields *f, int level )
910 {
911 const char *value;
912 int n;
913
914 n = fields_find( f, "RESOURCE", level );
915 if ( n==FIELDS_NOTFOUND ) return;
916
917 value = ( const char * ) fields_value( f, n, FIELDS_CHRP );
918 if ( is_marc_resource( value ) ) {
919 output_fil( outptr, lvl2indent(level), "typeOfResource", f, n, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
920 } else {
921 fprintf( stderr, "Illegal typeofResource = '%s'\n", value );
922 }
923 }
924
925 static void
output_type(FILE * outptr,fields * f,int level)926 output_type( FILE *outptr, fields *f, int level )
927 {
928 int n;
929
930 /* silence warnings about INTERNAL_TYPE being unused */
931 n = fields_find( f, "INTERNAL_TYPE", LEVEL_MAIN );
932 if ( n!=FIELDS_NOTFOUND ) fields_set_used( f, n );
933
934 output_resource( outptr, f, level );
935 output_genre( outptr, f, level );
936 }
937
938 /* output_abs()
939 *
940 * <abstract>xxxx</abstract>
941 */
942 static void
output_abs(FILE * outptr,fields * f,int level)943 output_abs( FILE *outptr, fields *f, int level )
944 {
945 int n;
946
947 n = fields_find( f, "ABSTRACT", level );
948 output_fil( outptr, lvl2indent(level), "abstract", f, n, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
949 }
950
951 static void
output_notes(FILE * outptr,fields * f,int level)952 output_notes( FILE *outptr, fields *f, int level )
953 {
954 int i, n;
955 char *t;
956
957 n = fields_num( f );
958 for ( i=0; i<n; ++i ) {
959 if ( fields_level( f, i ) != level ) continue;
960 t = fields_tag( f, i, FIELDS_CHRP_NOUSE );
961 if ( !strcasecmp( t, "NOTES" ) )
962 output_fil( outptr, lvl2indent(level), "note", f, i, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
963 else if ( !strcasecmp( t, "PUBSTATE" ) )
964 output_fil( outptr, lvl2indent(level), "note", f, i, TAG_OPENCLOSE | TAG_NEWLINE, "type", "publication status", NULL );
965 else if ( !strcasecmp( t, "ANNOTE" ) )
966 output_fil( outptr, lvl2indent(level), "bibtex-annote", f, i, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
967 else if ( !strcasecmp( t, "TIMESCITED" ) )
968 output_fil( outptr, lvl2indent(level), "note", f, i, TAG_OPENCLOSE | TAG_NEWLINE, "type", "times cited", NULL );
969 else if ( !strcasecmp( t, "ANNOTATION" ) )
970 output_fil( outptr, lvl2indent(level), "note", f, i, TAG_OPENCLOSE | TAG_NEWLINE, "type", "annotation", NULL );
971 else if ( !strcasecmp( t, "ADDENDUM" ) )
972 output_fil( outptr, lvl2indent(level), "note", f, i, TAG_OPENCLOSE | TAG_NEWLINE, "type", "addendum", NULL );
973 else if ( !strcasecmp( t, "BIBKEY" ) )
974 output_fil( outptr, lvl2indent(level), "note", f, i, TAG_OPENCLOSE | TAG_NEWLINE, "type", "bibliography key", NULL );
975 }
976 }
977
978 /* output_key()
979 *
980 * <subject>
981 * <topic>KEYWORD/topic>
982 * </subject>
983 * <subject>
984 * <topic class="primary">EPRINTCLASS</topic>
985 * </subject>
986 */
987 static void
output_key(FILE * outptr,fields * f,int level)988 output_key( FILE *outptr, fields *f, int level )
989 {
990 int indent1, indent2;
991 vplist_index i;
992 char *value;
993 vplist keys;
994 int status;
995
996 vplist_init( &keys );
997
998 indent1 = lvl2indent( level );
999 indent2 = lvl2indent( incr_level( level, 1 ) );
1000
1001 /* output KEYWORDs */
1002
1003 status = fields_findv_each( f, level, FIELDS_CHRP, &keys, "KEYWORD" );
1004 if ( status!=FIELDS_OK ) goto out;
1005
1006 for ( i=0; i<keys.n; ++i ) {
1007 value = vplist_get( &keys, i );
1008 output_tag( outptr, indent1, "subject", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
1009 output_tag( outptr, indent2, "topic", value, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
1010 output_tag( outptr, indent1, "subject", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
1011 }
1012
1013 vplist_empty( &keys );
1014
1015 /* output EPRINTCLASSes */
1016
1017 status = fields_findv_each( f, level, FIELDS_CHRP, &keys, "EPRINTCLASS" );
1018 if ( status!=FIELDS_OK ) goto out;
1019
1020 for ( i=0; i<keys.n; ++i ) {
1021 value = vplist_get( &keys, i );
1022 output_tag( outptr, indent1, "subject", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
1023 output_tag( outptr, indent2, "topic", value, TAG_OPENCLOSE | TAG_NEWLINE,
1024 "class", "primary", NULL );
1025 output_tag( outptr, indent1, "subject", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
1026 }
1027
1028 out:
1029 vplist_free( &keys );
1030 }
1031
1032 static void
output_sn(FILE * outptr,fields * f,int level)1033 output_sn( FILE *outptr, fields *f, int level )
1034 {
1035 convert sn_types[] = {
1036 { "isbn", "ISBN", },
1037 { "isbn", "ISBN13", },
1038 { "lccn", "LCCN", },
1039 { "issn", "ISSN", },
1040 { "coden", "CODEN", },
1041 { "citekey", "REFNUM", },
1042 { "doi", "DOI", },
1043 { "eid", "EID", },
1044 { "eprint", "EPRINT", },
1045 { "eprinttype","EPRINTTYPE",},
1046 { "pubmed", "PMID", },
1047 { "MRnumber", "MRNUMBER", },
1048 { "medline", "MEDLINE", },
1049 { "pii", "PII", },
1050 { "pmc", "PMC", },
1051 { "arXiv", "ARXIV", },
1052 { "isi", "ISIREFNUM", },
1053 { "accessnum", "ACCESSNUM", },
1054 { "jstor", "JSTOR", },
1055 { "isrn", "ISRN", },
1056 { "serial number", "SERIALNUMBER", },
1057 };
1058 int ntypes = sizeof( sn_types ) / sizeof( sn_types[0] );
1059 int i, n, status, indent;
1060 vplist serialno;
1061
1062 vplist_init( &serialno );
1063
1064 indent = lvl2indent( level );
1065
1066 /* output call number */
1067 n = fields_find( f, "CALLNUMBER", level );
1068 output_fil( outptr, indent, "classification", f, n, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
1069
1070 /* output all types of serial numbers */
1071 for ( i=0; i<ntypes; ++i ) {
1072
1073 status = fields_findv_each( f, level, FIELDS_CHRP, &serialno, sn_types[i].internal );
1074 if ( status!=FIELDS_OK ) goto out;
1075
1076 output_vpl( outptr, indent, "identifier", &serialno, TAG_OPENCLOSE | TAG_NEWLINE,
1077 "type", sn_types[i].mods, NULL );
1078
1079 vplist_empty( &serialno );
1080 }
1081
1082 out:
1083 vplist_free( &serialno );
1084 }
1085
1086 /* output_url()
1087 *
1088 * <location>
1089 * <url>URL</url>
1090 * <url urlType="pdf">PDFLINK</url>
1091 * <url displayLabel="Electronic full text" access="raw object">PDFLINK</url>
1092 * <physicalLocation>LOCATION</physicalLocation>
1093 * </location>
1094 */
1095 static void
output_url(FILE * outptr,fields * f,int level)1096 output_url( FILE *outptr, fields *f, int level )
1097 {
1098 vplist fileattach, location, pdflink, url;
1099 int indent1, indent2, status;
1100
1101 vplist_init( &fileattach );
1102 vplist_init( &location );
1103 vplist_init( &pdflink );
1104 vplist_init( &url );
1105
1106 status = fields_findv_each( f, level, FIELDS_CHRP, &fileattach, "FILEATTACH" );
1107 if ( status!=FIELDS_OK ) goto out;
1108
1109 status = fields_findv_each( f, level, FIELDS_CHRP, &location, "LOCATION" );
1110 if ( status!=FIELDS_OK ) goto out;
1111
1112 status = fields_findv_each( f, level, FIELDS_CHRP, &pdflink, "PDFLINK" );
1113 if ( status!=FIELDS_OK ) goto out;
1114
1115 status = fields_findv_each( f, level, FIELDS_CHRP, &url, "URL" );
1116 if ( status!=FIELDS_OK ) goto out;
1117
1118
1119 /* do not write location tag if no elements found */
1120 if ( fileattach.n + location.n + pdflink.n + url.n == 0 ) goto out;
1121
1122
1123 /* output all of the found elements surrounded by <location>...</location>*/
1124
1125 indent1 = lvl2indent( level );
1126 indent2 = lvl2indent( incr_level( level, 1 ) );
1127
1128 output_tag( outptr, indent1, "location", NULL, TAG_OPEN | TAG_NEWLINE, NULL );
1129
1130 output_vpl( outptr, indent2, "url", &url, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
1131 output_vpl( outptr, indent2, "url", &pdflink, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
1132 output_vpl( outptr, indent2, "url", &fileattach, TAG_OPENCLOSE | TAG_NEWLINE,
1133 "displayLabel", "Electronic full text",
1134 "access", "raw object",
1135 NULL );
1136 output_vpl( outptr, indent2, "physicalLocation", &location, TAG_OPENCLOSE | TAG_NEWLINE, NULL );
1137
1138 output_tag( outptr, indent1, "location", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
1139
1140 out:
1141 vplist_free( &fileattach );
1142 vplist_free( &location );
1143 vplist_free( &pdflink );
1144 vplist_free( &url );
1145 }
1146
1147 static int
original_items(fields * f,int level)1148 original_items( fields *f, int level )
1149 {
1150 int i, targetlevel, n;
1151 if ( level < 0 ) return 0;
1152 targetlevel = -( level + 2 );
1153 n = fields_num( f );
1154 for ( i=0; i<n; ++i ) {
1155 if ( fields_level( f, i ) == targetlevel )
1156 return targetlevel;
1157 }
1158 return 0;
1159 }
1160
1161 static void
output_citeparts(FILE * outptr,fields * f,int level,int max)1162 output_citeparts( FILE *outptr, fields *f, int level, int max )
1163 {
1164 int orig_level;
1165
1166 output_title ( outptr, f, level );
1167 output_names ( outptr, f, level );
1168 output_origin ( outptr, f, level );
1169 output_type ( outptr, f, level );
1170 output_language ( outptr, f, level );
1171 output_description( outptr, f, level );
1172
1173 /* Recursively output relatedItems, which are host items like series for a book */
1174 if ( level >= 0 && level < max ) {
1175 output_tag( outptr, lvl2indent(level), "relatedItem", NULL, TAG_OPEN | TAG_NEWLINE, "type", "host", NULL );
1176 output_citeparts( outptr, f, incr_level(level,1), max );
1177 output_tag( outptr, lvl2indent(level), "relatedItem", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
1178 }
1179
1180 /* Recursively output relatedItems that are original item, if they exist;
1181 * these are the first publication of an item
1182 */
1183 orig_level = original_items( f, level );
1184 if ( orig_level ) {
1185 output_tag( outptr, lvl2indent(level), "relatedItem", NULL, TAG_OPEN | TAG_NEWLINE, "type", "original", NULL );
1186 output_citeparts( outptr, f, orig_level, max );
1187 output_tag( outptr, lvl2indent(level), "relatedItem", NULL, TAG_CLOSE | TAG_NEWLINE, NULL );
1188 }
1189
1190 output_abs ( outptr, f, level );
1191 output_notes ( outptr, f, level );
1192 output_toc ( outptr, f, level );
1193 output_key ( outptr, f, level );
1194 output_sn ( outptr, f, level );
1195 output_url ( outptr, f, level );
1196 output_part ( outptr, f, level );
1197 output_recordInfo( outptr, f, level );
1198 }
1199
1200 static int
no_unused_tags(fields * f)1201 no_unused_tags( fields *f )
1202 {
1203 int i, n;
1204
1205 n = fields_num( f );
1206 for ( i=0; i<n; ++i )
1207 if ( fields_used( f, i ) == 0 ) return 0;
1208
1209 return 1;
1210 }
1211
1212 static void
report_unused_tags(FILE * outptr,fields * f,param * p,unsigned long refnum)1213 report_unused_tags( FILE *outptr, fields *f, param *p, unsigned long refnum )
1214 {
1215 char *tag, *value, *prefix;
1216 int i, n, nwritten, level;
1217
1218 if ( no_unused_tags( f ) ) return;
1219
1220 n = fields_num( f );
1221
1222 if ( p->progname ) prefix = p->progname;
1223 else prefix = "modsout";
1224
1225 fprintf( outptr, "%s: Reference %lu has unused tags.\n", prefix, refnum+1 );
1226
1227 /* Report authors from level LEVEL_MAIN */
1228 nwritten = 0;
1229 for ( i=0; i<n; ++i ) {
1230 if ( fields_level( f, i ) != LEVEL_MAIN ) continue;
1231 tag = fields_tag( f, i, FIELDS_CHRP_NOUSE );
1232 if ( strcasecmp( tag, "AUTHOR" ) && strcasecmp( tag, "AUTHOR:ASIS" ) && strcasecmp( tag, "AUTHOR:CORP" ) ) continue;
1233 value = fields_value( f, i, FIELDS_CHRP_NOUSE );
1234 if ( nwritten==0 ) fprintf( outptr, "%s: Author(s): %s\n", prefix, value );
1235 else fprintf( outptr, "%s: %s\n", prefix, value );
1236 nwritten++;
1237 }
1238
1239 /* Report date from level LEVEL_MAIN */
1240 for ( i=0; i<n; ++i ) {
1241 if ( fields_level( f, i ) != LEVEL_MAIN ) continue;
1242 tag = fields_tag( f, i, FIELDS_CHRP_NOUSE );
1243 if ( strcasecmp( tag, "DATE:YEAR" ) && strcasecmp( tag, "PARTDATE:YEAR" ) ) continue;
1244 value = fields_value( f, i, FIELDS_CHRP_NOUSE );
1245 fprintf( outptr, "%s: Year: %s\n", prefix, value );
1246 break;
1247 }
1248
1249 /* Report title from level LEVEL_MAIN */
1250 for ( i=0; i<n; ++i ) {
1251 if ( fields_level( f, i ) != LEVEL_MAIN ) continue;
1252 tag = fields_tag( f, i, FIELDS_CHRP_NOUSE );
1253 if ( strncasecmp( tag, "TITLE", 5 ) ) continue;
1254 value = fields_value( f, i, FIELDS_CHRP_NOUSE );
1255 fprintf( outptr, "%s: Title: %s\n", prefix, value );
1256 break;
1257 }
1258
1259 fprintf( outptr, "%s: Unused entries: tag, value, level\n", prefix );
1260 for ( i=0; i<n; ++i ) {
1261 if ( fields_used( f, i ) ) continue;
1262 tag = fields_tag( f, i, FIELDS_CHRP_NOUSE );
1263 value = fields_value( f, i, FIELDS_CHRP_NOUSE );
1264 level = fields_level( f, i );
1265 fprintf( outptr, "%s: '%s', '%s', %d\n", prefix, tag, value, level );
1266 }
1267
1268 }
1269
1270 /* refnum should start with a non-number and not include spaces -- ignore this */
1271 static void
output_refnum(FILE * outptr,fields * f,int n)1272 output_refnum( FILE *outptr, fields *f, int n )
1273 {
1274 char *p = fields_value( f, n, FIELDS_CHRP_NOUSE );
1275 while ( p && *p ) {
1276 if ( !is_ws(*p) ) fprintf( outptr, "%c", *p );
1277 p++;
1278 }
1279 }
1280
1281 static void
output_head(FILE * outptr,fields * f,int dropkey)1282 output_head( FILE *outptr, fields *f, int dropkey )
1283 {
1284 int n;
1285 fprintf( outptr, "<mods");
1286 if ( !dropkey ) {
1287 n = fields_find( f, "REFNUM", LEVEL_MAIN );
1288 if ( n!=FIELDS_NOTFOUND ) {
1289 fprintf( outptr, " ID=\"");
1290 output_refnum( outptr, f, n );
1291 fprintf( outptr, "\"");
1292 }
1293 }
1294 fprintf( outptr, ">\n" );
1295 }
1296
1297 static inline void
output_tail(FILE * outptr)1298 output_tail( FILE *outptr )
1299 {
1300 fprintf( outptr, "</mods>\n" );
1301 }
1302
1303 static int
modsout_write(fields * f,FILE * outptr,param * p,unsigned long refnum)1304 modsout_write( fields *f, FILE *outptr, param *p, unsigned long refnum )
1305 {
1306 int max, dropkey;
1307
1308 max = fields_maxlevel( f );
1309 dropkey = ( p->format_opts & BIBL_FORMAT_MODSOUT_DROPKEY );
1310
1311 output_head( outptr, f, dropkey );
1312 output_citeparts( outptr, f, LEVEL_MAIN, max );
1313 output_tail( outptr );
1314 fflush( outptr );
1315
1316 report_unused_tags( stderr, f, p, refnum );
1317
1318 return BIBL_OK;
1319 }
1320
1321 /*****************************************************
1322 PUBLIC: int modsout_writeheader()
1323 *****************************************************/
1324
1325 static void
modsout_writeheader(FILE * outptr,param * p)1326 modsout_writeheader( FILE *outptr, param *p )
1327 {
1328 if ( p->utf8bom ) utf8_writebom( outptr );
1329 fprintf(outptr,"<?xml version=\"1.0\" encoding=\"%s\"?>\n",
1330 charset_get_xmlname( p->charsetout ) );
1331 fprintf(outptr,"<modsCollection xmlns=\"http://www.loc.gov/mods/v3\">\n");
1332 }
1333
1334 /*****************************************************
1335 PUBLIC: int modsout_writefooter()
1336 *****************************************************/
1337
1338 static void
modsout_writefooter(FILE * outptr)1339 modsout_writefooter( FILE *outptr )
1340 {
1341 fprintf(outptr,"</modsCollection>\n");
1342 fflush( outptr );
1343 }
1344