diff options
Diffstat (limited to 'support/texlab/crates/bibutils_sys/src/bibtexin.c')
-rw-r--r-- | support/texlab/crates/bibutils_sys/src/bibtexin.c | 1517 |
1 files changed, 1517 insertions, 0 deletions
diff --git a/support/texlab/crates/bibutils_sys/src/bibtexin.c b/support/texlab/crates/bibutils_sys/src/bibtexin.c new file mode 100644 index 0000000000..3401074547 --- /dev/null +++ b/support/texlab/crates/bibutils_sys/src/bibtexin.c @@ -0,0 +1,1517 @@ +/* + * bibtexin.c + * + * Copyright (c) Chris Putnam 2003-2019 + * + * Program and source code released under the GPL version 2 + * + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "is_ws.h" +#include "str.h" +#include "utf8.h" +#include "str_conv.h" +#include "fields.h" +#include "slist.h" +#include "name.h" +#include "title.h" +#include "url.h" +#include "reftypes.h" +#include "bibformats.h" +#include "generic.h" +#include "msvc_fix.h" + +static slist find = { 0, 0, 0, NULL }; +static slist replace = { 0, 0, 0, NULL }; + +extern variants bibtex_all[]; +extern int bibtex_nall; + +/***************************************************** + PUBLIC: void bibtexin_initparams() +*****************************************************/ + +static int bibtexin_convertf( fields *bibin, fields *info, int reftype, param *p ); +static int bibtexin_processf( fields *bibin, const char *data, const char *filename, long nref, param *p ); +static int bibtexin_cleanf( bibl *bin, param *p ); +static int bibtexin_readf( FILE *fp, char *buf, int bufsize, int *bufpos, str *line, str *reference, int *fcharset ); +static int bibtexin_typef( fields *bibin, const char *filename, int nrefs, param *p ); + +int +bibtexin_initparams( param *pm, const char *progname ) +{ + pm->readformat = BIBL_BIBTEXIN; + pm->charsetin = BIBL_CHARSET_DEFAULT; + pm->charsetin_src = BIBL_SRC_DEFAULT; + pm->latexin = 1; + pm->xmlin = 0; + pm->utf8in = 0; + pm->nosplittitle = 0; + pm->verbose = 0; + pm->addcount = 0; + pm->output_raw = 0; + + pm->readf = bibtexin_readf; + pm->processf = bibtexin_processf; + pm->cleanf = bibtexin_cleanf; + pm->typef = bibtexin_typef; + pm->convertf = bibtexin_convertf; + pm->all = bibtex_all; + pm->nall = bibtex_nall; + + slist_init( &(pm->asis) ); + slist_init( &(pm->corps) ); + + if ( !progname ) pm->progname = NULL; + else { + pm->progname = strdup( progname ); + if ( pm->progname==NULL ) return BIBL_ERR_MEMERR; + } + + return BIBL_OK; +} + +/***************************************************** + PUBLIC: int bibtexin_readf() +*****************************************************/ + +/* + * readf can "read too far", so we store this information in line, thus + * the next new text is in line, either from having read too far or + * from the next chunk obtained via str_fget() + * + * return 1 on success, 0 on error/end-of-file + * + */ +static int +readmore( FILE *fp, char *buf, int bufsize, int *bufpos, str *line ) +{ + if ( line->len ) return 1; + else return str_fget( fp, buf, bufsize, bufpos, line ); +} + +/* + * readf() + * + * returns zero if cannot get reference and hit end of-file + * returns 1 if last reference in file, 2 if reference within file + */ +static int +bibtexin_readf( FILE *fp, char *buf, int bufsize, int *bufpos, str *line, str *reference, int *fcharset ) +{ + int haveref = 0; + const char *p; + *fcharset = CHARSET_UNKNOWN; + while ( haveref!=2 && readmore( fp, buf, bufsize, bufpos, line ) ) { + if ( line->len == 0 ) continue; /* blank line */ + p = &(line->data[0]); + /* Recognize UTF8 BOM */ + if ( line->len > 2 && + (unsigned char)(p[0])==0xEF && + (unsigned char)(p[1])==0xBB && + (unsigned char)(p[2])==0xBF ) { + *fcharset = CHARSET_UNICODE; + p += 3; + } + p = skip_ws( p ); + if ( *p == '%' ) { /* commented out line */ + str_empty( line ); + continue; + } + if ( *p == '@' ) haveref++; + if ( haveref && haveref<2 ) { + str_strcatc( reference, p ); + str_addchar( reference, '\n' ); + str_empty( line ); + } else if ( !haveref ) str_empty( line ); + + } + return haveref; +} + +/***************************************************** + PUBLIC: int bibtexin_processf() +*****************************************************/ + +typedef struct loc { + const char *progname; + const char *filename; + long nref; +} loc; + +/* process_bibtextype() + * + * extract 'article', 'book', etc. from: + * + * @article{...} + * @book(...) + * + * return pointer after '{' or '(' character + */ +static const char* +process_bibtextype( const char *p, str *type ) +{ + str tmp; + + str_init( &tmp ); + + if ( *p=='@' ) p++; + p = skip_ws( p ); + + p = str_cpytodelim( &tmp, p, "{( \t\r\n", 0 ); + p = skip_ws( p ); + + if ( *p=='{' || *p=='(' ) p++; + p = skip_ws( p ); + + if ( str_has_value( &tmp ) ) str_strcpy( type, &tmp ); + else str_empty( type ); + + str_free( &tmp ); + + return p; +} + +static const char * +process_bibtexid( const char *p, str *id ) +{ + const char *start_p = p; + str tmp; + + str_init( &tmp ); + p = str_cpytodelim( &tmp, p, ",", 1 ); + + if ( str_has_value( &tmp ) ) { + if ( strchr( tmp.data, '=' ) ) { + /* Endnote writes bibtex files w/o fields, try to + * distinguish via presence of an equal sign.... if + * it's there, assume that it's a tag/data pair instead + * and roll back. + */ + p = start_p; + str_empty( id ); + } else { + str_strcpy( id, &tmp ); + } + } else { + str_empty( id ); + } + + str_free( &tmp ); + return skip_ws( p ); +} + +/* bibtex_tag() + * + * returns NULL on memory error, else position after tag+whitespace + */ +static const char * +bibtex_tag( const char *p, str *tag ) +{ + p = str_cpytodelim( tag, p, "= \t\r\n", 0 ); + if ( str_memerr( tag ) ) return NULL; + return skip_ws( p ); +} + +static int +quotation_mark_is_escaped( int nbraces, const char *p, const char *startp ) +{ + if ( nbraces!=0 ) return 1; + if ( p!=startp && *(p-1)=='\\' ) return 1; + return 0; +} + +static int +brace_is_escaped( int nquotes, const char *p, const char *startp ) +{ + if ( nquotes!=0 ) return 1; + if ( p!=startp && *(p-1)=='\\' ) return 1; + return 0; +} + +static int +char_is_escaped( int nquotes, int nbraces ) +{ + if ( nquotes!=0 || nbraces!=0 ) return 1; + return 0; +} + +static int +add_token( slist *tokens, str *token ) +{ + int status; + + if ( str_memerr( token ) ) return BIBL_ERR_MEMERR; + + status = slist_add( tokens, token ); + if ( status!=SLIST_OK ) return BIBL_ERR_MEMERR; + + str_empty( token ); + + return BIBL_OK; +} + +static const char * +bibtex_data( const char *p, slist *tokens, loc *currloc ) +{ + int nbraces = 0, nquotes = 0; + const char *startp = p; + int status; + str token; + + str_init( &token ); + + while ( p && *p ) { + + /* ...have we reached end-of-data? */ + if ( nquotes==0 && nbraces==0 ) { + if ( *p==',' || *p=='=' || *p=='}' || *p==')' ) goto out; + } + + if ( *p=='\"' ) { + str_addchar( &token, *p ); + if ( !quotation_mark_is_escaped( nbraces, p, startp ) ) { + nquotes = !nquotes; + if ( nquotes==0 ) { + status = add_token( tokens, &token ); + if ( status!=BIBL_OK ) { p=NULL; goto out0; } + } + } + } + + else if ( *p=='{' ) { + str_addchar( &token, *p ); + if ( !brace_is_escaped( nquotes, p, startp ) ) { + nbraces++; + } + } + + else if ( *p=='}' ) { + str_addchar( &token, *p ); + if ( !brace_is_escaped( nquotes, p, startp ) ) { + nbraces--; + if ( nbraces==0 ) { + status = add_token( tokens, &token ); + if ( status!=BIBL_OK ) { p=NULL; goto out0; } + } + if ( nbraces<0 ) { + goto out; + } + } + } + + else if ( *p=='#' ) { + if ( char_is_escaped( nquotes, nbraces ) ) { + str_addchar( &token, *p ); + } + /* ...this is a bibtex string concatentation token */ + else { + if ( str_has_value( &token ) ) { + status = add_token( tokens, &token ); + if ( status!=BIBL_OK ) { p=NULL; goto out0; } + } + status = slist_addc( tokens, "#" ); + if ( status!=SLIST_OK ) { p=NULL; goto out0; } + } + } + + /* ...add escaped white-space and non-white-space to current token */ + else if ( !is_ws( *p ) || char_is_escaped( nquotes, nbraces ) ) { + /* always add non-whitespace characters */ + if ( !is_ws( *p ) ) { + str_addchar( &token, *p ); + } + /* only add whitespace if token is non-empty; convert CR/LF to space */ + else if ( token.len!=0 ) { + if ( *p!='\n' && *p!='\r' ) + str_addchar( &token, *p ); + else { + str_addchar( &token, ' ' ); + while ( is_ws( *(p+1) ) ) p++; + } + } + } + + /* ...unescaped white-space marks the end of a token */ + else if ( is_ws( *p ) ) { + if ( token.len ) { + status = add_token( tokens, &token ); + if ( status!=BIBL_OK ) { p=NULL; goto out0; } + } + } + + p++; + } +out: + if ( nbraces!=0 ) { + fprintf( stderr, "%s: Mismatch in number of braces in file %s reference %ld.\n", currloc->progname, currloc->filename, currloc->nref ); + } + if ( nquotes!=0 ) { + fprintf( stderr, "%s: Mismatch in number of quotes in file %s reference %ld.\n", currloc->progname, currloc->filename, currloc->nref ); + } + if ( str_has_value( &token ) ) { + if ( str_memerr( &token ) ) { p = NULL; goto out; } + status = slist_add( tokens, &token ); + if ( status!=SLIST_OK ) p = NULL; + } +out0: + str_free( &token ); + return p; +} + +#define NOT_ESCAPED (0) +#define ESCAPED_QUOTES (1) +#define ESCAPED_BRACES (2) + +static int +token_is_escaped( str *s ) +{ + if ( s->data[0]=='\"' && s->data[s->len-1]=='\"' ) return ESCAPED_QUOTES; + if ( s->data[0]=='{' && s->data[s->len-1]=='}' ) return ESCAPED_BRACES; + return NOT_ESCAPED; +} + +/* replace_strings() + * + * do bibtex string replacement for data tokens + */ +static int +replace_strings( slist *tokens ) +{ + int i, n; + str *s; + + for ( i=0; i<tokens->n; ++i ) { + + s = slist_str( tokens, i ); + + /* ...skip if token is protected by quotation marks or braces */ + if ( token_is_escaped( s ) ) continue; + + /* ...skip if token is string concatentation symbol */ + if ( !str_strcmpc( s, "#" ) ) continue; + + n = slist_find( &find, s ); + if ( slist_wasnotfound( &find, n ) ) continue; + + str_strcpy( s, slist_str( &replace, n ) ); + if ( str_memerr( s ) ) return BIBL_ERR_MEMERR; + + } + + return BIBL_OK; +} + +static int +string_concatenate( slist *tokens, loc *currloc ) +{ + int i, status, esc_s, esc_t; + str *s, *t; + + i = 0; + while ( i < tokens->n ) { + + s = slist_str( tokens, i ); + if ( str_strcmpc( s, "#" ) ) { + i++; + continue; + } + + if ( i==0 || i==tokens->n-1 ) { + fprintf( stderr, "%s: Warning: Stray string concatenation ('#' character) in file %s reference %ld\n", + currloc->progname, currloc->filename, currloc->nref ); + status = slist_remove( tokens, i ); + if ( status!=SLIST_OK ) return BIBL_ERR_MEMERR; + continue; + } + + s = slist_str( tokens, i-1 ); + t = slist_str( tokens, i+1 ); + + esc_s = token_is_escaped( s ); + esc_t = token_is_escaped( t ); + + if ( esc_s != NOT_ESCAPED ) str_trimend( s, 1 ); + if ( esc_t != NOT_ESCAPED ) str_trimbegin( t, 1 ); + if ( esc_s != esc_t ) { + if ( esc_s == NOT_ESCAPED ) { + if ( esc_t == ESCAPED_QUOTES ) str_prepend( s, "\"" ); + else str_prepend( s, "{" ); + } + else { + if ( esc_t != NOT_ESCAPED ) str_trimend( t, 1 ); + if ( esc_s == ESCAPED_QUOTES ) str_addchar( t, '\"' ); + else str_addchar( t, '}' ); + } + } + + str_strcat( s, t ); + if ( str_memerr( s ) ) return BIBL_ERR_MEMERR; + + /* ...remove concatenated string t */ + status = slist_remove( tokens, i+1 ); + if ( status!=SLIST_OK ) return BIBL_ERR_MEMERR; + + /* ...remove concatentation token '#' */ + status = slist_remove( tokens, i ); + if ( status!=SLIST_OK ) return BIBL_ERR_MEMERR; + + } + + return BIBL_OK; +} + +#define KEEP_QUOTES (0) +#define STRIP_QUOTES (1) + +static int +merge_tokens_into_data( str *data, slist *tokens, int stripquotes ) +{ + int i, esc_s; + str *s; + + for ( i=0; i<tokens->n; i++ ) { + + s = slist_str( tokens, i ); + esc_s = token_is_escaped( s ); + + if ( ( esc_s == ESCAPED_BRACES ) || + ( stripquotes == STRIP_QUOTES && esc_s == ESCAPED_QUOTES ) ) { + str_trimbegin( s, 1 ); + str_trimend( s, 1 ); + } + + str_strcat( data, s ); + + } + + if ( str_memerr( data ) ) return BIBL_ERR_MEMERR; + else return BIBL_OK; +} + +/* return NULL on memory error */ +static const char * +process_bibtexline( const char *p, str *tag, str *data, uchar stripquotes, loc *currloc ) +{ + slist tokens; + int status; + + str_empty( data ); + + slist_init( &tokens ); + + p = bibtex_tag( skip_ws( p ), tag ); + if ( p ) { + if ( str_is_empty( tag ) ) { + p = skip_line( p ); + goto out; + } + } + + if ( p && *p=='=' ) { + p = bibtex_data( p+1, &tokens, currloc ); + } + + if ( p ) { + status = replace_strings( &tokens ); + if ( status!=BIBL_OK ) p = NULL; + } + + if ( p ) { + status = string_concatenate( &tokens, currloc ); + if ( status!=BIBL_OK ) p = NULL; + } + + if ( p ) { + status = merge_tokens_into_data( data, &tokens, stripquotes ); + if ( status!=BIBL_OK ) p = NULL; + } + +out: + slist_free( &tokens ); + return p; +} + +/* process_ref() + * + */ +static int +process_ref( fields *bibin, const char *p, loc *currloc ) +{ + int fstatus, status = BIBL_OK; + str type, id, tag, data; + + strs_init( &type, &id, &tag, &data, NULL ); + + p = process_bibtextype( p, &type ); + p = process_bibtexid( p, &id ); + + if ( str_is_empty( &type ) || str_is_empty( &id ) ) goto out; + + fstatus = fields_add( bibin, "INTERNAL_TYPE", str_cstr( &type ), LEVEL_MAIN ); + if ( fstatus!=FIELDS_OK ) { status = BIBL_ERR_MEMERR; goto out; } + + fstatus = fields_add( bibin, "REFNUM", str_cstr( &id ), LEVEL_MAIN ); + if ( fstatus!=FIELDS_OK ) { status = BIBL_ERR_MEMERR; goto out; } + + while ( *p ) { + + p = process_bibtexline( p, &tag, &data, STRIP_QUOTES, currloc ); + if ( p==NULL ) { status = BIBL_ERR_MEMERR; goto out; } + + if ( !str_has_value( &tag ) || !str_has_value( &data ) ) continue; + + fstatus = fields_add( bibin, str_cstr( &tag ), str_cstr( &data ), LEVEL_MAIN ); + if ( fstatus!=FIELDS_OK ) { status = BIBL_ERR_MEMERR; goto out; } + + } +out: + strs_free( &type, &id, &tag, &data, NULL ); + return status; +} + +/* process_string() + * + * Handle lines like: + * + * '@STRING{TL = {Tetrahedron Lett.}}' + * + * p should point to just after '@STRING' + * + * In BibTeX, if a string is defined several times, the last one is kept. + * + */ +static int +process_string( const char *p, loc *currloc ) +{ + int n, status = BIBL_OK; + str s1, s2, *t; + + strs_init( &s1, &s2, NULL ); + + while ( *p && *p!='{' && *p!='(' ) p++; + if ( *p=='{' || *p=='(' ) p++; + + p = process_bibtexline( skip_ws( p ), &s1, &s2, KEEP_QUOTES, currloc ); + if ( p==NULL ) { status = BIBL_ERR_MEMERR; goto out; } + + if ( str_has_value( &s2 ) ) { + str_findreplace( &s2, "\\ ", " " ); + } else { + str_strcpyc( &s2, "" ); + } + + if ( str_has_value( &s1 ) ) { + n = slist_find( &find, &s1 ); + if ( n==-1 ) { + status = slist_add_ret( &find, &s1, BIBL_OK, BIBL_ERR_MEMERR ); + if ( status!=BIBL_OK ) goto out; + status = slist_add_ret( &replace, &s2, BIBL_OK, BIBL_ERR_MEMERR ); + if ( status!=BIBL_OK ) goto out; + } else { + t = slist_set( &replace, n, &s2 ); + if ( t==NULL ) { status = BIBL_ERR_MEMERR; goto out; } + } + } + +out: + strs_free( &s1, &s2, NULL ); + return status; +} + +/* bibtexin_processf() + * + * Handle '@STRING', '@reftype', and ignore '@COMMENT' + */ +static int +bibtexin_processf( fields *bibin, const char *data, const char *filename, long nref, param *pm ) +{ + loc currloc; + + currloc.progname = pm->progname; + currloc.filename = filename; + currloc.nref = nref; + + if ( !strncasecmp( data, "@STRING", 7 ) ) { + process_string( data+7, &currloc ); + return 0; + } else if ( !strncasecmp( data, "@COMMENT", 8 ) ) { + /* Not sure if these are real Bibtex, but not references */ + return 0; + } else { + process_ref( bibin, data, &currloc ); + return 1; + } +} + +/***************************************************** + PUBLIC: void bibtexin_cleanf() +*****************************************************/ + +static int +bibtex_protected( str *data ) +{ + if ( data->data[0]=='{' && data->data[data->len-1]=='}' ) return 1; + if ( data->data[0]=='\"' && data->data[data->len-1]=='\"' ) return 1; + return 0; +} + +static int +bibtex_split( slist *tokens, str *s ) +{ + int i, n = s->len, nbrackets = 0, status = BIBL_OK; + str tok; + + str_init( &tok ); + + for ( i=0; i<n; ++i ) { + if ( s->data[i]=='{' && ( i==0 || s->data[i-1]!='\\' ) ) { + nbrackets++; + str_addchar( &tok, '{' ); + } else if ( s->data[i]=='}' && ( i==0 || s->data[i-1]!='\\' ) ) { + nbrackets--; + str_addchar( &tok, '}' ); + } else if ( !is_ws( s->data[i] ) || nbrackets ) { + str_addchar( &tok, s->data[i] ); + } else if ( is_ws( s->data[i] ) ) { + if ( str_has_value( &tok ) ) { + status = slist_add_ret( tokens, &tok, BIBL_OK, BIBL_ERR_MEMERR ); + if ( status!=BIBL_OK ) goto out; + } + str_empty( &tok ); + } + } + if ( str_has_value( &tok ) ) { + status = slist_add_ret( tokens, &tok, BIBL_OK, BIBL_ERR_MEMERR ); + if ( status!=BIBL_OK ) goto out; + } + + for ( i=0; i<tokens->n; ++i ) { + str_trimstartingws( slist_str( tokens, i ) ); + str_trimendingws( slist_str( tokens, i ) ); + } +out: + str_free( &tok ); + return status; +} + +static int +bibtex_addtitleurl( fields *info, str *in ) +{ + int fstatus, status = BIBL_OK; + const char *p; + str s; + + str_init( &s ); + + /* ...skip past "\href{" and copy to "}" */ + p = str_cpytodelim( &s, in->data + 6, "}", 1 ); + if ( str_memerr( &s ) ) { status = BIBL_ERR_MEMERR; goto out; } + + /* ...add to URL */ + fstatus = fields_add( info, "URL", s.data, 0 ); + if ( fstatus!=FIELDS_OK ) { status = BIBL_ERR_MEMERR; goto out; } + + /* ...return deleted fragment to str in */ + (void) str_cpytodelim( &s, p, "", 0 ); + if ( str_memerr( &s ) ) { status = BIBL_ERR_MEMERR; goto out; } + str_swapstrings( &s, in ); + +out: + str_free( &s ); + return status; +} + +static int +is_url_tag( str *tag ) +{ + if ( str_has_value( tag ) ) { + if ( !strcasecmp( str_cstr( tag ), "url" ) ) return 1; + } + return 0; +} + +static int +is_name_tag( str *tag ) +{ + if ( str_has_value( tag ) ) { + if ( !strcasecmp( str_cstr( tag ), "author" ) ) return 1; + if ( !strcasecmp( str_cstr( tag ), "editor" ) ) return 1; + } + return 0; +} + +static void +bibtex_process_tilde( str *s ) +{ + const char *p; + char *q; + int n = 0; + + p = s->data; + q = s->data; + if ( !p ) return; + while ( *p ) { + if ( *p=='~' ) { + *q = ' '; + } else if ( *p=='\\' && *(p+1)=='~' ) { + n++; + p++; + *q = '~'; + } else { + *q = *p; + } + p++; + q++; + } + *q = '\0'; + s->len -= n; +} + +static void +bibtex_process_bracket( str *s ) +{ + char *p, *q; + int n = 0; + + p = q = s->data; + if ( !p ) return; + while ( *p ) { + if ( *p=='\\' && ( *(p+1)=='{' || *(p+1)=='}' ) ) { + n++; + p++; + *q = *p; + q++; + } else if ( *p=='{' || *p=='}' ) { + n++; + } else { + *q = *p; + q++; + } + p++; + } + *q = '\0'; + s->len -= n; +} + +static void +bibtex_cleantoken( str *s ) +{ + /* 'textcomp' annotations */ + str_findreplace( s, "\\textit", "" ); + str_findreplace( s, "\\textbf", "" ); + str_findreplace( s, "\\textsl", "" ); + str_findreplace( s, "\\textsc", "" ); + str_findreplace( s, "\\textsf", "" ); + str_findreplace( s, "\\texttt", "" ); + str_findreplace( s, "\\textsubscript", "" ); + str_findreplace( s, "\\textsuperscript", "" ); + str_findreplace( s, "\\emph", "" ); + str_findreplace( s, "\\url", "" ); + str_findreplace( s, "\\mbox", "" ); + + /* Other text annotations */ + str_findreplace( s, "\\it ", "" ); + str_findreplace( s, "\\em ", "" ); + + str_findreplace( s, "\\%", "%" ); + str_findreplace( s, "\\$", "$" ); + while ( str_findreplace( s, " ", " " ) ) {} + + /* 'textcomp' annotations that we don't want to substitute on output*/ + str_findreplace( s, "\\textdollar", "$" ); + str_findreplace( s, "\\textunderscore", "_" ); + + bibtex_process_bracket( s ); + bibtex_process_tilde( s ); + +} + +static int +bibtex_cleandata( str *tag, str *s, fields *info, param *p ) +{ + int i, status; + slist tokens; + str *tok; + if ( str_is_empty( s ) ) return BIBL_OK; + /* protect url from undergoing any parsing */ + if ( is_url_tag( tag ) ) return BIBL_OK; + slist_init( &tokens ); + status = bibtex_split( &tokens, s ); + if ( status!=BIBL_OK ) goto out; + for ( i=0; i<tokens.n; ++i ) { + tok = slist_str( &tokens, i ); + if ( bibtex_protected( tok ) ) { + if (!strncasecmp(tok->data,"\\href{", 6)) { + bibtex_addtitleurl( info, tok ); + } + } + if ( p->latexin && !is_name_tag( tag ) && !is_url_tag( tag ) ) + bibtex_cleantoken( tok ); + } + str_empty( s ); + for ( i=0; i<tokens.n; ++i ) { + tok = slist_str( &tokens, i ); + if ( i>0 ) str_addchar( s, ' ' ); + str_strcat( s, tok ); + } +out: + slist_free( &tokens ); + return status; +} + +static int +bibtexin_cleanref( fields *bibin, param *p ) +{ + int i, n, status; + str *t, *d; + n = fields_num( bibin ); + for ( i=0; i<n; ++i ) { + t = fields_tag( bibin, i, FIELDS_STRP_NOUSE ); + d = fields_value( bibin, i, FIELDS_STRP_NOUSE ); + status = bibtex_cleandata( t, d, bibin, p ); + if ( status!=BIBL_OK ) return status; + } + return BIBL_OK; +} + +static long +bibtexin_findref( bibl *bin, char *citekey ) +{ + int n; + long i; + for ( i=0; i<bin->nrefs; ++i ) { + n = fields_find( bin->ref[i], "refnum", LEVEL_ANY ); + if ( n==FIELDS_NOTFOUND ) continue; + if ( !strcmp( bin->ref[i]->data[n].data, citekey ) ) return i; + } + return -1; +} + +static void +bibtexin_nocrossref( bibl *bin, long i, int n, param *p ) +{ + int n1 = fields_find( bin->ref[i], "REFNUM", LEVEL_ANY ); + if ( p->progname ) fprintf( stderr, "%s: ", p->progname ); + fprintf( stderr, "Cannot find cross-reference '%s'", + bin->ref[i]->data[n].data ); + if ( n1!=FIELDS_NOTFOUND ) fprintf( stderr, " for reference '%s'\n", + bin->ref[i]->data[n1].data ); + fprintf( stderr, "\n" ); +} + +static int +bibtexin_crossref_oneref( fields *bibref, fields *bibcross ) +{ + int j, n, nl, ntype, fstatus, status = BIBL_OK; + char *type, *nt, *nv; + + ntype = fields_find( bibref, "INTERNAL_TYPE", LEVEL_ANY ); + type = ( char * ) fields_value( bibref, ntype, FIELDS_CHRP_NOUSE ); + + n = fields_num( bibcross ); + for ( j=0; j<n; ++j ) { + nt = ( char * ) fields_tag( bibcross, j, FIELDS_CHRP_NOUSE ); + if ( !strcasecmp( nt, "INTERNAL_TYPE" ) ) continue; + if ( !strcasecmp( nt, "REFNUM" ) ) continue; + if ( !strcasecmp( nt, "TITLE" ) ) { + if ( !strcasecmp( type, "Inproceedings" ) || + !strcasecmp( type, "Incollection" ) ) + nt = "booktitle"; + } + nv = ( char * ) fields_value( bibcross, j, FIELDS_CHRP_NOUSE ); + nl = fields_level( bibcross, j ) + 1; + fstatus = fields_add( bibref, nt, nv, nl ); + if ( fstatus!=FIELDS_OK ) { + status = BIBL_ERR_MEMERR; + goto out; + } + } +out: + return status; +} + +static int +bibtexin_crossref( bibl *bin, param *p ) +{ + int i, n, ncross, status = BIBL_OK; + fields *bibref, *bibcross; + + for ( i=0; i<bin->nrefs; ++i ) { + bibref = bin->ref[i]; + n = fields_find( bibref, "CROSSREF", LEVEL_ANY ); + if ( n==FIELDS_NOTFOUND ) continue; + fields_setused( bibref, n ); + ncross = bibtexin_findref( bin, (char*) fields_value( bibref, n, FIELDS_CHRP ) ); + if ( ncross==-1 ) { + bibtexin_nocrossref( bin, i, n, p ); + continue; + } + bibcross = bin->ref[ncross]; + status = bibtexin_crossref_oneref( bibref, bibcross ); + if ( status!=BIBL_OK ) goto out; + } +out: + return status; +} + +static int +bibtexin_cleanf( bibl *bin, param *p ) +{ + int status = BIBL_OK; + long i; + + for ( i=0; i<bin->nrefs; ++i ) + status = bibtexin_cleanref( bin->ref[i], p ); + bibtexin_crossref( bin, p ); + return status; +} + +/***************************************************** + PUBLIC: int bibtexin_typef() +*****************************************************/ + +static int +bibtexin_typef( fields *bibin, const char *filename, int nrefs, param *p ) +{ + int ntypename, nrefname, is_default; + char *refname = "", *typename = ""; + + ntypename = fields_find( bibin, "INTERNAL_TYPE", LEVEL_MAIN ); + nrefname = fields_find( bibin, "REFNUM", LEVEL_MAIN ); + if ( nrefname!=FIELDS_NOTFOUND ) refname = fields_value( bibin, nrefname, FIELDS_CHRP_NOUSE ); + if ( ntypename!=FIELDS_NOTFOUND ) typename = fields_value( bibin, ntypename, FIELDS_CHRP_NOUSE ); + + return get_reftype( typename, nrefs, p->progname, p->all, p->nall, refname, &is_default, REFTYPE_CHATTY ); +} + +/***************************************************** + PUBLIC: int bibtexin_convertf(), returns BIBL_OK or BIBL_ERR_MEMERR +*****************************************************/ + +static int +bibtex_matches_list( fields *bibout, char *tag, char *suffix, str *data, int level, + slist *names, int *match ) +{ + int i, fstatus, status = BIBL_OK; + str newtag; + + *match = 0; + if ( names->n==0 ) return status; + + str_init( &newtag ); + + for ( i=0; i<names->n; ++i ) { + if ( strcmp( str_cstr( data ), slist_cstr( names, i ) ) ) continue; + str_initstrc( &newtag, tag ); + str_strcatc( &newtag, suffix ); + fstatus = fields_add( bibout, str_cstr( &newtag ), str_cstr( data ), level ); + if ( fstatus!=FIELDS_OK ) { + status = BIBL_ERR_MEMERR; + goto out; + } + *match = 1; + goto out; + } + +out: + str_free( &newtag ); + return status; +} + +/**** bibtexin_btorg ****/ + +/* + * BibTeX uses 'organization' in lieu of publisher if that field is missing. + * Otherwise output as + * <name type="corporate"> + * <namePart>The organization</namePart> + * <role> + * <roleTerm authority="marcrelator" type="text">organizer of meeting</roleTerm> + * </role> + * </name> + */ + +static int +bibtexin_btorg( fields *bibin, int m, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int n, fstatus; + n = fields_find( bibin, "publisher", LEVEL_ANY ); + if ( n==FIELDS_NOTFOUND ) + fstatus = fields_add( bibout, "PUBLISHER", str_cstr( invalue ), level ); + else + fstatus = fields_add( bibout, "ORGANIZER:CORP", str_cstr( invalue ), level ); + if ( fstatus==FIELDS_OK ) return BIBL_OK; + else return BIBL_ERR_MEMERR; +} + +/**** bibtexin_btsente() ****/ + +/* + * sentelink = {file://localhost/full/path/to/file.pdf,Sente,PDF} + * + * Sente is an academic reference manager for MacOSX and Apple iPad. + */ + +static int +bibtexin_btsente( fields *bibin, int n, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int fstatus, status = BIBL_OK; + str link; + + str_init( &link ); + str_cpytodelim( &link, skip_ws( invalue->data ), ",", 0 ); + str_trimendingws( &link ); + if ( str_memerr( &link ) ) status = BIBL_ERR_MEMERR; + + if ( status==BIBL_OK && link.len ) { + fstatus = fields_add( bibout, "FILEATTACH", str_cstr( &link ), level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + + str_free( &link ); + return status; +} + +/**** bibtexin_linkedfile() ****/ + +static int +count_colons( char *p ) +{ + int n = 0; + while ( *p ) { + if ( *p==':' ) n++; + p++; + } + return n; +} + +static int +first_colon( char *p ) +{ + int n = 0; + while ( p[n] && p[n]!=':' ) n++; + return n; +} + +static int +last_colon( char *p ) +{ + int n = strlen( p ) - 1; + while ( n>0 && p[n]!=':' ) n--; + return n; +} + +/* + * file={Description:/full/path/to/file.pdf:PDF} + */ +static int +bibtexin_linkedfile( fields *bibin, int m, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int fstatus, status = BIBL_OK; + char *p = invalue->data; + int i, n, n1, n2; + str link; + + n = count_colons( p ); + if ( n > 1 ) { + /* A DOS file can contain a colon ":C:/....pdf:PDF" */ + /* Extract after 1st and up to last colons */ + n1 = first_colon( p ) + 1; + n2 = last_colon( p ); + str_init( &link ); + for ( i=n1; i<n2; ++i ) { + str_addchar( &link, p[i] ); + } + str_trimstartingws( &link ); + str_trimendingws( &link ); + if ( str_memerr( &link ) ) { + status = BIBL_ERR_MEMERR; + goto out; + } + if ( link.len ) { + fstatus = fields_add( bibout, "FILEATTACH", link.data, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } +out: + str_free( &link ); + } else { + /* This field isn't formatted properly, so just copy directly */ + fstatus = fields_add( bibout, "FILEATTACH", p, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + return status; + +} + +/**** bibtexin_howpublished() ****/ + +/* howpublished={}, + * + * Normally indicates the manner in which something was + * published in lieu of a formal publisher, so typically + * 'howpublished' and 'publisher' will never be in the + * same reference. + * + * Occassionally, people put Diploma thesis information + * into the field, so check that first. + * + * Returns BIBL_OK or BIBL_ERR_MEMERR + */ + +static int +bibtexin_howpublished( fields *bibin, int n, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int fstatus, status = BIBL_OK; + if ( !strncasecmp( str_cstr( invalue ), "Diplom", 6 ) ) { + fstatus = fields_replace_or_add( bibout, "GENRE:BIBUTILS", "Diploma thesis", level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + else if ( !strncasecmp( str_cstr( invalue ), "HSabilitation", 13 ) ) { + fstatus = fields_replace_or_add( bibout, "GENRE:BIBUTILS", "Habilitation thesis", level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + else if ( !strncasecmp( str_cstr( invalue ), "Licentiate", 10 ) ) { + fstatus = fields_replace_or_add( bibout, "GENRE:BIBUTILS", "Licentiate thesis", level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + else if ( is_embedded_link( str_cstr( invalue ) ) ) { + status = urls_split_and_add( str_cstr( invalue ), bibout, level ); + } + else { + fstatus = fields_add( bibout, "PUBLISHER", str_cstr( invalue ), level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + return status; +} + +/**** bibtexin_eprint() ****/ + +/* Try to capture situations like + * + * eprint="1605.02026", + * archivePrefix="arXiv", + * + * or + * + * eprint="13211131", + * eprinttype="medline", + * + * If we don't know anything, concatenate archivePrefix:eprint + * and push into URL. (Could be wrong) + * + * If no info, just push eprint into URL. (Could be wrong) + */ +static int +process_eprint_with_prefix( fields *bibout, char *prefix, str *value, int level ) +{ + int fstatus, status = BIBL_OK; + str merge; + + if ( !strcmp( prefix, "arXiv" ) ) { + fstatus = fields_add( bibout, "ARXIV", value->data, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + + else if ( !strcmp( prefix, "jstor" ) ) { + fstatus = fields_add( bibout, "JSTOR", value->data, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + + else if ( !strcmp( prefix, "medline" ) ) { + fstatus = fields_add( bibout, "MEDLINE", value->data, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + + else if ( !strcmp( prefix, "pubmed" ) ) { + fstatus = fields_add( bibout, "PMID", value->data, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + } + + /* ...if this is unknown prefix, merge prefix & eprint */ + else { + str_init( &merge ); + str_mergestrs( &merge, prefix, ":", value->data, NULL ); + fstatus = fields_add( bibout, "URL", merge.data, level ); + if ( fstatus!=FIELDS_OK ) status = BIBL_ERR_MEMERR; + str_free( &merge ); + } + + return status; +} +static int +process_eprint_without_prefix( fields *bibout, str *value, int level ) +{ + int fstatus; + + /* ...no archivePrefix, need to handle just 'eprint' tag */ + fstatus = fields_add( bibout, "URL", value->data, level ); + + if ( fstatus!=FIELDS_OK ) return BIBL_ERR_MEMERR; + else return BIBL_OK; +} + +static int +bibtexin_eprint( fields *bibin, int m, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + char *prefix; + int n; + + /* ...do we have an archivePrefix too? */ + n = fields_find( bibin, "ARCHIVEPREFIX", level ); + if ( n==FIELDS_NOTFOUND ) n = fields_find( bibin, "EPRINTTYPE", level ); + if ( n!=FIELDS_NOTFOUND ) { + prefix = fields_value( bibin, n, FIELDS_CHRP ); + return process_eprint_with_prefix( bibout, prefix, invalue, level ); + } + + /* ...no we don't */ + return process_eprint_without_prefix( bibout, invalue, level ); +} + +/**** bibtexin_keyword() ****/ + +/* Split keywords="" with semicolons. + * Commas are also frequently used, but will break + * entries like: + * keywords="Microscopy, Confocal" + * Returns BIBL_OK or BIBL_ERR_MEMERR + */ + +static int +bibtexin_keyword( fields *bibin, int m, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int fstatus, status = BIBL_OK; + const char *p; + str keyword; + + p = invalue->data; + str_init( &keyword ); + + while ( *p ) { + p = str_cpytodelim( &keyword, skip_ws( p ), ";", 1 ); + str_trimendingws( &keyword ); + if ( str_memerr( &keyword ) ) { + status = BIBL_ERR_MEMERR; + goto out; + } + if ( keyword.len ) { + fstatus = fields_add( bibout, "KEYWORD", keyword.data, level ); + if ( fstatus!=FIELDS_OK ) { + status = BIBL_ERR_MEMERR; + goto out; + } + } + } +out: + str_free( &keyword ); + return status; +} + +/* + * bibtex_names( bibout, newtag, field, level); + * + * split names in author list separated by and's (use '|' character) + * and add names + * + * returns BIBL_OK on success, BIBL_ERR_MEMERR on memory error + */ + +static int +bibtexin_person( fields *bibin, int m, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int begin, end, ok, n, etal, i, status, match; + slist tokens; + + /* If we match the asis or corps list add and bail. */ + status = bibtex_matches_list( bibout, outtag, ":ASIS", invalue, level, &(pm->asis), &match ); + if ( match==1 || status!=BIBL_OK ) return status; + status = bibtex_matches_list( bibout, outtag, ":CORP", invalue, level, &(pm->corps), &match ); + if ( match==1 || status!=BIBL_OK ) return status; + + slist_init( &tokens ); + + bibtex_split( &tokens, invalue ); + for ( i=0; i<tokens.n; ++i ) + bibtex_cleantoken( slist_str( &tokens, i ) ); + + etal = name_findetal( &tokens ); + + begin = 0; + n = tokens.n - etal; + while ( begin < n ) { + + end = begin + 1; + + while ( end < n && strcasecmp( slist_cstr( &tokens, end ), "and" ) ) + end++; + + if ( end - begin == 1 ) { + ok = name_addsingleelement( bibout, outtag, slist_cstr( &tokens, begin ), level, 0 ); + if ( !ok ) { status = BIBL_ERR_MEMERR; goto out; } + } else { + ok = name_addmultielement( bibout, outtag, &tokens, begin, end, level ); + if ( !ok ) { status = BIBL_ERR_MEMERR; goto out; } + } + + begin = end + 1; + + /* Handle repeated 'and' errors: authors="G. F. Author and and B. K. Author" */ + while ( begin < n && !strcasecmp( slist_cstr( &tokens, begin ), "and" ) ) + begin++; + } + + if ( etal ) { + ok = name_addsingleelement( bibout, outtag, "et al.", level, 0 ); + if ( !ok ) status = BIBL_ERR_MEMERR; + } + +out: + slist_free( &tokens ); + return status; + +} + +/**** bibtexin_title() ****/ + +/* bibtexin_titleinbook_isbooktitle() + * + * Normally, the title field of inbook refers to the book. The + * section in a @inbook reference is untitled. If it's titled, + * the @incollection should be used. For example, in: + * + * @inbook{ + * title="xxx" + * } + * + * the booktitle is "xxx". + * + * However, @inbook is frequently abused (and treated like + * @incollection) so that title and booktitle are present + * and title is now 'supposed' to refer to the section. For example: + * + * @inbook{ + * title="yyy", + * booktitle="xxx" + * } + * + * Therefore report whether or not booktitle is present as well + * as title in @inbook references. If not, then make 'title' + * correspond to the title of the book, not the section. + * + */ +static int +bibtexin_titleinbook_isbooktitle( fields *bibin, char *intag ) +{ + int n; + + /* ...look only at 'title="xxx"' elements */ + if ( strcasecmp( intag, "TITLE" ) ) return 0; + + /* ...look only at '@inbook' references */ + n = fields_find( bibin, "INTERNAL_TYPE", LEVEL_ANY ); + if ( n==FIELDS_NOTFOUND ) return 0; + if ( strcasecmp( fields_value( bibin, n, FIELDS_CHRP ), "INBOOK" ) ) return 0; + + /* ...look to see if 'booktitle="yyy"' exists */ + n = fields_find( bibin, "BOOKTITLE", LEVEL_ANY ); + if ( n==FIELDS_NOTFOUND ) return 0; + else return 1; +} + +static int +bibtexin_title( fields *bibin, int n, str *intag, str *invalue, int level, param *pm, char *outtag, fields *bibout ) +{ + int ok; + + if ( bibtexin_titleinbook_isbooktitle( bibin, intag->data ) ) level=LEVEL_MAIN; + ok = title_process( bibout, "TITLE", invalue->data, level, pm->nosplittitle ); + if ( ok ) return BIBL_OK; + else return BIBL_ERR_MEMERR; +} + +static void +bibtexin_notag( param *p, char *tag ) +{ + if ( p->verbose && strcmp( tag, "INTERNAL_TYPE" ) ) { + if ( p->progname ) fprintf( stderr, "%s: ", p->progname ); + fprintf( stderr, "Cannot find tag '%s'\n", tag ); + } +} + +static int +bibtexin_convertf( fields *bibin, fields *bibout, int reftype, param *p ) +{ + static int (*convertfns[NUM_REFTYPES])(fields *, int, str *, str *, int, param *, char *, fields *) = { + // Patch: Fix compilation on MSVC target + [ 0 ] = generic_null, + [ 1 ] = generic_null, + [ 2 ] = generic_null, + [ 3 ] = generic_null, + [ 4 ] = generic_null, + [ 5 ] = generic_null, + [ 6 ] = generic_null, + [ 7 ] = generic_null, + [ 8 ] = generic_null, + [ 9 ] = generic_null, + [ 10 ] = generic_null, + [ 11 ] = generic_null, + [ 12 ] = generic_null, + [ 13 ] = generic_null, + [ 14 ] = generic_null, + [ 15 ] = generic_null, + [ 16 ] = generic_null, + [ 17 ] = generic_null, + [ 18 ] = generic_null, + [ 19 ] = generic_null, + [ 20 ] = generic_null, + [ 21 ] = generic_null, + [ 22 ] = generic_null, + [ 23 ] = generic_null, + [ 24 ] = generic_null, + [ 25 ] = generic_null, + [ SIMPLE ] = generic_simple, + [ TITLE ] = bibtexin_title, + [ PERSON ] = bibtexin_person, + [ PAGES ] = generic_pages, + [ KEYWORD ] = bibtexin_keyword, + [ EPRINT ] = bibtexin_eprint, + [ HOWPUBLISHED ] = bibtexin_howpublished, + [ LINKEDFILE ] = bibtexin_linkedfile, + [ NOTES ] = generic_notes, + [ GENRE ] = generic_genre, + [ BT_SENTE ] = bibtexin_btsente, + [ BT_ORG ] = bibtexin_btorg, + [ URL ] = generic_url + }; + + int process, level, i, nfields, status = BIBL_OK; + str *intag, *invalue; + char *outtag; + + nfields = fields_num( bibin ); + for ( i=0; i<nfields; ++i ) { + + if ( fields_used( bibin, i ) ) continue; /* e.g. successful crossref */ + if ( fields_notag( bibin, i ) ) continue; + if ( fields_nodata( bibin, i ) ) continue; + + intag = fields_tag( bibin, i, FIELDS_STRP ); + invalue = fields_value( bibin, i, FIELDS_STRP ); + + if ( !translate_oldtag( str_cstr( intag ), reftype, p->all, p->nall, &process, &level, &outtag ) ) { + bibtexin_notag( p, str_cstr( intag ) ); + continue; + } + + status = convertfns[ process ] ( bibin, i, intag, invalue, level, p, outtag, bibout ); + if ( status!=BIBL_OK ) return status; + } + + if ( status==BIBL_OK && p->verbose ) fields_report( bibout, stderr ); + + return status; +} |