Index: trunk/Ohana/src/opihi/pantasks/Makefile
===================================================================
--- trunk/Ohana/src/opihi/pantasks/Makefile	(revision 10734)
+++ trunk/Ohana/src/opihi/pantasks/Makefile	(revision 10997)
@@ -57,5 +57,6 @@
 $(SDIR)/task_command.$(ARCH).o \
 $(SDIR)/task_options.$(ARCH).o \
-$(SDIR)/version.$(ARCH).o
+$(SDIR)/version.$(ARCH).o \
+$(SDIR)/ipptool2book.$(ARCH).o
 
 client = \
Index: trunk/Ohana/src/opihi/pantasks/init.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/init.c	(revision 10734)
+++ trunk/Ohana/src/opihi/pantasks/init.c	(revision 10997)
@@ -21,4 +21,5 @@
 int verbose         PROTO((int, char **));
 int version         PROTO((int, char **));
+int ipptool2book    PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -44,4 +45,5 @@
   {"version",    version,      "show version information"},
   {"verbose",    verbose,      "set/toggle verbose mode"},
+  {"ipptool2book", ipptool2book, "convert queue with ipptool output to book"},
 }; 
 
Index: trunk/Ohana/src/opihi/pantasks/init_server.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/init_server.c	(revision 10734)
+++ trunk/Ohana/src/opihi/pantasks/init_server.c	(revision 10997)
@@ -17,4 +17,5 @@
 int version         PROTO((int, char **));
 int server          PROTO((int, char **));
+int ipptool2book    PROTO((int, char **));
 
 static Command cmds[] = {  
@@ -36,4 +37,5 @@
   {"version",    version,      	"show version information"},
   {"verbose",    verbose,      	"set/toggle verbose mode"},
+  {"ipptool2book", ipptool2book, "convert queue with ipptool output to book"},
 }; 
 
