text:
#include <mxml.h>
#include <time.h>
#include <limits.h>
#include <ctype.h>

/* Common IPP registry stuff */
#include "ipp-registry.h"

/* Windows case insensitive string compare */
#if _WIN32
#  define strcasecmp _stricmp
#endif /* _WIN32 */

/*
 * Constants...
 */

enum
{
  IN_NOTHING,				/* Nothing to process */
  IN_OPER_ATTRS,			/* Operation attributes */
  IN_PDESC_ATTRS,			/* Printer Description attributes */
  IN_PSTAT_ATTRS,			/* Printer Status attributes */
  IN_JDESC_ATTRS,			/* Job Description attributes */
  IN_JSTAT_ATTRS,			/* Job Status attributes */
  IN_JTMPL_ATTRS,			/* Job Template attributes */
  IN_DDESC_ATTRS,			/* Document Description attributes */
  IN_DSTAT_ATTRS,			/* Document Status attributes */
  IN_DTMPL_ATTRS,			/* Document Template attributes */
  IN_RDESC_ATTRS,			/* Resource Description attributes */
  IN_RSTAT_ATTRS,			/* Resource Status attributes */
  IN_SDESC_ATTRS,			/* Subscription Description attributes */
  IN_SSTAT_ATTRS,			/* Subscription Status attributes */
  IN_STMPL_ATTRS,			/* Subscription Template attributes */
  IN_SYSDESC_ATTRS,			/* System Description attributes */
  IN_SYSSTAT_ATTRS,			/* System Status attributes */
  IN_EVENT_ATTRS,			/* Event Notification attributes */
  IN_KEYWORD_VALS,			/* Keyword attribute values */
  IN_ENUM_VALS,				/* Enum Attribute values */
  IN_OPERATIONS,			/* Operations */
  IN_STATUS_CODES,			/* Status codes */
  IN_OBJECTS,				/* Objects */
  IN_ATTR_GROUP_VALS,			/* Attribute Group Values */
  IN_OUT_OF_BAND_VALS,			/* Out-of-Band Values */
  IN_MAX
};

/*
 * Structures...
 */

typedef struct				/* Record for sorting */
{
  int		index;			/* Index (to detect changes) */
  mxml_node_t	*node;			/* Node for this record */
  const char	*keys[4];		/* Sorting keys */
} reg_record_t;

typedef struct				/* Work counters */
{
  int		added,			/* Items added */
		updated,		/* Items updated/extended */
		ignored;		/* Items ignored */
} reg_counter_t;

/*
 * Local globals...
 */

static reg_counter_t	Attributes = { 0, 0, 0 };
static reg_counter_t	Enums = { 0, 0, 0 };
static reg_counter_t	Keywords = { 0, 0, 0 };
static reg_counter_t	Operations = { 0, 0, 0 };
static reg_counter_t	StatusCodes = { 0, 0, 0 };
static int		Verbosity = 0;

/*
 * Local functions...
 */

static int		add_attr(mxml_node_t *xml, FILE *logfile, const char *collection, const char *attrname, const char *membername, const char *submembername, const char *syntax, const char *xref, const char *xrefname);
static int		add_attr_group(mxml_node_t *xml, FILE *logfile, const char *value, const char *name, const char *xref, const char *xrefname);
static int		add_object(mxml_node_t *xml, FILE *logfile, const char *name, const char *xref, const char *xrefname);
static int		add_operation(mxml_node_t *xml, FILE *logfile, const char *name, const char *xref, const char *xrefname);
static int		add_out_of_band(mxml_node_t *xml, FILE *logfile, const char *value, const char *name, const char *xref, const char *xrefname);
static int		add_status_code(mxml_node_t *xml, FILE *logfile, const char *value, const char *name, const char *xref, const char *xrefname);
static int		add_value(mxml_node_t *xml, FILE *logfile, const char *enumval, const char *keyword, const char *attrname, const char *syntax, const char *xref, const char *xrefname);
static int		add_valattr(mxml_node_t *xml, FILE *logfile, const char *registry, const char *attrname, const char *syntax, const char *xref, const char *xrefname);
static int		add_value(mxml_node_t *xml, FILE *logfile, const char *enumval, const char *keyword, const char *attrname, const char *syntax, const char *xref, const char *xrefname);
static int		compare_record(reg_record_t *a, reg_record_t *b);
static int		compare_strings(const char *s, const char *t);
static int		read_text(mxml_node_t *xml, FILE *textfile, FILE *logfile, const char *title, const char *xref);
static const char	*save_cb(mxml_node_t *node, int column);
static int		usage(const char *opt);
static int		validate_registry(mxml_node_t *xml, const char *registry, const char *regname, int num_keys, const char * const *keys);
static const char	*xref_name(const char *xref, const char *title);

/*
 * 'main()' - Handle additions to the IANA registry.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  int		i;			/* Looping var */
  const char	*xmlin = NULL,		/* XML registration input file */
		*xmlout = NULL,		/* XML registration output file */
		*textin = NULL,		/* Text registration input file */
		*title = NULL,		/* Title for standard */
		*xref = NULL,		/* URL for standard (or rfcNNNN) */
		*opt;			/* Current option character */
  mxml_node_t	*xml;			/* XML registration file top node */
  char		xmlbackup[1024];	/* Backup file */
  FILE		*xmlfile,		/* XML registration file pointer */
		*textfile,		/* Text registration file */
		*logfile;		/* Log file */
  int		changed = 0;		/* Was the registration data changed? */
  static const char * const attribute_keys[] = { "collection", "name", "member_attribute", "sub-member_attribute" };
  static const char * const attribute_group_keys[] = { "value" };
  static const char * const keyword_keys[] = { "attribute", "value" };
  static const char * const enum_keys[] = { "attribute", "value" };
  static const char * const object_keys[] = { "name" };
  static const char * const operation_keys[] = { "name" };
  static const char * const out_of_band_keys[] = { "value" };
  static const char * const status_code_keys[] = { "value" };
					/* Sorting keys for the different registries */

 /*
  * Parse command-line arguments...
  */

  for (i = 1; i < argc; i ++)
  {
    if (argv[i][0] == '-')
    {
      for (opt = argv[i] + 1; *opt; opt ++)
      {
	switch (*opt)
	{
	  case 'f' : /* Force output */
	      changed = 1;
	      break;

	  case 'o' : /* Output file */
	      if (xmlout)
	      {
		fputs("Output file may only be specified once.\n", stderr);
		return (usage(NULL));
	      }

	      i ++;
	      if (i >= argc)
	      {
		fputs("Expected filename after '-o'.\n", stderr);
		return (usage(NULL));
	      }

	      xmlout = argv[i];
	      break;

	  case 't' : /* Standard title */
	      if (title)
	      {
		fputs("Title may only be specified once.\n", stderr);
		return (usage(NULL));
	      }

	      i ++;
	      if (i >= argc)
	      {
		fputs("Expected title after '-t'.\n", stderr);
		return (usage(NULL));
	      }

	      title = argv[i];
	      break;

	  case 'v' :	/* Be verbose */
	      Verbosity ++;
	      break;

	  case 'x' :	/* Standard URL or RFC number */
	      if (xref)
	      {
		fputs("URL may only be specified once.\n", stderr);
		return (usage(NULL));
	      }

	      i ++;
	      if (i >= argc)
	      {
		fputs("Expected URL or RFC number after '-x'.\n", stderr);
		return (usage(NULL));
	      }

	      if (strncmp(argv[i], "rfc", 3) && strncmp(argv[i], "http://", 7) && strncmp(argv[i], "https://", 8))
	      {
		fputs("Standard URL must be 'rfcNNNN', 'http://...', or 'https://...'.\n", stderr);
		return (usage(NULL));
	      }

	      xref = argv[i];
	      break;

	  default :
	      return (usage(opt));
	}
      }
    }
    else if (!xmlin)
      xmlin = argv[i];
    else if (!textin)
      textin = argv[i];
    else
    {
      fprintf(stderr, "Unknown argument '%s'.\n", argv[i]);
      return (usage(NULL));
    }
  }

  if (!title)
    title = "TEST";

  if (!xref)
    xref = "test";

  if (!xmlin)
  {
    fputs("Missing required input file.\n", stderr);
    return (usage(NULL));
  }

  if (!xmlout)
    xmlout = xmlin;

 /*
  * Load the XML registration file...
  */

  mxmlSetWrapMargin(INT_MAX);

  if ((xmlfile = fopen(xmlin, "rb")) == NULL)
  {
    perror(xmlin);
    return (1);
  }

  xml = mxmlLoadFile(NULL, xmlfile, ipp_load_cb);
  fclose(xmlfile);

  if (!xml)
  {
    fputs("Bad XML registration file.\n", stderr);
    return (1);
  }

 /*
  * Verify that the XML file is well-formed...
  */

  changed |= validate_registry(xml, IPP_REGISTRY_OBJECTS, "Objects",
			       (int)(sizeof(object_keys) /
				     sizeof(object_keys[0])),
			       object_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_ATTRIBUTES, "Attributes",
			       (int)(sizeof(attribute_keys) /
				     sizeof(attribute_keys[0])),
			       attribute_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_KEYWORDS, "Keyword Values",
			       (int)(sizeof(keyword_keys) /
				     sizeof(keyword_keys[0])),
			       keyword_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_ENUMS, "Enum Values",
			       (int)(sizeof(enum_keys) /
				     sizeof(enum_keys[0])),
			       enum_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_ATTRIBUTE_GROUPS, "Attribute Groups",
			       (int)(sizeof(attribute_group_keys) /
				     sizeof(attribute_group_keys[0])),
			       attribute_group_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_OUT_OF_BANDS, "Out-of-Band Values",
			       (int)(sizeof(out_of_band_keys) /
				     sizeof(out_of_band_keys[0])),
			       out_of_band_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_OPERATIONS, "Operations",
			       (int)(sizeof(operation_keys) /
				     sizeof(operation_keys[0])),
			       operation_keys);
  changed |= validate_registry(xml, IPP_REGISTRY_STATUS_CODES, "Status Codes",
			       (int)(sizeof(status_code_keys) /
				     sizeof(status_code_keys[0])),
			       status_code_keys);

 /*
  * Read the text registrations...
  */

  if (textin)
  {
    char	logname[1024],		/* Log filename */
		*logext;		/* Extension */

    if ((textfile = fopen(textin, "rb")) == NULL)
    {
      fprintf(stderr, "Unable to open '%s': %s\n", textin, strerror(errno));
      return (1);
    }

    strncpy(logname, textin, sizeof(logname) - 1);
    logname[sizeof(logname) - 1] = '\0';
    if ((logext = strrchr(logname, '.')) == NULL || strcasecmp(logext, ".txt"))
      logext = logname + strlen(logname);

    strncpy(logext, ".log", sizeof(logname) - 1 - (size_t)(logext - logname));
    if ((logfile = fopen(logname, "w")) == NULL)
    {
      fprintf(stderr, "Unable to create '%s': %s\n", logname, strerror(errno));
      logfile = stdout;
    }
  }
  else
  {
    textfile = stdin;
    logfile  = stdout;
  }

  changed |= read_text(xml, textfile, logfile, title, xref);

  if (textfile != stdin)
    fclose(textfile);

  if (logfile != stdout)
    fclose(logfile);

 /*
  * Save the XML registration file if there were changes...
  */

  if (changed || Verbosity)
  {
    if (Attributes.added || Attributes.updated || Attributes.ignored)
      printf("Attributes: %d added, %d updated, %d ignored\n", Attributes.added, Attributes.updated, Attributes.ignored);

    if (Enums.added || Enums.updated || Enums.ignored)
      printf("Enums: %d added, %d updated, %d ignored\n", Enums.added, Enums.updated, Enums.ignored);

    if (Keywords.added || Keywords.updated || Keywords.ignored)
      printf("Keywords: %d added, %d updated, %d ignored\n", Keywords.added, Keywords.updated, Keywords.ignored);

    if (Operations.added || Operations.updated || Operations.ignored)
      printf("Operations: %d added, %d updated, %d ignored\n", Operations.added, Operations.updated, Operations.ignored);

    if (StatusCodes.added || StatusCodes.updated || StatusCodes.ignored)
      printf("Status Codes: %d added, %d updated, %d ignored\n", StatusCodes.added, StatusCodes.updated, StatusCodes.ignored);
  }

  if (changed)
  {
    mxml_node_t *updated = mxmlFindElement(xml, xml, "updated", NULL, NULL, MXML_DESCEND);

    if (updated)
    {
      time_t curtime = time(NULL);	/* Current time in seconds */
      struct tm *curdate = gmtime(&curtime);
					/* Current date in UTC */
      char datestr[64];			/* Date string YYYY-MM-DD */

      strftime(datestr, sizeof(datestr), "%Y-%m-%d", curdate);

      mxmlSetOpaque(updated, datestr);
    }

    if (xmlin == xmlout)
    {
      snprintf(xmlbackup, sizeof(xmlbackup), "%s.O", xmlin);
      if (rename(xmlin, xmlbackup))
      {
	perror(xmlin);
	return (1);
      }
    }

    if ((xmlfile = fopen(xmlout, "wb")) == NULL)
    {
      perror(xmlout);
      if (xmlin == xmlout)
	rename(xmlbackup, xmlin);
      return (1);
    }

    i = mxmlSaveFile(xml, xmlfile, save_cb);
    fclose(xmlfile);

    if (i)
    {
      perror(xmlout);
      if (xmlin == xmlout)
	rename(xmlbackup, xmlin);
      return (1);
    }
    else
      printf("Wrote '%s'.\n", xmlout);
  }
  else
    puts("No changes written.");

  return (!changed);
}

