Bundled-up-and-rebased info patches



Rebased and stuck together as discussed on IRC. These are the same as
http://thread.gmane.org/gmane.comp.gnome.documentation.devel/545 and
http://thread.gmane.org/gmane.comp.gnome.documentation.devel/541.

Rupert

From 8931d0a20da80ac1973abbbcafd24707d26e7c01 Mon Sep 17 00:00:00 2001
From: Rupert Swarbrick <rswarbrick gmail com>
Date: Wed, 5 Jan 2011 15:25:53 +0000
Subject: [PATCH 1/5] Fix get_value_after in yelp-info-parser.c

As it was, this was returning silly things for at least the last page
of each info file. Also, it had unneccesary string copying.
---
 libyelp/yelp-info-parser.c |   69 +++++++++++++++++++++++++++----------------
 1 files changed, 43 insertions(+), 26 deletions(-)

diff --git a/libyelp/yelp-info-parser.c b/libyelp/yelp-info-parser.c
index edd3812..7d174a8 100644
--- a/libyelp/yelp-info-parser.c
+++ b/libyelp/yelp-info-parser.c
@@ -547,43 +547,60 @@ static GHashTable
 	return table;
 }
 
-static char
-*get_value_after (char *source, char *required)
+/*
+  Look for strings in source by key. For example, we extract "blah"
+  from "Node: blah," when the key is "Node: ". To know when to stop,
+  there are two strings: end and cancel.
+
+  If we find a character from end first, return a copy of the string
+  up to (not including) that character. If we find a character of
+  cancel first, return NULL. If we find neither, return the rest of
+  the string.
+
+  cancel can be NULL, in which case, we don't do its test.
+ */
+static char*
+get_value_after_ext (const char *source, const char *key,
+                     const char *end, const char *cancel)
 {
-	char *ret, *ret_cp;
-	char *source_cp;
-	char *ptr;
+  char *start;
+  size_t not_end, not_cancel;
 
-	source_cp = g_strdup (source);
-	
-	ptr = g_strstr_len (source_cp, strlen (source_cp), required);
-	if (!ptr) {
-	  g_free (source_cp);
-		return NULL;
-	}
-	ret = ptr + strlen (required);
-	ptr = g_strstr_len (ret, strlen (ret), ",");
-	/* if there is no pointer, we're at the end of the string */
-	if (ptr)
-		ret_cp = g_strndup (ret, ptr - ret);
-	else
-		ret_cp = g_strdup (ret);
+  start = strstr (source, key);
+  if (!start) return NULL;
+
+  start += strlen (key);
+
+  not_end = strcspn (start, end);
+  not_cancel = (cancel) ? strcspn (start, cancel) : not_end + 1;
+
+  if (not_cancel < not_end)
+    return NULL;
 
-	g_free (source_cp);
+  return g_strndup (start, not_end);
+}
 
-	return ret_cp;
+static char*
+get_value_after (const char* source, const char *key)
+{
+  return get_value_after_ext (source, key, ",", "\n\x7f");
 }
 
 static int
 node2page (GHashTable *nodes2offsets, GHashTable *offsets2pages, char *node)
 {
-	char *offset = NULL;
-	gint page;
+  char *offset;
+  gint page;
+  gboolean found;
+
+  offset = g_hash_table_lookup (nodes2offsets, node);
+  g_return_val_if_fail (offset, 0);
 
-	offset = g_hash_table_lookup (nodes2offsets, node);
-	page = GPOINTER_TO_INT (g_hash_table_lookup (offsets2pages, offset));
+  found = g_hash_table_lookup_extended (offsets2pages, offset,
+                                        NULL, (gpointer*) &page);
+  g_return_val_if_fail (found, 0);
 
-	return page;
+  return page;
 }
 
 static GtkTreeIter
-- 
1.7.4.4

From d2101298846ea5ffb0ad5aec794377163b7cdb38 Mon Sep 17 00:00:00 2001
From: Rupert Swarbrick <rswarbrick gmail com>
Date: Thu, 6 Jan 2011 12:50:53 +0000
Subject: [PATCH 2/5] Rewrite the way we read in the info file & calculate
 nodes' offsets.

This fixes a bug with how node2offset, offsets2pages etc. were
calculated when reading in a file with an indirect map. It also makes
the logic much simpler and (I think) the code is no less efficient.
---
 libyelp/yelp-info-parser.c |  298 ++++++++++++++++++++------------------------
 1 files changed, 134 insertions(+), 164 deletions(-)

diff --git a/libyelp/yelp-info-parser.c b/libyelp/yelp-info-parser.c
index 7d174a8..5ecdc5a 100644
--- a/libyelp/yelp-info-parser.c
+++ b/libyelp/yelp-info-parser.c
@@ -33,8 +33,6 @@
 #include "yelp-debug.h"
 
 
-typedef struct _TagTableFix TagTableFix;
-
 GtkTreeIter *         find_real_top                      (GtkTreeModel *model, 
 							  GtkTreeIter *it);
 GtkTreeIter *         find_real_sibling                  (GtkTreeModel *model,
@@ -53,9 +51,6 @@ gboolean              resolve_frag_id                    (GtkTreeModel *model,
 							  GtkTreePath *path, 
 							  GtkTreeIter *iter,
 							  gpointer data);
-void                  fix_tag_table                      (gchar *offset, 
-							  gpointer page, 
-							  TagTableFix *a);
 void   		      info_process_text_notes            (xmlNodePtr *node, 
 							  gchar *content,
 							  GtkTreeStore
@@ -373,7 +368,7 @@ page_type (char *page)
 }
 
 static char
-*open_info_file (char *file)
+*open_info_file (const gchar *file)
 {
     GFile *gfile;
     GConverter *converter;
@@ -409,7 +404,7 @@ static char
 }
 
 static gchar *
-find_info_part (gchar *part_name, gchar *base)
+find_info_part (gchar *part_name, const gchar *base)
 {
   /* New and improved.  We now assume that all parts are
    * in the same subdirectory as the base file.  Makes
@@ -446,14 +441,19 @@ find_info_part (gchar *part_name, gchar *base)
 }
 
 static char
-*process_indirect_map (char *page, gchar * file)
+*process_indirect_map (char *page, const gchar *file)
 {
 	char **lines;
 	char **ptr;
 	char *composite = NULL;
-	
+        size_t composite_len = 0;
+
 	lines = g_strsplit (page, "\n", 0);
 
+        /*
+          Go backwards down the list so that we allocate composite
+          big enough the first time around.
+        */
 	for (ptr = lines + 1; *ptr != NULL; ptr++);
 	for (ptr--; ptr != lines; ptr--)
 	{
@@ -492,13 +492,24 @@ static char
 			
 			if (!composite) /* not yet created, malloc it */
 			{
-				int length;
-				length = offset + plength;
+				composite_len = offset + plength;
 				composite = g_malloc (sizeof (char) *
-						      (length + 1));
-				memset (composite, '-', length);
-				composite[length] = '\0';
+						      (composite_len + 1));
+				memset (composite, '-', composite_len);
+				composite[composite_len] = '\0';
 			}
+
+                        /* Because we're going down the list
+                         * backwards, plength should always be short
+                         * enough to fit in the memory allocated. But
+                         * in case something's broken/malicious, we
+                         * should check anyway.
+                         */
+                        if (offset > composite_len)
+                          continue;
+                        if (plength + offset + 1 > composite_len)
+                          plength = composite_len - offset - 1;
+
 			composite[offset] = '';
 			memcpy (composite + offset + 1, pages[1], plength);
 			
@@ -514,37 +525,48 @@ static char
 	return composite;
 }
 
-static GHashTable
-*process_tag_table (char *page)
+/*
+  Open up the relevant info file and read it all into memory. If there
+  is an indirect table thingy, we resolve that as we go.
+
+  Returns a NULL-terminated list of pointers to pages on success and
+  NULL otherwise.
+ */
+static gchar**
+expanded_info_file (const gchar *file)
 {
-	/* Let's assume we've been passed a valid page */
+  gchar *slurp = open_info_file (file);
+  gchar **page_list;
+  gchar **page;
 
-	GHashTable *table;
-	char **lines;
-	char **ptr;
-	char **items;
+  if (!slurp) return NULL;
 
-	table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
-				       g_free);
-	lines = g_strsplit (page, "\n", 0);
+  /* TODO: There's a lot of copying of bits of memory here. With a bit
+   * more effort we could avoid it. Either we should fix this or
+   * measure the time taken and decide it's irrelevant...
+   *
+   * Note: \x1f\n is ^_\n
+   */
+  page_list = g_strsplit (slurp, "\x1f\n", 0);
 
-	for (ptr = lines; *ptr != NULL; ptr++)
-	{
-		if (strncmp (*ptr, "Node: ", 6) == 0)
-		{
-			items = g_strsplit (*ptr, "", 2);
-			debug_print (DB_DEBUG, "Node: %s Offset: %s\n",
-				    items[0] + 6, items[1]);
-			g_hash_table_insert (table,
-					g_strdup (items[0] + 6),
-					g_strdup (items[1]));
-			g_strfreev (items);
-		}
-	}
+  g_free (slurp);
 
-	g_strfreev (lines);
+  for (page = page_list; *page != NULL; page++) {
+    if (page_type (*page) == PAGE_INDIRECT) {
 
-	return table;
+      slurp = process_indirect_map (*page, file);
+      g_strfreev (page_list);
+
+      if (!slurp)
+        return NULL;
+
+      page_list = g_strsplit (slurp, "\x1f\n", 0);
+      g_free (slurp);
+      break;
+    }
+  }
+
+  return page_list;
 }
 
 /*
@@ -587,20 +609,17 @@ get_value_after (const char* source, const char *key)
 }
 
 static int
-node2page (GHashTable *nodes2offsets, GHashTable *offsets2pages, char *node)
+node2page (GHashTable *nodes2pages, char *node)
 {
-  char *offset;
   gint page;
-  gboolean found;
-
-  offset = g_hash_table_lookup (nodes2offsets, node);
-  g_return_val_if_fail (offset, 0);
 
-  found = g_hash_table_lookup_extended (offsets2pages, offset,
-                                        NULL, (gpointer*) &page);
-  g_return_val_if_fail (found, 0);
+  if (g_hash_table_lookup_extended (nodes2pages, node,
+                                    NULL, (gpointer*) &page))
+    return page;
 
-  return page;
+  /* This shouldn't happen: we should only ever have to look up pages
+   * that exist. */
+  g_return_val_if_reached (0);
 }
 
 static GtkTreeIter
