/* #module    GloScan    "2-001"
 ***********************************************************************
 *                                                                     *
 * The software was developed at the Monsanto Company and is provided  *
 * "as-is".  Monsanto Company and the auther disclaim all warranties   *
 * on the software, including without limitation, all implied warran-  *
 * ties of merchantabilitiy and fitness.                               *
 *                                                                     *
 * This software does not contain any technical data or information    *
 * that is proprietary in nature.  It may be copied, modified, and     *
 * distributed on a non-profit basis and with the inclusion of this    *
 * notice.                                                             *
 *                                                                     *
 ***********************************************************************
 */
/*
 * Module Name:       GloScan
 *
 * Author:      R L Aurbach     CR&DS MIS Group    20-Aug-1986
 *
 * Function:
 *      Scan a file for glossary definitions and build the internal data
 *      structure for the data.
 *
 * Modification History:
 *
 * Version     Initials    Date         Description
 * ------------------------------------------------------------------------
 * 1-001        RLA     20-Aug-1986     Original Code
 * 1-002        RLA     25-Aug-1986     Make Glo_Parse_Item more sophisticated.
 * 2-001        F.H.    17-May-1991     converted to portable C
 */

/*
 * $Id: gloscan.c 3.0.1.2 1994/04/04 22:42:59 steppler Exp $
 *
 * $Log: gloscan.c $
 * Revision 3.0.1.2  1994/04/04  22:42:59  steppler
 * real official release now
 *
 * Revision 3.0.1.1  1994/04/04  18:00:23  steppler
 * corrected use of prototypes
 *
 * Revision 3.0  1994/04/03  22:14:05  steppler
 * official release
 * added env var support (GDFINPUTS)
 *
 * Revision 2.1.2.1  1994/04/03  19:00:28  steppler
 * upgrade to new revision only
 *
 * Revision 2.1.1.1  1994/04/02  19:08:24  steppler
 * use complete prototypes only
 *
 *
 */

/*
 * Module GloScan - Module-Wide Data Description Section
 *
 * Include Files:
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/file.h>
#include "GloDef.h"
/*
 * Module Definitions:
 */
/*
 * Global Declarations:
 */
int Glo_Scan_File(STRING_PTR file);
/*
 * Static Declarations:
 */
static int glo_copy_text(char *line, NODE_PTR node);
static int glo_match_label(char *token);
static int glo_link_node(NODE_PTR node);
static int glo_parse_item(char *line, char *label, char *item, int *ptr);
/*
 * External References:
 */
extern void idx_build_spell_string(char *desc);
extern NODE_PTR root;
extern STRING_PTR labels;
/*
 * Functions Called:
 */
/*
 * Function Glo_Scan_File - Documentation Section
 *
 * Discussion:
 *      Scan the specified glossary file for definitions which have labels which
 *      match the labels in the label list.  If a match is found, copy the
 *      information to a NODE in the data structure and delete the label from
 *      the label list.  Entries are placed in the data structure in alphabetic
 *      order.
 *
 * Calling Synopsis:
 *      status = Glo_Scan_File (file)
 *
 * Inputs:
 *      file        ->  is a STRING_PTR, passed by value.  It contains the
 *                      descriptor for the file specification string to be
 *                      used to open the file.
 *
 * Outputs:
 *      none
 *
 * Return Value:
 *      status      ->  is a boolean flag which indicates the result of the
 *                      operation.  It will have a value of TRUE unless an
 *                      error occurs which is fatal to the execution of the
 *                      program.
 *
 * Global Data:
 *      root        ->  additions may be made to the internal data structure.
 *
 *      labels      ->  if a definition is found, its entry in the label list is
 *                      removed.
 *
 * Files Used:
 *      The specified definition file is opened for read access.
 *
 * Assumed Entry State:
 *      none
 *
 * Normal Exit State:
 *      status == TRUE  The operation succeeded.  This means that no error which
 *                      is fatal to the operation of the program occurred, not
 *                      that the program proceeded without error.
 *
 * Error Conditions:
 *      status == FALSE A fatal error occurred.  The reason for the error should
 *                      be described in a message to the user
 *
 * Algorithm:
 *      A. If the labels list is empty, return immediately.
 *      B. Convert the file spec to an ASCIZ string and open the file.
 *      C. For each entry in the file,
 *          1. Locate the label.
 *          2. If the label list contains that label,
 *              a. Delete the label from the label list.
 *              b. Allocate a NODE.
 *              c. Fill in the NODE with the item and spell string.
 *              d. Fill in the NODE with definition lines.
 *              e. Chain the NODE into the data structure, using the spell
 *                 string to position it correctly.
 *      D. Close the file.
 *
 * Special Notes:
 *      none
 */
