[PATCH] 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.

This depends on the patchset posted at
http://thread.gmane.org/gmane.comp.gnome.documentation.devel/541


---
 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.1



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