/* 
   sane-desc.c -- generate list of supported SANE devices

   Copyright (C) 2002-2004 Henning Meier-Geinitz <henning@meier-geinitz.de>
   Copyright (C) 2004 Jose Gato <jgato@gsyc.escet.urjc.es> (XML output)

   This file is part of the SANE package.

   This program 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.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.
*/

#define SANE_DESC_VERSION "2.5"

#define MAN_PAGE_LINK "http://www.sane-project.org/man/%s.5.html"
#define COLOR_MINIMAL      "\"#B00000\""
#define COLOR_BASIC        "\"#FF9000\""
#define COLOR_GOOD         "\"#90B000\""
#define COLOR_COMPLETE     "\"#007000\""
#define COLOR_UNTESTED     "\"#0000B0\""
#define COLOR_UNSUPPORTED  "\"#F00000\""
#define COLOR_NEW          "\"#F00000\""

#include <../include/sane/config.h>

#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <limits.h>
#include <ctype.h>
#include <time.h>

#include "../include/sane/sane.h"
#include "../include/sane/sanei.h"
#include "../include/sane/sanei_config.h"

#ifndef PATH_MAX
# define PATH_MAX 1024
#endif

#define DBG_ERR current_debug_level = 0; debug_call
#define DBG_WARN current_debug_level = 1; debug_call
#define DBG_INFO current_debug_level = 2; debug_call
#define DBG_DBG current_debug_level = 3; debug_call


typedef enum output_mode
{
  output_mode_ascii = 0,
  output_mode_xml,
  output_mode_html_backends,
  output_mode_html_backends_split,
  output_mode_html_mfgs
}
output_mode;

typedef enum parameter_type
{
  param_none = 0,
  param_string
}
parameter_type;

typedef enum status_entry
{
  status_unknown,
  status_untested,
  status_unsupported,
  status_minimal,
  status_basic,
  status_good,
  status_complete
}
status_entry;

typedef enum device_type
{
  type_unknown,
  type_scanner,
  type_stillcam,
  type_vidcam,
  type_meta,
  type_api
}
device_type;

typedef enum level
{
  level_backend,
  level_mfg,
  level_model,
  level_desc
}
level;

typedef struct url_entry
{
  struct url_entry *next;
  char *name;
}
url_entry;

typedef struct model_entry
{
  struct model_entry *next;
  char *name;
  char *interface;
  struct url_entry *url;
  char *comment;
  enum status_entry status;
}
model_entry;

typedef struct desc_entry
{
  struct desc_entry *next;
  char *desc;
  struct url_entry *url;
  char *comment;
}
desc_entry;

typedef struct mfg_entry
{
  struct mfg_entry *next;
  char *name;
  struct url_entry *url;
  char *comment;
  struct model_entry *model;
}
mfg_entry;

typedef struct type_entry
{
  struct type_entry *next;
  enum device_type type;
  struct desc_entry *desc;
  struct mfg_entry *mfg;
}
type_entry;

typedef struct backend_entry
{
  struct backend_entry *next;
  char *name;
  char *version;
  enum status_entry status;	/* deprecated */
  char *manpage;
  struct url_entry *url;
  char *comment;
  struct type_entry *type;
  SANE_Bool new;
}
backend_entry;

typedef struct model_record_entry
{
  struct model_record_entry *next;
  char *name;
  char *interface;
  struct url_entry *url;
  char *comment;
  enum status_entry status;
  struct backend_entry *be;
}
model_record_entry;

typedef struct mfg_record_entry
{
  struct mfg_record_entry *next;
  char *name;
  char *comment;
  struct url_entry *url;
  struct model_record_entry *model_record;
}
mfg_record_entry;


static char *program_name;
static int debug = 0;
static int current_debug_level = 0;
static char *search_dir = 0;
static backend_entry *first_backend = 0;
static enum output_mode mode = output_mode_ascii;
static char *title = 0;
static char *intro = 0;
static SANE_String desc_name = 0;

static void
debug_call (const char *fmt, ...)
{
  va_list ap;
  char *level_txt;

  va_start (ap, fmt);
  if (debug >= current_debug_level)
    {
      /* print to stderr */
      switch (current_debug_level)
	{
	case 0:
	  level_txt = "ERROR:";
	  break;
	case 1:
	  level_txt = "Warning:";
	  break;
	case 2:
	  level_txt = "Info:";
	  break;
	default:
	  level_txt = "";
	  break;
	}
      if (desc_name)
	fprintf (stderr, "%s: %8s ", desc_name, level_txt);
      else
	fprintf (stderr, "[%s] %8s ", program_name, level_txt);
      vfprintf (stderr, fmt, ap);
    }
  va_end (ap);
}

static void
print_usage (char *program_name)
{
  printf ("Usage: %s [-s dir] [-m mode] [-d level] [-h] [-V]\n",
	  program_name);
  printf ("  -s|--search-dir dir    Specify the directory that contains "
	  ".desc files\n");
  printf
    ("  -m|--mode mode         Output mode (ascii, html-backends-split,\n"
     "                         html-mfgs, xml)\n");
  printf ("  -t|--title \"title\"     The title used for HTML pages\n");
  printf ("  -i|--intro \"intro\"     A short description of the "
	  "contents of the page\n");
  printf ("  -d|--debug-level level Specify debug level (0-3)\n");
  printf ("  -h|--help              Print help message\n");
  printf ("  -V|--version           Print version information\n");
  printf ("Report bugs to <henning@meier-geinitz.de>\n");
}

static void
print_version (void)
{
  printf ("sane-desc %s (%s)\n", SANE_DESC_VERSION, PACKAGE_STRING);
  printf ("Copyright (C) 2002-2004 Henning Meier-Geinitz "
	  "<henning@meier-geinitz.de>\n"
	  "sane-desc comes with NO WARRANTY, to the extent permitted by "
	  "law.\n"
	  "You may redistribute copies of sane-desc under the terms of the "
	  "GNU General\n"
	  "Public License.\n"
	  "For more information about these matters, see the file named "
	  "COPYING.\n");
}

static SANE_Bool
get_options (int argc, char **argv)
{
  int longindex;
  int opt;
  static struct option desc_options[] = {
    {"search-dir", required_argument, NULL, 's'},
    {"mode", required_argument, NULL, 'm'},
    {"title", required_argument, NULL, 't'},
    {"intro", required_argument, NULL, 'i'},
    {"debug-level", required_argument, NULL, 'd'},
    {"help", 0, NULL, 'h'},
    {"version", 0, NULL, 'V'},
    {0, 0, 0, 0}
  };

  while ((opt = getopt_long (argc, argv, "s:m:t:i:d:hV", desc_options,
			     &longindex)) != -1)
    {
      switch (opt)
	{
	case 'h':
	  print_usage (argv[0]);
	  exit (0);
	case 'V':
	  print_version ();
	  exit (0);
	case 's':
	  search_dir = strdup (optarg);
	  DBG_INFO ("setting search directory to `%s'\n", search_dir);
	  break;
	case 'm':
	  if (strcmp (optarg, "ascii") == 0)
	    {
	      DBG_INFO ("Output mode: ascii\n");
	      mode = output_mode_ascii;
	    }
	  else if (strcmp (optarg, "xml") == 0)
	    {
	      DBG_INFO ("Output mode: xml\n");
	      mode = output_mode_xml;
	    }
	  else if (strcmp (optarg, "html-backends-split") == 0)
	    {
	      DBG_INFO ("Output mode: html-backends-split\n");
	      mode = output_mode_html_backends_split;
	    }
	  else if (strcmp (optarg, "html-mfgs") == 0)
	    {
	      DBG_INFO ("Output mode: html-mfgs\n");
	      mode = output_mode_html_mfgs;
	    }
	  else
	    {
	      DBG_ERR ("Unknown output mode: %s\n", optarg);
	      exit (1);
	    }
	  break;
	case 't':
	  title = optarg;
	  DBG_INFO ("setting title to `%s'\n", optarg);
	  break;
	case 'i':
	  intro = optarg;
	  DBG_INFO ("setting intro to `%s'\n", optarg);
	  break;
	case 'd':
	  debug = atoi (optarg);
	  DBG_INFO ("setting debug level to %d\n", debug);
	  break;
	case '?':
	  DBG_ERR ("unknown option (use -h for help)\n");
	  return SANE_FALSE;
	case ':':
	  DBG_ERR ("missing parameter (use -h for help)\n");
	  return SANE_FALSE;
	default:
	  DBG_ERR ("missing option (use -h for help)\n");
	  return SANE_FALSE;
	}
    }
  if (!search_dir)
    search_dir = ".";
  return SANE_TRUE;
}

