/* Time-stamp: <97/01/15 20:09:35 john> */

/* 
# Purpose: Parse an attribute file (see <a href="../doc/authoring/format.html">../doc/authoring/format.html</a>), producing entry structures.
   This file may later be interfaced to ELK. */

#include <stdio.h>
#include "attr.h"
#include "style.h"		/* required by readattr.h */
#include "misc.h"
#include "readattr.h"
#include "readbuffers.h"

/* #define debug 1 */
/* #define debug_entry 1 */

/* Return a given attribute of an entry.
 */

char *get_attr(struct entry *entry, char *key)
{
  struct attribute *attr = entry->attributes;

  while (attr != NULL)
    {
      if (strcmp(key, attr->key) == 0)
	{
	  return attr->value;
	}
      attr = attr->next;
    }
  return NULL;
}

int get_attr_integer(struct entry *entry, char *key, int default_int)
{
  char *nstr = get_attr(entry, key);
  int result = default_int;
  if ((nstr == NULL) || (sscanf(nstr, "%d", &result) != 1))
    return default_int;
  return result;
}

/* Return a given attribute of an entry, and remove it from that
   entry's attribute list; useful for transferring things from one
   entry to another.
 */

char *extract_attr(struct entry *entry, char *key)
{
  struct attribute *attr = entry->attributes;
  struct attribute **prev = &(entry->attributes);

  while (attr != NULL)
    {
      if (strcmp(key, attr->key) == 0)
	{
	  char *value = attr->value;
#if debug
	  printf("\nExtract: Found %s=%s, prev=%#X\n", attr->key, attr->value, prev);
#endif
	  Free(attr->key);
	  *prev = attr->next;
	  Free(attr);
#ifdef debug
	  printf("Returning %s<br>\n\n", value);
#endif
	  return value;
	}
      prev = &(attr->next);
      attr = attr->next;
    }
#ifdef debug
  printf("Not found<br>\n\n");
#endif
  return NULL;
}

/* Set an attribute of an entry, overwriting the old value of that
   attribute if there was one.
 */

int set_attr(struct entry *entry, char *key, char *value)
{
  struct attribute *attr = entry->attributes;

  while (attr != NULL)
    {
      if (strcmp(key, attr->key) == 0)
	{
	  char *new_value = (char*)malloc(strlen(value)+1);
	  if (new_value == NULL) return 0;
	  strcpy(new_value, value);
	  Free(attr->value);
	  attr->value = new_value;
	  return 1;
	}
      attr = attr->next;
    }
  {
    char *new_value = (char*)malloc(strlen(value)+1);
    if (new_value == NULL) return 0;
    {
      char *new_key = (char*)malloc(strlen(key)+1);
      if (new_key == NULL)
	{
	  Free(new_value);
	  return 0;
	}
      {
	struct attribute *new_attr = (struct attribute*)malloc(sizeof(struct attribute));
	if (new_attr == NULL)
	  {
	    Free(new_value);
	    Free(new_key);
	    return 0;
	  }
	strcpy(new_value, value);
	strcpy(new_key, key);
	new_attr->key = new_key;
	new_attr->value = new_value;
	new_attr->next = entry->attributes;
	entry->attributes = new_attr;
      }
    }
  }
  return 1;
}

/* Merge the attributes of two entries. If an attribute of the same
   name occurs in each, use the value from the second one. They all
   end up in the first one, including any present in only one or the
   other.
*/

void merge_entries(struct entry *extant, struct entry *incoming)
{
  struct attribute *incoming_attribute;
  struct attribute *next_incoming_attribute;

  if (incoming != NULL)
    {
      for (incoming_attribute = incoming->attributes;
	   incoming_attribute != NULL;
	   incoming_attribute = next_incoming_attribute)
	{
	  char *old_value = extract_attr(extant, incoming_attribute->key);

	  if ((strcmp(incoming_attribute->key, "name") == 0) &&
	      (get_attr(extant, "old-name") == NULL))
	    {
	      set_attr(extant, "old-name", old_value);
	    }

	  if (old_value != NULL) Free(old_value);

	  next_incoming_attribute = incoming_attribute->next;
	  incoming_attribute->next = extant->attributes;
	  extant->attributes = incoming_attribute;
	}
    }
  default_check_entry(extant, 0);
}

