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