static int
char_compare (char char1, char char2)
{
  char1 = toupper (char1);
  char2 = toupper (char2);

  if (char1 < char2)
    return -1;
  else if (char1 > char2)
    return 1;
  else
    return 0;
}

static int
num_compare (char *num_string1, char *num_string2)
{
  int num1 = atoi (num_string1);
  int num2 = atoi (num_string2);
  if (num1 < num2)
    return -1;
  else if (num1 > num2)
    return 1;
  else
    return 0;
}

/* Compare two strings, try to sort numbers correctly (600 < 1200) */
static int
string_compare (char *string1, char *string2)
{
  int count = 0;
  int compare = 0;
  while (string1[count] && string2[count])
    {
      if (isdigit (string1[count]) && isdigit (string2[count]))
	compare = num_compare (&string1[count], &string2[count]);
      else
	compare = char_compare (string1[count], string2[count]);
      if (compare != 0)
	return compare;
      count++;
    }
  return char_compare (string1[count], string2[count]);
}

/* Add URLs to the end of the list if they are unique */
static url_entry *
update_url_list (url_entry * first_url, char *new_url)
{
  url_entry *url = first_url;
  SANE_Bool found = SANE_FALSE;

  while (url && url->name)
    {
      if (string_compare (url->name, new_url) == 0)
	found = SANE_TRUE;
      url = url->next;
    }
  if (found)
    return first_url;

  url = first_url;
  if (url)
    {
      while (url->next)
	url = url->next;
      url->next = calloc (1, sizeof (url_entry));
      url = url->next;
    }
  else
    {
      first_url = calloc (1, sizeof (url_entry));
      url = first_url;
    }
  if (!url)
    {
      DBG_ERR ("update_url_list: couldn't calloc url_entry\n");
      exit (1);
    }
  url->name = new_url;
  return first_url;
}

/* Get the next token, ignoring escaped quotation marks */
static const char *
get_token (const char *str, char **string_const)
{
  const char *start;
  size_t len;

  str = sanei_config_skip_whitespace (str);

  if (*str == '"')
    {
      start = ++str;
      while (*str && (*str != '"' || *(str - 1) == '\\'))
	++str;
      len = str - start;
      if (*str == '"')
	++str;
      else
	start = 0;		/* final double quote is missing */
    }
  else
    {
      start = str;
      while (*str && !isspace (*str))
	++str;
      len = str - start;
    }
  if (start)
    *string_const = strndup (start, len);
  else
    *string_const = NULL;
  return str;
}

/* Checks a line for a keyword token and determines keyword/string argument */
static SANE_Status
read_keyword (SANE_String line, SANE_String keyword_token,
	      parameter_type p_type, void *argument)
{
  SANE_String_Const cp;
  SANE_Char *word;

  word = 0;

  cp = get_token (line, &word);

  if (!word)
    {
      DBG_ERR ("read_keyword: missing quotation mark: %s\n", line);
      return SANE_STATUS_INVAL;
    }

  if (strcmp (word, keyword_token) != 0)
    return SANE_STATUS_INVAL;

  free (word);
  word = 0;

  switch (p_type)
    {
    case param_none:
      return SANE_STATUS_GOOD;
    case param_string:
      {
	char *pos;
	cp = get_token (cp, &word);
	if (!word)
	  {
	    DBG_ERR ("read_keyword: missing quotation mark: %s\n", line);
	    return SANE_STATUS_INVAL;
	  }
	/* remove escaped quotations */
	while ((pos = strstr (word, "\\\"")) != 0)
	  *pos = ' ';

	DBG_DBG ("read_keyword: set entry `%s' to `%s'\n", keyword_token,
		 word);
	*(SANE_String *) argument = strdup (word);
	break;
      }
    default:
      DBG_ERR ("read_keyword: unknown param_type %d\n", p_type);
      return SANE_STATUS_INVAL;
    }

  if (word)
    free (word);
  word = 0;
  return SANE_STATUS_GOOD;
}