void free_attributes(struct attribute *attributes)
{
  while (attributes != NULL)
    {
      struct attribute *next_attr = attributes->next;
      if (attributes->key != NULL) Free(attributes->key);
      if (attributes->value) Free(attributes->value);
      Free(attributes);
      attributes = next_attr;
    }
}

/* Return a completely new copy of a list of attributes.
 */

static int copy_attributes(struct attribute *original,
			   struct attribute **pcopy)
{
  struct attribute *head = NULL;
  struct attribute *tail = NULL;
  struct attribute *old;
  
  for (old = original;
       old != NULL;
       old = old->next)
    {
      struct attribute *new = (struct attribute*)malloc(sizeof(struct attribute));
      char *new_key = (char*)malloc(strlen(old->key)+1);
      char *new_val = (char*)malloc(strlen(old->value)+1);
      if ((new == NULL) || (new_key == NULL) || (new_val == NULL))
	{
	  if (new != NULL) free(new);
	  if (new_key != NULL) free(new_key);
	  if (new_val != NULL) free(new_val);
	  free_attributes(head);
	  return 0;
	}
      strcpy(new_key, old->key);
      strcpy(new_val, old->value);
      new->key = new_key;
      new->value = new_val;
      new->next = NULL;
      if (tail != NULL)
	{
	  tail->next = new;
	} else {
	  head = new;
	}
      tail = new;
    }
  *pcopy = head;
  return 1;
}

void free_entries(struct entry *entries)
{
  while (entries != NULL)
    {
      struct entry *next_entry = entries->next;
      char *value;
      free_attributes(entries->attributes);
      value = entries->name;
      if (value != NULL) Free(value);
      value = entries->type;
      if (value != NULL) Free(value);
      Free(entries);
      entries = next_entry;
    }
}

static char *knockout_this_page_name = NULL;
static char *knockout_this_page_type = NULL;

static int debug_knockout = 0;
static int knockout_count = 0;
static int total_knockout_count = 0;

static int knockout_checker(struct entry *new_entry, int entry_count)
{
  char *which_page_name;
  char *which_page_type;

  /* check it is a valid entry, but it needn't have a direction, so
     pretend it's the first one in a file */
  if (default_check_entry(new_entry, 0) == ENTRY_BAD)
    {
      if (debug_knockout)
	{
	  printf("Pre_check for knockout (%s,%s) failed\n",
		 new_entry->type, new_entry->name);
	}
#if 0
      /* fussy */
      return ENTRY_BAD;
#else
      /* robust */
      return ENTRY_SKIP;
#endif
    }

  which_page_name = get_attr(new_entry, "pagename");
  which_page_type = get_attr(new_entry, "pagetype");

  if (debug_knockout)
    {
      printf("checking knockout for (%s,%s), against (%s,%s)\n",
	     which_page_type, which_page_name,
	     knockout_this_page_type, knockout_this_page_name);
    }

  if ((which_page_name != NULL) &&
      (strcmp(knockout_this_page_name, which_page_name) == 0) &&
      (which_page_type != NULL) &&
      (strcmp(knockout_this_page_type, which_page_type) == 0))
    {
      if (debug_knockout)
	{
	  printf("Yes! got update for ``%s''\n", new_entry->name);
	}
      return ENTRY_OK;
    }
  knockout_count++;
  total_knockout_count++;
  return ENTRY_SKIP;
}

static int addenda_checker(struct entry *new_entry, int entry_count)
{
  char *which_page_name;
  char *which_page_type;

  if (default_check_entry(new_entry, entry_count) == ENTRY_BAD)
    {
      return ENTRY_BAD;
    }

  which_page_name = get_attr(new_entry, "pagename");
  which_page_type = get_attr(new_entry, "pagetype");

  if (debug_knockout)
    {
      printf("is addendum (%s,%s) for (%s,%s)? ",
	     which_page_type, which_page_name,
	     knockout_this_page_type, knockout_this_page_name);
    }

  if ((which_page_name != NULL) &&
      (strcmp(knockout_this_page_name, which_page_name) == 0) &&
      (which_page_type != NULL) &&
      (strcmp(knockout_this_page_type, which_page_type) == 0))
    {
      if (debug_knockout)
	{
	  printf("Yes!\n");
	}
      return ENTRY_OK;
    }
      if (debug_knockout)
	{
	  printf("no\n");
	}

  return ENTRY_SKIP;
}

