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