/*
 * 'add_attr()' - Add a (member) attribute in the right position of the XML registry.
 */

static int				/* O - 1 if something was added, 0 if not */
add_attr(mxml_node_t *xml,		/* I - XML registry */
         FILE        *logfile,		/* I - Log file */
	 const char  *collection,	/* I - Collection within the attributes registry */
	 const char  *attrname,		/* I - Attribute name */
	 const char  *membername,	/* I - Member attribute name or NULL */
	 const char  *submembername,	/* I - Sub-member attribute name or NULL */
	 const char  *syntax,		/* I - Syntax string */
	 const char  *xref,		/* I - Standard URL/number */
	 const char  *xrefname)		/* I - Standard name or NULL */
{
  int		changed = 0,		/* Did something change? */
		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*collection_node,	/* <collection> */
		*name_node,		/* <name> */
		*membername_node,	/* <member_attribute> */
		*submembername_node,	/* <sub-member_attribute> */
		*syntax_node,		/* <syntax> */
		*xref_node;		/* <xref> */
  const char	*last_collection = NULL,/* Last collection */
		*last_name = NULL,	/* Last attribute name */
		*last_member = NULL,	/* Last member attribute */
		*last_submember = NULL; /* Last sub-member attribute */
  char		extname[256];		/* attribute-name(extension) */

 /*
  * Find any existing definition of the attribute...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id",
				       IPP_REGISTRY_ATTRIBUTES, MXML_DESCEND)) == NULL)
  {
    fputs("Unable to find '" IPP_REGISTRY_ATTRIBUTES "' registry in XML file.\n",
	  stderr);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    collection_node = mxmlFindElement(record_node, record_node, "collection", NULL, NULL,
				      MXML_DESCEND_FIRST);
    if (!collection_node || !mxmlGetOpaque(collection_node))
    {
      fputs("Attribute record missing <collection> in XML file.\n", stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if (last_collection)
    {
      if (compare_strings(last_collection, mxmlGetOpaque(collection_node)))
      {
	last_name = last_member = last_submember = NULL;

	if (!compare_strings(last_collection, collection))
	  break;
      }
    }

    last_collection = mxmlGetOpaque(collection_node);
    if (compare_strings(last_collection, collection))
      continue;

    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!name_node)
    {
      name_node = mxmlFindElement(record_node, record_node, "attribute", NULL,
				  NULL, MXML_DESCEND_FIRST);

      if (!name_node)
      {
	fputs("Attribute record missing <name> in XML file.\n", stderr);
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }
      else
      {
	fprintf(logfile, "Fixing attribute record for %s.\n", mxmlGetOpaque(name_node));
	mxmlSetElement(name_node, "name");
	changed = 1;
	Attributes.updated ++;
      }
    }

    if (last_name)
    {
      if ((result = compare_strings(last_name, mxmlGetOpaque(name_node))) > 0)
      {
	fputs("Attribute record out-of-order in XML file.\n", stderr);
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }

      if (result)
	last_member = last_submember = NULL;
    }

    last_name = mxmlGetOpaque(name_node);
    if ((result = compare_strings(attrname, last_name)) > 0)
      continue;
    else if (result < 0)
      break;

    membername_node = mxmlFindElement(record_node, record_node, "member_attribute",
				      NULL, NULL, MXML_DESCEND_FIRST);
    if (last_member && membername_node && mxmlGetOpaque(membername_node))
    {
      if ((result = compare_strings(last_member, mxmlGetOpaque(membername_node))) > 0)
      {
	fputs("Attribute record out-of-order in XML file.\n", stderr);
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }

      if (result)
	last_submember = NULL;
    }

    last_member = mxmlGetOpaque(membername_node);
    if (membername && !last_member)
    {
      result = -1;
      continue;
    }
    else if (last_member && !membername)
    {
      result = 1;
      break;
    }
    else if (membername)
    {
      if ((result = compare_strings(membername, last_member)) > 0)
	continue;
      else if (result < 0)
	break;
    }

    submembername_node = mxmlFindElement(record_node, record_node, "sub-member_attribute",
					 NULL, NULL, MXML_DESCEND_FIRST);
    if (last_submember && submembername_node && mxmlGetOpaque(submembername_node))
    {
      if (compare_strings(last_submember, mxmlGetOpaque(submembername_node)) > 0)
      {
	fputs("Attribute record out-of-order in XML file.\n", stderr);
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }
    }

    last_submember = mxmlGetOpaque(submembername_node);
    if (submembername && !last_submember)
    {
      result = -1;
      continue;
    }
    else if (last_submember && !submembername)
    {
      result = 1;
      break;
    }
    else if (submembername)
    {
      if ((result = compare_strings(submembername, last_submember)) > 0)
	continue;
      else if (result < 0)
	break;
    }

   /*
    * OK, at this point the current record matches the attribute we are trying
    * to add.  If the syntax doesn't match, treat this as an extension to the
    * original definition and change the attribute or member name to
    * "name(extension)" and add it below...
    */

    syntax_node = mxmlFindElement(record_node, record_node, "syntax",
				  NULL, NULL, MXML_DESCEND_FIRST);
    if (!syntax_node)
    {
      fputs("Attribute record missing <syntax> in XML file.\n", stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if (compare_strings(mxmlGetOpaque(syntax_node), syntax))
    {
      fprintf(logfile, "Extending syntax for %s to '%s'.\n", submembername ? submembername : membername ? membername : attrname, syntax);

      if (submembername)
      {
	snprintf(extname, sizeof(extname), "%s(extension)", submembername);
	submembername = extname;
      }
      else if (membername)
      {
	snprintf(extname, sizeof(extname), "%s(extension)", membername);
	membername = extname;
      }
      else
      {
	snprintf(extname, sizeof(extname), "%s(extension)", attrname);
	attrname = extname;
      }

      Attributes.updated ++;
    }
    else
      return (changed);
  }

 /*
  * If we get here the attribute does not exist.  Build a new record and add it
  * to the registry in the right place.
  */

  if (submembername)
    fprintf(logfile, "Adding member attribute %s/%s/%s (%s)...\n", attrname, membername, submembername, syntax);
  else if (membername)
    fprintf(logfile, "Adding member attribute %s/%s (%s)...\n", attrname, membername, syntax);
  else
    fprintf(logfile, "Adding attribute %s (%s)...\n", attrname, syntax);

  node = mxmlNewElement(NULL, "record");

  collection_node = mxmlNewElement(node, "collection");
  mxmlNewOpaque(collection_node, collection);

  name_node = mxmlNewElement(node, "name");
  mxmlNewOpaque(name_node, attrname);

  if (membername)
  {
    membername_node = mxmlNewElement(node, "member_attribute");
    mxmlNewOpaque(membername_node, membername);

    if (submembername)
    {
      submembername_node = mxmlNewElement(node, "sub-member_attribute");
      mxmlNewOpaque(submembername_node, submembername);
    }
  }

  syntax_node = mxmlNewElement(node, "syntax");
  mxmlNewOpaque(syntax_node, syntax);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  Attributes.added ++;

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER, record_node, node);

  return (1);
}