struct entry *find_matching_entry_for_merge(struct entry *kn,
					    struct entry *ex,
					    char *namefield_1,
					    char *namefield_2)
{
  char *kn_name = get_attr(kn, namefield_1);

  if ((kn_name == NULL) && (namefield_2 != NULL))
    {
      kn_name = get_attr(kn, namefield_2);
    
      if (kn_name == NULL)
	{
	  return NULL;
	}
    }

  if (debug_knockout) { printf("KN: %s; trying against:", kn_name); }

  for (;
       ex != NULL;
       ex = ex->next)
    {
      char *ex_name = get_attr(ex, "old-name");

      if (ex_name == NULL)
	{
	  ex_name = get_attr(ex, "name");
	}
      if (ex_name == NULL)
	{
	  continue;
	}

      if (debug_knockout) { printf("%s, ", ex_name); }

      if (strcmp(ex_name, kn_name) == 0)
	{
	  if (debug_knockout) { printf("That will do!\n"); }
	  return ex;
	}
    }

  if (debug_knockout)
    {
      printf("Could not find entry matching %s to update\n", kn_name);
    }

  return NULL;
}

void add_added_entries(struct entry *extant,
		       char *pagefile_dir,
		       char *pagefile_name,
		       char *addenda_dir,
		       char *addenda_file,
		       int debug)
{
  struct entry *addenda = NULL;
  struct entry *addendum;
  struct entry *next_addendum;

  knockout_this_page_name = pagefile_name;
  knockout_this_page_type = pagefile_dir;
  debug_knockout = debug;

  if (!read_attribute_file(addenda_dir, addenda_file, NULL,
			   &addenda,
			   NULL,
			   addenda_checker,
			   1, debug))
    {
      if (debug_knockout) { printf("Could not read addenda file\n"); }
      knockout_this_page_name = NULL;
      knockout_this_page_type = NULL;
      debug_knockout = 0;
      return;
    }

  if (debug)
    {
      printf("Read addenda file\n");
    }

#if 1
  for (addendum = addenda,
       next_addendum = ((addendum!=NULL) ?
			addendum->next :
			NULL);
       addendum != NULL;
       addendum = next_addendum,
       next_addendum=((addendum!=NULL) ?
		      addendum->next :
		      NULL))
    {
#if 0
      char *test_field;
      printf("addendum at %#X, next at %#X\n", addendum, next_addendum);
      test_field =  get_attr(addendum, "above-me");
      printf("above-me = %s\n", test_field!=NULL ? test_field : "not specified");
#else
      struct entry *above;
      char *above_name = get_attr(addendum, "above-me");

      if (debug)
	{
	  char *add_name = addendum->name;
	  if (add_name == NULL) add_name="anon";
	  printf("Processing addendum %s\n", add_name);
	}

      if (above_name == NULL)
	{
	  if (debug)
	    {
	      printf("Found addendum which does not mark its place\n");
	    }
	  continue;
	}

      above = find_matching_entry_for_merge(addendum,
					    extant,
					    "above-me", NULL);

      if (above == NULL)
	{
	  if (debug)
	    {
	      printf("Could not place addendum below %s\n",
		     above_name);
	    }
	  continue;
	}
      addendum->next = above->next;
      above->next = addendum;
      if (debug)
	{
	  printf("Placed addendum below %s\n",
		 above_name);
	}
#endif
    }
#endif

}

/* Go through the extant list, knocking out and replacing any which have
   the same name (and the right pagefile) as one in knockout_file. */
