The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* ------------------------------------------------------------------------
@NAME       : bibparse.c
@DESCRIPTION: Parses a series of BibTeX files, with command-line options
              to control the string post-processing behaviour of the
              library.  Prints the parsed entries out in a slightly
              different form that should be dead easy to parse in any
              language (most punctuation and whitespace gone, format is
              fixed and strictly line-based).
@GLOBALS    : 
@CREATED    : May 1996, Greg Ward
@MODIFIED   : 
@VERSION    : $Id$
@COPYRIGHT  : Copyright (c) 1996-97 by Gregory P. Ward.  All rights reserved.

              This file is part of the btparse distribution (but not part
              of the library itself).  This is free software; you can
              redistribute it and/or modify it under the terms of the GNU
              General Public License as published by the Free Software
              Foundation; either version 2 of the License, or (at your
              option) any later version.
-------------------------------------------------------------------------- */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdarg.h>
#include <assert.h>
#include <btparse.h>

#include "getopt.h"                     /* for optind */
#include "args.h"

#define DEBUG 0

extern void dump_ast (char *msg, AST *root); /* stolen from btparse */

char *  Usage = "usage: bibparse [options] file [...]\n";
char *  Help = 
"\n"
"Options:\n"
"  -check         check syntax only (ie. don't print entries out)\n"
"  -noquote       don't quote strings [default]\n"
"  -quote         put quotes around strings (warning: not bulletproof)\n"
"  -convert       convert numeric values to strings\n"
"  -noconvert     don't\n"
"  -expand        expand macros [default]\n"
"  -noexand       don't\n"
"  -paste         paste strings together (ie. obey # operator) [default]\n"
"  -nopaste       don't\n"
"  -collapse      collapse whitespace within strings [default]\n"
"  -nocollapse    don't\n"
"\n"
"Default behaviour is \"fully processed\":\n"
"  -noquote -convert -expand -paste -collapse\n"
"\n";

#if 0
#if DEBUG
void dprintf (char *format, ...)
{
   va_list  arglist;

   va_start (arglist, format);
   vfprintf (stdout, format, arglist);
   va_end (arglist);
}
#else
void dprintf (char *format, ...) {}
#endif
#endif

/* ------------------------------------------------------------------------
@NAME       : print_assigned_entry()
@INPUT      : stream
              top
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Prints out a "field-assignment"-type entry (ie. a macro 
              definition or regular entry).
@GLOBALS    : 
@CALLS      : btparse traversal functions
@CALLERS    : print_entry()
@CREATED    : 1997/08/12, GPW
@MODIFIED   : 
-------------------------------------------------------------------------- */
static void
print_assigned_entry (FILE *stream, AST *top, boolean quote_strings)
{
   char  *type, *key;
   char  *field_name;
   AST   *field;

   type = bt_entry_type (top);
   key = bt_entry_key (top);

   fprintf (stream, "@%s", type);
   if (key) fprintf (stream, " %s", key);
   fputc ('\n', stream);

   field = NULL;
   while ((field = bt_next_field (top, field, &field_name)))
   {
      AST *   value;
      bt_nodetype nodetype;
      char *  text;
      boolean first;

      fprintf (stream, "%s=", field_name);

      value = NULL;
      first = TRUE;
      
      while ((value = bt_next_value (field, value, &nodetype, &text)))
      {
         if (!first) fputc ('#', stream);
         if (text) 
         {
            if (nodetype == BTAST_STRING && quote_strings)
               fprintf (stream, "{%s}", text);
            else
               fputs (text, stream);
         }
         first = FALSE;
      }

      fputc ('\n', stream);             /* newline between fields */
   }

   fputc ('\n', stream);                /* blank line to end the entry */
} /* print_assigned_entry() */


/* ------------------------------------------------------------------------
@NAME       : print_value_entry()
@INPUT      : stream
              top
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Prints out a "value-only" entry (ie. comment or preamble).
@GLOBALS    : 
@CALLS      : btparse traversal functions
@CALLERS    : print_entry()
@CREATED    : 1997/08/13, GPW
@MODIFIED   : 
-------------------------------------------------------------------------- */
static void
print_value_entry (FILE *stream, AST *top)
{
   char *  type;
   AST *   value;
   char *  text;

   type = bt_entry_type (top);
   fprintf (stream, "@%s\n", type);

   value = NULL;
      
   while ((value = bt_next_value (top, value, NULL, &text)))
   {
      if (text) fprintf (stream, "%s\n", text);
   }

   fputc ('\n', stream);
   
} /* print_value_entry() */