/*
 * 'add_attr_group()' - Add an attribute group value to the registry.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_attr_group(mxml_node_t *xml,	/* I - XML registry */
	       FILE        *logfile,	/* I - Log file */
	       const char  *value,	/* I - Attribute group value */
	       const char  *name,	/* I - Attribute group name */
	       const char  *xref,	/* I - Standard URL/number */
	       const char  *xrefname)	/* I - Standard name or NULL */
{
  int		changed = 0,		/* Did something change? */
		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*name_node,		/* <name> */
		*value_node,		/* <value> */
		*xref_node;		/* <xref> */
  char		*nodeval;		/* Pointer into node value */

 /*
  * Find any existing definition of the attribute...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id",
				       IPP_REGISTRY_ATTRIBUTE_GROUPS,
				       MXML_DESCEND)) == NULL)
  {
    fputs("Unable to find '" IPP_REGISTRY_ATTRIBUTE_GROUPS
	  "' registry in XML file.\n", stderr);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    value_node = mxmlFindElement(record_node, record_node, "value", NULL, NULL,
				 MXML_DESCEND_FIRST);
    if (!value_node)
    {
      fputs("Status code record missing <value> in XML file.\n",
	    stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if ((result = strtol(value, NULL, 0) -
		  strtol(mxmlGetOpaque(value_node), &nodeval, 0)) > 0)
      continue;
    else if (result < 0)
      break;

    if (nodeval && *nodeval == '-')
    {
     /*
      * 0xXXXX-0xXXXX range; update the range to be 1 more than the current
      * value and then insert the new status code before this one...
      */

      char	newvalue[256];		/* New value */

      snprintf(newvalue, sizeof(newvalue), "0x%04x%s", (unsigned)strtol(value, NULL, 0) + 1, nodeval);
      mxmlSetOpaque(value_node, newvalue);
      changed = 1;
      break;
    }

   /*
    * OK, at this point the current record matches the value we are trying to add.
    */

    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!name_node)
    {
      fprintf(logfile, "Fixing record for attribute group '%s'.\n", value);
      name_node = mxmlNewElement(record_node, "name");
    }

    if (!mxmlGetOpaque(name_node))
    {
      fprintf(logfile, "Updating record for attribute group '%s'.\n", value);
      mxmlNewOpaque(name_node, name);
      return (1);
    }
    else if (compare_strings(name, mxmlGetOpaque(name_node)))
    {
      fprintf(logfile, "Updating record for attribute group '%s'.\n", value);
      mxmlSetOpaque(name_node, name);
      return (1);
    }
    else
      return (changed);
  }

 /*
  * If we get here the value does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  fprintf(logfile, "Adding attribute group %s '%s'...\n", value, name);

  node = mxmlNewElement(NULL, "record");

  value_node = mxmlNewElement(node, "value");
  mxmlNewOpaque(value_node, value);

  name_node = mxmlNewElement(node, "name");
  mxmlNewOpaque(name_node, name);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER, record_node,
	  node);

  return (1);
}

/*
 * 'add_valattr()' - Add a keyword/enum attribute definition.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_valattr(mxml_node_t *xml,		/* I - XML registry */
	    FILE        *logfile,	/* I - Log file */
	    const char	*registry,	/* I - Registry ID */
	    const char	*attrname,	/* I - Attribute name */
	    const char	*syntax,	/* I - Attribute syntax */
	    const char	*xref,		/* I - Standard URL/number */
	    const char	*xrefname)	/* I - Standard name or NULL */
{
  int		changed = 0,		/* Did something change? */
		compare_enums,		/* Compare enum values? */
		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*attr_node,		/* <attribute> */
		*syntax_node,		/* <syntax> */
		*xref_node;		/* <xref> */
  const char	*last_attr = NULL;	/* Last attribute name */

 /*
  * Find any existing definition of the attribute...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id", registry,
				       MXML_DESCEND)) == NULL)
  {
    fprintf(stderr, "Unable to find '%s' registry in XML file.\n",
	    registry);
    exit(1);
  }

  compare_enums = !strcmp(registry, IPP_REGISTRY_ENUMS);

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    attr_node = mxmlFindElement(record_node, record_node, "attribute", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!attr_node)
    {
      attr_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				  MXML_DESCEND_FIRST);

      if (!attr_node)
      {
	fprintf(stderr, "%s record missing <attribute> in XML file.\n",
		compare_enums ? "enum" : "keyword");
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }
      else
      {
	if (compare_enums)
	  Enums.updated ++;
	else
	  Keywords.updated ++;

	fprintf(logfile, "Fixing %s record for '%s'.\n", compare_enums ? "enum" : "keyword", mxmlGetOpaque(attr_node));
	mxmlSetElement(attr_node, "attribute");
	changed = 1;
      }
    }

    if (last_attr && mxmlGetOpaque(attr_node))
    {
      if (compare_strings(last_attr, mxmlGetOpaque(attr_node)) > 0)
      {
	fprintf(stderr, "%s record out-of-order in XML file.\n",
		compare_enums ? "Enum" : "Keyword");
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }
    }

    if ((result = compare_strings(attrname, mxmlGetOpaque(attr_node))) > 0)
      continue;
    else if (result < 0)
      break;

    if (mxmlFindElement(record_node, record_node, "value", NULL, NULL,
			MXML_DESCEND_FIRST))
      break;

   /*
    * OK, at this point the current record matches the attribute we are trying to add.
    * If the syntax doesn't match issue a warning and reset the syntax, otherwise just
    * return with no changes...
    */

    syntax_node = mxmlFindElement(record_node, record_node, "syntax",
				  NULL, NULL, MXML_DESCEND_FIRST);
    if (!syntax_node)
    {
      fputs("Attribute record missing <syntax> in XML file.\n", stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if (compare_strings(mxmlGetOpaque(syntax_node), syntax))
    {
      if (compare_enums)
        Enums.updated ++;
      else
        Keywords.updated ++;

      fprintf(logfile, "Changing syntax for '%s' to '%s'.\n", attrname, syntax);
      mxmlSetOpaque(syntax_node, syntax);
      return (1);
    }
    else
      return (changed);
  }

 /*
  * If we get here the attribute does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  if (compare_enums)
  {
    Enums.added ++;
    fprintf(logfile, "Adding enum attribute '%s'...\n", attrname);
  }
  else
  {
    Keywords.added ++;
    fprintf(logfile, "Adding keyword attribute '%s'...\n", attrname);
  }

  node = mxmlNewElement(NULL, "record");

  attr_node = mxmlNewElement(node, "attribute");
  mxmlNewOpaque(attr_node, attrname);

  syntax_node = mxmlNewElement(node, "syntax");
  mxmlNewOpaque(syntax_node, syntax);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER, record_node,
	  node);

  return (1);
}

/*
 * 'add_object()' - Add an object to the registry.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_object(mxml_node_t *xml,		/* I - XML registry */
	   FILE        *logfile,	/* I - Log file */
	   const char  *name,		/* I - Object name */
	   const char  *xref,		/* I - Standard URL/number */
	   const char  *xrefname)	/* I - Standard name or NULL */
{
  int		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*name_node,		/* <name> */
		*xref_node;		/* <xref> */

 /*
  * Find any existing definition of the operation...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id", IPP_REGISTRY_OBJECTS, MXML_DESCEND)) == NULL)
  {
    fputs("Unable to find '" IPP_REGISTRY_OBJECTS "' registry in XML file.\n",
	  stderr);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!name_node)
    {
      fputs("Object record missing <name> in XML file.\n", stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if ((result = compare_strings(name, mxmlGetOpaque(name_node))) == 0)
    {
      fprintf(logfile, "Duplicate object '%s'.\n", name);
      return (0);
    }
    else if (result < 0)
      break;
  }

 /*
  * If we get here the value does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  fprintf(logfile, "Adding object '%s'...\n", name);

  node = mxmlNewElement(NULL, "record");

  name_node = mxmlNewElement(node, "name");
  mxmlNewOpaque(name_node, name);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER,
	  record_node, node);

  return (1);
}

/*
 * 'add_operation()' - Add an operation to the registry.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_operation(mxml_node_t *xml,		/* I - XML registry */
	      FILE        *logfile,	/* I - Log file */
	      const char  *name,	/* I - Operation name */
	      const char  *xref,	/* I - Standard URL/number */
	      const char  *xrefname)	/* I - Standard name or NULL */
{
  int		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*name_node,		/* <name> */
		*xref_node;		/* <xref> */

 /*
  * Find any existing definition of the operation...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id",
				       IPP_REGISTRY_OPERATIONS, MXML_DESCEND)) == NULL)
  {
    fputs("Unable to find '" IPP_REGISTRY_OPERATIONS "' registry in XML file.\n",
	  stderr);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!name_node)
    {
      fputs("Operation record missing <name> in XML file.\n",
	    stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if ((result = compare_strings(name, mxmlGetOpaque(name_node))) == 0)
    {
      Operations.ignored ++;
      fprintf(logfile, "Duplicate operation '%s'.\n", name);
      return (0);
    }
    else if (result < 0)
      break;
  }

 /*
  * If we get here the value does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  Operations.added ++;
  fprintf(logfile, "Adding operation '%s'...\n", name);

  node = mxmlNewElement(NULL, "record");

  name_node = mxmlNewElement(node, "name");
  mxmlNewOpaque(name_node, name);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER,
	  record_node, node);

  return (1);
}

/*
 * 'add_out_of_band()' - Add an out-of-band value to the registry.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_out_of_band(mxml_node_t *xml,	/* I - XML registry */
		FILE        *logfile,	/* I - Log file */
		const char  *value,	/* I - Out-of-band value */
		const char  *name,	/* I - Out-of-band name */
		const char  *xref,	/* I - Standard URL/number */
		const char  *xrefname)	/* I - Standard name or NULL */
{
  int		changed = 0,		/* Did something change? */
		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*name_node,		/* <name> */
		*value_node,		/* <value> */
		*xref_node;		/* <xref> */
  char		*nodeval;		/* Pointer into node value */

 /*
  * Find any existing definition of the attribute...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id",
				       IPP_REGISTRY_OUT_OF_BANDS,
				       MXML_DESCEND)) == NULL)
  {
    fputs("Unable to find '" IPP_REGISTRY_OUT_OF_BANDS
	  "' registry in XML file.\n", stderr);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    value_node = mxmlFindElement(record_node, record_node, "value", NULL, NULL,
				 MXML_DESCEND_FIRST);
    if (!value_node)
    {
      fputs("Out-of-band record missing <value> in XML file.\n",
	    stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if ((result = strtol(value, NULL, 0) -
		  strtol(mxmlGetOpaque(value_node), &nodeval, 0)) > 0)
      continue;
    else if (result < 0)
      break;

    if (nodeval && *nodeval == '-')
    {
     /*
      * 0xXXXX-0xXXXX range; update the range to be 1 more than the current
      * value and then insert the new out-of-band value before this one...
      */

      char	newvalue[256];		/* New value */

      snprintf(newvalue, sizeof(newvalue), "0x%04x%s", (unsigned)strtol(value, NULL, 0) + 1, nodeval);
      mxmlSetOpaque(value_node, newvalue);
      changed = 1;
      break;
    }

   /*
    * OK, at this point the current record matches the value we are trying to add.
    */

    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!name_node)
    {
      fprintf(logfile, "Fixing record for out-of-band value %s.\n", value);
      name_node = mxmlNewElement(record_node, "name");
    }

    if (!mxmlGetOpaque(name_node))
    {
      fprintf(logfile, "Updating record for out-of-band value %s.\n", value);
      mxmlNewOpaque(name_node, name);
      return (1);
    }
    else if (compare_strings(name, mxmlGetOpaque(name_node)))
    {
      fprintf(logfile, "Updating record for out-of-band value %s.\n", value);
      mxmlSetOpaque(name_node, name);
      return (1);
    }
    else
      return (changed);
  }

 /*
  * If we get here the value does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  fprintf(logfile, "Adding out-of-band value %s '%s'\n", value, name);

  node = mxmlNewElement(NULL, "record");

  value_node = mxmlNewElement(node, "value");
  mxmlNewOpaque(value_node, value);

  name_node = mxmlNewElement(node, "name");
  mxmlNewOpaque(name_node, name);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER, record_node,
	  node);

  return (1);
}

/*
 * 'add_status_code()' - Add a status code to the registry.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_status_code(mxml_node_t *xml,	/* I - XML registry */
		FILE        *logfile,	/* I - Log file */
		const char  *value,	/* I - Status code value */
		const char  *name,	/* I - Status code name */
		const char  *xref,	/* I - Standard URL/number */
		const char  *xrefname)	/* I - Standard name or NULL */
{
  int		changed = 0,		/* Did something change? */
		result;			/* Result of comparison */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*name_node,		/* <name> */
		*value_node,		/* <value> */
		*xref_node;		/* <xref> */
  char		*nodeval;		/* Pointer into node value */

 /*
  * Find any existing definition of the attribute...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id",
				       IPP_REGISTRY_STATUS_CODES,
				       MXML_DESCEND)) == NULL)
  {
    fputs("Unable to find '" IPP_REGISTRY_STATUS_CODES
	  "' registry in XML file.\n", stderr);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    value_node = mxmlFindElement(record_node, record_node, "value", NULL, NULL,
				 MXML_DESCEND_FIRST);
    if (!value_node)
    {
      fputs("Status code record missing <value> in XML file.\n",
	    stderr);
      mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
      exit(1);
    }

    if ((result = strtol(value, NULL, 0) -
		  strtol(mxmlGetOpaque(value_node), &nodeval, 0)) > 0)
      continue;
    else if (result < 0)
      break;

    if (nodeval && *nodeval == '-')
    {
     /*
      * 0xXXXX-0xXXXX range; update the range to be 1 more than the current
      * value and then insert the new status code before this one...
      */

      char	newvalue[256];		/* New value */

      snprintf(newvalue, sizeof(newvalue), "0x%04x%s", (unsigned)strtol(value, NULL, 0) + 1, nodeval);
      mxmlSetOpaque(value_node, newvalue);
      changed = 1;
      break;
    }

   /*
    * OK, at this point the current record matches the value we are trying to add.
    */

    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!name_node)
    {
      fprintf(logfile, "Fixing record for status code '%s'.\n", value);
      name_node = mxmlNewElement(record_node, "name");
    }

    if (!mxmlGetOpaque(name_node))
    {
      StatusCodes.updated ++;
      fprintf(logfile, "Updating record for status code '%s'.\n", value);
      mxmlNewOpaque(name_node, name);
      return (1);
    }
    else if (compare_strings(name, mxmlGetOpaque(name_node)))
    {
      StatusCodes.updated ++;
      fprintf(logfile, "Updating record for status code '%s'.\n", value);
      mxmlSetOpaque(name_node, name);
      return (1);
    }
    else
      return (changed);
  }

 /*
  * If we get here the value does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  StatusCodes.added ++;

  fprintf(logfile, "Adding status code %s '%s'...\n", value, name);

  node = mxmlNewElement(NULL, "record");

  value_node = mxmlNewElement(node, "value");
  mxmlNewOpaque(value_node, value);

  name_node = mxmlNewElement(node, "name");
  mxmlNewOpaque(name_node, name);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER, record_node,
	  node);

  return (1);
}

/*
 * 'add_value()' - Add a keyword/enum value to the registry.
 */