void knockout_entries(struct entry *extant,
		      char *pagefile_dir,
		      char *pagefile_name,
		      char *knockout_dir,
		      char *knockout_file,
		      int debug)
{
  struct entry *knockouts = NULL;
  struct entry *kn;

  int knockout_error = READ_OK;

  /* for passing to callback in reading the knockout file */
  knockout_this_page_name = pagefile_name;
  knockout_this_page_type = pagefile_dir;
  debug_knockout = debug;
  knockout_count = 0;
  total_knockout_count = 0;

  if (!read_attribute_file(knockout_dir, knockout_file, NULL,
			   &knockouts, &knockout_error,
			   knockout_checker,
			   1, debug))
    {
      if (debug_knockout) { printf("Could not read knockouts file\n"); }

      report_read_error(knockout_error, knockout_dir, knockout_file);

      knockout_this_page_name = NULL;
      knockout_this_page_type = NULL;

      debug_knockout = 0;
#if 1
      return;
#endif
    }

  if (debug_knockout)
    {
      printf("Read %d knockouts from knockout file, %d of them for this file\n",
	     total_knockout_count, knockout_count);
    }

  for (kn = knockouts;
       kn != NULL;
       kn = kn->next)
    {
      struct entry *ex;

      if (debug_knockout)
	{
	  printf("Trying to find match for knockout %s(%s)\n",
		 get_attr(kn, "old-name"), get_attr(kn, "name"));
	}

      ex = find_matching_entry_for_merge(kn, extant, "old-name", "name");

      if (ex != NULL)
	{

	  if (debug_knockout)
	    {
	      printf("Got one, merging with existing entry\n");
	    }

	  merge_entries(ex, kn);
	}
    }
#if 0
  free_entries(knockouts);
#endif

  knockout_this_page_name = NULL;
  knockout_this_page_type = NULL;
  debug_knockout = 0;
  return;
}

static void error_show_entry(struct entry *entry)
{
  struct attribute *attr;

  if (entry == NULL)
    {
      fprintf(stderr, "Entry in question is null.\n");
    } else {
      fprintf(stderr, "Entry in question:\n");

      for (attr = entry->attributes;
	   attr != NULL;
	   attr = attr->next)
	{
	  fprintf(stderr, "%s=\"%s\"\n", attr->key, attr->value);
	}
    }
}

/* check that a string is safe for inclusion in a filename */
int vet_name(char *string)
{
  int i;
  char c;

  for (i = 0;
       (c = string[i]) != '\0';
       i++)
    {
      switch (c)
	{
	case '.':
	case '/':
	case '\\':
	case ':':
	case '<':
	case '>':
	case '*':
	case '?':
	case '\"':
	case '\'':
	  return 0;
	}
    }
  return (i <= 500);
}

int default_check_entry(struct entry *new_entry, int entry_count)
{
  char *attr=get_attr(new_entry, "name");
#ifdef debug
  printf("Checking entry %d: %s\n", entry_count, attr);
#endif
  if (attr == NULL)
    {
      error_message("No name\n", new_entry);
      return ENTRY_BAD;
    } else {
      new_entry->name = attr;
    }
#ifdef debug
  printf("Reading %s\n", new_entry->name);
#endif
  attr=get_attr(new_entry, "type");
  if (attr == NULL)
    {
      error_message("No type\n", new_entry);
      return ENTRY_BAD;
    } else {
      new_entry->type = attr;
    }

  attr=get_attr(new_entry, "max-height");

  if (attr != NULL)
    {
      int n;
      if (sscanf(attr, "%d", &n) != 1)
	{
	  error_message("bad max_height", new_entry);
	  return ENTRY_BAD;
	} else {
	  new_entry->max_height = n;
	}
    }


  attr=get_attr(new_entry, "direction");

  if (attr == NULL)
    {
      if (entry_count == 0)
	{
	  new_entry->column = 0;
	} else {
	  error_message("No direction\n", new_entry);
	  return ENTRY_BAD;
	}
    } else {
      int direction;

      if (strcmp(attr,"left") == 0)
	{
	  direction = -1;
	} else if (strcmp(attr,"right") == 0)
	  {
	    direction = 1;
	  } else 		
	    if (strcmp(attr,"ahead") == 0)
	      {
		direction = 0;
	      } else {
		if (entry_count == 1)
		  {
		    direction = 0;
		  } else {
		    error_message("bad direction\n", new_entry);
		    return ENTRY_BAD;
		  }
	      }

      attr = get_attr(new_entry, "rank");
      if (attr == NULL)
	{
	  new_entry->column = direction;
	} else {
	  int n;
	  if (sscanf(attr, "%d", &n) != 1)
	    {
	      error_message("bad rank", new_entry);
	      return ENTRY_BAD;
	    } else {
	      new_entry->column = direction * n;
	    }
	}
    }
  return ENTRY_OK;
}

