[yelp] Support for headings in info files.



commit 413493e93f4826df3c28f2891505f0ef81b1c9b1
Author: Rupert Swarbrick <rswarbrick gmail com>
Date:   Wed Jun 16 10:32:20 2010 +0100

    Support for headings in info files.

 libyelp/yelp-info-parser.c   |  173 +++++++++++++++++++++++++++++++++++++-----
 stylesheets/info2html.xsl.in |   17 ++++
 2 files changed, 170 insertions(+), 20 deletions(-)
---
diff --git a/libyelp/yelp-info-parser.c b/libyelp/yelp-info-parser.c
index 3310794..7d32905 100644
--- a/libyelp/yelp-info-parser.c
+++ b/libyelp/yelp-info-parser.c
@@ -1,4 +1,4 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil -*- */
 /*
  * Copyright (C) 2005 Davyd Madeley <davyd madeley id au>
  *
@@ -58,8 +58,13 @@ void                  fix_tag_table                      (gchar *offset,
 							  TagTableFix *a);
 void   		      info_process_text_notes            (xmlNodePtr *node, 
 							  gchar *content,
-							  GtkTreeStore *tree);
+							  GtkTreeStore
+							  *tree);
 
+/*
+  Used to output the correct <heading level="?" /> tag.
+ */
+static const gchar* level_headings[] = { NULL, "1", "2", "3" };
 
 static GHashTable *
 info_image_get_attributes (gchar const* string)
@@ -141,15 +146,144 @@ info_insert_image (xmlNodePtr parent, GMatchInfo *match_info)
 }
 
 /*
-  Convert body text CONTENT to xml nodes, processing info image tags
-  when found.  IWBN add a regex match for *Note: here and call the
-  *Note ==> <a href> logic of info_process_text_notes from here.
+  If every element of `str' is `ch' then return TRUE, else FALSE.
  */
-static xmlNodePtr
-info_body_text (xmlNodePtr parent, xmlNsPtr ns, gchar const *name, gchar const *content)
+static gboolean
+string_all_char_p (const gchar* str, gchar ch)
+{
+  for (; *str; str++) {
+    if (*str != ch) return FALSE;
+  }
+  return TRUE;
+}
+
+/*
+  If `line' is a line of '*', '=' or '-', return 1,2,3 respectively
+  for the heading level. If it's anything else, return 0.
+ */
+static int
+header_underline_level (const gchar* line)
+{
+  if (*line != '*' && *line != '=' && *line != '-')
+    return 0;
+
+  if (string_all_char_p (line, '*')) return 1;
+  if (string_all_char_p (line, '=')) return 2;
+  if (string_all_char_p (line, '-')) return 3;
+
+  return 0;
+}
+
+/*
+  Use g_strjoinv to join up the strings from `strings', but they might
+  not actually be a null-terminated array. `end' should be strings+n,
+  where I want the first n strings (strings+0, ..., strings+(n-1)). It
+  shouldn't point outside of the array allocated, but it can point at
+  the null string at the end.
+ */
+static gchar*
+join_strings_subset (const gchar *separator,
+                     gchar** strings, gchar** end)
+{
+  g_assert(end > strings);
+
+  gchar *ptr = *end;
+  *end = NULL;
+  
+  gchar *glob = g_strjoinv (separator, strings);
+  *end = ptr;
+  return glob;
+}
+
+/*
+  Create a text node, child of `parent', with the lines strictly
+  between `first' and `last'.
+*/
+static void
+lines_subset_text_child (xmlNodePtr parent, xmlNsPtr ns,
+                         gboolean inline_p,
+                         gchar** first, gchar** last)
 {
-  if (!strstr (content, INFO_C_IMAGE_TAG_OPEN))
-    return xmlNewTextChild (parent, ns, BAD_CAST name, BAD_CAST content);
+  /* TODO? Currently we're copying the split strings again, which is
+     less efficient than somehow storing lengths and using a sort of
+     window on `content'. But that's much more difficult, so unless
+     there's a problem, let's go with the stupid approach. */
+  gchar *glob;
+  if (last > first) {
+    glob = join_strings_subset ("\n", first, last);
+    xmlNewTextChild (parent, ns,
+                     inline_p ? BAD_CAST "para1" : BAD_CAST "para",
+                     BAD_CAST glob);
+    g_free (glob);
+  }
+}
+
+/*
+  Convert body text CONTENT to xml nodes. This function is responsible
+  for spotting headings etc and splitting them out correctly.
+
+  If `inline_p' is true, end with a <para1> tag. Otherwise, end with a
+  <para> tag. 
+
+  TODO: IWBN add a regex match for *Note: here and call the *Note ==>
+  <a href> logic of info_process_text_notes from here.
+ */
+static void
+info_body_parse_text (xmlNodePtr parent, xmlNsPtr ns,
+                      gboolean inline_p, const gchar *content)
+{
+  /* The easiest things to spot are headings: they look like a line of
+   * '*','=' or '-', corresponding to heading levels 1,2 or 3. To spot
+   * them, we split content into single lines and work with them. */
+  gchar **lines = g_strsplit (content, "\n", 0);
+  gchar **first = lines, **last = lines+1;
+  int header_level;
+  xmlNodePtr header_node;
+
+  /* Deal with the possibility that `content' is empty */
+  if (*lines == NULL) {
+    if (!inline_p) {
+      xmlNewTextChild (parent, NULL, BAD_CAST "para", BAD_CAST "");
+    }
+    return;
+  }
+
+  for (; *last; last++) {
+    header_level = header_underline_level (*last);
+    if (header_level) {
+      /* Write out any lines beforehand */
+      lines_subset_text_child (parent, ns, FALSE, first, last-1);
+      /* Now write out the actual header line */
+      header_node = xmlNewTextChild (parent, ns, BAD_CAST "header",
+                                     BAD_CAST *(last-1));
+      xmlNewProp (header_node, BAD_CAST "level",
+                  BAD_CAST level_headings[header_level]);
+      
+      first = last+1;
+      last = first+1;
+    }
+  }
+  /* Write out any lines left */
+  lines_subset_text_child (parent, ns, inline_p, first, last);
+  
+  g_strfreev (lines);
+}
+
+/*
+  info_body_text is responsible for taking a hunk of the info page's
+  body and turning it into paragraph tags. It searches out images and
+  marks them up properly if necessary.
+
+  It uses info_body_parse_text to mark up the actual bits of text.
+ */
+static void
+info_body_text (xmlNodePtr parent, xmlNsPtr ns,
+                gboolean inline_p, gchar const *content)
+{
+  if (!strstr (content, INFO_C_IMAGE_TAG_OPEN)) {
+    info_body_parse_text (parent, ns, inline_p, content);
+    return;
+  }
 
   gint content_len = strlen (content);
   gint pos = 0;
@@ -164,16 +298,15 @@ info_body_text (xmlNodePtr parent, xmlNsPtr ns, gchar const *name, gchar const *
 						     &image_start, &image_end);
       gchar *before = g_strndup (&content[pos], image_start - pos);
       pos = image_end + 1;
-      xmlNewTextChild (parent, NULL, BAD_CAST "para1", BAD_CAST (before));
+      info_body_parse_text (parent, NULL, TRUE, before);
       g_free (before);
       if (image_found)
 	info_insert_image (parent, match_info);
       g_match_info_next (match_info, NULL);
     }
   gchar *after = g_strndup (&content[pos], content_len - pos);