@@ -679,9 +698,9 @@ GtkTreeIter * find_real_sibling (GtkTreeModel *model,
 }
 
 static void
-process_page (GtkTreeStore *tree, GHashTable *nodes2offsets,
-		GHashTable *offsets2pages, GHashTable *nodes2iters,
-		int *processed_table, char **page_list, char *page_text)
+process_page (GtkTreeStore *tree,
+              GHashTable *nodes2pages, GHashTable *nodes2iters,
+              int *processed_table, char **page_list, char *page_text)
 {
 	GtkTreeIter *iter;
 	
@@ -712,7 +731,7 @@ process_page (GtkTreeStore *tree, GHashTable *nodes2offsets,
 	}
 
 	/* check to see if this page has been processed already */
-	page = node2page (nodes2offsets, offsets2pages, node);
+	page = node2page (nodes2pages, node);
 	if (processed_table[page]) {
 		return;
 	}
@@ -724,11 +743,11 @@ process_page (GtkTreeStore *tree, GHashTable *nodes2offsets,
 	/* check to see if we need to process our parent and siblings */
 	if (up && g_ascii_strncasecmp (up, "(dir)", 5) && strcmp (up, "Top"))
 	{
-		page = node2page (nodes2offsets, offsets2pages, up);
+		page = node2page (nodes2pages, up);
 		if (!processed_table[page])
 		{
 		  debug_print (DB_DEBUG, "%% Processing Node %s\n", up);
-			process_page (tree, nodes2offsets, offsets2pages,
+                  process_page (tree, nodes2pages,
 				nodes2iters, processed_table, page_list,
 				page_list[page]);
 		}
@@ -738,11 +757,11 @@ process_page (GtkTreeStore *tree, GHashTable *nodes2offsets,
 	    if (strncmp (node, "Top", 3)) {
 	      /* Special case the Top node to always appear first */
 	    } else {
-	      page = node2page (nodes2offsets, offsets2pages, prev);
+	      page = node2page (nodes2pages, prev);
 	      if (!processed_table[page])
 		{
 		  debug_print (DB_DEBUG, "%% Processing Node %s\n", prev);
-		  process_page (tree, nodes2offsets, offsets2pages,
+		  process_page (tree, nodes2pages,
 				nodes2iters, processed_table, page_list,
 				page_list[page]);
 		}
@@ -808,7 +827,7 @@ process_page (GtkTreeStore *tree, GHashTable *nodes2offsets,
 	debug_print (DB_DEBUG, "size: %i\n", g_hash_table_size (nodes2iters));
 
 	/*tmp = g_strdup_printf ("%i",
-	  node2page (nodes2offsets, offsets2pages, node));*/
+	  node2page (nodes2pages, node));*/
 	tmp = g_strdup (node);
 	tmp = g_strdelimit (tmp, " ", '_');
 	gtk_tree_store_set (tree, iter,
@@ -825,31 +844,42 @@ process_page (GtkTreeStore *tree, GHashTable *nodes2offsets,
 	g_strfreev (parts);
 }
 
-/* These are used to fix the tag tables to the correct offsets.
- * Assuming out offsets are correct (because we calculated them, these values
- * are used to overwrite the offsets declared in the info file */
-struct _TagTableFix {
-  GHashTable *nodes2offsets;
-  GHashTable *pages2nodes;
+struct TagTableFix {
+  GHashTable *nodes2pages; /* Build this... */
+  GHashTable *pages2nodes; /* ... using this. */
 };
 
-void
-fix_tag_table (gchar *offset, gpointer page, TagTableFix *a)
+static void
+use_offset2page (gpointer o, gpointer p, gpointer ud)
 {
-  gchar *node_name = NULL;
-
-  node_name = g_hash_table_lookup (a->pages2nodes, page);
+  struct TagTableFix* ttf = (struct TagTableFix*)ud;
 
-  if (!node_name) 
-    return;
+  const gchar* node = g_hash_table_lookup (ttf->pages2nodes, p);
+  if (node) {
+    g_hash_table_insert (ttf->nodes2pages, g_strdup (node), p);
+  }
+}
 
-  g_hash_table_replace (a->nodes2offsets, g_strdup (node_name), g_strdup (offset));
+/*
+  We had a nodes2offsets hash table, but sometimes these things
+  lie. How terribly rude. Anyway, use offsets2pages and pages2nodes
+  (and injectivity!) to construct the nodes2pages hash table.
+*/
+static GHashTable *
+make_nodes2pages (GHashTable* offsets2pages,
+                  GHashTable* pages2nodes)
+{
+  struct TagTableFix ttf;
 
+  ttf.nodes2pages =
+    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  ttf.pages2nodes = pages2nodes;
 
+  g_hash_table_foreach (offsets2pages, use_offset2page, &ttf);
 
+  return ttf.nodes2pages;
 }
 
-
 /**
  * Parse file into a GtkTreeStore containing useful information that we can
  * later convert into a nice XML document or something else.
@@ -857,33 +887,24 @@ fix_tag_table (gchar *offset, gpointer page, TagTableFix *a)
 GtkTreeStore
 *yelp_info_parser_parse_file (char *file)
 {
-	char **page_list;
+	gchar **page_list;
 	char **ptr;
 	int pages;
 	int offset;
-	GHashTable *nodes2offsets = NULL;
 	GHashTable *offsets2pages = NULL;
 	GHashTable *pages2nodes = NULL;
+        GHashTable *nodes2pages = NULL;
 	GHashTable *nodes2iters = NULL;
 	int *processed_table;
 	GtkTreeStore *tree;
 	int pt;
-	char *str = NULL;
-	gboolean chained_info;
-	TagTableFix *ttf;
 	
-	str = open_info_file (file);
-	if (!str) {
-		return NULL;
-	}	
-	page_list = g_strsplit (str, "\n", 0);
-
-	g_free (str);
-	str = NULL;
+	page_list = expanded_info_file (file);
+	if (!page_list)
+          return NULL;
 	
 	pages = 0;
 	offset = 0;
-	chained_info = FALSE;
 
 	offsets2pages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
 					       NULL);
@@ -893,84 +914,33 @@ GtkTreeStore
 	for (ptr = page_list; *ptr != NULL; ptr++)
 	{
 	  gchar *name = NULL;
-	  debug_print (DB_DEBUG, "page %i at offset %i\n", pages, offset);
-
-		g_hash_table_insert (offsets2pages,
-				g_strdup_printf ("%i", offset), 
-				GINT_TO_POINTER (pages));
-		name = get_value_after (*ptr, "Node: ");
-		if (name)
-		  g_hash_table_insert (pages2nodes,
-				       GINT_TO_POINTER (pages), name);
-		
-		offset += strlen (*ptr);
-		if (pages) offset += 2;
-		pages++;
-		pt = page_type (*ptr);
-		if (pt == PAGE_TAG_TABLE)
-		{
-		  debug_print (DB_DEBUG, "Have the Tag Table\n");
-			/* this needs to be freed later too */
-			nodes2offsets = process_tag_table (*ptr);
-			break;
-		}
-		else if (pt == PAGE_INDIRECT)
-		{
-		  debug_print (DB_DEBUG, "Have the indirect mapping table\n");
-			chained_info = TRUE;
-			str = process_indirect_map (*ptr, file);
-			if (!str) {
-				return NULL;
-			}
-		}
-	}
 
-	if (chained_info)
-	{
-		/* this is a chained info file, and therefore will require
-		 * more processing */
-		g_strfreev (page_list);
-		g_hash_table_destroy (offsets2pages);
-		offsets2pages = g_hash_table_new_full (g_str_hash, 
-						       g_str_equal, g_free,
-						       NULL);
-		
-		pages = 0;
-		offset = 0;
+          g_hash_table_insert (offsets2pages,
+                               g_strdup_printf ("%i", offset),
+                               GINT_TO_POINTER (pages));
 
-		page_list = g_strsplit (str, "\n", 0);
+          name = get_value_after (*ptr, "Node: ");
+          if (name)
+            g_hash_table_insert (pages2nodes,
+                                 GINT_TO_POINTER (pages), name);
 		
-		g_free (str);
-		
-		for (ptr = page_list; *ptr != NULL; ptr++)
-		{
-		  debug_print (DB_DEBUG, "page %i at offset %i\n", pages, offset);
-			g_hash_table_insert (offsets2pages,
-					g_strdup_printf ("%i", offset),
-					 GINT_TO_POINTER (pages));
-			offset += strlen (*ptr);
-			if (pages) offset += 2;
-			pages++;
-		}
+          offset += strlen (*ptr);
+          if (pages) offset += 2;
+          pages++;
+
+          pt = page_type (*ptr);
+          if (pt == PAGE_INDIRECT) {
+            g_warning ("Found an indirect page in a file "
+                       "we thought we'd expanded.");
+          }
 	}
-	if (!nodes2offsets)
-	  return NULL;
-	/* We now go through the offsets2pages dictionary and correct the entries
-	 * as the tag tables are known to lie.  Yes, they do.  Consistantly and
-	 * maliciously
-	 */	
-	ttf = g_new0 (TagTableFix, 1);
-	ttf->nodes2offsets = nodes2offsets;
-	ttf->pages2nodes = pages2nodes;
-	g_hash_table_foreach (offsets2pages, (GHFunc) fix_tag_table, ttf);
-	g_free (ttf);
-
-
-	/* at this point we have two dictionaries,
-	 * 	node names:offsets, and
-	 * 	offsets:page numbers
-	 * rather then consolidating these into one dictionary, we'll just
-	 * chain our lookups */
+
+        /* Now consolidate (and correct) the two hash tables */
+        nodes2pages = make_nodes2pages (offsets2pages, pages2nodes);
+
+	g_hash_table_destroy (offsets2pages);
+        g_hash_table_destroy (pages2nodes);
+
 	processed_table = g_malloc0 (pages * sizeof (int));
 	tree = gtk_tree_store_new (INFO_PARSER_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
 			G_TYPE_STRING);
@@ -981,15 +951,15 @@ GtkTreeStore
 	for (ptr = page_list; *ptr != NULL; ptr++)
 	{
 	  if (page_type (*ptr) != PAGE_NODE) continue;
-	  process_page (tree, nodes2offsets, offsets2pages, nodes2iters,
+	  process_page (tree, nodes2pages, nodes2iters,
 			processed_table, page_list, *ptr);
 	}
 
 	g_strfreev (page_list);
 
-	g_hash_table_destroy (nodes2offsets);
-	g_hash_table_destroy (offsets2pages);
 	g_hash_table_destroy (nodes2iters);
+	g_hash_table_destroy (nodes2pages);
+
 	g_free (processed_table);
 
 	return tree;
-- 
1.7.4.4

From 09bb3ebb48b40b154b47b9bd43f4f66946c9573b Mon Sep 17 00:00:00 2001
From: Rupert Swarbrick <rswarbrick gmail com>
Date: Mon, 10 Jan 2011 21:00:44 +0000
Subject: [PATCH 3/5] Use glib's g_uri_unescape_string instead of our own
 decode_url.

---
 libyelp/yelp-uri.c |   62 +---------------------------------------------------
 1 files changed, 1 insertions(+), 61 deletions(-)

diff --git a/libyelp/yelp-uri.c b/libyelp/yelp-uri.c
index 14b80e1..9cdc8b8 100644
--- a/libyelp/yelp-uri.c
+++ b/libyelp/yelp-uri.c
@@ -995,66 +995,6 @@ resolve_man_uri (YelpUri *uri)
     }
 }
 
-/*
-  Return 1 if ch is a number from 0 to 9 or a letter a-f or A-F and 0
-  otherwise. This is sort of not utf8-safe, but since we are only
-  looking for 7-bit things, it doesn't matter.
- */
-static int
-is_hex (gchar ch)
-{
-    if (((48 <= ch) && (ch <= 57)) ||
-        ((65 <= ch) && (ch <= 70)) ||
-        ((97 <= ch) && (ch <= 102)))
-        return 1;
-    return 0;
-}
-
-/*
-  Return a newly allocated string, where %ab for a,b in [0, f] is
-  replaced by the character it represents.
- */
-static gchar*
-decode_url (const gchar *url)
-{
-    if (!url) return NULL;
-
-    unsigned int len = strlen (url);
-    int hex;
-    gchar *ret = g_new (gchar, len + 1);
-    const gchar *ptr = url, *end = url + len;
-    gchar *retptr = ret, *tmp;
-
-    while (ptr < end) {
-        if (*ptr == '%' && is_hex(*(ptr + 1)) && is_hex(*(ptr + 2))) {
-            *retptr = *(ptr+1);
-            *(retptr+1) = *(ptr+2);
-            *(retptr+2) = '\0';
-
-            sscanf (retptr, "%x", &hex);
-
-            if (hex < 0 || hex > 127) {
-                g_warning ("Skipping non-7-bit character.");
-                ptr++;
-                continue;
-            }
-            *retptr = (gchar)hex;
-
-            retptr++;
-            ptr += 3;
-        }
-        else {
-            tmp = g_utf8_next_char(ptr);
-            memcpy (retptr, ptr, (tmp-ptr));
-            retptr += tmp-ptr;
-            ptr = tmp;
-        }
-    }
-    *retptr = '\0';
-
-    return ret;
-}
-
 static void
 resolve_info_uri (YelpUri *uri)
 {
@@ -1216,7 +1156,7 @@ resolve_xref_uri (YelpUri *uri)
           the url again here.
          */
         gchar* tmp = priv->page_id;
-        priv->page_id = decode_url (priv->page_id);
+        priv->page_id = g_uri_unescape_string (tmp, NULL);
         g_free (tmp);
     }
 
-- 
1.7.4.4

From 240d9dadead75a2ba4042900f48d00b67727b40b Mon Sep 17 00:00:00 2001
From: Rupert Swarbrick <rswarbrick gmail com>
Date: Wed, 12 Jan 2011 11:43:55 +0000
Subject: [PATCH 4/5] Parse info files one page at a time.

With the previous implementation, parsing a large info file takes a
significant amount of time (several seconds). But most of this time is
spent parsing the thing to xml.

This version instead quickly reads the file into memory (negligible
time required) and then only parses pages to xml when they're needed.
---
 libyelp/yelp-info-document.c |  302 +++++++++++-------
 libyelp/yelp-info-parser.c   |  721 ++++++++++++++----------------------------
 libyelp/yelp-info-parser.h   |   18 +-
 stylesheets/info2html.xsl.in |   69 ++---
 4 files changed, 456 insertions(+), 654 deletions(-)

diff --git a/libyelp/yelp-info-document.c b/libyelp/yelp-info-document.c
index 136b274..3d7ace1 100644
--- a/libyelp/yelp-info-document.c
+++ b/libyelp/yelp-info-document.c
@@ -40,23 +40,38 @@
 #define STYLESHEET DATADIR"/yelp/xslt/info2html.xsl"
 
 typedef enum {
-    INFO_STATE_BLANK,   /* Brand new, run transform as needed */
+    INFO_STATE_READY,   /* Brand new, run transform as needed */
     INFO_STATE_PARSING, /* Parsing/transforming document, please wait */
-    INFO_STATE_PARSED,  /* All done, if we ain't got it, it ain't here */
     INFO_STATE_STOP     /* Stop everything now, object to be disposed */
 } InfoState;
 
 typedef struct _YelpInfoDocumentPrivate  YelpInfoDocumentPrivate;
 struct _YelpInfoDocumentPrivate {
     YelpUri       *uri;
-    InfoState    state;
 
     GMutex     *mutex;
     GThread    *thread;
 
-    xmlDocPtr   xmldoc;
-    GtkTreeModel  *sections;
+    /***** Members that should be protected by mutex. *****/
 
+    /*
+      The keys are the page id's (eg "Getting_Started"). The values
+      in unparsed_sections are (opaque) structs which basically just
+      contain the string contents of the relevant page.
+
+      In parsed_sections, the values are xmlDocPtr's for parsed
+      versions.
+    */
+    GHashTable *unparsed_sections;
+    GHashTable *parsed_sections;
+
+    /*
+      Since we don't automatically parse and transform all the
+      sections any more, keep a queue of "wanted pages".
+     */
+    GQueue *wanted_pages;
+
+    InfoState   state;
     gboolean    process_running;
     gboolean    transform_running;
 
@@ -64,11 +79,20 @@ struct _YelpInfoDocumentPrivate {
     guint          chunk_ready;
     guint          finished;
     guint          error;
-
-    gchar   *root_id;
-    gchar   *visit_prev_id;
 };
 
+/*
+  A helper structure to let us pass "two variables" to
+  transform_finalized.
+
+  id is owned by this structure; info (obviously) isn't.
+*/
+typedef struct _IdDocPair IdDocPair;
+struct _IdDocPair
+{
+    gchar *id;
+    YelpInfoDocument *info;
+};
 
 static void           yelp_info_document_class_init       (YelpInfoDocumentClass  *klass);
 static void           yelp_info_document_init             (YelpInfoDocument       *info);
@@ -90,14 +114,8 @@ static void           transform_finished        (YelpTransform        *transform
                                                  YelpInfoDocument     *info);
 static void           transform_error           (YelpTransform        *transform,
                                                  YelpInfoDocument     *info);
-static void           transform_finalized       (YelpInfoDocument     *info,
-                                                 gpointer              transform);
 
 static void           info_document_process     (YelpInfoDocument     *info);
-static gboolean       info_sections_visit       (GtkTreeModel         *model,
-                                                 GtkTreePath          *path,
-                                                 GtkTreeIter          *iter,
-                                                 YelpInfoDocument     *info);
 static void           info_document_disconnect  (YelpInfoDocument     *info);
 
 
@@ -123,8 +141,7 @@ yelp_info_document_init (YelpInfoDocument *info)
 {
     YelpInfoDocumentPrivate *priv = GET_PRIV (info);
 
-    priv->state = INFO_STATE_BLANK;
-    priv->xmldoc = NULL;
+    priv->state = INFO_STATE_READY;
     priv->mutex = g_mutex_new ();
 }
 
@@ -138,16 +155,15 @@ yelp_info_document_dispose (GObject *object)
         priv->uri = NULL;
     }
 
-    if (priv->sections) {
-        g_object_unref (priv->sections);
-        priv->sections = NULL;
-    }
-
     if (priv->transform) {
         g_object_unref (priv->transform);
         priv->transform = NULL;
     }
 
+    g_hash_table_destroy (priv->unparsed_sections);
+    g_hash_table_destroy (priv->parsed_sections);
+    g_queue_free (priv->wanted_pages);
+
     G_OBJECT_CLASS (yelp_info_document_parent_class)->dispose (object);
 }
 
@@ -156,12 +172,6 @@ yelp_info_document_finalize (GObject *object)
 {
     YelpInfoDocumentPrivate *priv = GET_PRIV (object);
 
-    if (priv->xmldoc)
-        xmlFreeDoc (priv->xmldoc);
-
-    g_free (priv->root_id);
-    g_free (priv->visit_prev_id);
-
     g_mutex_free (priv->mutex);
 
     G_OBJECT_CLASS (yelp_info_document_parent_class)->finalize (object);
@@ -187,6 +197,15 @@ yelp_info_document_new (YelpUri *uri)
 
     priv->uri = g_object_ref (uri);
 
+    priv->unparsed_sections = g_hash_table_new (g_str_hash, g_str_equal);
+    priv->parsed_sections = g_hash_table_new_full (
+        g_str_hash, g_str_equal,
+        g_free, (GDestroyNotify) xmlFreeDoc);
+
+    priv->wanted_pages = g_queue_new ();
+
+    yelp_document_set_page_id (YELP_DOCUMENT (info), NULL, "Top");
+
     return (YelpDocument *) info;
 }
 
@@ -206,8 +225,8 @@ info_request_page (YelpDocument         *document,
     GError *error;
     gboolean handled;
 
-    if (page_id == NULL)
-        page_id = priv->root_id;
+    if (!page_id)
+        page_id = "Top";
 
     handled =
         YELP_DOCUMENT_CLASS (yelp_info_document_parent_class)->request_page (document,
@@ -222,16 +241,20 @@ info_request_page (YelpDocument         *document,
     g_mutex_lock (priv->mutex);
 
     switch (priv->state) {
-    case INFO_STATE_BLANK:
+    case INFO_STATE_READY:
 	priv->state = INFO_STATE_PARSING;
 	priv->process_running = TRUE;
         g_object_ref (document);
+
+        g_queue_push_head (priv->wanted_pages, g_strdup (page_id));
+
 	priv->thread = g_thread_create ((GThreadFunc) info_document_process,
                                         document, FALSE, NULL);
 	break;
+
     case INFO_STATE_PARSING:
 	break;
-    case INFO_STATE_PARSED:
+
     case INFO_STATE_STOP:
         docuri = yelp_uri_get_document_uri (priv->uri);
         error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
@@ -300,15 +323,7 @@ transform_finished (YelpTransform    *transform,
     }
 
     info_document_disconnect (info);
-    priv->state = INFO_STATE_PARSED;
-
-    /* We want to free priv->xmldoc, but we can't free it before transform
-       is finalized.   Otherwise, we could crash when YelpTransform frees
-       its libxslt resources.
-     */
-    g_object_weak_ref ((GObject *) transform,
-                       (GWeakNotify) transform_finalized,
-                       info);
+    priv->state = INFO_STATE_READY;
 
     docuri = yelp_uri_get_document_uri (priv->uri);
     error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
@@ -341,21 +356,125 @@ transform_error (YelpTransform    *transform,
 }
 
 static void
-transform_finalized (YelpInfoDocument *info,
-                     gpointer          transform)
+transform_finalized (IdDocPair* idp, gpointer transform)
 {
-    YelpInfoDocumentPrivate *priv = GET_PRIV (info);
- 
-    if (priv->xmldoc)
-	xmlFreeDoc (priv->xmldoc);
-    priv->xmldoc = NULL;
-}
+    YelpInfoDocumentPrivate *priv = GET_PRIV (idp->info);
 
+    g_mutex_lock (priv->mutex);
+
+    if (!g_hash_table_remove (priv->parsed_sections, idp->id)) {
+        g_warning (_("Tried to remove nonexistant element '%s' from "
+                     "table of parsed sections"),
+                   idp->id);
+    }
 
+    g_free (idp->id);
+    g_free (idp);
+
+    g_mutex_unlock (priv->mutex);
+}
 
 /******************************************************************************/
 /** Threaded ******************************************************************/
 
+/*
+  This should be called with the mutex held (it returns quickly
+  because it does one parsing job and then starts a separate thread)
+*/
+static void
+transform_next_page (YelpInfoDocument *info)
+{
+    YelpInfoDocumentPrivate *priv = GET_PRIV (info);
+    gchar* page_id;
+    gint  params_i = 0;
+    gchar **params = NULL;
+    GError *error;
+    xmlDocPtr xml;
+    IdDocPair *idp;
+
+    if (g_queue_is_empty (priv->wanted_pages))
+        return;
+
+    page_id = (gchar*) g_queue_pop_tail (priv->wanted_pages);
+    g_assert (page_id);
+
+    /* There are two stages of work that need to be done. Firstly, to
+     * parse the section to xml and secondly to transform it to xhtml.
+     *
+     * We know that the latter hasn't happened yet and in theory, I
+     * think the former shouldn't have either, but we should check
+     * just in case we can avoid some work.
+     */
+    if (!(xml = g_hash_table_lookup (priv->parsed_sections, page_id))) {
+        UnprocessedSection *us =
+            g_hash_table_lookup (priv->unparsed_sections, page_id);
+        if (!us) {
+            error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
+                                 _("Cannot find section '%s' in document."),
+                                 page_id);
+            yelp_document_error_pending ((YelpDocument *) info,
+                                         error);
+            return;
+        }
+
+        xml = yelp_info_parser_parse_section (us, priv->unparsed_sections);
+        if (!xml) {
+            error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
+                                 _("Cannot parse page '%s'."),
+                                 page_id);
+            yelp_document_error_pending ((YelpDocument *) info,
+                                         error);
+            return;
+        }
+
+        g_hash_table_insert (priv->parsed_sections, page_id, xml);
+
+        yelp_document_set_page_id (YELP_DOCUMENT (info), page_id, page_id);
+    }
+
+    priv->transform = yelp_transform_new (STYLESHEET);
+    priv->chunk_ready =
+        g_signal_connect (priv->transform, "chunk-ready",
+                          (GCallback) transform_chunk_ready,
+                          info);
+    priv->finished =
+        g_signal_connect (priv->transform, "finished",
+                          (GCallback) transform_finished,
+                          info);
+    priv->error =
+        g_signal_connect (priv->transform, "error",
+                          (GCallback) transform_error,
+                          info);
+
+    params =
+        yelp_settings_get_all_params (yelp_settings_get_default (),
+                                      0, &params_i);
+
+    priv->transform_running = TRUE;
+
+    /* transform_finalized gets called when we destroy transform and
+     * frees the xml that we had stored in priv->parsed_sections.
+     *
+     * We have to use a weak_ref since if we free the memory before
+     * that of transform, we might crash when YelpTransform frees its
+     * libxslt resources.
+     */
+    idp = g_new (IdDocPair, 1);
+    idp->id = g_strdup (page_id);
+    idp->info = info;
+
+    g_object_weak_ref ((GObject *) priv->transform,
+                       (GWeakNotify) transform_finalized,
+                       idp);
+
+    yelp_transform_start (priv->transform,
+                          xml,
+                          NULL,
+                          (const gchar * const *) params);
+
+    g_strfreev (params);
+}
+
 static void
 info_document_process (YelpInfoDocument *info)
 {
@@ -363,14 +482,15 @@ info_document_process (YelpInfoDocument *info)
     GFile *file = NULL;
     gchar *filepath = NULL;
     GError *error;
-    gint  params_i = 0;
-    gchar **params = NULL;
 
     file = yelp_uri_get_file (priv->uri);
     if (file == NULL) {
+        gchar *uri = yelp_uri_get_canonical_uri (priv->uri);
         error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
-                             _("The file does not exist."));
+                             _("The file for uri '%s' does not exist."),
+                             uri);
         yelp_document_error_pending ((YelpDocument *) info, error);
+        g_free (uri);
         g_error_free (error);
         goto done;
     }
@@ -379,56 +499,28 @@ info_document_process (YelpInfoDocument *info)
     g_object_unref (file);
     if (!g_file_test (filepath, G_FILE_TEST_IS_REGULAR)) {
         error = g_error_new (YELP_ERROR, YELP_ERROR_NOT_FOUND,
-                             _("The file ‘%s’ does not exist."),
+                             _("The file '%s' does not exist."),
                              filepath);
         yelp_document_error_pending ((YelpDocument *) info, error);
         g_error_free (error);
         goto done;
     }
 
-    priv->sections = (GtkTreeModel *) yelp_info_parser_parse_file (filepath);
-    gtk_tree_model_foreach (priv->sections,
-                            (GtkTreeModelForeachFunc) info_sections_visit,
-                            info);
-    priv->xmldoc = yelp_info_parser_parse_tree ((GtkTreeStore *) priv->sections);
+    g_mutex_lock (priv->mutex);
 
-    if (priv->xmldoc == NULL) {
-	error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
-                             _("The file ‘%s’ could not be parsed because it is"
-                               " not a well-formed info page."),
+    priv->unparsed_sections = yelp_info_parser_read_file (filepath);
+    if (!priv->unparsed_sections) {
+        error = g_error_new (YELP_ERROR, YELP_ERROR_PROCESSING,
+                             _("The file '%s' could not be parsed as "
+                               "an info document."),
                              filepath);
-	yelp_document_error_pending ((YelpDocument *) info, error);
+        yelp_document_error_pending ((YelpDocument *) info, error);
+        g_mutex_unlock (priv->mutex);
         goto done;
     }
 
-    g_mutex_lock (priv->mutex);
-    if (priv->state == INFO_STATE_STOP) {
-	g_mutex_unlock (priv->mutex);
-	goto done;
-    }
-
-    priv->transform = yelp_transform_new (STYLESHEET);
-    priv->chunk_ready =
-        g_signal_connect (priv->transform, "chunk-ready",
-                          (GCallback) transform_chunk_ready,
-                          info);
-    priv->finished =
-        g_signal_connect (priv->transform, "finished",
-                          (GCallback) transform_finished,
-                          info);
-    priv->error =
-        g_signal_connect (priv->transform, "error",
-                          (GCallback) transform_error,
-                          info);
+    transform_next_page (info);
 
-    params = yelp_settings_get_all_params (yelp_settings_get_default (), 0, &params_i);
-
-    priv->transform_running = TRUE;
-    yelp_transform_start (priv->transform,
-                          priv->xmldoc,
-                          NULL,
-			  (const gchar * const *) params);
-    g_strfreev (params);
     g_mutex_unlock (priv->mutex);
 
  done:
@@ -437,38 +529,6 @@ info_document_process (YelpInfoDocument *info)
     g_object_unref (info);
 }
 
-static gboolean
-info_sections_visit (GtkTreeModel     *model,
-                     GtkTreePath      *path,
-                     GtkTreeIter      *iter,
-                     YelpInfoDocument *info)
-{
-    YelpInfoDocumentPrivate *priv = GET_PRIV (info);
-    gchar *page_id, *title;
-
-    gtk_tree_model_get (model, iter,
-                        INFO_PARSER_COLUMN_PAGE_NO, &page_id,
-                        INFO_PARSER_COLUMN_PAGE_NAME, &title,
-                        -1);
-    yelp_document_set_page_id ((YelpDocument *) info, page_id, page_id);
-    yelp_document_set_page_title ((YelpDocument *) info, page_id, title);
-
-    if (priv->root_id == NULL) {
-        priv->root_id = g_strdup (page_id);
-        yelp_document_set_page_id ((YelpDocument *) info, NULL, page_id);
-    }
-    yelp_document_set_root_id ((YelpDocument *) info, page_id, priv->root_id);
-
-    if (priv->visit_prev_id != NULL) {
-        yelp_document_set_prev_id ((YelpDocument *) info, page_id, priv->visit_prev_id);
-        yelp_document_set_next_id ((YelpDocument *) info, priv->visit_prev_id, page_id);
-        g_free (priv->visit_prev_id);
-    }
-    priv->visit_prev_id = page_id;
-    g_free (title);
-    return FALSE;
-}
-
 static void
 info_document_disconnect (YelpInfoDocument *info)
 {
diff --git a/libyelp/yelp-info-parser.c b/libyelp/yelp-info-parser.c
index 5ecdc5a..0f5d5f1 100644
--- a/libyelp/yelp-info-parser.c
+++ b/libyelp/yelp-info-parser.c
@@ -33,28 +33,73 @@
 #include "yelp-debug.h"
 
 
-GtkTreeIter *         find_real_top                      (GtkTreeModel *model, 
-							  GtkTreeIter *it);
-GtkTreeIter *         find_real_sibling                  (GtkTreeModel *model,
-							  GtkTreeIter *it, 
-							  GtkTreeIter *comp);
-xmlNodePtr            yelp_info_parse_menu               (GtkTreeStore *tree,
-							  xmlNodePtr *node,
-							  gchar *page_content,
-							  gboolean notes);
-gboolean              get_menuoptions                    (gchar *line, 
-							  gchar **title, 
-							  gchar **ref, 
-							  gchar **desc, 
-							  gchar **xref);
-gboolean              resolve_frag_id                    (GtkTreeModel *model, 
-							  GtkTreePath *path, 
-							  GtkTreeIter *iter,
-							  gpointer data);
-void   		      info_process_text_notes            (xmlNodePtr *node, 
-							  gchar *content,
-							  GtkTreeStore
-							  *tree);
+static void yelp_info_parse_menu    (xmlNodePtr root,
+                                     gchar *page_content,
+                                     gboolean notes);
+static gboolean get_menuoptions     (gchar *line, gchar **title,
+                                     gchar **ref, gchar **desc,
+                                     gchar **xref);
+static void info_process_text_notes (xmlNodePtr node, gchar *content);
+
+/*
+  This structure is used to represent links. name is the name which
+  should appear in the text (with spaces etc.) and link is the mangled
+  name which is used as an id.
+ */
+typedef struct _XRef XRef;
+struct _XRef {
+  gchar *name, *id;
+};
+
+/*
+  This is created in the first pass through the info file.
+
+  contents is the text content of the section, without any
+  processing. The XRefs are so that we can build next/prev links and
+  the upward linktrail.
+ */
+struct _UnprocessedSection {
+  gchar *contents;
+  XRef *node, *parent, *prev, *next;
+};
+
+static XRef*
+xref_new ()
+{
+  return g_new0 (XRef, 1);
+}
+
+static void
+xref_free (XRef *xref)
+{
+  g_free (xref->name);
+  g_free (xref->id);
+  g_free (xref);
+}
+
+static UnprocessedSection*
+unprocessed_section_new ()
+{
+  UnprocessedSection *ret = g_new0 (UnprocessedSection, 1);
+  ret->node = xref_new ();
+  ret->parent = xref_new ();
+  ret->prev = xref_new ();
+  ret->next = xref_new ();
+  return ret;
+}
+
+static void
+unprocessed_section_free (UnprocessedSection *us)
+{
+  if (!us) return;
+
+  g_free (us->contents);
+  xref_free (us->node);
+  xref_free (us->parent);
+  xref_free (us->prev);
+  xref_free (us->next);
+  g_free (us);
+}
 
 /*
   Used to output the correct <heading level="?" /> tag.
@@ -608,491 +653,195 @@ get_value_after (const char* source, const char *key)
   return get_value_after_ext (source, key, ",", "\n\x7f");
 }
 
-static int
-node2page (GHashTable *nodes2pages, char *node)
-{
-  gint page;
-
-  if (g_hash_table_lookup_extended (nodes2pages, node,
-                                    NULL, (gpointer*) &page))
-    return page;
-
-  /* This shouldn't happen: we should only ever have to look up pages
-   * that exist. */
-  g_return_val_if_reached (0);
-}
+/*
+  Convert a page name into something that can go in a xref. (At the
+  moment, this is just cleaning spaces).
 
-static GtkTreeIter
-*node2iter (GHashTable *nodes2iters, char *node)
+  Operates in place but returns a pointer to the string (so chains
+  with g_strdup are easier).
+ */
+static gchar*
+name2id (gchar* str)
 {
-	GtkTreeIter *iter;
-
-	iter = g_hash_table_lookup (nodes2iters, node);
-	d (if (!iter) debug_print (DB_WARN, "Could not retrieve iter for node !%s!\n", node));
-	return iter;
+  return g_strdelimit (str, " ", '_');
 }
 
-GtkTreeIter 
-*find_real_top (GtkTreeModel *model, GtkTreeIter *it)
+/*
+  process_page is responsible for understanding enough of page_text to
+  make an UnprocessedSection structure out of it and insert it
+  correctly into usections.
+ */
+static void
+process_page (GHashTable *usections, char *page_text)
 {
-  GtkTreeIter *r = NULL;
-  GtkTreeIter *tmp = NULL;
-  
-  if (!it)
-    return NULL;
+  char **parts;
+  UnprocessedSection *section = unprocessed_section_new ();
 
-  r = gtk_tree_iter_copy (it);
-  tmp = g_malloc0 (sizeof (GtkTreeIter));
-  while (gtk_tree_model_iter_parent (model, tmp, r)) {
-    gtk_tree_iter_free (r);
-    r = gtk_tree_iter_copy (tmp);
-  }
-  g_free (tmp);
+  /* Split out first line from text and find prev/next/up links. */
+  parts = g_strsplit (page_text, "\n", 3);
 
-  return r;
-}
-
-GtkTreeIter * find_real_sibling (GtkTreeModel *model,
-				 GtkTreeIter *it, GtkTreeIter *comp)
-{
-  GtkTreeIter *r;
-  GtkTreeIter *tmp = NULL;
-  gboolean result = FALSE;
-  gchar *title;
-  gchar *reftitle;
-
-  if (!it) {
-    return NULL;
+  section->node->name = get_value_after (parts[0], "Node: ");
+  if (!section->node->name) {
+    /* Section doesn't have a name, so nothing can link to it. */
+    goto cleanup;
   }
+  section->node->id = name2id (g_strdup (section->node->name));
 
-  r = gtk_tree_iter_copy (it);
-  tmp = gtk_tree_iter_copy (it);
-
-  reftitle = gtk_tree_model_get_string_from_iter (model, comp);
-
-  result = gtk_tree_model_iter_parent (model, r, it);
-  if (!result)
-    return it;
+  /*
+    Don't look for the parent if we're already at Top (you'll get
+    '(dir)', but that doesn't appear in *this* document)
+   */
+  if (strcmp (section->node->id, "Top"))
+    section->parent->name = get_value_after (parts[0], "Up: ");
+
+  section->prev->name = get_value_after (parts[0], "Prev: ");
+  section->next->name = get_value_after (parts[0], "Next: ");
+
+  if (section->parent->name)
+    section->parent->id = name2id (g_strdup (section->parent->name));
+  if (section->prev->name)
+    section->prev->id = name2id (g_strdup (section->prev->name));
+  if (section->next->name)
+    section->next->id = name2id (g_strdup (section->next->name));
+
+  if (section->next->id && g_str_equal (section->next->id, "Top")) {
+    g_free (section->next->id);
+    section->next->id = NULL;
+  }
 
-  title = gtk_tree_model_get_string_from_iter (model, r);
+  if (section->prev->id && g_str_equal (section->node->id, "Top")) {
+    g_free (section->prev->id);
+    section->prev->id = NULL;
+  }
 
-  while (!g_str_equal (title, reftitle) && result) {
-    gtk_tree_iter_free (tmp);
-    tmp = gtk_tree_iter_copy (r);
-    result = gtk_tree_model_iter_parent (model, r, tmp);
-    if (result)
-      title = gtk_tree_model_get_string_from_iter (model, r);
+  /* Check to see if this page has been processed already */
+  if (g_hash_table_lookup (usections, section->node->id)) {
+    goto cleanup;
   }
 
-  if (!g_str_equal (title, reftitle))
-    {
-      gtk_tree_iter_free (tmp);
-      tmp = NULL;
-    }
+  /* Set pointers to NULL so that the free-fest in cleanup doesn't
+     free the memory. */
+  section->contents = parts[2];
+  parts[2] = NULL;
 
-  gtk_tree_iter_free (r);
-  g_free (title);
-  g_free (reftitle);
-  return tmp;
+  g_hash_table_insert (usections,
+                       g_strdup (section->node->id), section);
+  section = NULL;
 
+ cleanup:
+  unprocessed_section_free (section);
+  g_strfreev (parts);
 }
 
-static void
-process_page (GtkTreeStore *tree,
-              GHashTable *nodes2pages, GHashTable *nodes2iters,
-              int *processed_table, char **page_list, char *page_text)
+GHashTable*
+yelp_info_parser_read_file (const gchar *file)
 {
-	GtkTreeIter *iter;
-	
-	char **parts;
-	char *node;
-	char *up;
-	char *prev;
-	char *next;
-	gchar *tmp;
-
-	int page;
-	
-	/* split out the header line and the text */
-	parts = g_strsplit (page_text, "\n", 3);
-
-	node = get_value_after (parts[0], "Node: ");
-	up = get_value_after (parts[0], "Up: ");
-	prev = get_value_after (parts[0], "Prev: ");
-	next = get_value_after (parts[0], "Next: ");
-
-	if (next && g_str_equal (next, "Top")) {
-	  g_free (next);
-	  next = NULL;
-	}
-	if (g_str_equal (node, "Top") && prev != NULL) {
-	  g_free (prev);
-	  prev = NULL;
-	}
-
-	/* check to see if this page has been processed already */
-	page = node2page (nodes2pages, node);
-	if (processed_table[page]) {
-		return;
-	}
-	processed_table[page] = 1;
-	
-	debug_print (DB_DEBUG, "-- Processing Page %s\n\tParent: %s\n", node, up);
+  gchar **page_list;
+  char **ptr;
+  GHashTable *usections;
 
-	iter = g_slice_alloc0 (sizeof (GtkTreeIter));
-	/* check to see if we need to process our parent and siblings */
-	if (up && g_ascii_strncasecmp (up, "(dir)", 5) && strcmp (up, "Top"))
-	{
-		page = node2page (nodes2pages, up);
-		if (!processed_table[page])
-		{
-		  debug_print (DB_DEBUG, "%% Processing Node %s\n", up);
-                  process_page (tree, nodes2pages,
-				nodes2iters, processed_table, page_list,
-				page_list[page]);
-		}
-	}
-	if (prev && g_ascii_strncasecmp (prev, "(dir)", 5))
-	  {
-	    if (strncmp (node, "Top", 3)) {
-	      /* Special case the Top node to always appear first */
-	    } else {
-	      page = node2page (nodes2pages, prev);
-	      if (!processed_table[page])
-		{
-		  debug_print (DB_DEBUG, "%% Processing Node %s\n", prev);
-		  process_page (tree, nodes2pages,
-				nodes2iters, processed_table, page_list,
-				page_list[page]);
-		}
-	    }
-	  }
-	
-	/* by this point our parent and older sibling should be processed */
-	if (!up || !g_ascii_strcasecmp (up, "(dir)"))
-	{
-	  debug_print (DB_DEBUG, "\t> no parent\n");
-		if (!prev || !g_ascii_strcasecmp (prev, "(dir)"))
-		{
-		  debug_print (DB_DEBUG, "\t> no previous\n");
-			gtk_tree_store_append (tree, iter, NULL);
-		}
-		else if (prev) {
-		  GtkTreeIter *real;
-		  real = find_real_top (GTK_TREE_MODEL (tree), 
-					node2iter (nodes2iters, prev));
-		  if (real) {
-		    gtk_tree_store_insert_after (tree, iter, NULL,
-						 real);
-		    gtk_tree_iter_free (real);
-		  }
-		  else 
-		    gtk_tree_store_append (tree, iter, NULL);
-		}
-	}
-	else if (!prev || !g_ascii_strcasecmp (prev, "(dir)") || !strcmp (prev, up))
-	{
-	  debug_print (DB_DEBUG, "\t> no previous\n");
-		gtk_tree_store_append (tree, iter,
-			node2iter (nodes2iters, up));
-	}
-	else if (up && prev)
-	{
-	  GtkTreeIter *upit = node2iter (nodes2iters, up);
-	  GtkTreeIter *previt = node2iter (nodes2iters, prev);
-	  GtkTreeIter *nit = NULL;
-	  debug_print (DB_DEBUG, "+++ Parent: %s Previous: %s\n", up, prev);
-	  
-	  d (if (upit) debug_print (DB_DEBUG, "++++ Have parent node!\n"));
-	  d (if (previt) debug_print (DB_DEBUG, "++++ Have previous node!\n"));
-	  nit = find_real_sibling (GTK_TREE_MODEL (tree), previt, upit);
-	  if (nit) {
-	    gtk_tree_store_insert_after (tree, iter,
-					 upit,
-					 nit);
-	    gtk_tree_iter_free (nit);
-	  }
-	  else
-	    gtk_tree_store_append (tree, iter, upit);
-	}
-	else
-	{
-	  debug_print (DB_DEBUG, "# node %s was not put in tree\n", node);
-	  return;
-	}
+  page_list = expanded_info_file (file);
+  if (!page_list)
+    return NULL;
 
-	d (if (iter) debug_print (DB_DEBUG, "Have a valid iter, storing for %s\n", node));
+  usections = g_hash_table_new_full (
+    g_str_hash, g_str_equal,
+    g_free, (GDestroyNotify)unprocessed_section_free);
 
-	g_hash_table_insert (nodes2iters, g_strdup (node), iter);
-	debug_print (DB_DEBUG, "size: %i\n", g_hash_table_size (nodes2iters));
+  for (ptr = page_list; *ptr != NULL; ptr++) {
+    if (page_type (*ptr) == PAGE_NODE)
+      process_page (usections, *ptr);
+  }
 
-	/*tmp = g_strdup_printf ("%i",
-	  node2page (nodes2pages, node));*/
-	tmp = g_strdup (node);
-	tmp = g_strdelimit (tmp, " ", '_');
-	gtk_tree_store_set (tree, iter,
-			    INFO_PARSER_COLUMN_PAGE_NO, tmp,
-			    INFO_PARSER_COLUMN_PAGE_NAME, node,
-			    INFO_PARSER_COLUMN_PAGE_CONTENT, parts[2],
-			    -1);
+  g_strfreev (page_list);
 
-	g_free (tmp);
-	g_free (node);
-	g_free (up);
-	g_free (prev);
-	g_free (next);
-	g_strfreev (parts);
+  return usections;
 }
 
-struct TagTableFix {
-  GHashTable *nodes2pages; /* Build this... */
-  GHashTable *pages2nodes; /* ... using this. */
-};
-
-static void
-use_offset2page (gpointer o, gpointer p, gpointer ud)
-{
-  struct TagTableFix* ttf = (struct TagTableFix*)ud;
-
-  const gchar* node = g_hash_table_lookup (ttf->pages2nodes, p);
-  if (node) {
-    g_hash_table_insert (ttf->nodes2pages, g_strdup (node), p);
-  }
-}
+/* End Part 1 */
+/* Part 2: Parse Tree into XML */
 
 /*
-  We had a nodes2offsets hash table, but sometimes these things
-  lie. How terribly rude. Anyway, use offsets2pages and pages2nodes
-  (and injectivity!) to construct the nodes2pages hash table.
-*/
-static GHashTable *
-make_nodes2pages (GHashTable* offsets2pages,
-                  GHashTable* pages2nodes)
-{
-  struct TagTableFix ttf;
-
-  ttf.nodes2pages =
-    g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
-  ttf.pages2nodes = pages2nodes;
-
-  g_hash_table_foreach (offsets2pages, use_offset2page, &ttf);
-
-  return ttf.nodes2pages;
-}
-
-/**
- * Parse file into a GtkTreeStore containing useful information that we can
- * later convert into a nice XML document or something else.
+  Returns a list of XRef*'s for the parents of usection, with the most
+  senior first.
  */
-GtkTreeStore
-*yelp_info_parser_parse_file (char *file)
+static GSList*
+get_parent_list (const UnprocessedSection *usection,
+                 GHashTable *usections)
 {
-	gchar **page_list;
-	char **ptr;
-	int pages;
-	int offset;
-	GHashTable *offsets2pages = NULL;
-	GHashTable *pages2nodes = NULL;
-        GHashTable *nodes2pages = NULL;
-	GHashTable *nodes2iters = NULL;
-	int *processed_table;
-	GtkTreeStore *tree;
-	int pt;
-	
-	page_list = expanded_info_file (file);
-	if (!page_list)
-          return NULL;
-	
-	pages = 0;
-	offset = 0;
+  GSList *ret = NULL;
 
-	offsets2pages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
-					       NULL);
-	pages2nodes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, 
-					     g_free);
-
-	for (ptr = page_list; *ptr != NULL; ptr++)
-	{
-	  gchar *name = NULL;
-
-          g_hash_table_insert (offsets2pages,
-                               g_strdup_printf ("%i", offset),
-                               GINT_TO_POINTER (pages));
-
-          name = get_value_after (*ptr, "Node: ");
-          if (name)
-            g_hash_table_insert (pages2nodes,
-                                 GINT_TO_POINTER (pages), name);
-		
-          offset += strlen (*ptr);
-          if (pages) offset += 2;
-          pages++;
-
-          pt = page_type (*ptr);
-          if (pt == PAGE_INDIRECT) {
-            g_warning ("Found an indirect page in a file "
-                       "we thought we'd expanded.");
-          }
-	}
-
-        /* Now consolidate (and correct) the two hash tables */
-        nodes2pages = make_nodes2pages (offsets2pages, pages2nodes);
-
-	g_hash_table_destroy (offsets2pages);
-        g_hash_table_destroy (pages2nodes);
-
-	processed_table = g_malloc0 (pages * sizeof (int));
-	tree = gtk_tree_store_new (INFO_PARSER_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
-			G_TYPE_STRING);
-	nodes2iters = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
-					     (GDestroyNotify) gtk_tree_iter_free);
-
-	pages = 0;
-	for (ptr = page_list; *ptr != NULL; ptr++)
-	{
-	  if (page_type (*ptr) != PAGE_NODE) continue;
-	  process_page (tree, nodes2pages, nodes2iters,
-			processed_table, page_list, *ptr);
-	}
-
-	g_strfreev (page_list);
-
-	g_hash_table_destroy (nodes2iters);
-	g_hash_table_destroy (nodes2pages);
-
-	g_free (processed_table);
+  while (usection->parent->id && usection->parent->name) {
+    ret = g_slist_prepend (ret, usection->parent);
+    usection = g_hash_table_lookup (usections, usection->parent->id);
+    if (!usection)
+      break;
+  }
 
-	return tree;
+  return ret;
 }
 
-/* End Part 1 */
-/* Part 2: Parse Tree into XML */
-static void
-parse_tree_level (GtkTreeStore *tree, xmlNodePtr *node, GtkTreeIter iter)
+xmlDocPtr
+yelp_info_parser_parse_section (const UnprocessedSection* usection,
+                                GHashTable *usections)
 {
-    GtkTreeIter children, parent;
-	xmlNodePtr newnode;
+  xmlDocPtr doc;
+  xmlNodePtr root, parents, parent;
+  gboolean notes = FALSE;
+  GSList *parent_list;
+  XRef *xref;
+
+  doc = xmlNewDoc (BAD_CAST "1.0");
+  root = xmlNewNode (NULL, BAD_CAST "Info");
+  xmlDocSetRootElement (doc, root);
+
+  if (strstr (usection->contents, "*Note") ||
+      strstr (usection->contents, "*note")) {
+    notes = TRUE;
+  }
 
-	char *page_no = NULL;
-	char *page_name = NULL;
-	char *page_content = NULL;
-	gboolean notes = FALSE;
+  if (strstr (usection->contents, "* Menu:")) {
+    yelp_info_parse_menu (root, usection->contents, notes);
+  }
+  else {
+    if (!notes) {
+      info_body_text (root, NULL, NULL, FALSE, usection->contents);
+    }
+    else {
+      info_process_text_notes (root, usection->contents);
+    }
+  }
 
-	debug_print (DB_DEBUG, "Decended\n");
-	do
-	{
-		gtk_tree_model_get (GTK_TREE_MODEL (tree), &iter,
-				INFO_PARSER_COLUMN_PAGE_NO, &page_no,
-				INFO_PARSER_COLUMN_PAGE_NAME, &page_name,
-				INFO_PARSER_COLUMN_PAGE_CONTENT, &page_content,
-				-1);
-		debug_print (DB_DEBUG, "Got Section: %s\n", page_name);
-		if (strstr (page_content, "*Note") || 
-		    strstr (page_content, "*note")) {
-		  notes = TRUE;
-		}
-		if (strstr (page_content, "* Menu:")) {
-		  newnode = yelp_info_parse_menu (tree, node, page_content, notes);
-		} else {
-		  newnode = xmlNewTextChild (*node, NULL,
-					     BAD_CAST "Section",
-					     NULL);
-		  if (!notes)
-		    info_body_text (newnode, NULL, NULL, FALSE, page_content);
-
-		  else {
-		    /* Handle notes here */
-		    info_process_text_notes (&newnode, page_content, tree);
-		  }
-		}
-		/* if we free the page content, now it's in the XML, we can
-		 * save some memory */
-		g_free (page_content);
-		page_content = NULL;
-
-                if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (tree), &parent, &iter)) {
-                    gchar *parent_id;
-                    gtk_tree_model_get (GTK_TREE_MODEL (tree), &parent,
-                                        INFO_PARSER_COLUMN_PAGE_NO, &parent_id,
-                                        -1);
-                    xmlNewProp (newnode, BAD_CAST "up", BAD_CAST parent_id);
-                    g_free (parent_id);
-                }
-
-		xmlNewProp (newnode, BAD_CAST "id", 
-			    BAD_CAST page_no);
-		xmlNewProp (newnode, BAD_CAST "name", 
-			    BAD_CAST page_name);
-		if (gtk_tree_model_iter_children (GTK_TREE_MODEL (tree),
-				&children,
-				&iter))
-		  parse_tree_level (tree, &newnode, children);
-		g_free (page_no);
-		g_free (page_name);
-	}
-	while (gtk_tree_model_iter_next (GTK_TREE_MODEL (tree), &iter));
-	debug_print (DB_DEBUG, "Ascending\n");
-}
+  if (usection->next->id && usection->next->name) {
+    xmlNewProp (root, BAD_CAST "next", BAD_CAST usection->next->id);
+    xmlNewProp (root, BAD_CAST "next-name", BAD_CAST usection->next->name);
+  }
+  if (usection->prev->id && usection->prev->name) {
+    xmlNewProp (root, BAD_CAST "prev", BAD_CAST usection->prev->id);
+    xmlNewProp (root, BAD_CAST "prev-name", BAD_CAST usection->prev->name);
+  }
 
-xmlDocPtr
-yelp_info_parser_parse_tree (GtkTreeStore *tree)
-{
-	xmlDocPtr doc;
-	xmlNodePtr node;
-	GtkTreeIter iter;
-
-	/*
-	xmlChar *xmlbuf;
-	int bufsiz;
-	*/
-
-	doc = xmlNewDoc (BAD_CAST "1.0");
-	node = xmlNewNode (NULL, BAD_CAST "Info");
-	xmlDocSetRootElement (doc, node);
-
-	/* functions I will want:
-	gtk_tree_model_get_iter_first;
-	gtk_tree_model_iter_next;
-	gtk_tree_model_iter_children;
-	*/
-
-	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (tree), &iter))
-		parse_tree_level (tree, &node, iter);
-	d (else debug_print (DB_DEBUG, "Empty tree?\n"));
-
-	/*
-	xmlDocDumpFormatMemory (doc, &xmlbuf, &bufsiz, 1);
-	g_print ("XML follows:\n%s\n", xmlbuf);
-	*/
-
-	return doc;
-}
+  xmlNewProp (root, BAD_CAST "id", BAD_CAST usection->node->id);
+  xmlNewProp (root, BAD_CAST "name", BAD_CAST usection->node->name);
 