int merger_check_entry(struct entry *new_entry, int entry_count)
{
  char *attr=get_attr(new_entry, "name");
#ifdef debug
  printf("Checking entry %d: %s\n", entry_count, attr);
#endif
  if (attr == NULL)
    {
      error_message("No name\n", new_entry);
      return ENTRY_BAD;
    } else {
      new_entry->name = attr;
    }
#ifdef debug
  printf("Reading %s\n", new_entry->name);
#endif
  attr=get_attr(new_entry, "type");
  if (attr == NULL)
    {
      error_message("No type\n", new_entry);
      return ENTRY_BAD;
    } else {
      new_entry->type = attr;
    }
  return ENTRY_OK;
}

int make_entry(struct attribute *head_attribute,
	       struct entry **result,
	       struct entry **tail_entry,
	       int *entry_count,
	       int (*checker)(struct entry *new_entry, int entry_count))
{
  char *attr;
  int checked;
  struct entry *new_entry = (struct entry*)malloc(sizeof(struct entry));

  if (new_entry == NULL)
    {
      error_message("out of memory\n", NULL);
      free_entries(*result);
      return 0;
    }

  new_entry->next = NULL;
  new_entry->grid = NULL;
  new_entry->attributes = head_attribute;
  new_entry->inclusions_included = 0;

  new_entry->width = 1;
  new_entry->height = 1;
  new_entry->max_height = MAX_MAX_HEIGHT;
  new_entry->left = -1;
  new_entry->top = -1;

  checked = ((checker)(new_entry, *entry_count));
	
  switch (checked)
    {
    case ENTRY_OK:
      if (*tail_entry == NULL)
	{
	  *result = new_entry;
	} else {
#if 0
	  new_entry->next = (*tail_entry)->next;
#endif
	  (*tail_entry)->next = new_entry;
	}
      *tail_entry = new_entry;

      (*entry_count)++;
      return ENTRY_OK;
    case ENTRY_BAD:
      Free(new_entry);
      return ENTRY_BAD;
    case ENTRY_SKIP:
      Free(new_entry);
      return ENTRY_OK;
    }

  return checked;
}

#define next_char(_s_,_f_) (((_s_)!=NULL) ? (*((_s_)++)) : (getc(_f_)))

#define more_chars(_s_,_f_) (((_s_)!=NULL) ? ((*(_s_)) != '\0') : (!feof(_f_)))

