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