/*
 * Function Glo_Scan_File - Code Section
 */
int Glo_Scan_File(STRING_PTR file)
{
/*
 * Local Declarations
 */
    char dna[256];
    FILE *f = NULL;
    int counter = 0;
    char line[linesz];
    char token[linesz];
    char item[linesz];
    char *gdfenv;
    char gdfinputs[256];
    int ptr;
    int status;
    NODE_PTR node;
    int mode = FALSE;
/*
 * Module Body
 */
    if (labels == 0)
        return (TRUE);

/* Open the glossary definition file */
    gdfenv = getenv("GDFINPUTS");
    /* found non empty env variable */
    /* and filename does not contain a path description. */
    if (gdfenv && *gdfenv && !strpbrk(file->desc, "/:"))
    {
        char *start_ptr, *end_ptr;

        strcpy(gdfinputs, gdfenv);
        end_ptr = start_ptr = gdfinputs;
        do
        {
            end_ptr = strchr(start_ptr, ',');
            if (end_ptr)
                *end_ptr = '\0';
            *dna = '\0';
            /* not the current dir */
            if (strcmp(start_ptr, "."))
            {
                int len = strlen(start_ptr);

                strcpy(dna, start_ptr);
                /* append a slash to pathname? */
                if (start_ptr[len - 1] != ':' && start_ptr[len - 1] != '/')
                    strcat(dna, "/");
            }
            strcat(dna, file->desc);
            strcat(dna, ".gdf");
            if (f = fopen(dna, "r"))
                break;
            if (end_ptr)
                start_ptr = ++end_ptr;
        }
        while (end_ptr);
        free (file->desc);
        end_ptr = strstr(dna, ".gdf");
        if (end_ptr)
            *end_ptr = '\0';
        file->desc = strdup(dna);
        if (!file->desc)
        {
            printf("Out of memory!\n");
            return (TRUE);
        }
        /* failed */
        if (!f)
        {
            (void) printf("Could not find the glossary definition file %s\n",
                          file->desc);
            return (TRUE);
        }
    }
    else
    {
        (void) sprintf(dna, "%s.gdf", file->desc);
        if ((f = fopen(dna, "r")) == NULL)
        {
            (void) printf("Could not find the glossary definition file %s\n",
                          file->desc);
            return (TRUE);
        }
    }
    while (fgets(line, linesz, f) != 0)
    {
/*
 * If mode = FALSE, we are in discard mode.  In this mode, we throw away any
 * line which does not begin with "@entry".  If mode = TRUE, we are in copy
 * mode.  In this mode, we copy any text to the current node.  In either case,
 * if the line begins with "@entry", we must process it.
 */
        status = strncmp(line, "@entry{", 7);
        if (status != 0)
        {
            if (mode)
                (void) glo_copy_text(line, node);
            continue;
        }
/*
 * We've found a new entry in the file.  Begin processing it.
 * Search for the first line of a definition entry and parse out the label.
 */
        status = glo_parse_item(line, token, item, &ptr);
        if (!status)
        {
            mode = FALSE;
            continue;
        }
/* Search the label list for a label with the same name as the token. */
        status = glo_match_label(token);
        if (!status)
        {
            mode = FALSE;
            continue;
        }
        counter++;
/* Allocate and build the NODE structure */
        node = (NODE_PTR) malloc(sizeof(NODE));
        if (node == 0)
        {
            (void) printf("Could not allocate space for a new definition\n");
            return (FALSE);
        }
        node->next = 0;
        node->hdr = 0;
        node->spell = strdup(item);
        idx_build_spell_string(node->spell);
        node->item = strdup(item);
/* Link the NODE into the data structure in alphabetical order */
        status = glo_link_node(node);
        if (!status)
        {
            (void) printf("Duplicate glossary entry \'%s\' ignored\n", item);
            mode = FALSE;
            continue;
        }
/* If there is text on the @entry line, use it. */
        if (line[ptr] != '\0')
            (void) glo_copy_text(&line[ptr], node);
        mode = TRUE;
    }
/* End of file seen. */
    (void) fclose(f);
    if (counter == 0)
    {
        (void) printf("No definitions found in file %s.gdf\n", file->desc);
    }
    else
        (void) printf("%d definitions extracted from file %s.gdf\n",
                      counter, file->desc);
    return (TRUE);
}