/* Reads from string if string != NULL, else stream */
int read_attributes_string_or_stream(char *string,
				     FILE *stream,
				     struct entry **result,
				     int *read_error,
				     int (*checker)(struct entry *new_entry,
						    int entry_count),
				     int read_all,
				     int debug_reading_attributes)
{
  int c = ' ';
  int pc;

  int keyi = 0;
  int vali = 0;

  int latest_was_unquoted = 0;

  int entry_count = 0;

  struct entry *tail_entry = NULL;
  struct attribute *head_attribute = NULL;
  struct attribute *tail_attribute = NULL;

#if 0
  debug_reading_attributes=1;
#endif

  if ((string != NULL) && (stream != NULL))
      {
	error_header("Internal inconsistency!");
	printf("read_attributes_string_or_stream given string and stream! Ignoring stream.\n");
      } else {
	if ((string == NULL) && (stream == NULL))
	  {
	    error_header("Internal inconsistency!");
	    printf("read_attributes_string_or_stream given neither string nor stream! Returning 0.");
	    if (read_error != NULL) *read_error = READ_NO_DATA_SOURCE;
	    error_trailer();
	    return 0;
	  }
      }

#ifdef debug
  printf("In read_attributes_string_or_stream(%#X, %#X)\n", string, stream);
  if (string != NULL)
    {
      printf("Reading from <begin>\"%s\"<end>\n", string);
    } else {
      printf("Reading from stream, currently feof=%#X\n", feof(stream));
    }
#endif
  while (more_chars(string, stream))
    {
      pc = c;
      c = next_char(string, stream);

#define optional_quotes 1

      switch (c)
	{
	case '=':
	  {
	    int vc;
	    int pvc = ' ';
	    int got_it = 0;
	    int in_quotes = 0;
	    keybuf[keyi] = '\0';
	    vali = 0;

	    while ((!got_it) && (more_chars(string, stream)))
	      {
		vc = next_char(string, stream);
		switch (vc)
		  {
		  case '\"':
		    got_it = 1;
		    in_quotes = 1;
		    break;
		  case EOF:
		  case '\0':
		    lex_error_message("EOF while looking for value start at key=\"%s\"\n");
		    if (read_error != NULL) *read_error = READ_EOF_AFTER_KEY;
		    return 0;
#ifdef optional_quotes
		  default:
		    got_it = 1;
		    in_quotes = 0;
		    valbuf[vali++] = vc;
		    Check_Valindex(vali);
		    break;
#endif
		  }
	      }

	    got_it = 0;

#ifdef optional_quotes
#ifdef debug_entry
#if 0
	    printf("Got key \"%s\", vc=\'%c\', in_quotes=%d\n", keybuf, vc, in_quotes);
#endif
#endif
#endif

	    while ((!got_it) && more_chars(string, stream))
	      {
		pvc = vc;
		vc = next_char(string, stream);

		if (vc == '\r') vc = '\n';

		switch (vc)
		  {
		  case '\\':
		    if (pvc == '\\')
		      {
			valbuf[vali++] = vc;
			Check_Valindex(vali);
		      }
		    break;
		  case EOF:
		  case '\0':
		    lex_error_message("EOF while reading value for key=\"%s\"\n");
		    if (read_error != NULL) *read_error = READ_EOF_INSIDE_VALUE;
		    return 0;
		  case '\"':
#ifdef optional_quotes 
		  case '\n':
#endif
		    if
#ifdef optional_quotes
		      (((!in_quotes) && (vc=='\n')) ||
		       (in_quotes && ((vc!='\n') &&
				      ((vc='\"') && (pvc != '\\')))))
#else
			 (pvc != '\\')
#endif
		      {
			char *str;
			struct attribute *new_attr =
			  (struct attribute*)malloc(sizeof(struct attribute));
			valbuf[vali] = '\0';

			if (new_attr == NULL)
			  {
			    error_message("out of memory", NULL);
			    free_entries(*result);
			    if (read_error != NULL) *read_error = READ_NO_MEM;
			    return 0;
			  }

			str = (char*)malloc(keyi+1);
			if (str == NULL)
			  {
			    error_message("out of memory\n", NULL);
			    free_entries(*result);
			    free_attributes(head_attribute);
			    if (read_error != NULL) *read_error = READ_NO_MEM;
			    return 0;
			  }
			strcpy(str, keybuf);
			new_attr->key = str;
			str = (char*)malloc(vali+1);
			if (str == NULL)
			  {
			    error_message("out of memory\n", NULL);
			    free_attributes(head_attribute);
			    free_entries(*result);
			    Free(new_attr->key);
			    if (read_error != NULL) *read_error = READ_NO_MEM;
			    return 0;
			  }
			strcpy(str, valbuf);
			new_attr->value = str;

#ifdef debug_entry
			printf("Got attr %s=%s, c='%c' vc='%c' pc='%c'\n",
			       new_attr->key, new_attr->value,
			       c, vc, pc);
#endif

			latest_was_unquoted = !in_quotes;

			if (head_attribute == NULL)
			  {
			    head_attribute = new_attr;
			  } else {
			    tail_attribute->next = new_attr;
			  }
			tail_attribute = new_attr;
			new_attr->next = NULL;

			got_it = 1;
		      } else {
			valbuf[vali++] = vc;
			Check_Valindex(vali);
		      }
		    break;
		  default:
		    valbuf[vali++] = vc;
		    Check_Valindex(vali);
		    break;
		  }
	      }
	    keyi = 0;
	    vali = 0;
	  }
	  break;
	case '\n':
	case '\r':
	case EOF:
	case '\0':
#ifdef debug_entry
	  printf("At eol, c='%c', pc='%c'\n", c, pc);
#endif
	  if (latest_was_unquoted || (pc == c))
	    {
	      if (head_attribute != NULL)
		{
#ifdef debug_entry
		  printf("Making entry\n");
#endif
		  if (make_entry(head_attribute,
				 result,
				 &tail_entry,
				 &entry_count,
				 checker) == ENTRY_BAD)
		    {
#ifdef debug_entry
		      printf("make_entry returned ENTRY_BAD\n");
#endif
		      free_entries(*result);
		      if (read_error !=NULL) *read_error = READ_FAILED_TO_MAKE_ENTRY;
		      return 0;
		    }
#ifdef debug_entry
		  printf("Made entry\n");
#endif
		}
	      head_attribute = NULL;
	      tail_attribute = NULL;
	    }
	  break;
	default:
	  keybuf[keyi++] = c;
	  Check_Keyindex(keyi);
	  break;
#if 0
	case EOF:
	case '\0':
#endif
	  break;
	}
    }

  if (head_attribute != NULL)
    {
      if (make_entry(head_attribute, result, &tail_entry, &entry_count, checker) == ENTRY_BAD)
	{
	  free_entries(*result);
	  if (read_error !=NULL) *read_error = READ_FAILED_TO_MAKE_ENTRY;
	  return 0;
	}
    }

  return 1;
}