Index: trunk/Ohana/src/opihi/pantasks/ipptool2book.c
===================================================================
--- trunk/Ohana/src/opihi/pantasks/ipptool2book.c	(revision 10997)
+++ trunk/Ohana/src/opihi/pantasks/ipptool2book.c	(revision 10997)
@@ -0,0 +1,209 @@
+# include "pantasks.h"
+
+# define FREEKEYS \
+  if (keys) { for (i = 0; i < Nkeys; i++) { free (keys[i]); } free (keys); }
+
+// convert the named queue with ipptool metadata output into book format
+int ipptool2book (int argc, char **argv) {
+
+  int i, N, onPage, found, Unique, Nkeys, NKEYS;
+  char *line, *tmpword, *tmpvalue;
+  char pagename[512], *bookName, **keys, *p, *q;
+  Page *page;
+  Book *book;
+  Queue *queue;
+
+  Unique = FALSE;
+  if ((N = get_argument (argc, argv, "-uniq"))) {
+    remove_argument (N, &argc, argv);
+    Unique = TRUE;
+  }
+
+  Nkeys = 0;
+  keys = NULL;
+  if ((N = get_argument (argc, argv, "-id"))) {
+    remove_argument (N, &argc, argv);
+
+    /* parse the key (word:word:word) into constituents */
+    NKEYS = 10;
+    ALLOCATE (keys, char *, NKEYS);
+    
+    p = q = argv[N];
+    while (q != NULL) {
+      q = strchr (p, ':');
+      keys[Nkeys] = (q == NULL) ? strcreate (p) : strncreate (p, q - p);
+      p = q + 1;
+      Nkeys ++;
+      CHECK_REALLOCATE (keys, char *, NKEYS, Nkeys, 10);
+    }
+    remove_argument (N, &argc, argv);
+  }
+
+  if (argc != 3) {
+    gprint (GP_ERR, "USAGE: ipptool2book (queue) (book) [-uniq] [-id key[:key..]]\n");
+    FREEKEYS;
+    return (FALSE);
+  }
+
+  queue = FindQueue (argv[1]);
+  if (queue == NULL) {
+    gprint (GP_ERR, "queue %s not found\n", argv[1]);
+    FREEKEYS;
+    return (FALSE);
+  }
+    
+  book = CreateBook (argv[2]);
+
+  /* the first non-whitespace line of the queue should contain:
+     BookName MULTI
+  */
+
+  /* scan for first non-emtpy line */
+  while (TRUE) {
+    line = PopQueue (queue);
+    if (!line) {
+      FREEKEYS;
+      return (TRUE); // no output in queue - not an error?
+    }
+    if (*line) break;
+    free (line);
+  }
+  bookName = thisword (line);
+  tmpword = nextword (line);
+
+  if (strcmp(tmpword, "MULTI")) {
+    gprint (GP_ERR, "ERROR: missing metadata output name on first line\n");
+    free (bookName);
+    free (line);
+    FREEKEYS;
+    return FALSE;
+  }
+  if (strcmp(bookName, argv[2])) {
+    gprint (GP_ERR, "WARNING: metadata output name does not match expected bookname\n");
+  }
+  free (line);
+  
+  onPage = FALSE;
+  while (TRUE) {
+    line = PopQueue (queue);
+    if (!line) {
+      free (bookName);
+      if (onPage) {
+	gprint (GP_ERR, "ERROR: unterminated metadata\n");
+	FREEKEYS;
+	return (FALSE);
+      }
+      FREEKEYS;
+      return (TRUE);
+    }
+    if (!*line) {
+      free (line);
+      continue;
+    }
+    
+    tmpword = thisword (line);
+    if (tmpword == NULL) abort(); // should not happen if line exists
+
+    if (!onPage) {
+      if (strcmp(tmpword, bookName)) {
+	gprint (GP_ERR, "ERROR: unexpected metadata name %s\n", tmpword);
+	free (line);
+	free (tmpword);
+	free (bookName);
+	FREEKEYS;
+	return (FALSE);
+      }
+      free (tmpword);
+      tmpword = thisword (nextword (line));
+      if (strcmp(tmpword, "METADATA")) {
+	gprint (GP_ERR, "ERROR: missing METADATA tag\n");
+	free (line);
+	free (tmpword);
+	free (bookName);
+	FREEKEYS;
+	return FALSE;
+      }
+      onPage = TRUE;
+      // we don't know the page name until we load its data?
+      sprintf (pagename, "page.%03d", book[0].Npages);
+      page = CreatePage (book, pagename);
+      free (line);
+      free (tmpword);
+      continue;
+    } 
+
+    /* close out the page */
+    if (!strcmp(tmpword, "END")) {
+      // XXX set the page name based on the contents
+      free (tmpword);
+      free (line);
+
+      if (keys) {
+	memset (pagename, 0, 512);
+	for (i = 0; i < Nkeys; i++) {
+	  tmpword = BookGetWord (page, keys[i]);
+	  if (tmpword == NULL) {
+	    gprint (GP_ERR, "ERROR: missing key %s for ID\n", keys[i]);
+	    FREEKEYS;
+	    return (FALSE);
+	  }
+	  if (i > 0) strcat (pagename, ":");
+	  strcat (pagename, tmpword);
+	}
+	free (page[0].name);
+	page[0].name = strcreate (pagename);
+      }
+
+      if (Unique) {
+	/* search for an existing page with this name
+	   delete this one if one is found */
+	/* XXX don't attach this page to the book until this step is passed */	
+
+	found = FALSE;
+	for (i = 0; !found && (i < book[0].Npages); i++) {
+	  if (book[0].pages[i] == page) continue;
+	  if (!strcmp (book[0].pages[i][0].name, page[0].name)) {
+	    found = TRUE;
+	  }
+	}
+	if (found) {
+	  DeletePage (book, page);
+	}
+      }
+
+      onPage = FALSE;
+      continue;
+    }
+
+    tmpvalue = thisword (nextword(nextword(line)));
+    if (tmpvalue == NULL) {
+      gprint (GP_ERR, "ERROR: missing value for %s\n", tmpword);
+      free (tmpword);
+      free (line);
+      free (bookName);
+      FREEKEYS;
+      return (FALSE);
+    }
+    BookSetWord (page, tmpword, tmpvalue);
+    free (tmpword);
+    free (tmpvalue);
+    free (line);
+  }    
+  FREEKEYS;
+  return (FALSE);
+}
+
+/* scan through the queue lines.  these should look like:  
+
+bookName METADATA
+key type value
+key type value
+...
+END
+
+bookName METADATA
+key type value
+key type value
+...
+END
+*/