/* ------------------------------------------------------------------------
@NAME       : print_entry()
@INPUT      : 
@OUTPUT     : 
@RETURNS    : 
@DESCRIPTION: Prints a BibTeX entry in a very simplistic form by calling
              either print_assigned_entry() or print_value_entry().  These
              in turn work by calling the AST traversal routines in the
              btparse library, providing canonical examples of how to use
              these routines.
@GLOBALS    : 
@CALLS      : 
@CREATED    : 1997/01/22, GPW
@MODIFIED   : 1997/08/13, GPW: changed to differentiate between the two,
                               ahem, meta-meta-types of entries
-------------------------------------------------------------------------- */
static void
print_entry (FILE *stream, AST *top, boolean quote_strings)
{

#if DEBUG
   dump_ast ("print_entry: AST before traversing =\n", top);
#endif

   switch (bt_entry_metatype (top))
   {
      case BTE_MACRODEF:
      case BTE_REGULAR:
         print_assigned_entry (stream, top, quote_strings);
         break;

      case BTE_COMMENT:
      case BTE_PREAMBLE:
         print_value_entry (stream, top);
         break;

      default:
         fprintf (stderr, "warning: unknown entry type \"%s\"\n",
                  bt_entry_type (top));
         break;
   }
#if DEBUG
   dump_ast ("print_entry: AST after traversing =\n", top);
#endif
} /* print_entry() [2nd version] */


/* ------------------------------------------------------------------------
@NAME       : process_file
@INPUT      : filename
@OUTPUT     : 
@RETURNS    : true if there were no errors or only trivial errors
              false if there were serious errors
@DESCRIPTION: Parses an entire BibTeX file one entry at a time.  Each 
              entry is separately read, parsed, and printed back out
              to minimize memory use.
@GLOBALS    : 
@CALLS      : 
@CREATED    : Jan 1997, GPW
@MODIFIED   : 
@COMMENTS   : this *might* eventually wind up in the library, with
              a function pointer argument to specify what to do 
              to each entry
-------------------------------------------------------------------------- */
static int
process_file (char *filename, parser_options *options)
{
   FILE   *infile;
   AST    *cur_entry;
   boolean status, overall_status;

   /*
    * If a string was given, and it's *not* "-", then open that filename.
    * Otherwise just use stdin.
    */

   if (filename != NULL && strcmp (filename, "-") != 0)
   {
      infile = fopen (filename, "r");
      if (infile == NULL)
      {
         perror (filename);
         return 0;
      }
   }
   else
   {
      filename = "(stdin)";
      infile = stdin;
   }

   bt_set_stringopts (BTE_MACRODEF, options->string_opts);
   bt_set_stringopts (BTE_REGULAR, options->string_opts);
   bt_set_stringopts (BTE_COMMENT, options->string_opts);
   bt_set_stringopts (BTE_PREAMBLE, options->string_opts);
      
   overall_status = 1;                  /* assume success */
   while (1)
   {
      cur_entry = bt_parse_entry (infile, filename, 
                                  options->other_opts,
                                  &status);
      overall_status &= status;
      if (!cur_entry) break;
      if (!options->check_only)
         print_entry (stdout, cur_entry, options->quote_strings);
      if (options->dump_ast)
         dump_ast ("AST for whole entry:\n", cur_entry);
      bt_free_ast (cur_entry);
   }

   fclose (infile);
   return overall_status;

} /* process_file() */


int main (int argc, char *argv[])
{
   parser_options   *options;

   options = parse_args (argc, argv);
   bt_initialize ();

   if (argv[optind])            /* any leftover arguments (filenames) */
   {
      int i;

      for (i = optind; i < argc; i++)
         process_file (argv[i], options);
   }
   else
   {
      fprintf (stderr, "%s", Usage);
      fprintf (stderr, "%s", Help);
      fprintf (stderr, "Not enough arguments\n");
      exit (1);
   }

   bt_cleanup ();
   free (options);
   exit (bt_error_status (NULL));
}