#define MAX_NAME 1024

int read_attribute_file(char *attr_file_directory,
			char *attr_file_name,
			char *attr_file_sub_directory,
			struct entry **result,
			int *read_error,
			int (*checker)(struct entry *new_entry,
				       int entry_count),
			int read_all,
			int debug_reading_attributes)
{
  char file_name_buf[MAX_NAME];
  FILE* input;
  int success;

  if ((strlen(attr_file_directory) +
       ((attr_file_sub_directory != NULL) ?
	strlen(attr_file_sub_directory) :
	0) +
       strlen(attr_file_directory) +
       12) >= MAX_NAME)
    {

      if (debug_reading_attributes)
	{
	  printf("Could not open (%s %s %s) as name is too long\n",
		 attr_file_directory, attr_file_sub_directory!=NULL ?
		 attr_file_sub_directory : "<none>", attr_file_name);
	}

      if (read_error != NULL)
	{
	  *read_error = READ_SILLY_FILENAME;
	}

      return 0;
    }

  if (attr_file_sub_directory != NULL)
    {
      sprintf(file_name_buf, "../%s/%s/%s.attr",
	      attr_file_directory,
	      attr_file_sub_directory,
	      attr_file_name);
    } else {
      sprintf(file_name_buf, "../%s/%s.attr",
	      attr_file_directory,
	      attr_file_name);
    }

  input = fopen(file_name_buf, "r");

  if (input == NULL)
    {

      if (debug_reading_attributes)
	{
	  printf("Could not open \"%s\"\n", file_name_buf);
	}

      if (read_error != NULL)
	{
	  *read_error = READ_NO_FILE;
	}

      return 0;
    }

  if (debug_reading_attributes)
    {
      printf("Reading from \"%s\"\n", file_name_buf);
    }

  success = read_attributes_string_or_stream(NULL,
					     input,
					     result,
					     read_error,
					     checker,
					     read_all,
					     debug_reading_attributes);

  if (debug_reading_attributes)
    {
      printf("Reader success code = %d\n", success);
    }

  fclose(input);
  return success;
}

/* Read an attribute file of a given name and type, along with any
   corrections to it (unless unvetted changes are excluded). The file
   is in ../${TYPEDIR}s/${NAME}.attr or ../${TYPEDIR}s/new/${NAME}.attr, and
   the corrections are in ../edit-requests/new-entries.attr and
   ../edit-requests/new-edits.attr
 */