/* Read and interprete the .desc files */
static SANE_Bool
read_files (void)
{
  struct stat stat_buf;
  DIR *dir;
  struct dirent *dir_entry;
  FILE *fp;
  char file_name[PATH_MAX];
  SANE_Char line[4096], *word;
  SANE_String_Const cp;
  backend_entry *current_backend = 0;
  type_entry *current_type = 0;
  mfg_entry *current_mfg = 0;
  model_entry *current_model = 0;
  enum level current_level = level_backend;

  DBG_INFO ("looking for .desc files in `%s'\n", search_dir);
  if (stat (search_dir, &stat_buf) < 0)
    {
      DBG_ERR ("cannot stat `%s' (%s)\n", search_dir, strerror (errno));
      return SANE_FALSE;
    }
  if (!S_ISDIR (stat_buf.st_mode))
    {
      DBG_ERR ("`%s' is not a directory\n", search_dir);
      return SANE_FALSE;
    }
  if ((dir = opendir (search_dir)) == 0)
    {
      DBG_ERR ("cannot read directory `%s' (%s)\n", search_dir,
	       strerror (errno));
      return SANE_FALSE;
    }

  while ((dir_entry = readdir (dir)) != NULL)
    {
      if (strlen (dir_entry->d_name) > 5 &&
	  strcmp (dir_entry->d_name + strlen (dir_entry->d_name) - 5,
		  ".desc") == 0)
	{
	  if (strlen (search_dir)
	      + strlen (dir_entry->d_name) + 1 + 1 > PATH_MAX)
	    {
	      DBG_ERR ("filename too long\n");
	      return SANE_FALSE;
	    }
	  sprintf (file_name, "%s/%s", search_dir, dir_entry->d_name);
	  DBG_INFO ("-> reading desc file: %s\n", file_name);
	  fp = fopen (file_name, "r");
	  if (!fp)
	    {
	      DBG_ERR ("can't open desc file: %s (%s)\n", file_name,
		       strerror (errno));
	      return SANE_FALSE;
	    }
	  /* now we check if everything is ok with the previous backend 
	     before we read the new one */
	  if (current_backend)
	    {
	      type_entry *current_type = current_backend->type;
	      while (current_type)
		{
		  if (current_type->type == type_scanner ||
		      current_type->type == type_stillcam ||
		      current_type->type == type_vidcam)
		    {
		      mfg_entry *current_mfg = current_type->mfg;

		      while (current_mfg)
			{
			  model_entry *current_model = current_mfg->model;

			  while (current_model)
			    {
			      if (current_model->status == status_unknown)
				DBG_WARN
				  ("`%s' `%s' does not have a status\n",
				   current_mfg->name, current_model->name);
			      current_model = current_model->next;
			    }
			  current_mfg = current_mfg->next;
			}
		    }
		  current_type = current_type->next;
		}
	    }
	  desc_name = dir_entry->d_name;
	  current_backend = 0;
	  current_type = 0;
	  current_mfg = 0;
	  current_model = 0;
	  while (sanei_config_read (line, sizeof (line), fp))
	    {
	      char *string_entry = 0;
	      word = 0;

	      cp = get_token (line, &word);
	      if (!word || cp == line)
		{
		  DBG_DBG ("ignoring empty line\n");
		  if (word)
		    free (word);
		  word = 0;
		  continue;
		}
	      if (word[0] == ';')
		{
		  DBG_DBG ("ignoring comment line\n");
		  free (word);
		  word = 0;
		  continue;
		}
	      DBG_DBG ("line: %s\n", line);

	      if (read_keyword (line, ":backend", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  backend_entry *be = first_backend, *prev_be = 0, *new_be =
		    0;
		  DBG_INFO ("creating backend entry `%s'\n", string_entry);

		  new_be = calloc (1, sizeof (backend_entry));
		  if (!new_be)
		    {
		      DBG_ERR ("calloc failed (%s)\n", strerror (errno));
		      return SANE_FALSE;
		    }
		  new_be->name = string_entry;
		  new_be->status = status_unknown;
		  new_be->new = SANE_FALSE;

		  if (!be)
		    {
		      first_backend = new_be;
		      be = new_be;
		    }
		  else
		    {
		      while (be)
			{
			  int compare =
			    string_compare (new_be->name, be->name);
			  if (compare <= 0)
			    {
			      backend_entry *be_tmp = be;
			      be = new_be;
			      be->next = be_tmp;
			      if (!prev_be)
				first_backend = be;
			      else
				prev_be->next = be;
			      break;
			    }
			  prev_be = be;
			  be = be->next;
			}
		      if (!be)	/* last entry */
			{
			  prev_be->next = new_be;
			  be = prev_be->next;
			}
		    }
		  current_backend = be;
		  current_type = 0;
		  current_mfg = 0;
		  current_model = 0;
		  current_level = level_backend;
		  continue;
		}
	      if (!current_backend)
		{
		  DBG_ERR ("use `:backend' keyword first\n");
		  return SANE_FALSE;
		}
	      if (read_keyword (line, ":version", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  if (current_backend->version)
		    {
		      DBG_WARN ("overwriting version of backend `%s' to `%s'"
				"(was: `%s')\n",
				current_backend->name, string_entry,
				current_backend->version,
				current_backend->version);
		    }

		  DBG_INFO ("setting version of backend `%s' to `%s'\n",
			    current_backend->name, string_entry);
		  current_backend->version = string_entry;
		  continue;
		}
	      if (read_keyword (line, ":status", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  switch (current_level)
		    {
		    case level_backend:
		      if (current_backend->status != status_unknown)
			{
			  DBG_WARN ("overwriting status of backend `%s'\n",
				    current_backend->name);
			}
		      if (strcmp (string_entry, ":new") == 0)
			{
			  DBG_WARN ("ignored `%s' status :new, use keyword "
				    "`:new :yes' instead\n",
				    current_backend->name);
			  current_backend->status = status_unknown;
			}
		      else if (strcmp (string_entry, ":alpha") == 0)
			{
			  DBG_WARN
			    ("DEPRECATED backend status `alpha': setting status of backend `%s' to `basic'\n",
			     current_backend->name);
			  current_backend->status = status_basic;
			}
		      else if (strcmp (string_entry, ":beta") == 0)
			{
			  DBG_WARN
			    ("DEPRECATED backend status `beta': setting status of backend `%s' to `good'\n",
			     current_backend->name);
			  current_backend->status = status_good;
			}
		      else if (strcmp (string_entry, ":stable") == 0)
			{
			  DBG_WARN
			    ("DEPRECATED backend status `stable': setting status of backend `%s' to `complete'\n",
			     current_backend->name);
			  current_backend->status = status_complete;
			}
		      else
			{
			  DBG_ERR ("unknown status of backend `%s': `%s'\n",
				   current_backend->name, string_entry);
			  current_backend->status = status_unknown;
			  return SANE_FALSE;
			}
		      break;
		    case level_model:
		      if (current_model->status != status_unknown)
			{
			  DBG_WARN
			    ("overwriting status of model `%s' (backend `%s')\n",
			     current_model->name, current_backend->name);
			}
		      if (strcmp (string_entry, ":alpha") == 0)
			{
			  DBG_WARN
			    ("DEPRECATED status `alpha': setting status of model `%s' to `basic' (backend `%s')\n",
			     current_model->name, current_backend->name);
			  current_model->status = status_basic;
			}
		      else if (strcmp (string_entry, ":beta") == 0)
			{
			  DBG_WARN
			    ("DEPRECATED status `beta': setting status of model `%s' to `good' (backend `%s')\n",
			     current_model->name, current_backend->name);
			  current_model->status = status_good;
			}
		      else if (strcmp (string_entry, ":stable") == 0)
			{
			  DBG_WARN
			    ("DEPRECATED status `stable': setting status of model `%s' to `complete' (backend `%s')\n",
			     current_model->name, current_backend->name);
			  current_model->status = status_complete;
			}
		      else if (strcmp (string_entry, ":minimal") == 0)
			{
			  DBG_INFO
			    ("setting status of model `%s' to `minimal'\n",
			     current_model->name);
			  current_model->status = status_minimal;
			}
		      else if (strcmp (string_entry, ":basic") == 0)
			{
			  DBG_INFO
			    ("setting status of model `%s' to `basic'\n",
			     current_model->name);
			  current_model->status = status_basic;
			}
		      else if (strcmp (string_entry, ":good") == 0)
			{
			  DBG_INFO
			    ("setting status of model `%s' to `good'\n",
			     current_model->name);
			  current_model->status = status_good;
			}
		      else if (strcmp (string_entry, ":complete") == 0)
			{
			  DBG_INFO
			    ("setting status of model `%s' to `complete'\n",
			     current_model->name);
			  current_model->status = status_complete;
			}
		      else if (strcmp (string_entry, ":untested") == 0)
			{
			  DBG_INFO
			    ("setting status of model `%s' to `untested'\n",
			     current_model->name);
			  current_model->status = status_untested;
			}
		      else if (strcmp (string_entry, ":unsupported") == 0)
			{
			  DBG_INFO
			    ("setting status of model `%s' to `unsupported'\n",
			     current_model->name);
			  current_model->status = status_unsupported;
			}
		      else
			{
			  DBG_ERR
			    ("unknown status of model `%s': `%s' (backend `%s')\n",
			     current_model->name, string_entry,
			     current_backend->name);
			  current_model->status = status_untested;
			  return SANE_FALSE;
			}
		      break;
		    default:
		      DBG_ERR
			("level %d not implemented for :status (backend `%s')\n",
			 current_level, current_backend->name);
		      return SANE_FALSE;
		    }


		  continue;
		}
	      if (read_keyword (line, ":new", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  if (strcmp (string_entry, ":yes") == 0)
		    {
		      DBG_INFO ("backend %s is new in this SANE release\n",
				current_backend->name);
		      current_backend->new = SANE_TRUE;
		    }
		  else if (strcmp (string_entry, ":no") == 0)
		    {
		      DBG_INFO
			("backend %s is NOT new in this SANE release\n",
			 current_backend->name);
		      current_backend->new = SANE_FALSE;
		    }
		  else
		    {
		      DBG_ERR ("unknown :new parameter of backend `%s': "
			       "`%s'\n", current_backend->name, string_entry);
		      current_backend->new = SANE_FALSE;
		      return SANE_FALSE;
		    }
		  continue;
		}
	      if (read_keyword (line, ":manpage", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  if (current_backend->manpage)
		    {
		      DBG_WARN ("overwriting manpage of backend `%s' to `%s'"
				"(was: `%s')\n",
				current_backend->name, string_entry,
				current_backend->manpage);
		    }

		  DBG_INFO ("setting manpage of backend `%s' to `%s'\n",
			    current_backend->name, string_entry);
		  current_backend->manpage = string_entry;
		  continue;
		}
	      if (read_keyword
		  (line, ":devicetype", param_string,
		   &string_entry) == SANE_STATUS_GOOD)
		{
		  type_entry *type = 0;

		  type = current_backend->type;

		  DBG_INFO ("adding `%s' to list of device types of backend "
			    "`%s'\n", string_entry, current_backend->name);

		  if (type)
		    {
		      while (type->next)
			type = type->next;
		      type->next = calloc (1, sizeof (type_entry));
		      type = type->next;
		    }
		  else
		    {
		      current_backend->type = calloc (1, sizeof (type_entry));
		      type = current_backend->type;
		    }

		  type->type = type_unknown;
		  if (strcmp (string_entry, ":scanner") == 0)
		    {
		      DBG_INFO ("setting device type of backend `%s' to "
				"scanner\n", current_backend->name);
		      type->type = type_scanner;
		    }
		  else if (strcmp (string_entry, ":stillcam") == 0)
		    {
		      DBG_INFO ("setting device type of backend `%s' to "
				"still camera\n", current_backend->name);
		      type->type = type_stillcam;
		    }
		  else if (strcmp (string_entry, ":vidcam") == 0)
		    {
		      DBG_INFO ("setting device type of backend `%s' to "
				"video camera\n", current_backend->name);
		      type->type = type_vidcam;
		    }
		  else if (strcmp (string_entry, ":api") == 0)
		    {
		      DBG_INFO ("setting device type of backend `%s' to "
				"API\n", current_backend->name);
		      type->type = type_api;
		    }
		  else if (strcmp (string_entry, ":meta") == 0)
		    {
		      DBG_INFO ("setting device type of backend `%s' to "
				"meta\n", current_backend->name);
		      type->type = type_meta;
		    }
		  else
		    {
		      DBG_ERR ("unknown device type of backend `%s': `%s'\n",
			       current_backend->name, string_entry);
		      type->type = type_unknown;
		      return SANE_FALSE;
		    }
		  current_type = type;
		  current_mfg = 0;
		  current_model = 0;
		  continue;
		}
	      if (read_keyword (line, ":desc", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  if (!current_type)
		    {
		      DBG_ERR
			("use `:devicetype' keyword first (backend `%s')\n",
			 current_backend->name);
		      return SANE_FALSE;
		    }
		  if (current_type->type < type_meta)
		    {
		      DBG_ERR
			("use `:desc' for `:api' and `:meta' only (backend `%s')\n",
			 current_backend->name);
		      return SANE_FALSE;
		    }

		  if (current_type->desc)
		    {
		      DBG_WARN ("overwriting description of  device type of "
				"backend `%s' to `%s' (was: `%s')\n",
				current_backend->name, string_entry,
				current_type->desc);
		    }

		  DBG_INFO ("setting description of backend `%s' to `%s'\n",
			    current_backend->name, string_entry);
		  current_type->desc = calloc (1, sizeof (desc_entry));
		  if (!current_type->desc)
		    {
		      DBG_ERR ("calloc failed (%s)\n", strerror (errno));
		      return SANE_FALSE;
		    }
		  current_type->desc->desc = string_entry;
		  current_level = level_desc;
		  current_mfg = 0;
		  current_model = 0;
		  continue;
		}
	      if (read_keyword (line, ":mfg", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  mfg_entry *mfg = 0;

		  if (!current_type)
		    {
		      DBG_ERR
			("use `:devicetype' keyword first (backend `%s')\n",
			 current_backend->name);
		      return SANE_FALSE;
		    }
		  if (current_type->type >= type_meta)
		    {
		      DBG_ERR
			("use `:mfg' for hardware devices only (backend `%s')\n",
			 current_backend->name);
		      return SANE_FALSE;
		    }

		  mfg = current_type->mfg;
		  if (mfg)
		    {
		      while (mfg->next)
			mfg = mfg->next;
		      mfg->next = calloc (1, sizeof (mfg_entry));
		      mfg = mfg->next;
		    }
		  else
		    {
		      current_type->mfg = calloc (1, sizeof (mfg_entry));
		      mfg = current_type->mfg;
		    }

		  if (!mfg)
		    {
		      DBG_ERR ("calloc failed (%s)\n", strerror (errno));
		      return SANE_FALSE;
		    }
		  mfg->name = string_entry;
		  DBG_INFO ("adding mfg entry %s to backend `%s'\n",
			    string_entry, current_backend->name);
		  current_mfg = mfg;
		  current_model = 0;
		  current_level = level_mfg;
		  continue;
		}
	      if (read_keyword (line, ":model", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  model_entry *model = 0;

		  if (!current_type)
		    {
		      DBG_ERR
			("use `:devicetype' keyword first (backend `%s')\n",
			 current_backend->name);
		      return SANE_FALSE;
		    }
		  if (current_level != level_mfg
		      && current_level != level_model)
		    {
		      DBG_ERR ("use `:mfg' keyword first (backend `%s')\n",
			       current_backend->name);
		      return SANE_FALSE;
		    }
		  model = current_mfg->model;
		  if (model)
		    {
		      while (model->next)
			model = model->next;
		      model->next = calloc (1, sizeof (model_entry));
		      model = model->next;
		    }
		  else
		    {
		      current_mfg->model = calloc (1, sizeof (model_entry));
		      model = current_mfg->model;
		    }

		  if (!model)
		    {
		      DBG_ERR ("calloc failed (%s)\n", strerror (errno));
		      return SANE_FALSE;
		    }
		  model->name = string_entry;
		  model->status = status_unknown;
		  DBG_INFO ("adding model entry %s to manufacturer `%s'\n",
			    string_entry, current_mfg->name);
		  current_model = model;
		  current_level = level_model;
		  continue;
		}
	      if (read_keyword
		  (line, ":interface", param_string,
		   &string_entry) == SANE_STATUS_GOOD)
		{
		  if (!current_model)
		    {
		      DBG_WARN ("ignored `%s' :interface, only allowed for "
				"hardware devices\n", current_backend->name);
		      continue;
		    }

		  if (current_model->interface)
		    {
		      DBG_WARN ("overwriting `%s's interface of model "
				"`%s' to `%s' (was: `%s')\n",
				current_backend->name, current_model->name,
				string_entry, current_model->interface);
		    }

		  DBG_INFO ("setting interface of model `%s' to `%s'\n",
			    current_model->name, string_entry);
		  current_model->interface = string_entry;
		  continue;
		}
	      if (read_keyword (line, ":url", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  switch (current_level)
		    {
		    case level_backend:
		      current_backend->url =
			update_url_list (current_backend->url, string_entry);
		      DBG_INFO ("adding `%s' to list of urls of backend "
				"`%s'\n", string_entry,
				current_backend->name);
		      break;
		    case level_mfg:
		      current_mfg->url =
			update_url_list (current_mfg->url, string_entry);
		      DBG_INFO ("adding `%s' to list of urls of mfg "
				"`%s'\n", string_entry, current_mfg->name);
		      break;
		    case level_desc:
		      current_type->desc->url =
			update_url_list (current_type->desc->url,
					 string_entry);
		      DBG_INFO ("adding `%s' to list of urls of description "
				"for backend `%s'\n", string_entry,
				current_backend->name);
		      break;
		    case level_model:
		      current_model->url =
			update_url_list (current_model->url, string_entry);
		      DBG_INFO ("adding `%s' to list of urls of model "
				"`%s'\n", string_entry, current_model->name);
		      break;
		    default:
		      DBG_ERR
			("level %d not implemented for :url (backend `%s')\n",
			 current_level, current_backend->name);
		      return SANE_FALSE;
		    }
		  continue;
		}
	      if (read_keyword (line, ":comment", param_string, &string_entry)
		  == SANE_STATUS_GOOD)
		{
		  switch (current_level)
		    {
		    case level_backend:
		      current_backend->comment = string_entry;
		      DBG_INFO ("setting comment of backend %s to `%s'\n",
				current_backend->name, string_entry);
		      break;
		    case level_mfg:
		      current_mfg->comment = string_entry;
		      DBG_INFO
			("setting comment of manufacturer %s to `%s'\n",
			 current_mfg->name, string_entry);
		      break;
		    case level_desc:
		      current_type->desc->comment = string_entry;
		      DBG_INFO ("setting comment of description for "
				"backend %s to `%s'\n", current_backend->name,
				string_entry);
		      break;
		    case level_model:
		      current_model->comment = string_entry;
		      DBG_INFO ("setting comment of model %s to `%s'\n",
				current_model->name, string_entry);
		      break;
		    default:
		      DBG_ERR
			("level %d not implemented for `:comment' (backend `%s')\n",
			 current_level, current_backend->name);
		      return SANE_FALSE;
		    }
		  continue;
		}
	      DBG_ERR ("unknown keyword token in line `%s' of file `%s'\n",
		       line, file_name);
	      return SANE_FALSE;
	    }			/* while (sanei_config_readline) */
	  fclose (fp);
	}			/* if (strlen) */
    }				/* while (direntry) */

  desc_name = 0;
  if (!first_backend)
    {
      DBG_ERR ("Couldn't find any .desc file\n");
      return SANE_FALSE;
    }
  return SANE_TRUE;
}

/* Create a model_record_entry based on a model_entry */
static model_record_entry *
create_model_record (model_entry * model)
{
  model_record_entry *model_record;

  model_record = calloc (1, sizeof (model_record_entry));
  if (!model_record)
    {
      DBG_ERR ("create_model_record: couldn't calloc model_record_entry\n");
      exit (1);
    }
  model_record->name = model->name;
  model_record->status = model->status;
  model_record->interface = model->interface;
  model_record->url = model->url;
  model_record->comment = model->comment;
  return model_record;
}

/* Calculate the priority of statuses: */
/* minimal, basic, good, complete -> 2, untested -> 1, unsupported -> 0 */
static int
calc_priority (status_entry status)
{
  switch (status)
    {
    case status_untested:
      return 1;
    case status_unsupported:
      return 0;
    default:
      return 2;
    }
}

/* Insert model into list at the alphabetically correct position */
static model_record_entry *
update_model_record_list (model_record_entry * first_model_record,
			  model_entry * model, backend_entry * be)
{
  model_record_entry *model_record = first_model_record;

  if (!first_model_record)
    {
      /* First model for this manufacturer */
      first_model_record = create_model_record (model);
      model_record = first_model_record;
    }
  else
    {
      model_record_entry *prev_model_record = 0;

      while (model_record)
	{
	  int compare = string_compare (model->name, model_record->name);
	  if (compare <= 0)
	    {
	      model_record_entry *tmp_model_record = model_record;
	      if (compare == 0 &&
		  string_compare (model->interface, model_record->interface)
		  == 0)
		{
		  /* Two entries for the same model */
		  int new_priority = calc_priority (model->status);
		  int old_priority = calc_priority (model_record->status);
		  if (new_priority < old_priority)
		    {
		      DBG_DBG
			("update_model_record_list: model %s ignored, backend %s has "
			 "higher priority\n", model->name,
			 model_record->be->name);
		      return first_model_record;
		    }
		  if (new_priority > old_priority)
		    {
		      DBG_DBG
			("update_model_record_list: model %s overrides the one from backend %s\n",
			 model->name, model_record->be->name);
		      tmp_model_record = model_record->next;
		    }
		}
	      /* correct position */
	      model_record = create_model_record (model);
	      model_record->next = tmp_model_record;
	      if (!prev_model_record)
		first_model_record = model_record;
	      else
		prev_model_record->next = model_record;
	      break;
	    }
	  prev_model_record = model_record;
	  model_record = model_record->next;
	}
      if (!model_record)	/* last entry */
	{
	  prev_model_record->next = create_model_record (model);
	  model_record = prev_model_record->next;
	}
    }				/* if (first_model_record) */
  model_record->be = be;
  DBG_DBG ("update_model_record_list: added model %s\n", model->name);
  return first_model_record;
}


/* Insert manufacturer into list at the alphabetically correct position, */
/* create new record if neccessary */
static mfg_record_entry *
update_mfg_record_list (mfg_record_entry * first_mfg_record, mfg_entry * mfg,
			backend_entry * be)
{
  model_entry *model = mfg->model;
  mfg_record_entry *mfg_record = first_mfg_record;

  while (mfg_record)
    {
      if (string_compare (mfg_record->name, mfg->name) == 0)
	{
	  /* Manufacturer already exists */
	  url_entry *mfg_url = mfg->url;

	  /* Manufacturer comments and (additional) URLs? */
	  if (!mfg_record->comment)
	    mfg_record->comment = mfg->comment;
	  while (mfg_url && mfg_url->name)
	    {
	      mfg_record->url = update_url_list (mfg_record->url,
						 mfg_url->name);
	      mfg_url = mfg_url->next;
	    }
	  break;
	}
      mfg_record = mfg_record->next;
    }

  if (!mfg_record)
    {
      /* Manufacturer doesn't exist yet */
      url_entry *url = mfg->url;

      mfg_record = calloc (1, sizeof (mfg_record_entry));
      if (!mfg_record)
	{
	  DBG_ERR ("update_mfg_record_list: couldn't calloc "
		   "mfg_record_entry\n");
	  exit (1);
	}
      mfg_record->name = mfg->name;
      mfg_record->comment = mfg->comment;
      while (url)
	{
	  mfg_record->url = update_url_list (mfg_record->url, url->name);
	  url = url->next;
	}
      if (first_mfg_record != 0)
	{
	  /* We already have one manufacturer in the list */
	  mfg_record_entry *new_mfg_record = mfg_record;
	  mfg_record_entry *prev_mfg_record = 0;

	  mfg_record = first_mfg_record;

	  while (mfg_record)
	    {
	      int compare =
		string_compare (new_mfg_record->name, mfg_record->name);
	      if (compare <= 0)
		{
		  mfg_record_entry *tmp_mfg_record = mfg_record;
		  mfg_record = new_mfg_record;
		  mfg_record->next = tmp_mfg_record;
		  if (!prev_mfg_record)
		    first_mfg_record = mfg_record;
		  else
		    prev_mfg_record->next = mfg_record;
		  break;
		}
	      prev_mfg_record = mfg_record;
	      mfg_record = mfg_record->next;
	    }
	  if (!mfg_record)	/* last entry */
	    {
	      prev_mfg_record->next = new_mfg_record;
	      mfg_record = prev_mfg_record->next;
	    }
	}
      else
	first_mfg_record = mfg_record;
      DBG_DBG ("update_mfg_record_list: created mfg %s\n", mfg_record->name);
    }				/* if (!mfg_record) */

  /* create model entries */
  while (model)
    {
      mfg_record->model_record =
	update_model_record_list (mfg_record->model_record, model, be);
      model = model->next;
    }
  return first_mfg_record;
}

/* Create a sorted list of manufacturers based on the backends list */
static mfg_record_entry *
create_mfg_list (device_type dev_type)
{
  mfg_record_entry *first_mfg_record = 0;
  backend_entry *be = first_backend;

  DBG_DBG ("create_mfg_list: start\n");
  while (be)
    {
      type_entry *type = be->type;
      while (type)
	{
	  if (type->type == dev_type)
	    {
	      mfg_entry *mfg = type->mfg;
	      while (mfg)
		{
		  first_mfg_record =
		    update_mfg_record_list (first_mfg_record, mfg, be);
		  mfg = mfg->next;
		}
	    }
	  type = type->next;
	}
      be = be->next;
    }
  DBG_DBG ("create_mfg_list: exit\n");
  return first_mfg_record;
}

/* Print an ASCII list with all the information we have */
static void
ascii_print_backends (void)
{
  backend_entry *be;

  be = first_backend;
  while (be)
    {
      url_entry *url = be->url;
      type_entry *type = be->type;

      if (be->name)
	printf ("backend `%s'\n", be->name);
      else
	printf ("backend *none*\n");

      if (be->version)
	printf (" version `%s'\n", be->version);
      else
	printf (" version *none*\n");

      if (be->new)
	printf (" NEW!\n");

      if (be->manpage)
	printf (" manpage `%s'\n", be->manpage);
      else
	printf (" manpage *none*\n");

      if (url)
	while (url)
	  {
	    printf (" url `%s'\n", url->name);
	    url = url->next;
	  }
      else
	printf (" url *none*\n");

      if (be->comment)
	printf (" comment `%s'\n", be->comment);
      else
	printf (" comment *none*\n");

      if (type)
	while (type)
	  {
	    switch (type->type)
	      {
	      case type_scanner:
		printf (" type scanner\n");
		break;
	      case type_stillcam:
		printf (" type stillcam\n");
		break;
	      case type_vidcam:
		printf (" type vidcam\n");
		break;
	      case type_meta:
		printf (" type meta\n");
		break;
	      case type_api:
		printf (" type api\n");
		break;
	      default:
		printf (" type *unknown*\n");
		break;
	      }
	    if (type->desc)
	      {
		url_entry *url = type->desc->url;
		printf ("  desc `%s'\n", type->desc->desc);
		if (url)
		  while (url)
		    {
		      printf ("   url `%s'\n", url->name);
		      url = url->next;
		    }
		else
		  printf ("   url *none*\n");

		if (type->desc->comment)
		  printf ("   comment `%s'\n", type->desc->comment);
		else
		  printf ("   comment *none*\n");
	      }
	    else if (type->type >= type_meta)
	      printf ("  desc *none*\n");

	    if (type->mfg)
	      {
		mfg_entry *mfg = type->mfg;
		while (mfg)
		  {
		    model_entry *model = mfg->model;
		    url_entry *url = mfg->url;

		    printf ("  mfg `%s'\n", mfg->name);
		    if (url)
		      while (url)
			{
			  printf ("   url `%s'\n", url->name);
			  url = url->next;
			}
		    else
		      printf ("   url *none*\n");

		    if (mfg->comment)
		      printf ("   comment `%s'\n", mfg->comment);
		    else
		      printf ("   comment *none*\n");

		    if (model)
		      while (model)
			{
			  url_entry *url = model->url;
			  printf ("   model `%s'\n", model->name);
			  if (model->interface)
			    printf ("    interface `%s'\n", model->interface);
			  else
			    printf ("    interface *none*\n");
			  if (model->status == status_unknown)
			    model->status = be->status;
			  switch (model->status)
			    {
			    case status_minimal:
			      printf ("    status minimal\n");
			      break;
			    case status_basic:
			      printf ("    status basic\n");
			      break;
			    case status_good:
			      printf ("    status good\n");
			      break;
			    case status_complete:
			      printf ("    status complete\n");
			      break;
			    case status_untested:
			      printf ("    status untested\n");
			      break;
			    case status_unsupported:
			      printf ("    status unsupported\n");
			      break;
			    default:
			      printf ("    status *unknown*\n");
			      break;
			    }

			  if (url)
			    while (url)
			      {
				printf ("    url `%s'\n", url->name);
				url = url->next;
			      }
			  else
			    printf ("    url *none*\n");

			  if (model->comment)
			    printf ("    comment `%s'\n", model->comment);
			  else
			    printf ("    comment *none*\n");

			  model = model->next;
			}
		    else
		      printf ("   model *none*\n");

		    mfg = mfg->next;
		  }		/* while (mfg) */
	      }
	    else if (type->type < type_meta)
	      printf ("  mfg *none*\n");
	    type = type->next;
	  }			/* while (type) */
      else
	printf (" type *none*\n");
      be = be->next;
    }				/* while (be) */
}


static char *
clean_string (char *c)
{
  /* not avoided characters */

  char *aux;

  aux = malloc (strlen (c) * sizeof (char) * 6);

  *aux = '\0';

  while (*c != '\0')
    {

      switch (*c)
	{
	case '<':
	  aux = strcat (aux, "&lt;");
	  break;
	case '>':
	  aux = strcat (aux, "&gt;");
	  break;
	case '¡':
	  aux = strcat (aux, "!");
	  break;
	case '¿':
	  aux = strcat (aux, "?");
	  break;
	case '\'':
	  aux = strcat (aux, "&apos;");
	  break;
	case '&':
	  aux = strcat (aux, "&amp;");
	  break;
	default:
	  aux = strncat (aux, c, 1);
	}
      c = c + 1;
    }
  return aux;
}

/* Print an XML list with all the information we have */
static void
xml_print_backends (void)
{
  backend_entry *be;

  be = first_backend;
  printf ("<backends>\n");
  while (be)
    {
      url_entry *url = be->url;
      type_entry *type = be->type;

      if (be->name)
	printf ("<backend name=\"%s\">\n", clean_string (be->name));
      else
	printf ("<backend name=\"*none\">\n");

      if (be->version)
	printf ("<version>%s</version> \n", clean_string (be->version));
      else
	printf ("<version>*none*</version>\n");

      if (be->new)
	printf ("<new state=\"yes\"/>\n");
      else
	printf ("<new state=\"no\"/>\n");


      if (be->manpage)
	printf (" <manpage>%s</manpage>\n", clean_string (be->manpage));
      else
	printf (" <manpage>*none*</manpage>\n");

      if (url)
	while (url)
	  {
	    printf (" <url>%s</url>\n", clean_string (url->name));
	    url = url->next;
	  }
      else
	printf (" <url>*none*</url>\n");

      if (be->comment)
	printf (" <comment>%s</comment>\n", clean_string (be->comment));
      else
	printf (" <comment>*none*</comment>\n");

      if (type)
	while (type)
	  {

	    switch (type->type)
	      {
	      case type_scanner:
		printf (" <type def=\"scanner\">\n");
		break;
	      case type_stillcam:
		printf (" <type def=\"stillcam\">\n");
		break;
	      case type_vidcam:
		printf (" <type def=\"vidcam\">\n");
		break;
	      case type_meta:
		printf (" <type def=\"meta\">\n");
		break;
	      case type_api:
		printf (" <type def=\"api\">\n");
		break;
	      default:
		printf (" <type def=\"*unknown*\">\n");
		break;
	      }
	    if (type->desc)
	      {
		url_entry *url = type->desc->url;
		printf ("   <desc>%s</desc>\n",
			clean_string (type->desc->desc));
		if (url)
		  while (url)
		    {
		      printf ("   <url>%s</url>\n", clean_string (url->name));
		      url = url->next;
		    }
		else
		  printf ("   <url>*none*</url>\n");

		if (type->desc->comment)
		  printf ("   <comment>%s</comment>\n",
			  clean_string (type->desc->comment));
		else
		  printf ("   <comment>*none*</comment>\n");
	      }
	    else if (type->type >= type_meta)
	      printf ("  <desc>*none*</desc>\n");

	    if (type->mfg)
	      {
		mfg_entry *mfg = type->mfg;
		while (mfg)
		  {
		    model_entry *model = mfg->model;
		    url_entry *url = mfg->url;

		    printf (" <mfg name=\"%s\">\n", clean_string (mfg->name));
		    if (url)
		      while (url)
			{
			  printf ("  <url>`%s'</url>\n",
				  clean_string (url->name));
			  url = url->next;
			}
		    else
		      printf ("  <url>*none*</url>\n");

		    if (mfg->comment)
		      printf ("  <comment>%s</comment>\n",
			      clean_string (mfg->comment));
		    else
		      printf ("  <comment>*none*</comment>\n");

		    if (model)
		      while (model)
			{
			  url_entry *url = model->url;
			  printf ("   <model name=\"%s\">\n",
				  clean_string (model->name));
			  if (model->interface)
			    printf ("    <interface>%s</interface>\n",
				    clean_string (model->interface));
			  else
			    printf ("    <interface>*none*</interface>\n");

			  if (model->status == status_unknown)
			    model->status = be->status;
			  switch (model->status)
			    {
			    case status_minimal:
			      printf ("    <status>minimal</status>\n");
			      break;
			    case status_basic:
			      printf ("    <status>basic</status>\n");
			      break;
			    case status_good:
			      printf ("    <status>good</status>\n");
			      break;
			    case status_complete:
			      printf ("    <status>complete</status>\n");
			      break;
			    case status_untested:
			      printf ("    <status>untested</status>\n");
			      break;
			    case status_unsupported:
			      printf ("    <status>unsupported</status>\n");
			      break;
			    default:
			      printf ("    <status>*unknown*</status>\n");
			      break;
			    }

			  if (url)
			    while (url)
			      {
				printf ("    <url>%s</url>\n",
					clean_string (url->name));
				url = url->next;
			      }
			  else
			    printf ("    <url>*none*</url>\n");

			  if (model->comment)
			    printf ("    <comment>%s</comment>\n",
				    clean_string (model->comment));
			  else
			    printf ("    <comment>*none*</comment>\n");

			  model = model->next;
			  printf ("   </model>\n");
			}	/* while (model) */
		    else
		      printf ("   <model name=\"*none*\" />\n");

		    printf (" </mfg>\n");
		    mfg = mfg->next;
		  }		/* while (mfg) */
	      }
	    else if (type->type < type_meta)
	      printf ("  <mfg>*none*</mfg>\n");
	    type = type->next;
	    printf (" </type>\n");
	  }			/* while (type) */
      else
	printf (" <type>*none*</type>\n");
      printf ("</backend>\n");
      be = be->next;

    }				/* while (be) */
  printf ("</backends>\n");
}


/* Generate a name used for <a name=...> HTML tags */
static char *
html_generate_anchor_name (device_type dev_type, char *manufacturer_name)
{
  char *name = malloc (strlen (manufacturer_name) + 1 + 2);
  char *pointer = name;
  char type_char;

  if (!name)
    {
      DBG_ERR ("html_generate_anchor_name: couldn't malloc\n");
      return 0;
    }

  switch (dev_type)
    {
    case type_scanner:
      type_char = 'S';
      break;
    case type_stillcam:
      type_char = 'C';
      break;
    case type_vidcam:
      type_char = 'V';
      break;
    case type_meta:
      type_char = 'M';
      break;
    case type_api:
      type_char = 'A';
      break;
    default:
      type_char = 'Z';
      break;
    }

  snprintf (name, strlen (manufacturer_name) + 1 + 2, "%c-%s",
	    type_char, manufacturer_name);

  while (*pointer)
    {
      if (!isalnum (*pointer))
	*pointer = '-';
      else
	*pointer = toupper (*pointer);
      pointer++;
    }
  return name;
}


/* Generate one table per backend of all backends providing models */
/* of type dev_type */
static void
html_backends_split_table (device_type dev_type)
{
  backend_entry *be = first_backend;
  SANE_Bool first = SANE_TRUE;

  printf ("<p><b>Backends</b>: \n");
  while (be)			/* print link list */
    {
      type_entry *type = be->type;
      SANE_Bool found = SANE_FALSE;

      while (type)
	{
	  if (type->type == dev_type)
	    found = SANE_TRUE;
	  type = type->next;
	}
      if (found)
	{
	  if (!first)
	    printf (", \n");
	  first = SANE_FALSE;
	  printf ("<a href=\"#%s\">%s</a>",
		  html_generate_anchor_name (dev_type, be->name), be->name);
	}
      be = be->next;
    }
  be = first_backend;
  if (first)
    printf ("(none)\n");

  printf ("</p>\n");


  while (be)
    {
      type_entry *type = be->type;

      while (type)
	{
	  if (type->type == dev_type)
	    {
	      mfg_entry *mfg = type->mfg;
	      model_entry *model;

	      printf ("<h3><a name=\"%s\">Backend: %s\n",
		      html_generate_anchor_name (type->type, be->name),
		      be->name);

	      if (be->version || be->new)
		{
		  printf ("(");
		  if (be->version)
		    {
		      printf ("%s", be->version);
		      if (be->new)
			printf (", <font color=" COLOR_NEW ">NEW!</font>");
		    }
		  else
		    printf ("<font color=" COLOR_NEW ">NEW!</font>");
		  printf (")\n");
		}
	      printf ("</a></h3>\n");

	      printf ("<p>\n");

	      if (be->url && be->url->name)
		{
		  url_entry *url = be->url;
		  printf ("<b>Link(s):</b> \n");
		  while (url)
		    {
		      if (url != be->url)
			printf (", ");
		      printf ("<a href=\"%s\">%s</a>", url->name, url->name);
		      url = url->next;
		    }
		  printf ("<br>\n");
		}
	      if (be->manpage)
		printf ("<b>Manual page:</b> <a href=\"" MAN_PAGE_LINK
			"\">%s</a><br>\n", be->manpage, be->manpage);

	      if (be->comment)
		printf ("<b>Comment:</b> %s<br>\n", be->comment);


	      if (type->desc)
		{
		  if (type->desc->desc)
		    {
		      if (type->desc->url && type->desc->url->name)
			printf ("<b>Description:</b> "
				"<a href=\"%s\">%s</a><br>\n",
				type->desc->url->name, type->desc->desc);
		      else
			printf ("<b>Description:</b> %s<br>\n",
				type->desc->desc);
		    }

		  if (type->desc->comment)
		    printf ("<b>Comment:</b> %s<br>\n", type->desc->comment);
		  printf ("</p>\n");
		  type = type->next;
		  continue;
		}
	      printf ("</p>\n");

	      if (!mfg)
		{
		  type = type->next;
		  continue;
		}

	      printf ("<table border=1>\n");

	      printf ("<tr bgcolor=E0E0FF>\n");
	      printf ("<th align=center>Manufacturer</th>\n");
	      printf ("<th align=center>Model</th>\n");
	      printf ("<th align=center>Interface</th>\n");
	      printf ("<th align=center>Status</th>\n");
	      printf ("<th align=center>Comment</th>\n");
	      printf ("</tr>\n");

	      mfg = type->mfg;
	      while (mfg)
		{
		  model = mfg->model;
		  if (model)
		    {
		      int num_models = 0;

		      while (model)	/* count models for rowspan */
			{
			  model = model->next;
			  num_models++;
			}
		      model = mfg->model;
		      printf ("<tr>\n");
		      printf ("<td align=center rowspan=%d>\n", num_models);
		      if (mfg->url && mfg->url->name)
			printf ("<a href=\"%s\">%s</a>\n", mfg->url->name,
				mfg->name);
		      else
			printf ("%s\n", mfg->name);

		      while (model)
			{
			  enum status_entry status = model->status;

			  if (model != mfg->model)
			    printf ("<tr>\n");

			  if (model->url && model->url->name)
			    printf
			      ("<td align=center><a href=\"%s\">%s</a></td>\n",
			       model->url->name, model->name);
			  else
			    printf ("<td align=center>%s</td>\n",
				    model->name);

			  if (model->interface)
			    printf ("<td align=center>%s</td>\n",
				    model->interface);
			  else
			    printf ("<td align=center>?</td>\n");

			  printf ("<td align=center>");
			  if (status == status_unknown)
			    status = be->status;
			  switch (status)
			    {
			    case status_minimal:
			      printf ("<font color=" COLOR_MINIMAL
				      ">minimal</font>");
			      break;
			    case status_basic:
			      printf ("<font color=" COLOR_BASIC
				      ">basic</font>");
			      break;
			    case status_good:
			      printf ("<font color=" COLOR_GOOD
				      ">good</font>");
			      break;
			    case status_complete:
			      printf ("<font color=" COLOR_COMPLETE
				      ">complete</font>");
			      break;
			    case status_untested:
			      printf ("<font color=" COLOR_UNTESTED
				      ">untested</font>");
			      break;
			    case status_unsupported:
			      printf ("<font color=" COLOR_UNSUPPORTED
				      ">unsupported</font>");
			      break;
			    default:
			      printf ("?");
			      break;
			    }
			  printf ("</td>\n");

			  if (model->comment && model->comment[0] != 0)
			    printf ("<td>%s</td>\n", model->comment);
			  else
			    printf ("<td>&nbsp;</td>\n");

			  model = model->next;
			  printf ("</tr>\n");
			}	/* while (model) */
		    }		/* if (num_models) */
		  mfg = mfg->next;
		}		/* while (mfg) */
	      printf ("</table>\n");
	    }			/* if (type->type) */
	  type = type->next;
	}			/* while (type) */
      be = be->next;
    }				/* while (be) */
  /*  printf ("</table>\n"); */
}

/* Generate one table per manufacturer constructed of all backends */
/* providing models of type dev_type */
static void
html_mfgs_table (device_type dev_type)
{
  mfg_record_entry *mfg_record = 0, *first_mfg_record = 0;

  first_mfg_record = create_mfg_list (dev_type);
  mfg_record = first_mfg_record;

  printf ("<p><b>Manufacturers</b>: \n");
  while (mfg_record)
    {
      if (mfg_record != first_mfg_record)
	printf (", \n");
      printf ("<a href=\"#%s\">%s</a>",
	      html_generate_anchor_name (type_unknown, mfg_record->name),
	      mfg_record->name);
      mfg_record = mfg_record->next;
    }
  mfg_record = first_mfg_record;
  if (!mfg_record)
    printf ("(none)\n");
  printf ("</p>\n");
  while (mfg_record)
    {
      model_record_entry *model_record = mfg_record->model_record;

      printf ("<h3><a name=\"%s\">Manufacturer: %s</a></h3>\n",
	      html_generate_anchor_name (type_unknown, mfg_record->name),
	      mfg_record->name);
      printf ("<p>\n");
      if (mfg_record->url && mfg_record->url->name)
	{
	  url_entry *url = mfg_record->url;
	  printf ("<b>Link(s):</b> \n");
	  while (url)
	    {
	      if (url != mfg_record->url)
		printf (", ");
	      printf ("<a href=\"%s\">%s</a>", url->name, url->name);
	      url = url->next;
	    }
	  printf ("<br>\n");
	}
      if (mfg_record->comment)
	printf ("<b>Comment:</b> %s<br>\n", mfg_record->comment);
      printf ("</p>\n");
      if (!model_record)
	{
	  mfg_record = mfg_record->next;
	  continue;
	}
      printf ("<table border=1>\n");
      printf ("<tr bgcolor=E0E0FF>\n");

      printf ("<th align=center>Model</th>\n");
      printf ("<th align=center>Interface</th>\n");
      printf ("<th align=center>Status</th>\n");
      printf ("<th align=center>Comment</th>\n");
      printf ("<th align=center>Backend</th>\n");
      printf ("<th align=center>Manpage</th>\n");
      printf ("</tr>\n");

      while (model_record)
	{
	  enum status_entry status = model_record->status;

	  if (model_record->url && model_record->url->name)
	    printf ("<tr><td align=center><a "
		    "href=\"%s\">%s</a></td>\n",
		    model_record->url->name, model_record->name);
	  else
	    printf ("<tr><td align=center>%s</td>\n", model_record->name);

	  if (model_record->interface)
	    printf ("<td align=center>%s</td>\n", model_record->interface);
	  else
	    printf ("<td align=center>?</td>\n");

	  printf ("<td align=center>");

	  if (status == status_unknown)
	    status = model_record->be->status;

	  switch (status)
	    {
	    case status_minimal:
	      printf ("<font color=" COLOR_MINIMAL ">minimal</font>");
	      break;
	    case status_basic:
	      printf ("<font color=" COLOR_BASIC ">basic</font>");
	      break;
	    case status_good:
	      printf ("<font color=" COLOR_GOOD ">good</font>");
	      break;
	    case status_complete:
	      printf ("<font color=" COLOR_COMPLETE ">complete</font>");
	      break;
	    case status_untested:
	      printf ("<font color=" COLOR_UNTESTED ">untested</font>");
	      break;
	    case status_unsupported:
	      printf ("<font color=" COLOR_UNSUPPORTED ">unsupported</font>");
	      break;
	    default:
	      printf ("?");
	      break;
	    }
	  printf ("</td>\n");

	  if (model_record->comment && model_record->comment[0] != 0)
	    printf ("<td>%s</td>\n", model_record->comment);
	  else
	    printf ("<td>&nbsp;</td>\n");

	  printf ("<td align=center>\n");
	  if (model_record->be->url && model_record->be->url->name)
	    printf ("<a href=\"%s\">%s</a>\n",
		    model_record->be->url->name, model_record->be->name);
	  else
	    printf ("%s", model_record->be->name);

	  if (model_record->be->version || model_record->be->new)
	    {
	      printf ("<br>(");
	      if (model_record->be->version)
		{
		  printf ("%s", model_record->be->version);
		  if (model_record->be->new)
		    printf (", <font color=" COLOR_NEW ">NEW!</font>");
		}
	      else
		printf ("<font color=" COLOR_NEW ">NEW!</font>");
	      printf (")\n");
	    }

	  printf ("</td>\n");
	  if (model_record->be->manpage)
	    printf ("<td align=center><a href=\""
		    MAN_PAGE_LINK "\">%s</a></td>\n",
		    model_record->be->manpage, model_record->be->manpage);
	  else
	    printf ("<td align=center>?</td>\n");

	  printf ("</tr>\n");
	  model_record = model_record->next;
	}			/* while model_record */
      printf ("</table>\n");
      mfg_record = mfg_record->next;
    }				/* while mfg_record */
}

/* Print the HTML headers and an introduction */
static void
html_print_header (void)
{
  printf
    ("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"
     "<html> <head>\n"
     "<meta http-equiv=\"Content-Type\" content=\"text/html; "
     "charset=iso-8859-1\">\n");
  printf ("<title>%s</title>\n", title);
  printf
    ("</head>\n"
     "<body bgcolor=FFFFFF>\n"
     "<div align=center>\n"
     "<img src=\"http://www.sane-project.org/images/sane.png\" alt=\"SANE\">\n");
  printf ("<h1>%s</h1>\n", title);
  printf ("</div>\n" "<hr>\n");
  printf ("%s\n", intro);
  printf
    ("<p>This is only a summary!\n"
     "Please consult the manpages and the author-supplied webpages\n"
     "for more detailed (and usually important) information\n"
     "concerning each backend.</p>\n");
  printf
    ("<p>If you have new information or corrections, please file a\n"
     "<a href=\"http://www.sane-project.org/bugs.html\">bug report</a>\n"
     "with as many details as possible. Also please tell us if your scanner \n"
     "isn't mentioned in this list at all.</p>\n"
     "<p>For an explanation of the tables, see the\n"
     "<a href=\"#legend\">legend</a>.\n");
  printf
    ("<p>There are tables for <a href=\"#SCANNERS\">scanners</a>,\n"
     "<a href=\"#STILL\">still cameras</a>,\n"
     "<a href=\"#VIDEO\">video cameras</a>,\n"
     "<a href=\"#API\">APIs</a>, and\n"
     "<a href=\"#META\">meta backends</a>.\n");
}

/* Print the HTML footers and contact information */
static void
html_print_footer (void)
{
  time_t current_time = time (0);

  printf
    ("<hr>\n"
     "<a href=\"http://www.sane-project.org/\">SANE homepage</a>\n"
     "<address>\n"
     "<a href=\"http://www.sane-project.org/imprint.html\"\n"
     ">Contact</a>\n" "</address>\n" "<font size=-1>\n");
  printf ("This page was last updated on %s\n",
	  asctime (localtime (&current_time)));
  printf ("</font>\n");
  printf ("</body> </html>\n");
}


/* print parts of the legend */
static void
html_print_legend_backend (void)
{
  printf
    ("  <dt><b>Backend:</b></dt>\n"
     "  <dd>Name of the backend,  in parentheses if available:\n"
     "      Version of backend/driver; newer versions may be\n"
     "      available from their home sites.<br>"
     "      <font color=" COLOR_NEW ">NEW!</font> means brand-new to the\n"
     "      current release of SANE.<br>\n"
     "      UNMAINTAINED means that nobody maintains that backend. Expect no \n"
     "      new features or newly supported devices. You are welcome to take over \n"
     "      maintainership.\n" "  </dd>\n");
}

static void
html_print_legend_link (void)
{
  printf
    ("  <dt><b>Link(s):</b></dt>\n"
     "  <dd>Link(s) to more extensive and\n"
     "      detailed information, if it exists, or the email address\n"
     "      of the author or maintainer.\n");
}

static void
html_print_legend_manual (void)
{
  printf
    ("  <dt><b>Manual Page:</b></dt>\n"
     "  <dd>A link to the man-page online, if it exists.</dd>\n");
}

static void
html_print_legend_comment (void)
{
  printf
    ("  <dt><b>Comment:</b></dt>\n"
     "  <dd>More information about the backend or model, e.g. the level of "
     "      support and possible problems.</dd>\n");
}

static void
html_print_legend_manufacturer (void)
{
  printf
    ("  <dt><b>Manufacturer:</b></dt>\n"
     "  <dd>Manufacturer, vendor or brand name of the device.</dd>\n");
}

static void
html_print_legend_model (void)
{
  printf
    ("  <dt><b>Model:</b></dt>\n" "  <dd>Name of the the device.</dd>\n");
}

static void
html_print_legend_interface (void)
{
  printf
    ("  <dt><b>Interface:</b></dt>\n"
     "  <dd>How the device is connected to the computer.</dd>\n");
}

static void
html_print_legend_status (void)
{
  printf
    ("  <dt><b>Status</b>:</dt>\n"
     "  <dd>Indicates how many of the features the device provides \n"
     "      are supported by SANE.\n"
     "      <ul><li><font color=" COLOR_UNSUPPORTED ">unsupported</font>"
     "        means the device is not supported at least by this backend. "
     "        It may be supported by other backends, however.\n");
  printf
    ("      <li><font color=" COLOR_UNTESTED ">untested</font> means the "
     "        device may be supported but couldn't be tested. Be very "
     "        careful and report success/failure.\n"
     "      <li><font color=" COLOR_MINIMAL ">minimal</font> means that the\n"
     "        device is detected and scans at least in one mode. But the quality \n"
     "        is bad or important features won't work.\n");
  printf
    ("      <li><font color=" COLOR_BASIC ">basic</font> means it works at \n"
     "        least in the most important modes but quality is not perfect.\n"
     "      <li><font color=" COLOR_GOOD
     ">good</font> means the device is usable \n"
     "        for day-to-day work. Some rather exotic features may be missing.\n"
     "      <li><font color=" COLOR_COMPLETE
     ">complete</font> means the backends \n"
     "        supports everything the device can do.\n" "      </ul></dd>\n");
}

static void
html_print_legend_description (void)
{
  printf
    ("  <dt><b>Description</b>:</dt>\n"
     "  <dd>The scope of application of the backend.\n");
}

/* Print the HTML page with one table of models per backend */
static void
html_print_backends_split (void)
{
  if (!title)
    title = "SANE: Backends (Drivers)";
  if (!intro)
    intro = "<p> The following table summarizes the backends/drivers "
      "distributed with the latest version of sane-backends, and the hardware "
      "or software they support. </p>";

  html_print_header ();

  printf ("<h2><a name=\"SCANNERS\">Scanners</a></h2>\n");
  html_backends_split_table (type_scanner);

  printf ("<h2><a name=\"STILL\">Still Cameras</a></h2>\n");
  html_backends_split_table (type_stillcam);

  printf ("<h2><a name=\"VIDEO\">Video Cameras</a></h2>\n");
  html_backends_split_table (type_vidcam);

  printf ("<h2><a name=\"API\">APIs</a></h2>\n");
  html_backends_split_table (type_api);

  printf ("<h2><a name=\"META\">Meta Backends</a></h2>\n");
  html_backends_split_table (type_meta);

  printf ("<h3><a name=\"legend\">Legend:</a></h3>\n" "<blockquote><dl>\n");

  html_print_legend_backend ();
  html_print_legend_link ();
  html_print_legend_manual ();
  html_print_legend_comment ();
  html_print_legend_manufacturer ();
  html_print_legend_model ();
  html_print_legend_interface ();
  html_print_legend_status ();
  html_print_legend_description ();

  printf ("</dl></blockquote>\n");

  html_print_footer ();
}

/* Print the HTML page with one table of models per manufacturer */
static void
html_print_mfgs (void)
{
  if (!title)
    title = "SANE: Supported Devices";

  if (!intro)
    intro = "<p> The following table summarizes the devices supported "
      "by the latest version of sane-backends. </p>";

  html_print_header ();

  printf ("<h2><a name=\"SCANNERS\">Scanners</a></h2>\n");
  html_mfgs_table (type_scanner);

  printf ("<h2><a name=\"STILL\">Still Cameras</a></h2>\n");
  html_mfgs_table (type_stillcam);

  printf ("<h2><a name=\"VIDEO\">Video Cameras</a></h2>\n");
  html_mfgs_table (type_vidcam);

  printf ("<h2><a name=\"API\">APIs</a></h2>\n");
  html_backends_split_table (type_api);

  printf ("<h2><a name=\"META\">Meta Backends</a></h2>\n");
  html_backends_split_table (type_meta);

  printf
    ("<h3><a name=\"legend\">Legend:</a></h3>\n" "<blockquote>\n" "<dl>\n");

  html_print_legend_model ();
  html_print_legend_interface ();
  html_print_legend_status ();
  html_print_legend_comment ();
  html_print_legend_backend ();
  html_print_legend_manual ();

  html_print_legend_manufacturer ();
  html_print_legend_description ();

  printf ("</dl>\n" "</blockquote>\n");

  html_print_footer ();
}


int
main (int argc, char **argv)
{
  program_name = strrchr (argv[0], '/');
  if (program_name)
    ++program_name;
  else
    program_name = argv[0];

  if (!get_options (argc, argv))
    return 1;
  if (!read_files ())
    return 1;
  switch (mode)
    {
    case output_mode_ascii:
      ascii_print_backends ();
      break;
    case output_mode_xml:
      xml_print_backends ();
      break;
    case output_mode_html_backends_split:
      html_print_backends_split ();
      break;
    case output_mode_html_mfgs:
      html_print_mfgs ();
      break;
    default:
      DBG_ERR ("Unknown output mode\n");
      return 1;
    }

  return 0;
}