static int				/* O - 1 if something changed, 0 otherwise */
add_value(mxml_node_t *xml,		/* I - XML registry */
	  FILE        *logfile,		/* I - Log file */
	  const char  *enumval,		/* I - Enumeration value or NULL */
	  const char  *keyword,		/* I - Keyword */
	  const char  *attrname,	/* I - Attribute name */
	  const char  *syntax,		/* I - Attribute syntax */
	  const char  *xref,		/* I - Standard URL/number */
	  const char  *xrefname)	/* I - Standard name or NULL */
{
  int		changed = 0,		/* Did something change? */
		result;			/* Result of comparison */
  const char	*registry,		/* Registry */
		*name;			/* <name> value */
  mxml_node_t	*node,			/* New <registry> node */
		*registry_node,		/* <registry> */
		*record_node,		/* <record> */
		*attr_node,		/* <attribute> */
		*syntax_node,		/* <syntax> */
		*name_node,		/* <name> */
		*value_node,		/* <value> */
		*xref_node;		/* <xref> */

 /*
  * Find any existing definition of the attribute...
  */

  registry = enumval ? IPP_REGISTRY_ENUMS : IPP_REGISTRY_KEYWORDS;

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id", registry,
				       MXML_DESCEND)) == NULL)
  {
    fprintf(stderr, "Unable to find '%s' registry in XML file.\n",
	    registry);
    exit(1);
  }

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST);
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND))
  {
    attr_node = mxmlFindElement(record_node, record_node, "attribute", NULL, NULL,
				MXML_DESCEND_FIRST);
    if (!attr_node)
    {
      attr_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				  MXML_DESCEND_FIRST);

      if (!attr_node)
      {
	fprintf(stderr, "%s record missing <attribute> in XML file.\n",
		enumval ? "enum" : "keyword");
	mxmlSaveFile(record_node, stderr, MXML_NO_CALLBACK);
	exit(1);
      }
      else
      {
        if (enumval)
          Enums.updated ++;
        else
          Keywords.updated ++;

	fprintf(logfile, "Fixing %s record for %s.\n", enumval ? "enum" : "keyword", mxmlGetOpaque(attr_node));
	mxmlSetElement(attr_node, "attribute");
	changed = 1;
      }
    }

    if ((result = compare_strings(attrname, mxmlGetOpaque(attr_node))) > 0)
      continue;
    else if (result < 0)
      break;

    if ((value_node = mxmlFindElement(record_node, record_node, "value", NULL,
				      NULL, MXML_DESCEND_FIRST)) == NULL)
      continue;
    else if (enumval)
    {
      if ((result = compare_strings(enumval,
				    mxmlGetOpaque(value_node))) > 0)
	continue;
      else if (result < 0)
	break;
      else if (result == 0 && strstr(keyword, "(deprecated)") != NULL)
      {
	record_node = mxmlFindElement(record_node, registry_node, "record", NULL, NULL, MXML_NO_DESCEND);
	break;
      }
    }
    else if ((result = compare_strings(keyword, mxmlGetOpaque(value_node))) > 0)
      continue;
    else if (result < 0)
      break;

   /*
    * OK, at this point the current record matches the value we are trying to add.
    */

    name_node = mxmlFindElement(record_node, record_node, "name", NULL, NULL,
				MXML_DESCEND_FIRST);
    name      = mxmlGetOpaque(name_node);

    if (enumval)
    {
     /*
      * Replace the name with the new value...
      */

      if (!name_node)
      {
        Enums.updated ++;
	fprintf(logfile, "Fixing bad enum record %s for attribute '%s'.\n", enumval, attrname);
	name_node = mxmlNewElement(record_node, "name");
      }

      if (!name)
	mxmlNewOpaque(name_node, keyword);
      else
      {
        Enums.updated ++;
	fprintf(logfile, "Renaming keyword for enum record %s for attribute '%s'.\n", enumval, attrname);
	mxmlSetOpaque(name_node, keyword);
      }

      return (1);
    }

    if (enumval)
    {
      Enums.ignored ++;
      fprintf(logfile, "Duplicate enum value %s for attribute '%s'.\n", enumval, attrname);
    }
    else
    {
      Keywords.ignored ++;
      fprintf(logfile, "Duplicate keyword value '%s' for attribute '%s'.\n", keyword, attrname);
    }

    return (changed);
  }

 /*
  * If we get here the value does not exist.  Build a new record and add it to the
  * registry in the right place.
  */

  if (enumval)
  {
    Enums.added ++;
    fprintf(logfile, "Adding enum %s '%s' for '%s (%s)'...\n", enumval, keyword, attrname, syntax);
  }
  else
  {
    Keywords.added ++;
    fprintf(logfile, "Adding keyword '%s' for '%s (%s)'...\n", keyword, attrname, syntax);
  }

  node = mxmlNewElement(NULL, "record");

  value_node = mxmlNewElement(node, "value");
  mxmlNewOpaque(value_node, enumval ? enumval : keyword);

  if (enumval)
  {
    name_node = mxmlNewElement(node, "name");
    mxmlNewOpaque(name_node, keyword);
  }
  else if (!strcmp(attrname, "media"))
  {
   /*
    * All new media registrations are for size names now...
    */

    mxml_node_t *type_node = mxmlNewElement(node, "type");
    mxmlNewOpaque(type_node, "size name");
  }

  attr_node = mxmlNewElement(node, "attribute");
  mxmlNewOpaque(attr_node, attrname);

  syntax_node = mxmlNewElement(node, "syntax");
  mxmlNewOpaque(syntax_node, syntax);

  xref_node = mxmlNewElement(node, "xref");
  mxmlElementSetAttr(xref_node, "data", xref);
  if (xrefname)
  {
    mxmlElementSetAttr(xref_node, "type", "uri");
    mxmlNewOpaque(xref_node, xrefname);
  }
  else
    mxmlElementSetAttr(xref_node, "type", "rfc");

  mxmlAdd(registry_node, record_node ? MXML_ADD_BEFORE : MXML_ADD_AFTER, record_node,
	  node);

  return (1);
}

/*
 * 'compare_record()' - Compare two registry records...
 */

static int				/* O - Result of comparison */
compare_record(reg_record_t *a,		/* I - First record */
	       reg_record_t *b)		/* I - Second record */
{
  int	i,				/* Looping var */
	result;				/* Result of comparison */

  for (i = 0; i < (int)(sizeof(a->keys) / sizeof(a->keys[0])); i ++)
    if ((result = compare_strings(a->keys[i], b->keys[i])) != 0)
      return (result);

  return (0);
}

/*
 * 'compare_strings()' - Compare two strings.
 *
 * This function basically does a strcmp() of the two strings, but is also
 * aware of numbers so that "a2" < "a100".
 */

static int				/* O - Result of comparison */
compare_strings(const char *s,		/* I - First string */
		const char *t)		/* I - Second string */
{
  int		diff;			/* Difference between digits */

  if (!s)
  {
    if (t)
      return (-1);
    else
      return (0);
  }
  else if (!t)
    return (1);

 /*
  * Loop through both names, returning only when a difference is
  * seen.  Also, compare whole numbers rather than just characters, too!
  */

  while (*s && *t)
  {
    if (isdigit(*s & 255) && isdigit(*t & 255))
    {
     /*
      * Got numbers; scan the numbers and compare...
      */

      int sn = (int)strtol(s, (char **)&s, 0);
      int tn = (int)strtol(t, (char **)&t, 0);

      if ((diff = sn - tn) != 0)
	return (diff);
    }
    else if ((diff = *s - *t) != 0)
      return (diff);
    else
    {
      s ++;
      t ++;
    }
  }

 /*
  * Return the results of the final comparison...
  */

  if (*s)
    return (1);
  else if (*t)
    return (-1);
  else
    return (0);
}

/*
 * 'read_text()' - Read text registration data from the file.
 */