int read_data(char *attr_file_directory,
	      char *attr_file_name,
	      struct entry **result,
	      int *read_error,
	      int (*checker)(struct entry *new_entry,
			     int entry_count),
	      int read_all,
	      int read_unvetted,
	      int debug)
{
  char type_dir_name_buf[1024];

  if (debug)
    {
      printf("<h2>Reading (\"%s\",\"%s\")</h2>\n<pre>", attr_file_directory, attr_file_name);
    }

  if ((attr_file_directory != NULL) &&
      strlen(attr_file_directory) < 900)
    {
      sprintf(type_dir_name_buf, "%ss", attr_file_directory);
    } else {
      if (attr_file_directory == NULL)
	{
	  error_header("Missing type name");
	} else {
	  error_header("Over-long type name");
	}
      error_trailer();
      exit (0);
    }


  if (!read_attribute_file(type_dir_name_buf,
			   attr_file_name,
			   NULL,
			   result,
			   read_error,
			   checker,
			   read_all,
			   debug))
    {
      if (debug)
	{
	  printf("Failed to read main file, trying in new files area\n");
	}

      if (!read_attribute_file(type_dir_name_buf,
			       attr_file_name,
			       "new",
			       result,
			       read_error,
			       checker,
			       read_all,
			       debug))
	{
	  if (debug)
	    {
	      printf("Read failed\n</pre>\n");
	    }
	  return 0;
	}
      if (debug)
	{
	  printf("Read successfully from new files area\n");
	}
    }

  if (debug)
    {
      printf("Base read succeeded\n");
    }

  if (read_unvetted)
    {
      if (debug)
	{
	  printf("Reading unvetted changes\n");
	}

      /* read new entries */
      add_added_entries(*result ,
			attr_file_directory,
			attr_file_name,
			"edit-requests", "new-entries",
			debug);

      /* read changed and deleted entries */
      knockout_entries(*result ,
		       attr_file_directory,
		       attr_file_name,
		       "edit-requests", "new-edits",
		       debug);
    
      if (debug)
	{
	  printf("Changes read successfully\n");
	}

      {
	struct entry *deletes;

	for (deletes = *result;
	     deletes != NULL;
	     deletes = deletes->next)
	  {
	    struct entry *the_next = deletes->next;
	    char *del_attr;
	    if (the_next == NULL) break;
	    del_attr = get_attr(the_next, "status");
	    if ((del_attr != NULL) &&
		(strcmp(del_attr, "deleted")==0))
	      {
		if (debug)
		  {
		    printf("Deleted: %s\n", the_next->name);
		  }
		deletes->next = the_next->next;
		free_attributes(the_next->attributes);
		Free(the_next->name);
		Free(the_next->type);
		Free(the_next);
	      }
	  }
      }
    } else {
      if (debug)
	{
	  printf("Not reading unvetted changes (was asked not to)\n");
	}
    }

#if 1
  {
    struct entry *maybe_across;

    for (maybe_across = *result;
	 maybe_across != NULL;
	 maybe_across = maybe_across->next)
      {
	char *direction = get_attr(maybe_across, "direction");
	if ((direction != NULL) && (strcmp(direction, "across") == 0))
	  {
#if 0
	    struct attr *copied_attrs;
	    struct entry *next = maybe_across->next;
	  
	    if (!copy_attributes(maybe_across->attributes,
				 &copied_attrs))
	      {
		return 0;
	      }

	    make_entry(copied_attrs, &maybe_across, &maybe_across, 1, checker);
#endif
	  }
      }
  }
#endif
  if (debug)
    {
      printf("</pre>\n");
    }
  return 1;
}

/* Fetch the inclusion, if any, for an entry. See
   ..doc/authoring/field-types.html#include for details.
*/

void fetch_inclusions(struct entry *entry,
		      struct style *style)
{
  char *include;

  if (entry->inclusions_included)
    {
      return;
    }
  
  include = get_attr(entry, "include");
  
  if ((include != NULL) &&
      vet_name(entry->type) &&
      vet_name(include))
    {
      if (style->edit)
	{
	  printf("<p>There is more detail for this entry available from the file \"%s\", which is not included here as it should be <a href=\"getplan.cgi?name=include&type=%s\">edited there</a> instead.</p>\n",
		 include, include, entry->type);
	} else {
	  struct entry *added = NULL;
	  if (read_data(entry->type, include,
			&added, NULL,
			default_check_entry, 0, style->unvetted, style->debug))
	    {
	      merge_entries(entry, added);
	      entry->inclusions_included = 1;
	    }
	}
    }
}

/* Describe a read error (attribute file syntax error, etc) */

void report_read_error(int read_error, char *type, char *name)
{
  switch (read_error)
    {
    case READ_OK:
      printf("<p>Error kind not specified.</p>\n");
      break;
    case READ_SILLY_FILENAME:
      printf("<p>The filenmae is malformed -- perhaps too long.</p>\n");
      break;
    case READ_NO_FILE:
      printf("<p>File not found.</p>\n");
      if (strcmp(type, "road") == 0)
	{
	  printf("<p><a href=\"addpageform.cgi?name=%s\">Add road ``%s''</a></p>\n",
		 name, name);
	}
      break;
    case READ_NO_MEM:
      printf("<p>Not enough memory available to complete this request!</p>\n");
      break;
    case READ_EOF_AFTER_KEY:
      printf("<p>Syntax error: the file ended after a key, without a following value.</p>\n");
      break;
    case READ_NO_DATA_SOURCE:
      printf("<p>No data source!</p>\n");
      break;
    case READ_FAILED_TO_MAKE_ENTRY:
      printf("<p>Failed to make entry</p>\n");
      break;
    default:
      printf("<p>Unknown error, type %d.</p>\n", read_error);
      break;
    }
}

/* end of read-attr.c */