/*
 * Function Glo_Link_Node - Documentation Section
 *
 * Discussion:
 *      Link a node into the node list in alphabetical order.
 *
 * Calling Synopsis:
 *      status = Glo_Link_Node (node)
 *
 * Inputs:
 *      node        ->  is a NODE_PTR, passed by reference.
 *
 * Outputs:
 *      none
 *
 * Return Value:
 *      status      ->  is a boolean integer.  TRUE means a successful link.
 *                      FALSE means a duplicate entry was found.
 *
 * Global Data:
 *      root        ->  the node is linked into the root list.
 *
 * Files Used:
 *      none
 *
 * Assumed Entry State:
 *      none
 *
 * Normal Exit State:
 *      status == TRUE  node was linked in.
 *
 * Error Conditions:
 *      status == FALSE a node with this spell string was already linked into
 *                      the list.  This node is NOT linked in.
 *
 * Algorithm:
 *      A. If the list is empty, link the node in at the root.
 *      B. Else,
 *          1. Beginning at the first node in the list,
 *              a. If spell-string < node-spell-string,
 *                  1. Get next node.
 *              b. If spell-string = node-spell-string,
 *                  1. Return FALSE.
 *              c. If spell-string > node-spell-string,
 *                  1. Link it in.
 *
 * Special Notes:
 *      none
 */
/*
 * Function Glo_Link_Node - Code Section
 */
static int glo_link_node(NODE_PTR node)
{
/*
 * Local Declarations
 */
    NODE_PTR old_node;
    NODE_PTR new_node;
    int status;
/*
 * Module Body
 */
    old_node = 0;
    new_node = root;
    while (new_node != 0)
    {
        status = strcmp(new_node->spell, node->spell);
        if (status < 0)
        {
            old_node = new_node;
            new_node = new_node->next;
            continue;
        }
        if (status == 0)
            return (FALSE);
        if (status > 0)
        {
            if (old_node == 0)
                root = node;
            else
                old_node->next = node;
            node->next = new_node;
            return (TRUE);
        }
    }
/* If the list is exhausted, link the node in at the end. */
    if (old_node == 0)
        root = node;
    else
        old_node->next = node;
    return (TRUE);
}

/*
 * Function Glo_Match_Label - Documentation Section
 *
 * Discussion:
 *      See if a label in the label list matches the current file entry.
 *      If it does, then deallocate the label entry and return TRUE.
 *      Otherwise, return FALSE.
 *
 * Calling Synopsis:
 *      status = Glo_Match_Label (token)
 *
 * Inputs:
 *      token       ->  is the file token string to be matched against the label
 *                      list.  It is an ASCIZ string.
 *
 * Outputs:
 *      none
 *
 * Return Value:
 *      status      ->  is a boolean integer.  TRUE indicates that this entry
 *                      corresponds to a valid label in the label list.  The
 *                      label list entry is removed.  FALSE indicates that
 *                      this entry does not match a label in the label list.
 *
 * Global Data:
 *      labels      ->  if the entry matches a label, that STRING in the label
 *                      list is removed.
 *
 * Files Used:
 *      none
 *
 * Assumed Entry State:
 *      none
 *
 * Normal Exit State:
 *      status == TRUE      We have a match.
 *      status == FALSE     No match.
 *
 * Error Conditions:
 *      none
 *
 * Algorithm:
 *      A. For each entry in the label list,
 *          1. Compare the label with the token.
 *          2. If they match,
 *              a. Unchain and deallocate the STRING.
 *              b. Return TRUE.
 *          3. Else,
 *              a. Get the next label.
 *      B. If no match,
 *          1. Return FALSE.
 *
 * Special Notes:
 *      none
 */
/*
 * Function Glo_Match_Label - Code Section
 */
static int glo_match_label(char *token)
{
/*
 * Local Declarations
 */
    int length = strlen(token);
    STRING_PTR old;
    STRING_PTR new;
/*
 * Module Body
 */
    if (labels == 0)
        return (FALSE);
    old = labels;
    new = labels;
    while (new != 0)
    {
/* Search the label list for a matching token */
        if ((strlen(new->desc)) != length)
        {
            old = new;
            new = new->next;
            continue;
        }
        if (strncmp(token, new->desc, length) != 0)
        {
            old = new;
            new = new->next;
            continue;
        }
/* Found the matching token.  Delete the entry in the labels list. */
        if (new == labels)
            labels = new->next;
        else
            old->next = new->next;
        (void) free((char *) new->desc);
        (void) free((char *) new);
        return (TRUE);
    }
    return (FALSE);
}