-gboolean
-resolve_frag_id (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
-		 gpointer data)
-{
-  gchar *page_no = NULL;
-  gchar *page_name = NULL;
-  gchar **xref = data;
-
-  gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
-		      INFO_PARSER_COLUMN_PAGE_NO, &page_no,
-		      INFO_PARSER_COLUMN_PAGE_NAME, &page_name,
-		      -1);
-  if (g_str_equal (page_name, *xref)) {
-    g_free (*xref);
-    *xref = g_strdup (page_name);
-    *xref = g_strdelimit (*xref, " ", '_');
-
-    g_free (page_name);
-    g_free (page_no);
-    return TRUE;
+  /*
+    Add
+      <parents><parent>id1</parent><parent>id2</parent></parents>
+    to allow us to make a linktrail in the xslt.
+   */
+  parent_list = get_parent_list (usection, usections);
+  if (parent_list) {
+    parents = xmlNewChild (root, NULL, BAD_CAST "parents", NULL);
+    while (parent_list) {
+      xref = (XRef *)parent_list->data;
+      parent = xmlNewChild (parents, NULL, BAD_CAST "parent", NULL);
+      xmlNewProp (parent, BAD_CAST "id", BAD_CAST xref->id);
+      xmlNewProp (parent, BAD_CAST "name", BAD_CAST xref->name);
+      parent_list = g_slist_next (parent_list);
+    }
   }
-  g_free (page_name);
-  g_free (page_no);
 
-  return FALSE;
+  return doc;
 }
 
 gboolean