-  xmlNewTextChild (parent, NULL, BAD_CAST "para1", BAD_CAST (after));
+  info_body_parse_text (parent, NULL, TRUE, after);
   g_free (after);
-  return 0;
 }
 
 /* Part 1: Parse File Into Tree Store */
@@ -840,7 +973,7 @@ parse_tree_level (GtkTreeStore *tree, xmlNodePtr *node, GtkTreeIter iter)
 					     BAD_CAST "Section",
 					     NULL);
 		  if (!notes)
-		    info_body_text (newnode, NULL, "para", page_content);
+		    info_body_text (newnode, NULL, FALSE, page_content);
 		  
 		  else {
 		    /* Handle notes here */
@@ -1005,7 +1138,7 @@ yelp_info_parse_menu (GtkTreeStore *tree, xmlNodePtr *node,
 
   tmp = g_strconcat (split[0], "\n* Menu:", NULL);
   if (!notes)
-    info_body_text (newnode, NULL, "para", tmp);
+    info_body_text (newnode, NULL, FALSE, tmp);
   else {
     info_process_text_notes (&newnode, tmp, tree);
   }
@@ -1119,7 +1252,7 @@ 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 (holder, NULL, "para1", (*current_real));
+	info_body_text (holder, NULL, TRUE, (*current_real));
 	continue;
       }
       /* If we got to here, we now gotta parse the note reference */
@@ -1128,13 +1261,13 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
 	/* Special type of note that isn't really a note, but pretends
 	 * it is
 	 */
-	info_body_text (holder, NULL, "para1",
+	info_body_text (holder, NULL, TRUE,
 			g_strconcat ("*Note", *current_real, NULL));
 	continue;
       }
       append = strchr (*current_real, ':');
       if (!append) {
-	info_body_text (holder, NULL, "para1", *current_real);
+	info_body_text (holder, NULL, TRUE, *current_real);
 	continue;
       }
       append++;
@@ -1149,7 +1282,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 (holder, NULL, "para1", *current_real);
+	info_body_text (holder, NULL, TRUE, *current_real);
 	continue;
       }
       if (!append || alt_append || alt_append1) {
@@ -1285,14 +1418,14 @@ info_process_text_notes (xmlNodePtr *node, gchar *content, GtkTreeStore *tree)
 	ref1 = xmlNewTextChild (holder, NULL, BAD_CAST "a",
 				BAD_CAST link_text);
 	if (*(ulink+1) != NULL)
-	  info_body_text (holder, NULL, "para", "");
+	  info_body_text (holder, NULL, FALSE, "");
 
 	g_free (link_text);
 	xmlNewProp (ref1, BAD_CAST "href", BAD_CAST href);
       }
       g_strfreev (urls);
       /* Finally, we can add the text as required */
-      info_body_text (holder, NULL, "para1", append);
+      info_body_text (holder, NULL, TRUE, append);
       g_free (url);
       g_free (href);
     }
diff --git a/stylesheets/info2html.xsl.in b/stylesheets/info2html.xsl.in
index ec75878..c029148 100644
--- a/stylesheets/info2html.xsl.in
+++ b/stylesheets/info2html.xsl.in
@@ -115,6 +115,23 @@ a.navbar-next::after {
   <xsl:value-of select="node()"/>
 </xsl:template>
 
+<xsl:template match="header">
+  <xsl:choose>
+    <xsl:when test='@level = 1'>
+      <h1><xsl:value-of select="node()"/></h1>
+    </xsl:when>
+    <xsl:when test='@level = 2'>
+      <h2><xsl:value-of select="node()"/></h2>
+    </xsl:when>
+    <xsl:when test='@level = 3'>
+      <h3><xsl:value-of select="node()"/></h3>
+    </xsl:when>
+    <xsl:otherwise>
+      <h1>(Unknown heading level) <xsl:value-of select="node()"/></h1>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
 <xsl:template match="spacing">
   <xsl:value-of select="node()"/>
 </xsl:template>



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