static int				/* O - 1 if something was added, 0 if not */
read_text(mxml_node_t *xml,		/* I - XML registration document */
	  FILE	      *textfile,	/* I - Text registration file */
	  FILE        *logfile,		/* I - Log file */
	  const char  *title,		/* I - Standard title */
	  const char  *xref)		/* I - Standard URL */
{
  int		i,			/* Looping var */
		blanks = 0,		/* Number of blank lines */
		changed = 0,		/* Was something changed? */
		state = IN_NOTHING,	/* Current state */
		linenum = 0,		/* Current line number */
		paren = 0;		/* Parenthesis level */
  char		line[1024],		/* Line from text registration file */
		*ptr,			/* Pointer into line */
		*first,			/* First value */
		*second,		/* Second value */
		attrname[128],		/* Current attribute name */
		membername[128],	/* Current member attribute name */
		syntax[128];		/* Current syntax */
  const char	*xrefname;		/* Standard number */
  static struct
  {
    const char	*header;		/* Header string */
    size_t	headerlen;		/* Header length */
  }	states[] =	/* Headers for each group */
  {
    { NULL, 0 },
    { "Operation attributes:", 21 },
    { "Printer Description attributes:", 31 },
    { "Printer Status attributes:", 26 },
    { "Job Description attributes:", 27 },
    { "Job Status attributes:", 22 },
    { "Job Template attributes:", 24 },
    { "Document Description attributes:", 32 },
    { "Document Status attributes:", 27 },
    { "Document Template attributes:", 29 },
    { "Resource Description attributes:", 32 },
    { "Resource Status attributes:", 27 },
    { "Subscription Description attributes:", 36 },
    { "Subscription Status attributes:", 31 },
    { "Subscription Template attributes:", 33 },
    { "System Description attributes:", 30 },
    { "System Status attributes:", 15 },
    { "Event Notification attributes:", 30 },
    { "Keyword Attribute Value", 23 },
    { "Enum Value", 10 },
    { "Operation Name", 14 },
    { "Value	Status Code Name", 25 },
    { "Object Name", 11 },
    { "Attribute Group Value", 21 },
    { "Out-of-Band Value", 17 }
  };
  static const char * const collections[] =
  {					/* Collection names for each state */
    NULL,
    "Operation",
    "Printer Description",
    "Printer Status",
    "Job Description",
    "Job Status",
    "Job Template",
    "Document Description",
    "Document Status",
    "Document Template",
    "Resource Description",
    "Resource Status",
    "Subscription Description",
    "Subscription Status",
    "Subscription Template",
    "System Description",
    "System Status",
    "Event Notification",
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL
  };

 /*
  * Read lines from the text registration file and make changes as needed...
  */

  memset(attrname, 0, sizeof(attrname));
  memset(membername, 0, sizeof(membername));
  memset(syntax, 0, sizeof(syntax));

  xrefname = xref_name(xref, title);

  while (fgets(line, sizeof(line), textfile))
  {
    linenum ++;

   /*
    * Strip "[something]"...
    */

    if ((ptr = strchr(line, '[')) != NULL)
      *ptr = '\0';

   /*
    * Strip leading and trailing whitespace...
    */

    ptr = line + strlen(line) - 1;
    while (ptr >= line)
      if (isspace(*ptr & 255))
	*ptr-- = '\0';
      else
	break;

    for (ptr = line; *ptr && isspace(*ptr & 255); ptr ++);

   /*
    * Skip blank lines and lines that start with "-"...
    */

    if (!*ptr)
    {
      blanks ++;
      if (blanks > 1)
	state = IN_NOTHING;

      continue;
    }
    else if (*ptr == '-')
      continue;

    blanks = 0;

   /*
    * Look for section headings...
    */

    for (i = IN_OPER_ATTRS; i < IN_MAX; i ++)
      if (!strncmp(ptr, states[i].header, states[i].headerlen))
      {
	state	      = i;
	attrname[0]   = '\0';
	membername[0] = '\0';
	syntax[0]     = '\0';
	break;
      }

    if (i >= IN_MAX)
    {
      switch (state)
      {
	case IN_OPER_ATTRS :
	case IN_PDESC_ATTRS :
	case IN_PSTAT_ATTRS :
	case IN_JDESC_ATTRS :
	case IN_JSTAT_ATTRS :
	case IN_JTMPL_ATTRS :
	case IN_DDESC_ATTRS :
	case IN_DSTAT_ATTRS :
	case IN_DTMPL_ATTRS :
	case IN_RDESC_ATTRS :
	case IN_RSTAT_ATTRS :
	case IN_SDESC_ATTRS :
	case IN_SSTAT_ATTRS :
	case IN_STMPL_ATTRS :
	case IN_SYSDESC_ATTRS :
	case IN_SYSSTAT_ATTRS :
	case IN_EVENT_ATTRS :
	    if (!islower(*ptr & 255))
	      continue;

	   /*
	    * Attributes are not indented, member attributes *are*.
	    */

	    first = ptr;
	    while (*ptr && !isspace(*ptr & 255))
	      ptr ++;
	    if (!*ptr)
	    {
	      fprintf(stderr, "Bad attribute definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';

	    if (*ptr != '(')
	    {
	      fprintf(stderr, "Bad attribute definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    ptr ++;
	    second = ptr;
	    for (paren = 1; *ptr && paren > 0; ptr ++)
	    {
	      if (*ptr == '(')
		paren ++;
	      else if (*ptr == ')')
	      {
		paren --;
		if (paren == 0)
		  *ptr = '\0';
	      }
	    }

	    if (first == line)
	    {
	     /*
	      * attribute syntax
	      */

	      changed |= add_attr(xml, logfile, collections[state], first, NULL, NULL, second, xref, xrefname);

	      strncpy(attrname, first, sizeof(attrname));
	    }
	    else if ((first - 2) == line)
	    {
	     /*
	      * member syntax
	      */

	      if (!attrname[0])
	      {
		fprintf(stderr, "Bad member attribute definition on line %d.\n",
			linenum);
		exit(1);
	      }

	      changed |= add_attr(xml, logfile, collections[state], attrname, first, NULL, second, xref, xrefname);

	      strncpy(membername, first, sizeof(membername));
	    }
	    else
	    {
	     /*
	      * sub-member syntax
	      */

	      if (!attrname[0] || !membername[0])
	      {
		fprintf(stderr, "Bad sub-member attribute definition on line %d.\n", linenum);
		exit(1);
	      }

	      changed |= add_attr(xml, logfile, collections[state], attrname, membername, first, second, xref, xrefname);
	    }
	    break;

	case IN_KEYWORD_VALS :
	    if (*ptr != '<' && !islower(*ptr & 255))
	      continue;

	   /*
	    * Attributes are not indented, keywords *are*.
	    */

	    first = ptr;
	    if (*ptr == '<')
	    {
	      while (*ptr && *ptr != '>')
		ptr ++;

	      if (*ptr == '>')
		ptr ++;
	    }
	    else
	    {
	      while (*ptr && !isspace(*ptr & 255))
		ptr ++;
	    }

	    if (!*ptr && first == line)
	    {
	      fprintf(stderr, "Bad attribute/keyword definition on line %d.\n", linenum);
	      exit(1);
	    }

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';

	    if (first == line)
	    {
	      if (*ptr != '(')
	      {
		fprintf(stderr, "Bad attribute definition on line %d.\n", linenum);
		exit(1);
	      }

	      ptr ++;
	      second = ptr;
	      for (paren = 1; *ptr && paren > 0; ptr ++)
	      {
		if (*ptr == '(')
		  paren ++;
		else if (*ptr == ')')
		{
		  paren --;
		  if (paren == 0)
		    *ptr = '\0';
		}
	      }

	     /*
	      * attribute syntax
	      */

	      changed |= add_valattr(xml, logfile, IPP_REGISTRY_KEYWORDS, first, second, xref, xrefname);

	      strncpy(attrname, first, sizeof(attrname));
	      strncpy(syntax, second, sizeof(syntax));
	    }
	    else if (!attrname[0])
	    {
	      fprintf(stderr, "Missing attribute before keyword value on line %d.\n", linenum);
	      exit(1);
	    }
	    else
	    {
	     /*
	      * keyword
	      */

	      changed |= add_value(xml, logfile, NULL, first, attrname, syntax, xref, xrefname);
	    }
	    break;

	case IN_ENUM_VALS :
	    if (*ptr != '<' && !islower(line[0] & 255) && !isdigit(*ptr & 255))
	      continue;

	   /*
	    * Attributes are not indented, enums *are*.
	    */

	    first = ptr;
	    if (*ptr == '<')
	    {
	      while (*ptr && *ptr != '>')
		ptr ++;

	      if (*ptr == '>')
		ptr ++;
	    }
	    else
	    {
	      while (*ptr && !isspace(*ptr & 255))
		ptr ++;
	    }

	    if (!*ptr && first == line)
	    {
	      fprintf(stderr, "Bad attribute/enum definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';

	    if (first == line)
	    {
	      if (*ptr != '(')
	      {
		fprintf(stderr, "Bad attribute definition on line %d.\n",
			linenum);
		exit(1);
	      }

	      ptr ++;
	      second = ptr;
	      for (paren = 1; *ptr && paren > 0; ptr ++)
	      {
		if (*ptr == '(')
		  paren ++;
		else if (*ptr == ')')
		{
		  paren --;
		  if (paren == 0)
		    *ptr = '\0';
		}
	      }

	     /*
	      * attribute syntax
	      */

	      changed |= add_valattr(xml, logfile, IPP_REGISTRY_ENUMS, first, second, xref, xrefname);

	      strncpy(attrname, first, sizeof(attrname));
	      strncpy(syntax, second, sizeof(syntax));
	    }
	    else if (!attrname[0])
	    {
	      fprintf(stderr, "Missing attribute before enum value on line %d.\n",
		      linenum);
	      exit(1);
	    }
	    else if (!*ptr && first[0] != '<')
	    {
	      fprintf(stderr, "Bad enum definition on line %d.\n",
		      linenum);
	      exit(1);
	    }
	    else
	    {
	     /*
	      * enum keyword
	      */

	      for (second = ptr, ptr ++; *ptr && !isspace(*ptr); ptr ++);

	      *ptr = '\0';
	      if (first[0] == '<')
		second = NULL;

	      changed |= add_value(xml, logfile, first, second, attrname, syntax, xref, xrefname);
	    }
	    break;

	case IN_OPERATIONS :
	    if (!isupper(*ptr & 255))
	      continue;

	    first = ptr;
	    while (*ptr && !isspace(*ptr & 255))
	      ptr ++;
	    if (!strncmp(ptr, " (extension)", 12))
	      ptr += 12;
	    *ptr = '\0';

	    changed |= add_operation(xml, logfile, first, xref, xrefname);
	    break;

	case IN_STATUS_CODES :
	    if (strncmp(ptr, "0x", 2) || strchr(ptr, ':'))
	      continue;

	    first = ptr;
	    while (*ptr && !isspace(*ptr & 255))
	      ptr ++;

	    if (!*ptr)
	    {
	      fprintf(stderr,
		      "Bad status code definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';

	    second = ptr;
	    if (!islower(*ptr & 255))
	    {
	      fprintf(stderr,
		      "Bad status code definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (*ptr && (islower(*ptr & 255) || *ptr == '-'))
	      ptr ++;
	    *ptr = '\0';

	    changed |= add_status_code(xml, logfile, first, second, xref, xrefname);
	    break;

	case IN_OBJECTS :
	    first = ptr;
	    while (*ptr && !isspace(*ptr & 255))
	      ptr ++;

	    *ptr = '\0';

	    changed |= add_object(xml, logfile, first, xref, xrefname);
	    break;

	case IN_ATTR_GROUP_VALS :
	    if (strncmp(ptr, "0x", 2) || strchr(ptr, ':'))
	      continue;

	    first = ptr;
	    while (*ptr && !isspace(*ptr & 255))
	      ptr ++;

	    if (!*ptr)
	    {
	      fprintf(stderr,
		      "Bad attribute group value definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';

	    second = ptr;
	    if (!islower(*ptr & 255))
	    {
	      fprintf(stderr,
		      "Bad attribute group value definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (*ptr && (islower(*ptr & 255) || *ptr == '-'))
	      ptr ++;
	    *ptr = '\0';

	    changed |= add_attr_group(xml, logfile, first, second, xref, xrefname);
	    break;

	case IN_OUT_OF_BAND_VALS :
	    if (strncmp(ptr, "0x", 2) || strchr(ptr, ':'))
	      continue;

	    first = ptr;
	    while (*ptr && !isspace(*ptr & 255))
	      ptr ++;

	    if (!*ptr)
	    {
	      fprintf(stderr,
		      "Bad out-of-band value definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';

	    second = ptr;
	    if (!islower(*ptr & 255))
	    {
	      fprintf(stderr,
		      "Bad out-of-band value definition on line %d.\n",
		      linenum);
	      exit(1);
	    }

	    while (*ptr && (islower(*ptr & 255) || *ptr == '-'))
	      ptr ++;
	    *ptr = '\0';

	    changed |= add_out_of_band(xml, logfile, first, second, xref, xrefname);
	    break;

	default :
	    break;
      }
    }
  }

  return (changed);
}

/*
 * 'save_cb()' - Save (whitespace) callback...
 */

static const char *			/* O - Whitespace to output */
save_cb(mxml_node_t *node,		/* I - Current node */
	int	    where)		/* I - Where we are */
{
  int	level;				/* Indentation level */
  static const char *spaces = "                                        ";
					/* 40 spaces */

  if (mxmlGetParent(node) && !strcmp(mxmlGetElement(mxmlGetParent(node)), "note"))
    return (NULL);

  if (where == MXML_WS_AFTER_OPEN)
  {
    const char *name = mxmlGetElement(node);

    if (name[0] == '!' || name[0] == '?' ||
	!strcmp(name, "registry") || !strcmp(name, "record") || !strcmp(name, "range") ||
	(!mxmlGetFirstChild(node) && strcmp(mxmlGetElement(mxmlGetParent(node)), "note")))
      return ("\n");
    else
      return (NULL);
  }
  else if (where == MXML_WS_BEFORE_CLOSE)
  {
    const char *name = mxmlGetElement(node);

    if (strcmp(name, "registry") && strcmp(name, "record") && strcmp(name, "range"))
      return (NULL);
  }
  else if (where == MXML_WS_AFTER_CLOSE)
    return ("\n");

  for (level = -4; node; node = mxmlGetParent(node))
    level += 2;

  if (level <= 0)
    return (NULL);
  else if (level > 40)
    return (spaces);
  else
    return (spaces + 40 - level);
}

/*
 * 'usage()' - Show program usage.
 */

static int				/* O - Exit status */
usage(const char *opt)			/* I - Pointer to option or NULL */
{
  if (opt)
    fprintf(stderr, "Unknown option '-%c'.\n", *opt);

  puts("\nUsage: ./register options filename.xml [filename.txt]\n");
  puts("Options:");
  puts("  -o newfile.xml	    New XML file instead of replacing filename.xml");
  puts("  -t Title		    Title of standard");
  puts("  -x {rfcNNNN|URL}	    URL or RFC for standard");
  return (1);
}

/*
 * 'validate_registry()' - Validate the sorting of a registry.
 */

static int				/* O - 1 if changed, 0 otherwise... */
validate_registry(
    mxml_node_t		*xml,		/* I - XML registry */
    const char		*registry,	/* I - Registry to validate */
    const char		*regname,	/* I - Name for message output */
    int			num_keys,	/* I - Number of sorting keys */
    const char * const *keys)		/* I - Sorting keys */
{
  int		i,			/* Looping var */
		changed = 0,		/* Did something change? */
		num_records;		/* Number of records */
  reg_record_t	*records,		/* Records */
		*record;		/* Current record */
  mxml_node_t	*registry_node,		/* <registry> node */
		*record_node,		/* Current <record> node */
		*key_node,		/* Current key node */
		*last_node,		/* Last node in sequence */
		*range_node,		/* <range> node */
		*value_node,		/* <value> node */
		*syntax_node;		/* <syntax> node */
  const char	*value,			/* Value string */
		*syntax;		/* Syntax string */
  char		*type3;			/* Pointer to a "type3" definition */

 /*
  * Find the registry...
  */

  if ((registry_node = mxmlFindElement(xml, xml, "registry", "id", registry,
				       MXML_DESCEND)) == NULL)
  {
    fprintf(stderr, "Unable to find registry '%s' (%s) in XML file.\n",
	    registry, regname);
    exit(1);
  }

 /*
  * Scan for "type3" registration rules...
  */

  for (range_node = mxmlFindElement(registry_node, registry_node, "range", NULL, NULL, MXML_DESCEND);
       range_node;
       range_node = mxmlFindElement(range_node, registry_node, "range", NULL, NULL, MXML_DESCEND))
  {
    value_node = mxmlFindElement(range_node, range_node, "value", NULL, NULL, MXML_DESCEND);
    value      = mxmlGetOpaque(value_node);

    if (value && !strcmp(value, "type3"))
    {
      fputs("Removing type3 registration rule.\n", stderr);
      mxmlDelete(range_node);
      range_node = registry_node;
      changed = 1;
    }
  }

 /*
  * Scan for "type3" registration syntaxes...
  */

  for (syntax_node = mxmlFindElement(registry_node, registry_node, "syntax", NULL, NULL, MXML_DESCEND);
       syntax_node;
       syntax_node = mxmlFindElement(syntax_node, registry_node, "syntax", NULL, NULL, MXML_DESCEND))
  {
    syntax = mxmlGetOpaque(syntax_node);
    while (syntax && (type3 = strstr(syntax, "type3")) != NULL)
    {
     /*
      * Change type3 to type2...
      */

      type3[4] = '2';
      changed  = 1;
    }
  }

 /*
  * Figure out the number of records...
  */

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST),
	   num_records = 0;
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND),
	   num_records ++);

  if (Verbosity > 1)
    printf("Registry '%s' (%s) has %d records.\n", registry, regname, num_records);

  if (num_records == 0)
    return (0);

  records = calloc(num_records, sizeof(reg_record_t));

 /*
  * Get the records...
  */

  for (record_node = mxmlFindElement(registry_node, registry_node, "record",
				     NULL, NULL, MXML_DESCEND_FIRST),
	   record = records;
       record_node;
       record_node = mxmlFindElement(record_node, registry_node, "record",
				     NULL, NULL, MXML_NO_DESCEND),
	   record ++)
  {
    record->index = (int)(record - records);
    record->node  = record_node;

    for (i = 0; i < num_keys; i ++)
    {
      key_node = mxmlFindElement(record_node, record_node, keys[i], NULL, NULL,
				 MXML_DESCEND_FIRST);
      if (!key_node && !strcmp(keys[i], "name"))
      {
	key_node = mxmlFindElement(record_node, record_node, "attribute",
				   NULL, NULL, MXML_DESCEND_FIRST);

	if (key_node)
	{
	  fprintf(stderr, "Fixing record for '%s' to use <name>.\n",
		  mxmlGetOpaque(key_node));
	  mxmlSetElement(key_node, "name");
	  changed = 1;
	}
      }
      else if (!key_node && !strcmp(keys[i], "attribute"))
      {
	key_node = mxmlFindElement(record_node, record_node, "name",
				   NULL, NULL, MXML_DESCEND_FIRST);

	if (key_node)
	{
	  fprintf(stderr,
		  "Fixing record for '%s' to use <attribute>.\n",
		  mxmlGetOpaque(key_node));
	  mxmlSetElement(key_node, "attribute");
	  changed = 1;
	}
      }

      record->keys[i] = mxmlGetOpaque(key_node);
    }
  }

 /*
  * Sort the records and see if anything changed...
  */

  qsort(records, num_records, sizeof(reg_record_t),
	(int (*)(const void *, const void *))compare_record);

  for (i = 0, record = records; i < num_records; i ++, record ++)
    if (record->index != i)
      break;

  if (i < num_records)
  {
    fprintf(stderr, "Fixing sorting of records in '%s' (%s).\n",
	    registry, regname);
    changed = 1;

    last_node = mxmlFindElement(registry_node, registry_node, "note",
				NULL, NULL, MXML_DESCEND_FIRST);

    for (i = 0, record = records; i < num_records; i ++, record ++)
    {
      if (last_node)
	mxmlAdd(registry_node, MXML_ADD_AFTER, last_node, record->node);
      else
	mxmlAdd(registry_node, MXML_ADD_BEFORE, MXML_ADD_TO_PARENT,
		record->node);

      last_node = record->node;
    }
  }

  free(records);

  return (changed);
}

/*
 * 'xref_name()' - Return the name to show for the referenced standard.
 */

static const char *			/* O - Name or NULL */
xref_name(const char *xref,		/* I - URL or "rfcNNNN" reference */
	  const char *title)		/* I - Title */
{
  char	*ptr;				/* Pointer into URL/name */
  static char	name[128];		/* Name */

  if (!strncmp(xref, "rfc", 3))
    return (NULL);
  else if ((ptr = (char *)strstr(xref, "-51")) == NULL)
    return (title);

  snprintf(name, sizeof(name), "PWG%s", ptr + 1);
  if ((ptr = strchr(name, '-')) == NULL)
    ptr = strrchr(name, '.');

  if (*ptr)
    *ptr = '\0';

  return (name);
}


text:
namespace crashpad {

//! \brief Type used to represent an address in a process, potentially across
//!     bitness.
using LinuxVMAddress = uint64_t;

//! \brief Type used to represent the size of a memory range (with a
//!     LinuxVMAddress), potentially across bitness.
using LinuxVMSize = uint64_t;

//! \brief Type used to represent an offset from a LinuxVMAddress, potentially
//!     across bitness.
using LinuxVMOffset = int64_t;

}  // namespace crashpad

#endif  // CRASHPAD_UTIL_LINUX_ADDRESS_TYPES_H_

redpajama_set_name: RedPajamaGithub


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using FinancialControl.DataTransferObjects;
using FinancialControl.Persistence;

namespace FinancialControl.WebAPI.Controllers
{
	public abstract class TransactionalApiContoller
		: ApiController
	{
		//Controller é uma instância por requisição
		private FinancialControl.Model.UserSession UserSessionProxy { get; set; }

		protected string UserName
		{
			get {
				return this.UserSessionProxy != null
					&& this.UserSessionProxy.UserLoggedIn != null
					? this.UserSessionProxy.UserLoggedIn.UserName
					: string.Empty;
			}
		}

		protected ResultType InvokeCommandInsideTransactionAnoymously<ResultType>(Func<DAOFactory, ResultType> command)
			where ResultType : class, ResponseEnvelop, new()
		{
			using (TransactionManager tx = GlobalContext.Instance().TransactionManagerFactory.Create())
			{
				return tx.InvokeCommandInsideTransactionAnonymously(command);
			}
		}

		protected ResultType InvokeCommandInsideTransaction<ResultType>(Func<DAOFactory, ResultType> command)
			where ResultType : class, ResponseEnvelop, new()
		{
			try
			{
				using (TransactionManager tx = GlobalContext.Instance().TransactionManagerFactory.Create())
				{
					SetUserSessionInfoToRequestContext();
					return tx.InvokeCommandInsideTransaction(command, this.UserSessionProxy);
				}
			}
			catch(Exception ex)
			{
				var dto = new ResultType();
				dto.Response.Exception = ex.ToString();
				return dto;
			}
		}

		private void SetUserSessionInfoToRequestContext()
		{
			const string USER_NAME_HEADER = "UserName";
			const string USER_SESSION_ID_HEADER = "SessionId";

			this.UserSessionProxy = new FinancialControl.Model.UserSession();
			this.UserSessionProxy.SessionId = RetrieveUserSessionHeader(USER_SESSION_ID_HEADER);
			this.UserSessionProxy.UserLoggedIn = new FinancialControl.Model.User();
			this.UserSessionProxy.UserLoggedIn.UserName = RetrieveUserSessionHeader(USER_NAME_HEADER);
		}

		private string RetrieveUserSessionHeader(string headerName)
		{
			IEnumerable<string> values = Request.Headers.GetValues(headerName);
			return values.FirstOrDefault();
		}

		protected void UserSiege(string username)
		{
			if (!string.Equals(username, this.UserName, StringComparison.InvariantCultureIgnoreCase))
				throw new Exception(string.Format("User conflict: {0} {1}", username, this.UserName));
		}
	}
}


text:
package com.github.bingoohuang.utils.lang;

public class Person {
    String name;
    String idNo;
    String creditCard;

    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", idNo='" + idNo + '\'' +
                ", creditCard='" + creditCard + '\'' +
                '}';
    }

    public Person(String name, String idNo, String creditCard) {
        this.name = name;
        this.idNo = idNo;
        this.creditCard = creditCard;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (name != null ? !name.equals(person.name) : person.name != null) return false;
        if (idNo != null ? !idNo.equals(person.idNo) : person.idNo != null) return false;
        return !(creditCard != null ? !creditCard.equals(person.creditCard) : person.creditCard != null);

    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (idNo != null ? idNo.hashCode() : 0);
        result = 31 * result + (creditCard != null ? creditCard.hashCode() : 0);
        return result;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getIdNo() {
        return idNo;
    }

    public void setIdNo(String idNo) {
        this.idNo = idNo;
    }

    public String getCreditCard() {
        return creditCard;
    }

    public void setCreditCard(String creditCard) {
        this.creditCard = creditCard;
    }
}


text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using E2Print.BL.Interfaces;
using E2Print.Domain.Entities;
using E2Print.WebUI.Models;
using System.Text.RegularExpressions;
using System.IO;

namespace E2Print.WebUI.Controllers
{
    public class ProductController : Controller
    {
        IProduct productRepository;
        ICategory categoryRepository;
        IProductAttribute paRepository;
        public ProductController(IProduct repository, ICategory categoryRepository, IProductAttribute paRepository)
        {
            this.productRepository = repository;
            this.categoryRepository = categoryRepository;
            this.paRepository = paRepository;
        }
        // GET: Product
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public JsonResult GetProductList(int jtStartIndex, int jtPageSize)
        {
            try
            {
                int productsCount = productRepository.GetProductCount();
                var pageProducts = productRepository.GetProducts(jtStartIndex, jtPageSize);
                var productGridModel = pageProducts.Select(c =>
                    new ProductViewModel()
                    {
                        Id = c.Id,
                        Name = c.Name,
                        Description = c.Description,
                        Color=c.Color,
                        Size=c.Size,
                        Material = c.Material,
                        BuyingQty = c.BuyingQty,
                        Price = c.Price,
                        CategoryId = c.Category.Id,
                        CategoryName=c.Category.Name
                    });
                return Json(new { Result = "OK", Records = productGridModel, TotalRecordCount = productsCount });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }

        [HttpPost]
        public JsonResult CreateProduct(ProductViewModel model)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return Json(new
                    {
                        Result = "ERROR",
                        Message = "Form is not valid! " + "Please correct it and try again."
                    });
                }
                Category category = categoryRepository.GetById(model.CategoryId);
                Product newProduct = new Product();
                newProduct.Name = model.Name;
                newProduct.Description = model.Description;
                newProduct.Color=model.Color;
                newProduct.Size=model.Size;
                newProduct.Material = model.Material;
                newProduct.BuyingQty = model.BuyingQty;
                newProduct.Price = model.Price;
                newProduct.Category = category;
                model.Id = productRepository.Create(newProduct).Id;
                return Json(new { Result = "OK", Record = model });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }

        [HttpPost]
        public JsonResult UpdateProduct(ProductViewModel model)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return Json(new
                    {
                        Result = "ERROR",
                        Message = "Form is not valid! " + "Please correct it and try again."
                    });
                }
                Category newCategory = categoryRepository.GetById(int.Parse(Request.Form["CategoryId"]));
                Product productToEdit = new Product();
                productToEdit.Id = model.Id;
                productToEdit.Name = model.Name;
                productToEdit.Description = model.Description;
                productToEdit.Color = model.Color;
                productToEdit.Size = model.Size;
                productToEdit.Material = model.Material;
                productToEdit.BuyingQty = model.BuyingQty;
                productToEdit.Price = model.Price;
                productToEdit.Category = newCategory;
                productRepository.Update(productToEdit);
                return Json(new { Result = "OK" });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }

        [HttpPost]
        public JsonResult DeleteProduct(ProductViewModel model)
        {
            try
            {
                productRepository.Delete(model.Id);
                return Json(new { Result = "OK" });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }

        public ActionResult Detail(int id)
        {
            Product product = productRepository.GetById(id);

            Regex reg = new Regex(@"^.*" + id + "_[0-9]{1}$");
            string path = Server.MapPath("~/Content/Images/Product/");
            var photos = Directory.GetFiles(path).Where(f => reg.IsMatch(Path.GetFileNameWithoutExtension(f))).Select(c => "/Content/Images/Product/" + Path.GetFileName(c));

            List<Product> products = new List<Product>();
            if (product != null)
            {
                products = productRepository.GetByProductName(product.Name??"");
            }

            //bool onsale = promotionRepository.GetAll().Where(c => c.ItemId == id).Count() > 0;
            ProductAndRelatedProductsViewModel viewModel = new ProductAndRelatedProductsViewModel
            {
                Product = product,
                OnSale = false,
                Discount = 0.8m,// onsale ? promotionRepository.GetAll().Where(c => c.ItemId == id).First().DiscountAmount.Value : 1,
                Products = products,
                Photos = photos.Any()? photos.ToList() : new List<string> { "" },
                Sizes = products.GroupBy(c => c.Size).Select(grp => grp.Key).ToList<string>(),
                Colors = products.GroupBy(c => c.Color).Select(grp => grp.Key).ToList<string>(),
                Packs = products.GroupBy(c => c.BuyingQty).Select(grp => grp.Key.ToString()).ToList<string>()
            };

            return View(viewModel);
        }

        public ActionResult List()
        {
            return View();
        }

        [Authorize(Roles = "Admin")]
        public ActionResult ManageProduct()
        {
            List<string> a = paRepository.GetAll().GroupBy(c => c.AttributeName).Select(grp => grp.Key).ToList();
            SelectListViewModel<ProductAttribute> viewModel = new SelectListViewModel<ProductAttribute>
            {
                Keys = a,
                Items = paRepository.GetAll()
            };
            return View(viewModel);
        }

        [HttpPost]
        public JsonResult CreateProductAttribute(ProductAttribute model)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return Json(new
                    {
                        Result = "ERROR",
                        Message = "Form is not valid! " + "Please correct it and try again."
                    });
                }
                ProductAttribute newAttribute = new ProductAttribute();
                newAttribute.AttributeName = model.AttributeName;
                newAttribute.AttributeValue = model.AttributeValue;
                newAttribute.Description = model.Description;
                model.Id = paRepository.Create(newAttribute).Id;
                return Json(new { Result = "OK", Record = model });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }
        [HttpPost]
        public JsonResult DeleteProductAttributeById(int attributeId)
        {
            try
            {
                paRepository.Delete(attributeId);
                return Json(new { Result = "OK" });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }
        [HttpPost]
        public JsonResult DeleteProductAttributeByName(string AttributeName)
        {
            try
            {
                paRepository.DeleteByAttributeName(AttributeName);
                return Json(new { Result = "OK" });
            }
            catch (Exception ex)
            {
                return Json(new { Result = "ERROR", Message = ex.Message });
            }
        }
    }
}


text:
'use strict';

var util = require('util');

module.exports = {
    name: 'hexLength',
    nodeTypes: ['decl'],
    message: '%s should be written in the %s-form format.',

    lint: function hexLengthLinter (config, node) {

        var results = [];
        var parser = require('postcss-values-parser');
        var ast;
        var self = this;

        // just in case, since postcss will sometimes include the semicolon
        // in declaration values
        node.value = node.value.replace(/;$/, '');

        ast = parser(node.value).parse();

        ast.first.walk(function (child) {
            var color;
            var canShorten = false;
            var valid = true;

            if (child.type !== 'word' || !child.isColor) {
                return;
            }

            color = child.value;

            switch (config.style) {
                case 'long':
                    if (color.length === 4 && !/^#[0-9a-f]{6}$/i.test(color)) {
                        valid = false;
                    }

                    break;
                case 'short':
                    // We want to allow longhand form on hex values that can't be shortened
                    if (color.length === 7) {
                        canShorten = (color[1] === color[2] && color[3] === color[4] && color[5] === color[6]);

                        if (!/^#[0-9a-f]{3}$/i.test(color) && canShorten) {
                            valid = false;
                        }
                    }

                    break;
                default:
                    throw new Error(
                        'Invalid setting value for hexLength: ' + config.style
                    );
            }

            if (!valid) {
                results.push({
                    column: node.source.start.column + node.prop.length + node.raws.between.length + child.source.start.column - 1,
                    message: util.format(self.message, color, config.style)
                });
            }

        });

        if (results.length) {
            return results;
        }
    }
};


text:
comments: true
date: 2015-04-20 15:59:57+00:00
layout: post
slug: tutorial-2-line-follower-robot-with-android-and-arduino
title: '[Tutorial #2] Line Follower Robot with Android and Arduino'
categories:
- Android
- Arduino
tags:
- Android
- Arduino
- bluetooth
- course
- diy
- learn
- line follower
- make
- robot
- robotics
---

### Welcome, Aron here!

In the last year I worked in a [Line Follower Robot with Android and Arduino](https://bytedebugger.wordpress.com/2015/02/02/line-follower-robot-with-android-and-arduino/), or the project **Timótheo**.

To complete this project, we created a Framework, [.PNG Arduino Framework](https://github.com/aron-bordin/PNG-Arduino-Framework).

Using it we created an autonomous robot that uses an Android device to analyze the environment using the [Camera](https://bytedebugger.wordpress.com/2015/04/02/tutorial-how-to-use-camera-with-android-and-android-studio/), and then send commands to the robot.

And now, you'll be able to check a full tutorial to learn how to create a similar project and how to hack it and add custom features.

Check the video bellow and let's start.

{% include banner-top.html %}
{% include about-line-follower.html %}

## III - Robot Schematics

I'll not be covering a completed and detailed electronic tutorial here, because my main objective is to show how to code it, the code is the most important part in this project.

So if you have any question about the electronics, feel free to comment here and ask about it :)

But let's see how to connect each component individually.

**1) Keyes L298**

We are going to use this item to control the motors:

We'll use 6 PINs. Check this image:

Connect the PINs ENA, IN1, IN2, IN3, IN4 and ENB with your Arduino board, and add a 5V power supply.

I'm using these PINs:

| **Arduino PIN** 	| **Dual H-Bridge L298**						|
| 6									| IN1 (Controls motor 1 direction)  |
| 7									| IN2 (Controls motor 1 direction)  |
| 5									| ENA (Enable motor 1) 							|
| 8									| IN3 (Controls motor 2 direction)  |
| 9									| IN4 (Controls motor 2 direction)  |
| 11 								| ENA (Enable motor 2)							|

If you don't know how to use this component, please take a look in some tutorial about it, and if you have any question, just comment.

Some useful links:

[http://blog.whatgeek.com.pt/arduino/l298-dual-h-bridge-motor-driver](http://blog.whatgeek.com.pt/arduino/l298-dual-h-bridge-motor-driver)

[https://billwaa.wordpress.com/2012/06/06/arduino-l298-dual-h-bridge-motor-driver/](http://blog.whatgeek.com.pt/arduino/l298-dual-h-bridge-motor-driver)

**2) Bluetooth HC-05**

Check this image, you need to connect your bluetooth module like this one:

 I'm using different PINs.

| **Arduino PIN**				|	**Bluetooth HC-05**			|
| 3											| TX PIN(Purple) 	 	      |
| 2											| RX PIN(Blue)						|

Just make sure to use the right PINs here.

The RX PIN from Arduino will be connected in the TX on HC-05, and TX PIN on HC-05. The RX PIN on HC-05 supports 3.3V, so you'll need to use a voltage divisor to convert 5V to 3V.

**3) Ultrasonic HC-SR04**

Check this image:

You'll need to connect the HC-SR04 with a power supply and connect it with Arduino.

The HC-SR04 has two PINs, ECHO_PIN and TRIGGER_PIN. In this tutorial, I'm using these Arduino PINs:

| **Arduino PIN**				| **HC-SR04** 		|
| A4										| ECHO 						|
| A5										|	TRIGGER					|

Just a tip, if you are not experienced with these items, test them individually to learn more about it. If you are a little confused here, I'm really sorry, but I'm trying to cover the logic and programming in this project. I'll be publishing a separated tutorial about each item, so keep connected with the blog to learn more :)

Here is a table with all PINs used:

| **Arduino PIN** 	| **Dual H-Bridge L298**						|
| 6									| IN1 (Controls motor 1 direction)  |
| 7									| IN2 (Controls motor 1 direction)  |
| 5									| ENA (Enable motor 1) 							|
| 8									| IN3 (Controls motor 2 direction)  |
| 9									| IN4 (Controls motor 2 direction)  |
| 11 								| ENA (Enable motor 2)							|
| **Arduino PIN**				|	**Bluetooth HC-05**			|
| 3											| TX PIN(Purple) 	 	      |
| 2											| RX PIN(Blue)						|
| **Arduino PIN**				| **HC-SR04** 		|
| A4										| ECHO 						|
| A5										|	TRIGGER					|

## IV - Communication protocol(Optional)

This step is optional, you can jump to the next one if you don't want to see how the communication works. I recommend you to read it and try to understand, but if you skip, you'll be able to follow the tutorial.

We created a communication protocol to be easier to connected any device with this robot using bluetooth, radio, wifi, etc. I'm not going to show the whole protocol, major part of it it's not necessary here, so this step is optional, just to help you to understand why we are using this format in the messages.

The Arduino will receive messages in the format "[NUMBER]@[NUMBER]#" or "[NUMBER]@[NUMBER]@[NUMBER]#", where @ is a parameter delimiter and "#" is the end of the message. Let me show you how it works.

The first number will represent one of the following options:

| **First number**		| **Action**																		|
| 1										| Set a value to an Arduino PIN											|
| 2										| Get a value from an Arduino PIN										|
| 3										| Call a method																			|

We're not using all options, to keep this tutorial easier, let's do everything with the number 3.

The second number represents which method we want to run. One of the following:

| **Second number**				| **Action**			|
| 1												| goFront					|
| 2												| goRight					|
| 3												| goRightStrong   |
| 4												| goLeft					|
| 5												| goLeftStrong		|
| 6												| goBack					|
| 7												| goStop					|
| 8												| readDistanceCM  |
| 9												| delay						|
|99												|	Alert when finish the delay |

The third parameter is optional.

It'll be used to send an parameter to the method, for example the number of delay.

And it'll be used when we request a value, for example, read the ultrasonic distance.

So, if the Arduino receives the message "3@1#", for example, this message means Go Front.

If Arduino receive the message "3@8@[ANY-NUMBER]#", it well read and send the distance read by the Ultrasonic.

To return a value, this protocol has some rules too. The Arduino will only send a value if it was requested.

To request a value, in this tutorial we are using the format "3@[METHOD]@[REQUEST_ID]#".

The request_id is important, it's an unique int value to identify the request. So, the Android device can ask the ultrasonic value with the message: "3@8@1#".

To return a value, Arduino will send in this format "[REQUEST_ID]@[VALUE]#".

So, if we ask the distance with "3@8@5#", Arduino will send the message "5@[VALUE]#".

You can think "Why do I need to do it?". Let me show you an example. Let's say the the Android ask to read the ultrasonic value 5 times, run the delay and read it again twice. The bluetooth communication is asynchronous, so when the Android receives the value **30**, for example, it's necessary to know which call this value represents. We made 7 requests, so, to identify each request we use an unique id.

## V - Arduino Code

The complete project will have less the 100 lines of code :) And the most important part, using the communication protocol explained in the previous step, we can use this robot with new projects, without editing the source code :)

Perfect, right? So, let's see how the code works.

To code the project, we're using [.PNG Arduino Framework. You can read more about in this link: http://png-arduino-framework.readthedocs.org/](http://png-arduino-framework.readthedocs.org/)

This framework will help us with objects to allow the control of each component. You can read the documentation for more details if necessary.

**1) Creating the necessary objects**

This first step is simple, we'll just create each Arduino Object with the correct PIN value and run the setup(). Check the necessary code:

{% highlight cpp %}

#include <SoftwareSerial.h>
#include "Bluetooth.h"
#include "Motor.h"
#include "Ultrasonic.h"

Ultrasonic *ult = new Ultrasonic(A5, A4); //TRIGGER_PIN, ECHO_PIN
Motor *motor = new Motor(6, 7, 5, 8, 9, 11); //IN1, IN2, ENA, IN3, IN4, ENB
Bluetooth *blue = new Bluetooth(2, 3); //RX PIN, TX PIN

void setup(){
	Serial.begin(9600);

	blue->setName("Timotheo"); //Set the bluetooth name
	blue->setPIN(6666); //set the PIN NUMBER
	blue->setMessageEnd('#'); //use the # as the end of the message
	blue->setupBluetooth();  //applay the changes to the bluetooth module

	motor->setSpeed(180); //set the motor speed, value from 0 to 255
}

{% endhighlight %}

2) Arduino loop()

The loop() is really simple. The Arduino robot only receives Bluetooth messages and analyze it.

Check the code bellow:

{% highlight cpp %}

void loop(){
	String msg = blue->read();
	if(msg.length() > 1) //check the length of the message
		Serial.println(msg); //shows it on serial
		readMsg(msg); //analyze the message
	delay(20); //wait 20 ms
}

{% endhighlight %}

We just receive a message from bluetooth, show in the Serial and call the readMsg to analyze it.

**3) readMsg()**

This is the most complicated code in this post. If you want to understand how this function works, please make sure to read about the communication protocol. It's commented to help you.

{% highlight cpp %}
void readMsg(String msg){
	String SB, TB; //second byte and third byte, the parameters in the message
	int iSB, iTB;
	int i;
	switch (msg.charAt(0)) { //we read the first number to check the action
	    case '1'://not used in this tutorial. Set an Android PIN value
	      break;
	    case '2'://not used in this tutorial. Get an Android PIN value
	      break;

	    case '3': //call a method
	    	for(i = 2; i < msg.length(); i++){//we start at pos 2, because the msg[0] is 3 and msg[1] is '@'
	    		if(msg.charAt(i) == '#') //end of the message
	    			break;
	    		if(msg.charAt(i) == '@') //read the next parameter
	    			for(int k = i+1; k < msg.length(); k++){
	    				if(msg.charAt(k) == '#')
	    					break;
	    				TB += msg.charAt(k);
	    			}
	    		SB += msg.charAt(i);
	    	}
	    	iSB = SB.toInt();
	    	iTB = TB.toInt();
	    	callMethod(iSB, iTB); //we call the method sending the second and third value received
	    break;
	}
}
{% endhighlight %}

**4) returnValue()**

This method will send a value to the Android device using the communication protocol explained above.

{% highlight cpp %}
void returnValue(int id, int value){
	char Id[5], Val[5], msg[1024];
	sprintf(Id, "%d", id);
	sprintf(Val, "%d", value);
	strcpy(msg, Id);
	strcat(msg, "@");
	strcat(msg, Val);
	strcat(msg, "#");

	blue->send(msg); //send the message in the format "ID@VALUE#"
}
{% endhighlight %}

**5) callMethod()**

And finally, we call the desired method.

{% highlight cpp %}

void callMethod(int cod, int arg1){
	switch (cod) {
		case 1: motor->goFront(); break;
		case 2: motor->goRight(); break;
		case 3: motor->goRightStrong(); break;
		case 4: motor->goLeft(); break;
		case 5: motor->goLeftStrong(); break;
		case 6: motor->goBack(); break;
		case 7: motor->goStop(); break;
		case 8: returnValue(arg1, (int)ult->readDistanceCM()); break;
		case 9: delay(arg1); break;
		case 99: returnValue(1, 99); break;
	}
}
{% endhighlight %}

In the case 8 and case 99, we return an value using the REQUEST_ID. The case 99 just returns 1. We can use it just to know if the communication is ready to run a command, for example.

[The code in this step of the tutorials can be found here.](https://github.com/aron-bordin/Line-Follower-Robot-with-Android-and-Arduino/tree/part2)

That's it. We finished the second part of the tutorial. In the next part we'll code the Android app to send commands to Arduino with bluetooth, and then we'll have finished this project :)

So, see you in the next post and thx for reading.

Aron Bordin.

{% include banner-bottom.html %}


text:
package org.renjin.primitives.annotations;

public enum CastStyle {
  IMPLICIT,
  EXPLICIT
}


text:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  	<modelVersion>4.0.0</modelVersion>
	<artifactId>org.scribble.trace.editor</artifactId>
	<packaging>eclipse-plugin</packaging>
	<name>Scribble::Tools::Eclipse::Plugins::Trace Editor</name>

	<parent>
		<groupId>org.scribble.tools.eclipse</groupId>
		<artifactId>plugins</artifactId>
		<version>0.1.0-SNAPSHOT</version>
	</parent>

</project>


text:
package net.ontopia.topicmaps.query.parser;

import java.util.Map;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.query.core.InvalidQueryException;
import net.ontopia.topicmaps.query.impl.basic.QueryMatches;

/**
 * INTERNAL: Represents a parsed MERGE statement.
 */
public class MergeStatement extends ModificationStatement {

  @Override
  public int doStaticUpdates(TopicMapIF topicmap, Map arguments)
    throws InvalidQueryException {
    TopicIF topic1 = (TopicIF) getValue(litlist.get(0), arguments);
    TopicIF topic2 = (TopicIF) getValue(litlist.get(1), arguments);
    if (!topic1.equals(topic2)) {
      topic1.merge(topic2);
    }
    return 1;
  }

  @Override
  public int doUpdates(QueryMatches matches)
    throws InvalidQueryException {
    int merges = 0;

    Map parameters = matches.getQueryContext().getParameters();
    Object arg1 = getValue(litlist.get(0), parameters);
    int varix1 = getIndex(arg1, matches);
    Object arg2 = getValue(litlist.get(1), parameters);
    int varix2 = getIndex(arg2, matches);

    for (int row = 0; row <= matches.last; row++) {
      if (varix1 != -1) {
        arg1 = matches.data[row][varix1];
      }

      if (varix2 != -1) {
        arg2 = matches.data[row][varix2];
      }

      TopicIF topic1 = (TopicIF) arg1;
      TopicIF topic2 = (TopicIF) arg2;
      if (!topic1.equals(topic2) &&
          topic1.getTopicMap() != null &&
          topic2.getTopicMap() != null) {
        topic1.merge(topic2);
        merges++;
      }
    }

    return merges;
  }

  @Override
  public String toString() {
    String str = "merge " + toStringLitlist();
    if (query != null) {
      str += "\nfrom" + query.toStringFromPart();
    }
    return str;
  }
}