/*
 * Function Glo_Parse_Item - Documentation Section
 *
 * Discussion:
 *      Parse the label and item strings from a line with the format
 *          @entry{label,item}[text]
 *      leaving the pointer pointing to the optional text string.
 *
 * Calling Synopsis:
 *      status = Glo_Parse_Item (line, label, item, ptr)
 *
 * Inputs:
 *      line        ->  is the text line to parse.  It is an ASCIZ text string.
 *
 *
 * Outputs:
 *      label       ->  is the label text string.  It is an ASCIZ text string.
 *
 *      item        ->  is the item text string.  It is an ASCIZ text string.
 *
 *      ptr         ->  is an integer index value, passed by reference.  It is
 *                      the array index of the next character of the line
 *                      string, after the closing "}".
 *
 * Return Value:
 *      status      ->  is a result indicator.  If TRUE, the label and item
 *                      elements were found.  If FALSE, there was a problem
 *                      with the parse operation.
 *
 * Global Data:
 *      none
 *
 * Files Used:
 *      none
 *
 * Assumed Entry State:
 *      none
 *
 * Normal Exit State:
 *      status == TRUE  Operation succeeded.  All output variables were
 *                      returned.
 *
 * Error Conditions:
 *      status == FALSE Operation failed due to a missing syntax element.
 *                      None of the output variables is trustworthy.
 *
 * Algorithm:
 *      A. Parse out the label.  It begins with the first character after the
 *         leading "{" and is terminated by a ",".
 *      B. Find the beginning of the item.  It is starts at the first
 *         non-whitespace character after the ",".
 *      C. Parse out the item.  Internally, braces are allowed as long as they
 *         properly match.
 *
 * Special Notes:
 *      none
 */
/*
 * Function Glo_Parse_Item - Code Section
 */
static int glo_parse_item(char *line, char *label, char *item, int *ptr)
{
/*
 * Local Declarations
 */
    int i;
    int brace;
    char x;
/*
 * Module Body
 */
/* Copy the label to the output string. */
    i = 0;
    brace = 1;
    *ptr = 7;
    while (TRUE)
    {
        x = line[(*ptr)++];
        if (x == '\0')
            return (FALSE);
        if (x == '{')
            if (line[*ptr - 2] != '\\')
                brace++;
        if (x == '}')
        {
            if (line[*ptr - 2] != '\\')
                brace--;
            if (brace <= 0)
                break;
        }
        if (x == ',')
            break;
        label[i++] = x;
    }
    label[i] = '\0';
/* Find the beginning of the item string. */
    while (isspace(line[*ptr]) != 0)
        (*ptr)++;
/* Copy the item to the output string. */
    i = 0;
    while (brace > 0)
    {
        x = line[(*ptr)++];
        if (x == '\0')
            return (FALSE);
        if (x == '{')
            if (line[*ptr - 2] != '\\')
                brace++;
        if (x == '}')
        {
            if (line[*ptr - 2] != '\\')
                brace--;
            if (brace <= 0)
                break;
        }
        item[i++] = x;
    }
    item[i] = '\0';
/* Check to see if the item is missing. If it is, default to the label. */
    if (i == 0)
        (void) strcpy(item, label);
    return (TRUE);
}

/*
 * Function Glo_Copy_Text - Documentation Section
 *
 * Discussion:
 *      Create a STRING containing the current line of text and chain it in
 *      to the current node's definition list.
 *
 * Calling Synopsis:
 *      status = Glo_Copy_Text (line, node)
 *
 * Inputs:
 *      line        ->  is the current line of text.  It is an ASCIZ string.
 *
 *      node        ->  is the NODE_PTR for the current glossary entry.
 *
 * Outputs:
 *      none
 *
 * Return Value:
 *      status      ->  is a boolean integer which reflects success or
 *                      failure.  TRUE will be returned unless there is a
 *                      failure to allocate the string.
 *
 * Global Data:
 *      none
 *
 * Files Used:
 *      none
 *
 * Assumed Entry State:
 *      none
 *
 * Normal Exit State:
 *      status == TRUE      success.
 *
 * Error Conditions:
 *      status == FALSE     virtual memory allocation failure.
 *
 * Algorithm:
 *      A. Allocate a STRING data structure.
 *      B. If the input string has a non-zero length,
 *          1. Copy the string to the STRING data structure.
 *      C. Chain the structure in to the end of the hdr structure.
 *
 * Special Notes:
 *      none
 */
/*
 * Function Glo_Copy_Text - Code Section
 */
static int glo_copy_text(char *line, NODE_PTR node)
{
/*
 * Local Declarations
 */
    int length;
    STRING_PTR old;
    STRING_PTR text;
/*
 * Module Body
 */
    length = strlen(line);
    if (line[length - 1] == '\n')
        length--;
    if (length > 0)
    {
        text = (STRING_PTR) malloc(sizeof(STRING));
        if (text == 0)
            return (FALSE);
        text->next = 0;
        text->desc = strdup(line);
        if (node->hdr == 0)
            node->hdr = text;
        else
        {
            old = node->hdr;
            while (old->next != 0)
                old = old->next;
            old->next = text;
        }
    }
    return (TRUE);
}