@@ -1155,26 +904,22 @@ first_non_space (gchar* str)
   return str;
 }
 
-xmlNodePtr
-yelp_info_parse_menu (GtkTreeStore *tree, xmlNodePtr *node, 
+static void
+yelp_info_parse_menu (xmlNodePtr root,
 		      gchar *page_content, gboolean notes)
 {
   gchar **split;
   gchar **menuitems;
   gchar *tmp = NULL;
-  xmlNodePtr newnode, menu_node, mholder = NULL;
+  xmlNodePtr menu_node, mholder = NULL;
   int i=0;
 
   split = g_strsplit (page_content, "* Menu:", 2);
   
-  newnode = xmlNewChild (*node, NULL,
-			 BAD_CAST "Section", NULL);
-    
-
   if (!notes)
-    info_body_text (newnode, NULL, NULL, FALSE, split[0]);
+    info_body_text (root, NULL, NULL, FALSE, split[0]);
   else {
-    info_process_text_notes (&newnode, split[0], tree);
+    info_process_text_notes (root, split[0]);
   }
 
   menuitems = g_strsplit (split[1], "\n", -1);
@@ -1205,7 +950,7 @@ yelp_info_parse_menu (GtkTreeStore *tree, xmlNodePtr *node,
 
   if (menuitems[0] != NULL) {
     /* If there are any menu items, make the <menu> node */
-    menu_node = xmlNewChild (newnode, NULL, BAD_CAST "menu", NULL);
+    menu_node = xmlNewChild (root, NULL, BAD_CAST "menu", NULL);
   }
 
   while (menuitems[i] != NULL) {
@@ -1227,8 +972,8 @@ yelp_info_parse_menu (GtkTreeStore *tree, xmlNodePtr *node,
 
     if (menu) {
       mholder = xmlNewChild (menu_node, NULL, BAD_CAST "menuholder", NULL);
-      gtk_tree_model_foreach (GTK_TREE_MODEL (tree), resolve_frag_id, &xref);
-      
+      name2id (xref);
+
       if (ref == NULL) { /* A standard type menu */
         /* title+2 skips the "* ". We know we haven't jumped over the
            end of the string because strlen (title) >= 3 */
@@ -1311,12 +1056,10 @@ yelp_info_parse_menu (GtkTreeStore *tree, xmlNodePtr *node,
 
   }
   g_strfreev (menuitems);
-  
-  return newnode;
 }
 
 void
-info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
+info_process_text_notes (xmlNodePtr node, gchar *content)
 {
   gchar **notes;
   gchar **current;
@@ -1336,7 +1079,7 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
   notes = g_regex_split_simple ("\\*[Nn]ote(?!_)", content, 0, 0);
 
   for (current = notes; *current != NULL; current++) {
-    gchar *url, **urls, **ulink;
+    gchar *url, **urls;
     gchar *append;
     gchar *alt_append, *alt_append1;
     gchar *link_text;
@@ -1348,14 +1091,14 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
        * start, so we can just add it and forget about it.
        */
       first = FALSE;
-      info_body_text (*node, &paragraph, NULL, TRUE, (*current));
+      info_body_text (node, &paragraph, NULL, TRUE, (*current));
       continue;
     }
 
     /* If we got to here, we now gotta parse the note reference */
     append = strchr (*current, ':');
     if (!append) {
-      info_body_text (*node, &paragraph, NULL, TRUE, *current);
+      info_body_text (node, &paragraph, NULL, TRUE, *current);
       continue;
     }
     append++;
@@ -1370,7 +1113,7 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
     }
     alt_append1 = strchr (alt_append1, ',');
     if (!append && !alt_append && !alt_append1) {
-      info_body_text (*node, &paragraph, NULL, TRUE, *current);
+      info_body_text (node, &paragraph, NULL, TRUE, *current);
       continue;
     }
     if (!append || alt_append || alt_append1) {
@@ -1477,14 +1220,14 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
       else
         frag = g_strndup (url, tmp1 - url);
       g_strstrip (frag);
-      gtk_tree_model_foreach (GTK_TREE_MODEL (tree), resolve_frag_id, &frag);
+      name2id (frag);
       href = g_strconcat ("xref:", frag, NULL);
       g_free (frag);
     }
 
     /* Check we've got a valid paragraph node */
     if (!paragraph) {
-      paragraph = xmlNewChild (*node, NULL, BAD_CAST "para", NULL);
+      paragraph = xmlNewChild (node, NULL, BAD_CAST "para", NULL);
     }
 
     /*
@@ -1502,7 +1245,7 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
     g_strfreev (urls);
 
     /* Finally, we can add the following text as required */
-    info_body_text (*node, &paragraph, NULL, TRUE, append);
+    info_body_text (node, &paragraph, NULL, TRUE, append);
 
     g_free (url);
     g_free (href);
diff --git a/libyelp/yelp-info-parser.h b/libyelp/yelp-info-parser.h
index d338a59..5d82446 100644
--- a/libyelp/yelp-info-parser.h
+++ b/libyelp/yelp-info-parser.h
@@ -34,8 +34,22 @@ enum {
     INFO_PARSER_N_COLUMNS
 };
 
+/*
+  A structure for information about a section which has been read into
+  memory but not yet parsed to xml.
+ */
+typedef struct _UnprocessedSection UnprocessedSection;
 
-GtkTreeStore          *yelp_info_parser_parse_file  (char           *file);
-xmlDocPtr	       yelp_info_parser_parse_tree  (GtkTreeStore   *tree);
+/*
+  Read the given file into memory. Stores each (unparsed) section
+  keyed by page_id into a hash table. Returns NULL on failure.
+ */
+GHashTable*    yelp_info_parser_read_file  (const gchar  *file);
+
+/*
+  Parse a particular section to xml.
+ */
+xmlDocPtr      yelp_info_parser_parse_section (const UnprocessedSection* usection,
+                                               GHashTable *usections);
 
 #endif /* __YELP_INFO_PARSER_H__ */
diff --git a/stylesheets/info2html.xsl.in b/stylesheets/info2html.xsl.in
index a97b054..e91008d 100644
--- a/stylesheets/info2html.xsl.in
+++ b/stylesheets/info2html.xsl.in
@@ -12,37 +12,11 @@
 <xsl:import href="@XSL_HTML@"/>
 <xsl:include href="yelp-common.xsl"/>
 
-<xsl:template name="linktrails">
-  <xsl:param name="up" select="@up"/>
-  <xsl:variable name="upnode" select="/Info//Section[@id = $up]"/>
-  <xsl:if test="$upnode/@up">
-    <xsl:call-template name="linktrails">
-      <xsl:with-param name="up" select="$upnode/@up"/>
-    </xsl:call-template>
-  </xsl:if>
-  <a href="xref:{$upnode/@id}">
-    <xsl:value-of select="$upnode/@name"/>
-  </a>
-  <xsl:text>&#x00A0;» </xsl:text>
-</xsl:template>
-
-<xsl:template match="/">
-  <xsl:for-each select="/Info/Section">
-    <xsl:call-template name="html.output"/>
-  </xsl:for-each>
-</xsl:template>
-
-<xsl:template mode="html.output.after.mode" match="Section">
-  <xsl:for-each select="Section">
-    <xsl:call-template name="html.output"/>
-  </xsl:for-each>
-</xsl:template>
-
-<xsl:template mode="html.title.mode" match="Section">
+<xsl:template mode="html.title.mode" match="Info">
   <xsl:value-of select="@name"/>
 </xsl:template>
 
-<xsl:template mode="html.css.mode" match="Section">
+<xsl:template mode="html.css.mode" match="Info">
   <xsl:param name="direction"/>
   <xsl:param name="left"/>
   <xsl:param name="right"/>
@@ -72,35 +46,46 @@ a.navbar-next::after {
 </xsl:text>
 </xsl:template>
 
-<xsl:template mode="html.header.mode" match="Section">
-  <xsl:if test="@up">
+<xsl:template mode="linktrail" match="parent">
+  <a href="xref:{@id}">
+    <xsl:value-of select="@name"/>
+  </a>
+  <xsl:text>&#x00A0;» </xsl:text>
+</xsl:template>
+
+<xsl:template mode="html.header.mode" match="Info">
+  <xsl:if test="parents">
     <div class="trails">
       <div class="trail">
-        <xsl:call-template name="linktrails"/>
+        <xsl:apply-templates select="parents/parent"
+                             mode="linktrail"/>
       </div>
     </div>
   </xsl:if>
 </xsl:template>
 
-<xsl:template mode="html.body.mode" match="Section">
+<xsl:template mode="html.body.mode" match="Info">
   <div class="navbar">
-    <xsl:variable name="preceding" select="(parent::Section[1] | preceding::Section[1])[last()]"/>
-    <xsl:variable name="following" select="(Section[1] | following::Section[1])[1]"/>
-    <xsl:if test="$preceding">
-      <a class="navbar-prev" href="xref:{$preceding/@id}">
-        <xsl:value-of select="$preceding/@name"/>
+    <xsl:variable name="next-name" select="@next-name"/>
+    <xsl:variable name="next" select="@next"/>
+    <xsl:variable name="prev-name" select="@prev-name"/>
+    <xsl:variable name="prev" select="@prev"/>
+
+    <xsl:if test="$prev">
+      <a class="navbar-prev" href="xref:{$prev}">
+        <xsl:value-of select="$prev-name"/>
       </a>
     </xsl:if>
-    <xsl:if test="$preceding and $following">
+    <xsl:if test="$prev and $next">
       <xsl:text>&#x00A0;&#x00A0;|&#x00A0;&#x00A0;</xsl:text>
     </xsl:if>
-    <xsl:if test="$following">
-      <a class="navbar-next" href="xref:{$following/@id}">
-        <xsl:value-of select="$following/@name"/>
+    <xsl:if test="$next">
+      <a class="navbar-next" href="xref:{$next}">
+        <xsl:value-of select="$next-name"/>
       </a>
     </xsl:if>
   </div>
-  <xsl:apply-templates select="node()[not(self::Section)]"/>
+  <xsl:apply-templates select="node()[not(self::Info)]"/>
 </xsl:template>
 
 
-- 
1.7.4.4

Attachment: pgp8U7c1zcVAi.pgp
Description: PGP signature



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]