[libgda] Initial support for rich text rendering in reports



commit 3fc781cbfdf52c0b7091779fea320bf74d65650a
Author: Vivien Malerba <malerba gnome-db org>
Date:   Thu Jan 6 15:58:58 2011 +0100

    Initial support for rich text rendering in reports

 configure.ac                             |    8 +
 doc/C/libgda-4.0-docs.sgml               |    8 +-
 doc/C/tmpl/gda-report-engine.sgml        |   10 +-
 libgda-report/Makefile.am                |    3 +-
 libgda-report/engine/.gitignore          |    1 +
 libgda-report/engine/Makefile.am         |   11 +-
 libgda-report/engine/gda-report-engine.c |   47 +-
 libgda-report/engine/rt-parser.c         | 1283 ++++++++++++++++++++++++++++++
 libgda-report/engine/rt-parser.h         |   69 ++
 libgda-report/engine/test-rt-parser.c    |  106 +++
 po/POTFILES.in                           |    1 +
 11 files changed, 1535 insertions(+), 12 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 93f2425..e7dc59c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -198,6 +198,10 @@ AS_HELP_STRING([--with-graphviz], [Enable using Graphviz]),
 
 if test x"$have_ui" = "xyes"
 then
+	PKG_CHECK_MODULES(GDKPIXBUF, "gdk-pixbuf-2.0", [
+			AC_DEFINE(HAVE_GDKPIXBUF, [1], [Gdkpixbuf support enabled])
+			have_gdkpixbuf=yes], [have_gdkpixbuf=no])
+
 	if test "$with_sourceview" = "auto" -o "$with_sourceview" = "yes"
 	then
 		PKG_CHECK_MODULES(GTKSOURCEVIEW, "gtksourceview-2.0", [
@@ -235,6 +239,10 @@ then
 	fi
 fi
 
+AM_CONDITIONAL(HAVE_GDKPIXBUF, test x"$have_gdkpixbuf" = "xyes")
+AC_SUBST(GDKPIXBUF_CFLAGS)
+AC_SUBST(GDKPIXBUF_LIBS)
+
 AM_CONDITIONAL(HAVE_GTKSOURCEVIEW, test x"$have_sourceview" = "xyes")
 AC_SUBST(GTKSOURCEVIEW_CFLAGS)
 AC_SUBST(GTKSOURCEVIEW_LIBS)
diff --git a/doc/C/libgda-4.0-docs.sgml b/doc/C/libgda-4.0-docs.sgml
index c7c7915..78bcbfd 100644
--- a/doc/C/libgda-4.0-docs.sgml
+++ b/doc/C/libgda-4.0-docs.sgml
@@ -1588,7 +1588,11 @@ Description for type: DROP_COLUMN
       <title>Introduction</title>
       <para>
 	&LIBGDA;'s report feature has been reworked and is only offers it <link linkend="GdaReportEngine">report engine object</link>, 
-	a low level general usage engine to generate reports in the XML format.
+	a low level general usage engine to generate reports in the XML format. More specifically it converts
+	an XML tree containing special tags into another XML tree where all the specific tags have been
+	expanded/replaced with database contents. For more information about the special tags taken
+	into account, please refer to the <link linkend="GdaReportEngine">GdaReportEngine</link>'s
+	documentation.
       </para>
       <para>
 	Working on any XML file allows the report engine to work with all the existing post-processors which will actually
@@ -1599,7 +1603,7 @@ Description for type: DROP_COLUMN
 	      (see <ulink url="http://wiki.docbook.org/topic/DocBookXsltPublishingModelDiagram";>this docbook Wiki page</ulink>)
 	  </para></listitem>
 	  <listitem><para>RML files (Report Markup Language), see <ulink url="http://www.reportlab.org/";>ReportLab</ulink> or
-	      <ulink url="http://en.openreport.org/index.py/static/page/trml2pdf";>OpenReport</ulink>) can be converted to HTML or PDF)
+	      <ulink url="http://oreports.com";>OpenReports</ulink> can be converted to HTML or PDF.
 	  </para></listitem>
 	  <listitem><para>some other XML dialects can also be used such as 
 	      <ulink url="http://sourceforge.net/projects/rlib/";>RLib</ulink>, 
diff --git a/doc/C/tmpl/gda-report-engine.sgml b/doc/C/tmpl/gda-report-engine.sgml
index 926e1cd..050d5a0 100644
--- a/doc/C/tmpl/gda-report-engine.sgml
+++ b/doc/C/tmpl/gda-report-engine.sgml
@@ -96,7 +96,15 @@ Low level report generator based on XML
           <entry>&lt;gda_report_param_value&gt;</entry>
           <entry>Replace the node with the value of a parameter. The parameter can either by defined globally
 	  (and declared to the GdaReportEngine), or defined as part of a section</entry>
-          <entry>"param_name" specifies the name of the parameter</entry>
+          <entry>
+	    <itemizedlist>
+	      <listitem><para>"param_name" specifies the name of the parameter</para></listitem>
+	      <listitem><para>"converter" optionnally specifies a conversion to apply to the parameter's
+		  contents (for now only "richtext::docbook" to convert text
+		  in <ulink url="http://txt2tags.org/";>rich text format</ulink> to
+		  the DocBook syntax)</para></listitem>
+	    </itemizedlist>
+	  </entry>
           <entry></entry>
         </row>
         <row>
diff --git a/libgda-report/Makefile.am b/libgda-report/Makefile.am
index 0df7a49..3ddd36a 100644
--- a/libgda-report/Makefile.am
+++ b/libgda-report/Makefile.am
@@ -24,7 +24,8 @@ libgda_report_4_0_la_LDFLAGS = -version-info $(GDA_CURRENT):$(GDA_REVISION):$(GD
 libgda_report_4_0_la_LIBADD = engine/libgda-report-engine-4.0.la \
 	DocBook/libgda-report-docbook-4.0.la \
 	RML/libgda-report-rml-4.0.la \
-	$(LIBGDA_LIBS)
+	$(LIBGDA_LIBS) \
+	$(GDKPIXBUF_LIBS)
 
 if PLATFORM_WIN32
 libgda_report_4_0_la_LDFLAGS += -export-symbols $(builddir)/libgda-report.def
diff --git a/libgda-report/engine/.gitignore b/libgda-report/engine/.gitignore
new file mode 100644
index 0000000..aaa2abd
--- /dev/null
+++ b/libgda-report/engine/.gitignore
@@ -0,0 +1 @@
+test-rt-parser
diff --git a/libgda-report/engine/Makefile.am b/libgda-report/engine/Makefile.am
index 1f6bcf2..27e69ca 100644
--- a/libgda-report/engine/Makefile.am
+++ b/libgda-report/engine/Makefile.am
@@ -1,4 +1,5 @@
 noinst_LTLIBRARIES = libgda-report-engine-4.0.la
+noinst_PROGRAMS = test-rt-parser
 
 AM_CPPFLAGS = \
 	-I$(top_builddir)/libgda-report \
@@ -8,17 +9,25 @@ AM_CPPFLAGS = \
 	-I$(top_srcdir)/libgda \
 	-I$(top_srcdir)/libgda/sqlite \
 	$(LIBGDA_CFLAGS) \
+	$(GDKPIXBUF_CFLAGS) \
 	$(LIBGDA_WFLAGS)
 
 gda_report_headers = \
 	gda-report-engine.h
 
 libgda_report_engine_4_0_la_SOURCES =	\
-	$(gda_report_headers)	\
+	$(gda_report_headers) \
+	rt-parser.h \
+	rt-parser.c \
 	gda-report-engine.c	
 
 libgda_report_engine_4_0_la_LIBADD = $(LIBGDA_LIBS) \
 	$(top_builddir)/libgda/libgda-4.0.la 
 
+test_rt_parser_SOURCES = \
+	test-rt-parser.c
+
+test_rt_parser_LDFLAGS = libgda-report-engine-4.0.la $(GDKPIXBUF_LIBS)
+
 gdareportincludedir=$(includedir)/libgda-$(GDA_ABI_MAJOR_VERSION).$(GDA_ABI_MINOR_VERSION)/libgda-report
 gdareportinclude_HEADERS=$(gda_report_headers)
diff --git a/libgda-report/engine/gda-report-engine.c b/libgda-report/engine/gda-report-engine.c
index 5b14d66..3bcbc7e 100644
--- a/libgda-report/engine/gda-report-engine.c
+++ b/libgda-report/engine/gda-report-engine.c
@@ -39,6 +39,8 @@
 #include <libgda/handlers/gda-handler-time.h>
 #include <libgda/handlers/gda-handler-type.h>
 
+#include "rt-parser.h"
+
 struct _GdaReportEnginePrivate {
 	xmlDocPtr     doc; /* may be %NULL */
 	xmlNodePtr    spec;
@@ -444,7 +446,7 @@ static gboolean command_gda_report_if (GdaReportEngine *engine, xmlNodePtr node,
 static GdaStatement *rewrite_statement (GdaReportEngine *engine, RunContext *context, GdaStatement *stmt, GError **error);
 static gboolean assign_parameters_values (GdaReportEngine *engine, RunContext *context, GdaSet *plist, GError **error);
 static GValue *evaluate_expression (GdaReportEngine *engine, RunContext *context, const gchar *expr, GError **error);
-static xmlNodePtr value_to_node (GdaReportEngine *engine, RunContext *context, const GValue *value);
+static xmlNodePtr value_to_node (GdaReportEngine *engine, RunContext *context, const GValue *value, GdaSet *options);
 
 /*
  * Function to be called when a "gda_report_..." node is found
@@ -804,6 +806,7 @@ run_context_pop (G_GNUC_UNUSED GdaReportEngine *engine, RunContext *context)
  *
  * uses node's contents: no
  * requested attributes: param_name
+ * optional attributes: converter => use "richtext::docbook"
  */
 static gboolean
 command_gda_report_param_value (GdaReportEngine *engine, xmlNodePtr node, GSList **created_nodes,
@@ -824,9 +827,18 @@ command_gda_report_param_value (GdaReportEngine *engine, xmlNodePtr node, GSList
 			/* Add a text node */
 			const GValue *value;
 			xmlNodePtr child;
+			GdaSet *options = NULL;
+			xmlChar *cprop;
 
+			cprop = xmlGetProp (node, BAD_CAST "converter");
+			if (cprop) {
+				options = gda_set_new_inline (1, "converter", G_TYPE_STRING, (gchar*) cprop);
+				xmlFree (cprop);
+			}
 			value = gda_holder_get_value (param);
-			child = value_to_node (engine, context, value);
+			child = value_to_node (engine, context, value, options);
+			if (options)
+				g_object_unref (options);
 			*created_nodes = g_slist_prepend (NULL, child);
 		}
 		xmlFree (prop);
@@ -858,7 +870,7 @@ command_gda_report_eval_expr (GdaReportEngine *engine, xmlNodePtr node, GSList *
 				     (const gchar *) xmlNodeGetContent (node), error);
 	if (!value)
 		return FALSE;
-	child = value_to_node (engine, context, value);
+	child = value_to_node (engine, context, value, NULL);
 	*created_nodes = g_slist_prepend (NULL, child);
 
 	return TRUE;
@@ -1166,9 +1178,12 @@ gtype_equal (gconstpointer a, gconstpointer b)
  * Converts @value to a string
  */
 static xmlNodePtr
-value_to_node (G_GNUC_UNUSED GdaReportEngine *engine, G_GNUC_UNUSED RunContext *context, const GValue *value)
+value_to_node (G_GNUC_UNUSED GdaReportEngine *engine, G_GNUC_UNUSED RunContext *context, const GValue *value, GdaSet *options)
 {
-	xmlNodePtr retnode;
+	xmlNodePtr retnode = NULL;
+	GdaHolder *converter = NULL;
+	if (options)
+		converter = gda_set_get_holder (options, "converter");
 
 	if (!value || gda_value_is_null (value))
 		retnode = xmlNewText (BAD_CAST "");
@@ -1207,13 +1222,31 @@ value_to_node (G_GNUC_UNUSED GdaReportEngine *engine, G_GNUC_UNUSED RunContext *
 			g_hash_table_insert (data_handlers, (gpointer) G_TYPE_UINT, gda_handler_numerical_new ());
 		}
 
+		gboolean converted = FALSE;
+
 		dh = g_hash_table_lookup (data_handlers, (gpointer) G_VALUE_TYPE (value));
 		if (dh) 
 			str = gda_data_handler_get_str_from_value (dh, value);
 		else
 			str = gda_value_stringify (value);
-			
-		retnode = xmlNewText (BAD_CAST (str ? str : ""));
+		if (converter) {
+			const GValue *cvalue;
+			cvalue = gda_holder_get_value (converter);
+			if ((G_VALUE_TYPE (cvalue) == G_TYPE_STRING) && g_value_get_string (cvalue)) {
+				gchar **array;
+				array = g_strsplit (g_value_get_string (cvalue), "::", 0);
+				if (array[0] && !strcmp (array[0], "richtext")) {
+					if (array[1] && !strcmp (array[1], "docbook")) {
+						retnode = xmlNewNode (NULL, BAD_CAST "para");
+						parse_rich_text_to_docbook (retnode, str);
+						converted = TRUE;
+					}
+				}
+			}
+		}
+		if (!converted)
+			retnode = xmlNewText (BAD_CAST (str ? str : ""));
+
 		g_free (str);
 	}
 
diff --git a/libgda-report/engine/rt-parser.c b/libgda-report/engine/rt-parser.c
new file mode 100644
index 0000000..317ccdf
--- /dev/null
+++ b/libgda-report/engine/rt-parser.c
@@ -0,0 +1,1283 @@
+/* rt-parser.c
+ *
+ * Copyright (C) 2010 Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "rt-parser.h"
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#ifdef HAVE_GDKPIXBUF
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+#endif
+
+/* RTE markup analysis */
+typedef enum {
+	MARKUP_NONE,      /* 0 */
+	MARKUP_BOLD,
+	MARKUP_TT,
+	MARKUP_VERBATIM,
+	MARKUP_ITALIC,
+	MARKUP_STRIKE,    /* 5 */
+	MARKUP_UNDERLINE,
+
+	MARKUP_TITLE1_S,
+	MARKUP_TITLE1_E,
+	MARKUP_TITLE2_S,
+	MARKUP_TITLE2_E,  /* 10 */
+
+	MARKUP_PICTURE_S,
+	MARKUP_PICTURE_E,
+
+	MARKUP_LIST_S,
+	MARKUP_LIST_E,
+
+	MARKUP_EOF
+} MarkupTag;
+
+/* for the RtMarkup enum */
+static gchar *markup_tag_text[] = {
+	"NONE", "PARA", "BOLD", "TT", "VERBATIM", "ITALIC", "STRIKE", "UNDERLINE",
+	"TITLE", "PICTURE", "LIST"
+};
+
+static
+RtMarkup
+internal_markup_to_external (MarkupTag markup, gint *out_offset)
+{
+	switch (markup) {
+	case MARKUP_NONE:
+		return RT_MARKUP_NONE;
+	case MARKUP_BOLD:
+		return RT_MARKUP_BOLD;
+	case MARKUP_TT:
+		return RT_MARKUP_TT;
+	case MARKUP_VERBATIM:
+		return RT_MARKUP_VERBATIM;
+	case MARKUP_ITALIC:
+		return RT_MARKUP_ITALIC;
+	case MARKUP_STRIKE:
+		return RT_MARKUP_STRIKE;
+	case MARKUP_UNDERLINE:
+		return RT_MARKUP_UNDERLINE;
+	case MARKUP_TITLE1_S:
+		*out_offset = 0;
+		return RT_MARKUP_TITLE;
+	case MARKUP_TITLE2_S:
+		*out_offset = 1;
+		return RT_MARKUP_TITLE;
+	case MARKUP_PICTURE_S:
+		return RT_MARKUP_PICTURE;
+	case MARKUP_LIST_S:
+		return RT_MARKUP_LIST;
+	default:
+		g_assert_not_reached ();
+	}
+	return MARKUP_NONE;
+}
+
+static MarkupTag get_markup_token (const gchar *alltext, const gchar *start, gint *out_nb_spaces_before,
+				   const gchar **out_end, MarkupTag start_tag);
+static MarkupTag get_token (const gchar *alltext, const gchar *start, gint *out_nb_spaces_before, const gchar **out_end,
+			    MarkupTag start_tag);
+
+/**
+ * get_token
+ *
+ * returns the token type starting from @iter, and positions @out_end to the last used position
+ * position.
+ */
+static MarkupTag
+get_token (const gchar *alltext, const gchar *start, gint *out_nb_spaces_before, const gchar **out_end,
+	   MarkupTag start_tag)
+{
+	MarkupTag retval;
+	const gchar *ptr;
+
+	retval = get_markup_token (alltext, start, out_nb_spaces_before, &ptr, start_tag);
+	if ((retval != MARKUP_NONE) || (retval == MARKUP_EOF)) {
+		*out_end = ptr;
+		return retval;
+	}
+
+	for (; *ptr ; ptr = g_utf8_next_char (ptr)) {
+		retval = get_markup_token (alltext, ptr, NULL, NULL, start_tag);
+		if ((retval != MARKUP_NONE) || (retval == MARKUP_EOF))
+			break;
+	}
+	*out_end = ptr;
+	return MARKUP_NONE;
+}
+
+/**
+ * get_markup_token
+ * @alltext: the complete text
+ * @start: starting point
+ * @out_nb_spaces_before: a place to set the number of spaces since the start of line
+ * @out_end: place to put the last used position, or %NULL
+ * @start_tag: the starting tag, if any (to detect the closing tag)
+ *
+ * Parses marking tokens, nothing else
+ *
+ * Returns: a markup token, or MARKUP_NONE or MARKUP_EOF otherwise
+ */
+static MarkupTag
+get_markup_token (const gchar *alltext, const gchar *start, gint *out_nb_spaces_before, const gchar **out_end,
+		  MarkupTag start_tag)
+{
+	gchar c;
+	gint ssol = -1; /* spaces since start of line */
+	MarkupTag start_markup = start_tag;
+	const gchar *ptr;
+
+#define SET_OUT \
+	if (out_end) {							\
+		ptr++;							\
+		*out_end = ptr;						\
+	}								\
+	if (out_nb_spaces_before)					\
+		*out_nb_spaces_before = ssol
+
+	if (start_tag)
+		start_markup = start_markup;
+
+	ptr = start;
+	if (out_end)
+		*out_end = ptr;
+	if (out_nb_spaces_before)
+		*out_nb_spaces_before = -1;
+	c = *ptr;
+
+	/* tests involving starting markup before anything else */
+	if (start_markup == MARKUP_PICTURE_S) {
+		if (c == ']') {
+			ptr++;
+			c = *ptr;
+			if (c == ']') {
+				ptr++;
+				c = *ptr;
+				if (c == ']') {
+					SET_OUT;
+					return MARKUP_PICTURE_E;
+				}
+			}
+		}
+		if (!c)
+			return MARKUP_EOF;
+		else
+			return MARKUP_NONE;
+	}
+	else if (start_markup == MARKUP_VERBATIM) {
+		if (c == '"') {
+			ptr++;
+			c = *ptr;
+			if (c == '"') {
+				ptr++;
+				c = *ptr;
+				if (c == '"') {
+					SET_OUT;
+					return MARKUP_VERBATIM;
+				}
+			}
+		}
+		if (!c)
+			return MARKUP_EOF;
+		else
+			return MARKUP_NONE;
+	}
+
+	if ((*ptr == '\n') && (start_markup == MARKUP_LIST_S)) {
+		SET_OUT;
+		return MARKUP_LIST_E;
+	}
+
+	if (!c)
+		return MARKUP_EOF;
+
+	/* other tests */
+	const gchar *ptr1 = ptr;
+	if (ptr == alltext) {
+		for (; *ptr1 == ' '; ptr1++);
+		ssol = ptr1 - ptr;
+	}
+	else if (ptr[-1] == '\n') {
+		for (; *ptr1 == ' '; ptr1++);
+		ssol = ptr1 - ptr;
+	}
+	if (ssol >= 0) {
+		/* we are on a line with only spaces since its start */
+		if (ssol == 0) {
+			if (c == '=') {
+				ptr++;
+				c = *ptr;
+				if (c == ' ') {
+					SET_OUT;
+					return MARKUP_TITLE1_S;
+				}
+				else if (c == '=') {
+					ptr++;
+					c = *ptr;
+					if (c == ' ') {
+						SET_OUT;
+						return MARKUP_TITLE2_S;
+					}
+				}
+			}
+		}
+		
+		c = *ptr1;
+		if (c == '-') {
+			ptr1++;
+			c = *ptr1;
+			if (c == ' ') {
+				ptr = ptr1;
+				SET_OUT;
+				return MARKUP_LIST_S;
+			}
+		}
+	}
+
+	if (c == '*') {
+		ptr++;
+		c = *ptr;
+		if (c == '*') {
+			SET_OUT;
+			return MARKUP_BOLD;
+		}
+	}
+	else if (c == '/') {
+		ptr++;
+		c = *ptr;
+		if (c == '/') {
+			SET_OUT;
+			return MARKUP_ITALIC;
+		}
+	}
+	else if (c == '_') {
+		ptr++;
+		c = *ptr;
+		if (c == '_') {
+			SET_OUT;
+			return MARKUP_UNDERLINE;
+		}
+	}
+	else if (c == '-') {
+		ptr++;
+		c = *ptr;
+		if (c == '-') {
+			SET_OUT;
+			return MARKUP_STRIKE;
+		}
+	}
+	else if (c == '`') {
+		ptr++;
+		c = *ptr;
+		if (c == '`') {
+			SET_OUT;
+			return MARKUP_TT;
+		}
+	}
+	else if (c == '"') {
+		ptr++;
+		c = *ptr;
+		if (c == '"') {
+			ptr++;
+			c = *ptr;
+			if (c == '"') {
+				SET_OUT;
+				return MARKUP_VERBATIM;
+			}
+		}
+	}
+	else if (c == ' ') {
+		ptr++;
+		c = *ptr;
+		if (c == '=') {
+			if (start_markup == MARKUP_TITLE1_S) {
+				/* ignore anything up to the EOL */
+				for (; *ptr && (*ptr != '\n'); ptr++);
+
+				SET_OUT;
+				return MARKUP_TITLE1_E;
+			}
+			else {
+				ptr++;
+				c = *ptr;
+				if (c == '=') {
+					/* ignore anything up to the EOL */
+					for (; *ptr && (*ptr != '\n'); ptr++);
+
+					SET_OUT;
+					return MARKUP_TITLE2_E;
+				}
+			}
+		}
+	}
+	else if (c == '[') {
+		ptr++;
+		c = *ptr;
+		if (c == '[') {
+			ptr++;
+			c = *ptr;
+			if (c == '[') {
+				SET_OUT;
+				return MARKUP_PICTURE_S;
+			}
+		}
+	}
+	return MARKUP_NONE;
+}
+
+/**
+ * steals @base64
+ */
+static gchar *
+remove_newlines_from_base64 (gchar *base64)
+{
+	GString *string;
+	gchar *ptr;
+	string = g_string_new ("");
+	for (ptr = base64; *ptr; ptr++) {
+		if (*ptr != '\n')
+			g_string_append_c (string, *ptr);
+	}
+	g_free (base64);
+	return g_string_free (string, FALSE);
+}
+
+static gchar *
+get_node_path (RtNode *node)
+{
+	gint i;
+	RtNode *tmp;
+	for (i = 0, tmp = node->prev; tmp; tmp = tmp->prev)
+		i++;
+	if (node->parent) {
+		gchar *str, *ret;
+		str = get_node_path (node->parent);
+		ret = g_strdup_printf ("%s:%d", str, i);
+		g_free (str);
+		return ret;
+	}
+	else
+		return g_strdup_printf ("%d", i);
+}
+
+static void
+rt_dump_tree_offset (RtNode *tree, gint offset)
+{
+	gchar *str = "";
+
+	if (offset) {
+		str = g_new (gchar, offset + 1);
+		memset (str, ' ', offset);
+		str [offset] = 0;
+	}
+
+	g_print ("%p-%s%s ", tree, str, markup_tag_text[tree->markup]);
+
+	gchar *path;
+	path = get_node_path (tree);
+	g_print ("[path=%s] ", path);
+	g_free (path);
+
+	if (tree->offset >= 0)
+		g_print ("[offset=%d] ", tree->offset);
+
+	if (tree->text) {
+#define MAXSIZE 100
+		g_print ("TEXT [");
+		gchar *copy, *ptr;
+		copy = g_strdup (tree->text);
+		if (strlen (copy) > MAXSIZE) {
+			copy [MAXSIZE] = 0;
+			copy [MAXSIZE - 1] = '.';
+			copy [MAXSIZE - 2] = '.';
+			copy [MAXSIZE - 3] = '.';
+		}
+		for (ptr = copy; *ptr; ptr++) {
+			if (*ptr == '\n') {
+				g_print ("\n      %s", str);
+			}
+			else
+				g_print ("%c", *ptr);
+		}
+		g_free (copy);
+		g_print ("]\n");
+	}
+	else if (tree->binary.data) {
+		g_print ("BINARY\n");
+	}
+	else
+		g_print ("\n");
+	
+	if (tree->child)
+		rt_dump_tree_offset (tree->child, offset + 8);
+	if (tree->next)
+		rt_dump_tree_offset (tree->next, offset);
+	if (offset)
+		g_free (str);
+}
+
+void
+rt_dump_tree (RtNode *tree)
+{
+	rt_dump_tree_offset (tree, 0);
+}
+
+static void
+rt_dump_tree_to_string (RtNode *tree, GString *string)
+{
+	gchar *path;
+
+	path = get_node_path (tree);
+
+	g_string_append_printf (string, "%s-%s ", path, markup_tag_text[tree->markup]);
+	g_free (path);
+
+	if (tree->offset >= 0)
+		g_string_append_printf (string, "[offset=%d] ", tree->offset);
+
+	if (tree->text) {
+		g_string_append (string, "TEXT [");
+		gchar *ptr;
+		for (ptr = tree->text; *ptr; ptr++) {
+			if (*ptr == '\n') {
+				g_string_append_c (string, '$');
+			}
+			else
+				g_string_append_printf (string, "%c", *ptr);
+		}
+		g_string_append (string, "]|");
+	}
+	else if (tree->binary.data)
+		g_string_append (string, "BINARY|");
+	else
+		g_string_append_c (string, '|');
+	
+	if (tree->child)
+		rt_dump_tree_to_string (tree->child, string);
+	if (tree->next)
+		rt_dump_tree_to_string (tree->next, string);
+}
+
+gchar *
+rt_dump_to_string (RtNode *tree)
+{
+	GString *string;
+	string = g_string_new ("");
+	rt_dump_tree_to_string (tree, string);
+	return g_string_free (string, FALSE);
+}
+
+void
+rt_free_node (RtNode *node)
+{
+	if (node->child)
+		rt_free_node (node->child);
+	if (node->next)
+		rt_free_node (node->next);
+	g_free (node->text);
+	if (node->binary.data)
+		g_free (node->binary.data);
+	g_free (node);
+}
+
+static gboolean merge_single_child_text (RtNode *tree);
+static gboolean merge_text_node_child (RtNode *tree);
+static gboolean merge_text_node_siblings (RtNode *tree);
+static gboolean reorganize_children (RtNode *tree, gboolean *out_tree_destroyed);
+
+/* if @tree is a RT_MARKUP_NONE with no binary data, and has a unique RT_MARKUP_NONE with no binary data child 
+ * then merge the child into it */
+static gboolean
+merge_text_node_child (RtNode *tree)
+{
+	RtNode *child;
+	child = tree->child;
+	if (! child)
+		return FALSE;
+
+	if ((tree->markup == RT_MARKUP_NONE) && !tree->binary.data &&
+	    (child->markup == RT_MARKUP_NONE) &&  ! child->child && child->text && !child->binary.data) {
+		if (tree->text) {
+			gchar *tmp;
+			tmp = tree->text;
+			tree->text = g_strconcat (tmp, child->text, NULL);
+			g_free (tmp);
+			g_free (child->text);
+		}
+		else
+			tree->text = child->text;
+		child->text = NULL;
+		RtNode *tnode = child->next;
+		child->next = NULL;
+		rt_free_node (child);
+		tree->child = tnode;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/* if @tree is a RT_MARKUP_NONE with no binary data, then merge all the siblings which are also
+ * RT_MARKUP_NONE into it */
+static gboolean
+merge_text_node_siblings (RtNode *tree)
+{
+	gboolean retval = FALSE;
+	while (1) {
+		if (! tree->next)
+			break;
+		RtNode *next = tree->next;
+		if ((tree->markup == RT_MARKUP_NONE) && !tree->binary.data &&
+		    (next->markup == RT_MARKUP_NONE) && !next->binary.data &&
+		    ! next->child && next->text) {
+			if (tree->text) {
+				gchar *tmp;
+				tmp = tree->text;
+				tree->text = g_strconcat (tmp, next->text, NULL);
+				g_free (tmp);
+				g_free (next->text);
+			}
+			else
+				tree->text = next->text;
+			next->text = NULL;
+			RtNode *tnode = next->next;
+			next->next = NULL;
+			rt_free_node (next);
+			tree->next = tnode;
+
+			retval = TRUE;
+		}
+		else
+			break;
+	}
+
+	return retval;
+}
+
+static gboolean
+merge_single_child_text (RtNode *tree)
+{
+	if (! (tree->text || tree->binary.data) &&
+	    tree->child && !tree->child->next &&
+	    ! tree->child->child &&
+	    (tree->child->text || tree->child->binary.data) &&
+	    (tree->child->markup == RT_MARKUP_NONE)) {
+		tree->text = tree->child->text;
+		tree->child->text = NULL;
+		tree->binary.data = tree->child->binary.data;
+		tree->child->binary.data = NULL;
+		tree->binary.binary_length = tree->child->binary.binary_length;
+		tree->child->binary.binary_length = 0;
+		rt_free_node (tree->child);
+		tree->child = NULL;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+reorganize_children (RtNode *tree, gboolean *out_tree_destroyed)
+{
+	gboolean retval = FALSE;
+	*out_tree_destroyed = FALSE;
+	if ((tree->markup == RT_MARKUP_PARA) && (tree->text && !*(tree->text)) &&
+	    !tree->child &&
+	    tree->next && 
+	    ((tree->next->markup == RT_MARKUP_LIST) || tree->next->markup == RT_MARKUP_TITLE)) {
+		/* simply get rid of useless node */
+		RtNode *n;
+		n = tree->next;
+		n->prev = tree->prev;
+		if (tree->prev)
+			tree->prev->next = n;
+		if (tree->parent && (tree->parent->child == tree))
+			tree->parent->child = n;
+		tree->prev = NULL;
+		tree->next = NULL;
+		rt_free_node (tree);
+		*out_tree_destroyed = TRUE;
+		return TRUE;
+	}
+	else if ((tree->markup == RT_MARKUP_PARA) && (tree->text && !*(tree->text)) &&
+		 !tree->child && !tree->next) {
+		/* simply get rid of useless node */
+		if (tree->prev)
+			tree->prev->next = NULL;
+		if (tree->parent && (tree->parent->child == tree))
+			tree->parent->child = NULL;
+		tree->prev = NULL;
+		tree->next = NULL;
+		rt_free_node (tree);
+		*out_tree_destroyed = TRUE;
+		return TRUE;
+	}
+	if (tree->markup == RT_MARKUP_LIST) {
+		RtNode *node;
+		for (node = tree->next; node;) {
+			if ((node->markup != RT_MARKUP_LIST) ||
+			    (node->offset <= tree->offset))
+				break;
+			RtNode *prev, *next;
+			prev = node->prev;
+			next = node->next;
+			if (tree->child) {
+				RtNode *n;
+				for (n = tree->child; n->next; n = n->next);
+				n->next = node;
+				node->prev = n;
+				node->next = NULL;
+			}
+			else {
+				tree->child = node;
+				node->prev = NULL;
+				node->next = NULL;
+			}
+			if (prev)
+				prev->next = next;
+			if (next)
+				next->prev = prev;
+			node->parent = tree;
+			node = next;
+			retval = TRUE;
+		}
+	}
+	else if (tree->markup == RT_MARKUP_TITLE) {
+		RtNode *node;
+		for (node = tree->next; node;) {
+			if ((node->markup == RT_MARKUP_TITLE) &&
+			    (node->offset <= tree->offset))
+				break;
+
+			RtNode *prev, *next;
+			prev = node->prev;
+			next = node->next;
+			if (tree->child) {
+				RtNode *n;
+				for (n = tree->child; n->next; n = n->next);
+				n->next = node;
+				node->prev = n;
+				node->next = NULL;
+			}
+			else {
+				tree->child = node;
+				node->prev = NULL;
+				node->next = NULL;
+			}
+			if (prev)
+				prev->next = next;
+			if (next)
+				next->prev = prev;
+			node->parent = tree;
+			node = next;
+			retval = TRUE;
+		}
+	}
+	else if (tree->markup == RT_MARKUP_PARA) {
+		RtNode *node;
+		for (node = tree->next; node;) {
+			if ((node->markup == RT_MARKUP_TITLE) ||
+			    (node->markup == RT_MARKUP_PARA))
+				break;
+
+			RtNode *prev, *next;
+			prev = node->prev;
+			next = node->next;
+			if (tree->child) {
+				RtNode *n;
+				for (n = tree->child; n->next; n = n->next);
+				n->next = node;
+				node->prev = n;
+				node->next = NULL;
+			}
+			else {
+				tree->child = node;
+				node->prev = NULL;
+				node->next = NULL;
+			}
+			if (prev)
+				prev->next = next;
+			if (next)
+				next->prev = prev;
+			node->parent = tree;
+			node = next;
+			retval = TRUE;
+		}
+	}
+	return retval;
+}
+
+/*
+ * Simplifies and reorganizes the tree
+ */
+static gboolean
+simplify_tree (RtNode *tree)
+{
+	gboolean mod, tree_del, retval = FALSE;
+
+	for (mod = TRUE, tree_del = FALSE; mod && !tree_del;) {
+		mod = FALSE;
+		if (tree->child)
+			mod = mod || simplify_tree (tree->child);
+		if (tree->next)
+			mod = mod || simplify_tree (tree->next);
+		mod = mod || merge_single_child_text (tree);
+		mod = mod || merge_text_node_child (tree);
+		mod = mod || merge_text_node_siblings (tree);
+		mod = mod || reorganize_children (tree, &tree_del);
+		if (mod)
+			retval = TRUE;
+	}
+	return retval;
+}
+
+static const gchar *
+serialize_tag (MarkupTag tag)
+{
+	switch (tag) {
+	case MARKUP_BOLD:
+		return "**";
+	case MARKUP_TT:
+		return "``";
+	case MARKUP_VERBATIM:
+		return "\"\"";
+	case MARKUP_ITALIC:
+		return "//";
+	case MARKUP_STRIKE:
+		return "--";
+	case MARKUP_UNDERLINE:
+		return "__";
+	case MARKUP_TITLE1_S:
+		return "= ";
+	case MARKUP_TITLE1_E:
+		return " =";
+	case MARKUP_TITLE2_S:
+		return "== ";
+	case MARKUP_TITLE2_E:
+		return "=  ";
+	case MARKUP_PICTURE_S:
+		return "[[[";
+	case MARKUP_PICTURE_E:
+		return "]]]";
+	case MARKUP_LIST_S:
+		return "- ";
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+typedef struct {
+	const gchar     *m_start;
+	const gchar     *m_end;
+	MarkupTag        markup;
+	RtNode          *rtnode;
+} TextTag;
+
+static gboolean
+markup_tag_match (TextTag *current, MarkupTag tag2, const gchar *last_position)
+{
+	const gchar *tmp;
+	gboolean sameline = TRUE;
+	for (tmp = current->m_start; *tmp && (tmp < last_position); tmp++) {
+		if (*tmp == '\n') {
+			sameline = FALSE;
+			break;
+		}
+	}
+
+	gboolean retval;
+	switch (current->markup) {
+	case MARKUP_BOLD:
+	case MARKUP_TT:
+	case MARKUP_VERBATIM:
+	case MARKUP_ITALIC:
+	case MARKUP_STRIKE:
+	case MARKUP_UNDERLINE:
+		retval = (current->markup == tag2) ? TRUE : FALSE;
+		break;
+	case MARKUP_TITLE1_S:
+		retval = (tag2 == MARKUP_TITLE1_E) ? TRUE : FALSE;
+		break;
+	case MARKUP_TITLE2_S:
+		retval = (tag2 == MARKUP_TITLE2_E) ? TRUE : FALSE;
+		break;
+	case MARKUP_PICTURE_S:
+		retval = (tag2 == MARKUP_PICTURE_E) ? TRUE : FALSE;
+		break;
+	case MARKUP_LIST_S:
+		retval = (tag2 == MARKUP_LIST_E) ? TRUE : FALSE;
+		break;
+	default:
+		retval = FALSE;
+		break;
+	}
+
+	if (retval) {
+		if ((current->markup != MARKUP_PICTURE_S) && (current->markup != MARKUP_LIST_S) &&
+		    (current->markup != MARKUP_VERBATIM))
+			retval = sameline ? TRUE : FALSE;
+	}
+	return retval;
+}
+
+RtNode *
+rt_parse_text (const gchar *text)
+{
+	RtNode *retnode, *contextnode;
+	GList *queue = NULL; /* list of TextTag pointers */
+	MarkupTag mt;
+	const gchar *ptr, *prev;
+	gint ssol;
+	TextTag *current = NULL;
+
+	retnode = g_new0 (RtNode, 1);
+	contextnode = retnode;
+
+	ptr = text;
+	prev = text;
+
+	for (mt = get_token (text, ptr, &ssol, &ptr, current ? current->markup : MARKUP_NONE);
+	     mt != MARKUP_EOF;
+	     mt = get_token (text, ptr, &ssol, &ptr, current ? current->markup : MARKUP_NONE)) {
+
+
+#ifdef GDA_DEBUG_NO
+		gchar *debug;
+		debug = g_strndup (prev, ptr - prev);
+		if (strlen (debug) > 10)
+		       debug [10] = 0;
+		g_print ("Token %d [%s] with SSOL %d\n", mt, debug, ssol);
+		g_free (debug);
+#endif
+
+		if (mt == MARKUP_NONE) {
+			gchar *part;
+			RtNode *node;
+			node = g_new0 (RtNode, 1);
+			node->parent = contextnode;
+			node->markup = RT_MARKUP_NONE;
+			if (prev == text)
+				node->markup = RT_MARKUP_PARA;
+			else if (prev[-1] == '\n')
+				node->markup = RT_MARKUP_PARA;
+			part = g_strndup (prev, ptr - prev);
+
+			if (contextnode->child) {
+				RtNode *n;
+				for (n = contextnode->child; n->next; n = n->next);
+				n->next = node;
+				node->prev = n;
+			}
+			else
+				contextnode->child = node;
+
+			if (contextnode->markup != RT_MARKUP_PICTURE) {
+				/* split the node in multiple parts, one for each paragraph */
+				gchar **array;
+				gint i;
+				array = g_strsplit (part, "\n", -1);
+				for (i = 0; array [i]; i++) {
+					if (! node->text)
+						node->text = array [i];
+					else {
+						RtNode *n;
+						n = g_new0 (RtNode, 1);
+						n->parent = contextnode;
+						n->markup = RT_MARKUP_PARA;
+						n->text = array [i];
+						node->next = n;
+						n->prev = node;
+						node = n;
+					}
+				}
+				g_free (part);
+			}
+			else {
+				gchar *tmp;
+				tmp = remove_newlines_from_base64 (part);
+				node->binary.data = g_base64_decode_inplace (tmp, (gsize*) & node->binary.binary_length);
+			}
+		}
+		else {
+			gboolean tag_matched = FALSE;
+			if (current) {
+			retry:
+				if (markup_tag_match (current, mt, ptr-1)) {
+					/*g_print ("Tags matched for %d,%d\n",
+					  current->markup, mt);*/
+					g_free (current);
+					queue = g_list_remove (queue, current);
+					current = NULL;
+					
+					if (queue)
+						current = (TextTag*) queue->data;
+					tag_matched = TRUE;
+
+					if (contextnode->parent)
+						contextnode = contextnode->parent;
+				}
+				else {
+					/* detect misplaced tags */
+					GList *list;
+					for (list = queue; list; list = list->next) {
+						TextTag *tt = (TextTag*) list->data;
+						
+						if (markup_tag_match (tt, mt, ptr-1)) {
+							/* remove all TextTag before @list */
+							while (queue != list) {
+								RtNode *lnode;
+								current = (TextTag*) queue->data;
+								lnode = current->rtnode;
+
+								lnode->markup = RT_MARKUP_NONE;
+								if (lnode->text) {
+									gchar *tmp;
+									tmp = lnode->text;
+									lnode->text = g_strconcat (serialize_tag (current->markup),
+												   tmp, NULL);
+									g_free (tmp);
+								}
+								else if (lnode->binary.data) {
+									TO_IMPLEMENT;
+								}
+								else
+								lnode->text = g_strdup (serialize_tag (current->markup));
+
+								g_free (current);
+								queue = g_list_delete_link (queue, queue);
+
+							}
+							g_assert (queue);
+							current = (TextTag*) queue->data;
+							contextnode = current->rtnode;
+
+							goto retry;
+						}
+					}
+				}
+			}
+
+			/*g_print ("Token %d with SSOL %d\n", mt, ssol);*/
+			if (! tag_matched) {
+				RtNode *node;
+				node = g_new0 (RtNode, 1);
+				node->parent = contextnode;
+				node->offset = ssol;
+				node->markup = internal_markup_to_external (mt, &(node->offset));
+				
+				if ((node->offset > 0) && (node->markup == RT_MARKUP_LIST)) {
+					/* add missing list nodes if offset > 0 */
+					if (contextnode->child) {
+						RtNode *n;
+						for (n = contextnode->child; n->next; n = n->next);
+
+						gint i = 0;
+                                                if (n->markup == RT_MARKUP_LIST)
+                                                        i = n->offset + 1;
+                                                for (; i < node->offset; i++) {
+                                                        RtNode *tmpn;
+                                                        tmpn = g_new0 (RtNode, 1);
+                                                        tmpn->parent = contextnode;
+                                                        tmpn->markup = RT_MARKUP_LIST;
+                                                        tmpn->offset = i;
+							tmpn->prev = n;
+							n->next = tmpn;
+							n = tmpn;
+                                                }
+
+						n->next = node;
+						node->prev = n;
+					}
+					else {
+						gint i;
+						RtNode *n = NULL;
+						for (i = 0; i < node->offset; i++) {
+                                                        RtNode *tmpn;
+                                                        tmpn = g_new0 (RtNode, 1);
+                                                        tmpn->parent = contextnode;
+                                                        tmpn->markup = RT_MARKUP_LIST;
+                                                        tmpn->offset = i;
+							if (n) {
+								tmpn->prev = n;
+								n->next = tmpn;
+							}
+							else
+								contextnode->child = tmpn;
+							n = tmpn;
+                                                }
+						g_assert (n);
+						n->next = node;
+						node->prev = n;
+					}
+				}
+				else {
+					if (contextnode->child) {
+						RtNode *n;
+						for (n = contextnode->child; n->next; n = n->next);
+						n->next = node;
+						node->prev = n;
+					}
+					else
+						contextnode->child = node;
+				}
+				contextnode = node;				
+
+				/* update @current */
+				current = g_new0 (TextTag, 1);
+				current->markup = mt;
+				current->m_start = prev;
+				current->m_end = ptr;
+				current->rtnode = node;
+				
+				queue = g_list_prepend (queue, current);
+			}
+		}
+		prev = ptr;
+	}
+
+	while (queue) {
+		current = (TextTag*) queue->data;
+		g_free (current);
+		queue = g_list_delete_link (queue, queue);
+	}
+
+#ifdef GDA_DEBUG_NO
+	g_print ("============= Before simplify_tree()\n");
+	rt_dump_tree (retnode);
+	simplify_tree (retnode);
+	g_print ("============= After simplify_tree()\n");
+	rt_dump_tree (retnode);
+#else
+	simplify_tree (retnode);
+#endif
+	return retnode;
+}
+
+/*
+ * DocBook rendering
+ */
+static gint file_nb = 0;
+typedef struct {
+	GHashTable *hash;
+	gchar      *file_path;
+	gchar      *file_prefix;
+} ContextDocbook;
+
+/*
+ * @hash: key = rtnode, value = corresponding xmlNodePtr
+ */
+static void
+rich_text_node_to_docbook (ContextDocbook *context, xmlNodePtr top_parent, RtNode *rtnode, xmlNodePtr parent)
+{
+	xmlNodePtr pattach = NULL, cattach = NULL;
+	gchar *realtext;
+	g_assert (parent);
+	g_assert (context);
+
+	if (rtnode->text) {
+		gchar *optr, *nptr;
+		gint len;
+		len = strlen ((gchar*) rtnode->text);
+		realtext = g_new (gchar, len + 1);
+		for (optr = (gchar*) rtnode->text, nptr = realtext; *optr; optr++) {
+			if (*optr != '\n') {
+				*nptr = *optr;
+				nptr++;
+			}
+		}
+		*nptr = 0;
+	}
+	else
+		realtext = (gchar *) rtnode->text;
+
+	switch (rtnode->markup) {
+	case RT_MARKUP_NONE:
+		if (parent) {
+			xmlNodeAddContent (parent, BAD_CAST realtext);
+			cattach = parent;
+		}
+		else {
+			cattach = xmlNewNode (NULL, BAD_CAST "para");
+			xmlNodeAddContent (cattach, BAD_CAST realtext);
+		}
+		break;
+	case RT_MARKUP_BOLD:
+		cattach = xmlNewChild (parent, NULL, BAD_CAST "emphasis", BAD_CAST realtext);
+		xmlSetProp (cattach, BAD_CAST "role", BAD_CAST "bold");
+		break;
+	case RT_MARKUP_PARA:
+		pattach = parent;
+		if ((parent != top_parent) &&
+		     ! strcmp ((gchar*) parent->name, "para"))
+			pattach = parent->parent;
+		cattach = xmlNewChild (pattach, NULL, BAD_CAST "para", BAD_CAST realtext);
+		parent = cattach;
+		break;
+	case RT_MARKUP_TT:
+	case RT_MARKUP_VERBATIM:
+	case RT_MARKUP_ITALIC:
+		cattach = xmlNewChild (parent, NULL, BAD_CAST "emphasis", BAD_CAST realtext);
+		break;
+	case RT_MARKUP_STRIKE:
+		cattach = xmlNewChild (parent, NULL, BAD_CAST "emphasis", BAD_CAST realtext);
+		xmlSetProp (cattach, BAD_CAST "role", BAD_CAST "strikethrough");
+		break;
+	case RT_MARKUP_UNDERLINE:
+		cattach = xmlNewChild (parent, NULL, BAD_CAST "emphasis", BAD_CAST realtext);
+		xmlSetProp (cattach, BAD_CAST "role", BAD_CAST "underline");
+		break;
+	case RT_MARKUP_PICTURE: {
+		gboolean saved = FALSE;
+		gint type = 2; /* 0 for image, 1 for TXT and 2 for general binary */
+		gchar *file, *tmp;
+		tmp = g_strdup_printf ("%s_%04d.jpg", context->file_prefix,
+				       file_nb ++);
+		file = g_build_filename (context->file_path, tmp, NULL);
+		g_free (tmp);
+
+#ifdef HAVE_GDKPIXBUF
+		GdkPixdata pixdata;
+		if (rtnode->binary.data &&
+		    gdk_pixdata_deserialize (&pixdata, rtnode->binary.binary_length,
+					     (guint8*) rtnode->binary.data, NULL)) {
+                        GdkPixbuf *pixbuf;
+                        pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, NULL);
+                        if (pixbuf) {
+				/* write to file */
+				if (gdk_pixbuf_save (pixbuf, file, "jpeg", NULL,
+						     "quality", "100", NULL)) {
+					g_print ("Writen JPG file '%s'\n", file);
+					saved = TRUE;
+					type = 0;
+				}
+				
+				g_object_unref (pixbuf);
+                        }
+                }
+#endif
+
+		if (!saved) {
+			if (rtnode->binary.data &&
+			    g_file_set_contents (file, (gchar*) rtnode->binary.data,
+						 rtnode->binary.binary_length, NULL)) {
+				g_print ("Writen BIN file '%s'\n", file);
+				saved = TRUE;
+				type = 2;
+			}
+			else if (rtnode->text)
+				type = 1;
+		}
+		if (! saved && (type != 1))
+			TO_IMPLEMENT;
+		else {
+			switch (type) {
+ 			case 0:
+				pattach =  xmlNewChild (parent, NULL, BAD_CAST "informalfigure",
+							NULL);
+				pattach =  xmlNewChild (pattach, NULL, BAD_CAST "mediaobject",
+							NULL);
+				pattach =  xmlNewChild (pattach, NULL, BAD_CAST "imageobject",
+							NULL);
+				cattach =  xmlNewChild (pattach, NULL, BAD_CAST "imagedata",
+							NULL);
+				xmlSetProp (cattach, BAD_CAST "fileref", BAD_CAST file);
+				break;
+ 			case 1:
+				xmlNodeAddContent (parent, BAD_CAST (rtnode->text));
+				break;
+ 			case 2:
+				cattach = xmlNewChild (parent, NULL, BAD_CAST "ulink",
+						       BAD_CAST _("link"));
+				xmlSetProp (cattach, BAD_CAST "url", BAD_CAST file);
+				break;
+			default:
+				g_assert_not_reached ();
+			}
+		}
+		g_free (file);
+		break;
+	}
+	case RT_MARKUP_TITLE: {
+		gchar *sect;
+		pattach = parent;
+		if (!strcmp ((gchar*) parent->name, "para"))
+			pattach = parent->parent;
+		sect = g_strdup_printf ("sect%d", rtnode->offset + 1);
+		cattach = xmlNewChild (pattach, NULL, BAD_CAST sect, NULL);
+		g_free (sect);
+		pattach = xmlNewChild (cattach, NULL, BAD_CAST "title", BAD_CAST realtext);
+		break;
+	}
+	case RT_MARKUP_LIST: {
+		xmlNodePtr tmp = NULL;
+
+		if (rtnode->prev &&
+		    (rtnode->prev->markup == RT_MARKUP_LIST)) {
+			tmp = g_hash_table_lookup (context->hash, rtnode->prev);
+			g_assert (tmp);
+			/* use the same <itemizedlist> */
+			g_assert (!strcmp ((gchar*) tmp->name, "itemizedlist"));
+			g_assert (rtnode->prev->offset == rtnode->offset);
+			g_hash_table_insert (context->hash, rtnode, tmp);
+			tmp = xmlNewChild (tmp, NULL, BAD_CAST "listitem", NULL);
+			cattach = xmlNewChild (tmp, NULL, BAD_CAST "para", BAD_CAST realtext);
+		}
+		else {
+			pattach = xmlNewChild (parent, NULL, BAD_CAST "itemizedlist", NULL);
+			g_hash_table_insert (context->hash, rtnode, pattach);
+			pattach = xmlNewChild (pattach, NULL, BAD_CAST "listitem", NULL);
+			cattach = xmlNewChild (pattach, NULL, BAD_CAST "para", BAD_CAST realtext);
+		}
+		break;
+	}
+	default:
+		if (rtnode->parent)
+			g_assert_not_reached ();
+		else
+			cattach = parent;
+		break;
+	}
+
+	if (rtnode->text)
+		g_free (realtext);
+
+	if (rtnode->child)
+		rich_text_node_to_docbook (context, top_parent, rtnode->child, cattach);
+	if (rtnode->next)
+		rich_text_node_to_docbook (context, top_parent, rtnode->next, parent);
+}
+
+void
+parse_rich_text_to_docbook (xmlNodePtr top, const gchar *text)
+{
+	RtNode *rtnode;
+	ContextDocbook context;
+
+	context.hash = g_hash_table_new (NULL, NULL);
+	context.file_path = ".";
+	context.file_prefix = "IMG";
+
+	rtnode = rt_parse_text (text);
+	/*rt_dump_tree (rtnode);*/
+	rich_text_node_to_docbook (&context, top, rtnode, top);
+	g_hash_table_destroy (context.hash);
+	rt_free_node (rtnode);
+}
diff --git a/libgda-report/engine/rt-parser.h b/libgda-report/engine/rt-parser.h
new file mode 100644
index 0000000..e5f014b
--- /dev/null
+++ b/libgda-report/engine/rt-parser.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2010 The GNOME Foundation.
+ *
+ * AUTHORS:
+ *      Vivien Malerba <malerba gnome-db org>
+ *
+ * This Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this Library; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __RT_PARSER_H__
+#define __RT_PARSER_H__
+
+#include <string.h>
+#include <libxml/tree.h>
+#include <glib.h>
+#include <gda-value.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	RT_MARKUP_NONE,
+	RT_MARKUP_PARA,
+	RT_MARKUP_BOLD,
+	RT_MARKUP_TT,
+	RT_MARKUP_VERBATIM,
+	RT_MARKUP_ITALIC,
+	RT_MARKUP_STRIKE,
+	RT_MARKUP_UNDERLINE,
+	RT_MARKUP_TITLE,
+	RT_MARKUP_PICTURE,
+	RT_MARKUP_LIST,
+} RtMarkup;
+
+typedef struct _RtNode RtNode;
+struct _RtNode {
+	RtNode    *parent;
+	RtNode    *child;
+	RtNode    *prev;
+	RtNode    *next;
+
+	RtMarkup   markup;
+	gint       offset;
+	gchar     *text;
+	GdaBinary  binary;
+};
+
+RtNode *rt_parse_text (const gchar *text);
+void    rt_free_node (RtNode *node);
+void    rt_dump_tree (RtNode *tree);
+gchar  *rt_dump_to_string (RtNode *tree);
+
+void parse_rich_text_to_docbook (xmlNodePtr top, const gchar *text);
+
+G_END_DECLS
+
+#endif
diff --git a/libgda-report/engine/test-rt-parser.c b/libgda-report/engine/test-rt-parser.c
new file mode 100644
index 0000000..369e24f
--- /dev/null
+++ b/libgda-report/engine/test-rt-parser.c
@@ -0,0 +1,106 @@
+#include <libgda/libgda.h>
+#ifdef HAVE_GDKPIXBUF
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdk-pixbuf/gdk-pixdata.h>
+#endif
+
+#include "rt-parser.c"
+
+typedef struct {
+	gchar *in;
+	gchar *exp_parsed;
+	gchar *exp_docbook;
+} TestCase;
+
+TestCase test_cases[] = {
+	/* 0 */
+	{"aa **bb** cc",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [aa ]|0:0:0-BOLD TEXT [bb]|0:0:1-NONE [offset=0] TEXT [ cc]|",
+	 "<top><para>aa <emphasis role=\"bold\">bb</emphasis> cc</para></top>"},
+
+	{"aa **bb //ii// dd** cc",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [aa ]|0:0:0-BOLD |0:0:0:0-NONE [offset=0] TEXT [bb ]|0:0:0:1-ITALIC TEXT [ii]|0:0:0:2-NONE [offset=0] TEXT [ dd]|0:0:1-NONE [offset=0] TEXT [ cc]|",
+	 "<top><para>aa <emphasis role=\"bold\">bb <emphasis>ii</emphasis> dd</emphasis> cc</para></top>"},
+
+	{"aa \n- item **1**\n- item2\nsome text",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [aa ]|0:0:0-LIST [offset=0] |0:0:0:0-NONE [offset=0] TEXT [item ]|0:0:0:1-BOLD TEXT [1]|0:0:1-LIST [offset=0] TEXT [item2]|0:1-PARA [offset=0] TEXT [some text]|",
+	 "<top><para>aa <itemizedlist><listitem><para>item <emphasis role=\"bold\">1</emphasis></para></listitem><listitem><para>item2</para></listitem></itemizedlist></para><para>some text</para></top>"},
+
+	{"aa \n- item 1\na nice pict: [[[pictdata]]]some more text\nsome text",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [aa ]|0:0:0-LIST [offset=0] TEXT [item 1]|0:1-PARA [offset=0] TEXT [a nice pict: ]|0:1:0-PICTURE BINARY|0:1:1-NONE [offset=0] TEXT [some more text]|0:2-PARA [offset=0] TEXT [some text]|",
+	 "<top><para>aa <itemizedlist><listitem><para>item 1</para></listitem></itemizedlist></para><para>a nice pict: <ulink url=\"./IMG_0000.jpg\">link</ulink>some more text</para><para>some text</para></top>"},
+
+	{"foreword\n= chapter 1 =\nsome **bold** text\n= chapter 2 =\ntext",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [foreword]|0:1-TITLE [offset=0] TEXT [chapter 1]|0:1:0-PARA [offset=0] TEXT [some ]|0:1:0:0-BOLD TEXT [bold]|0:1:0:1-NONE [offset=0] TEXT [ text]|0:2-TITLE [offset=0] TEXT [chapter 2]|0:2:0-PARA [offset=0] TEXT [text]|",
+	 "<top><para>foreword</para><sect1><title>chapter 1</title><para>some <emphasis role=\"bold\">bold</emphasis> text</para></sect1><sect1><title>chapter 2</title><para>text</para></sect1></top>"},
+
+	/* 5 */
+	{"- item 1\n - item 1.1\n - item 1.2\n- item 2\nsome text",
+	 "0-NONE [offset=0] |0:0-LIST [offset=0] TEXT [item 1]|0:0:0-LIST [offset=1] TEXT [item 1.1]|0:0:1-LIST [offset=1] TEXT [item 1.2]|0:1-LIST [offset=0] TEXT [item 2]|0:2-PARA [offset=0] TEXT [some text]|",
+	 "<top><itemizedlist><listitem><para>item 1<itemizedlist><listitem><para>item 1.1</para></listitem><listitem><para>item 1.2</para></listitem></itemizedlist></para></listitem><listitem><para>item 2</para></listitem></itemizedlist><para>some text</para></top>"},
+
+	{" - item 0.1\n - item 0.2\n- item 1\n - item 1.1\n - item 1.2\n- item 3\nEnd of list",
+	 "0-NONE [offset=0] |0:0-LIST [offset=0] |0:0:0-LIST [offset=1] TEXT [item 0.1]|0:0:1-LIST [offset=1] TEXT [item 0.2]|0:1-LIST [offset=0] TEXT [item 1]|0:1:0-LIST [offset=1] TEXT [item 1.1]|0:1:1-LIST [offset=1] TEXT [item 1.2]|0:2-LIST [offset=0] TEXT [item 3]|0:3-PARA [offset=0] TEXT [End of list]|",
+	 "<top><itemizedlist><listitem><para><itemizedlist><listitem><para>item 0.1</para></listitem><listitem><para>item 0.2</para></listitem></itemizedlist></para></listitem><listitem><para>item 1<itemizedlist><listitem><para>item 1.1</para></listitem><listitem><para>item 1.2</para></listitem></itemizedlist></para></listitem><listitem><para>item 3</para></listitem></itemizedlist><para>End of list</para></top>"
+	},
+
+	{"Intro\n= Title 1 =\nBlah\n== Title1.1 ==\nsome text\n== Title1.2 ==\nsome other text\n= Title2 =\nsome text",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [Intro]|0:1-TITLE [offset=0] TEXT [Title 1]|0:1:0-PARA [offset=0] TEXT [Blah]|0:1:1-TITLE [offset=1] TEXT [Title1.1]|0:1:1:0-PARA [offset=0] TEXT [some text]|0:1:2-TITLE [offset=1] TEXT [Title1.2]|0:1:2:0-PARA [offset=0] TEXT [some other text]|0:2-TITLE [offset=0] TEXT [Title2]|0:2:0-PARA [offset=0] TEXT [some text]|",
+	 "<top><para>Intro</para><sect1><title>Title 1</title><para>Blah</para><sect2><title>Title1.1</title><para>some text</para></sect2><sect2><title>Title1.2</title><para>some other text</para></sect2></sect1><sect1><title>Title2</title><para>some text</para></sect1></top>"},
+
+	{"line 1\n**bold text** blah",
+	 "0-NONE [offset=0] |0:0-PARA [offset=0] TEXT [line 1]|0:1-PARA [offset=0] TEXT []|0:1:0-BOLD [offset=0] TEXT [bold text]|0:1:1-NONE [offset=0] TEXT [ blah]|",
+	 "<top><para>line 1</para><para><emphasis role=\"bold\">bold text</emphasis> blah</para></top>"},
+
+	{"** bold -- still bold ** normal",
+	 "0-NONE [offset=0] |0:0-BOLD [offset=0] TEXT [ bold -- still bold ]|0:1-NONE [offset=0] TEXT [ normal]|",
+	 "<top><emphasis role=\"bold\"> bold -- still bold </emphasis> normal</top>"}
+};
+
+int 
+main (int argc, char **argv)
+{
+	gint i;
+
+	for (i = 0; i < (sizeof (test_cases) / sizeof (TestCase)); i++) {
+		TestCase *test = (TestCase*) &(test_cases[i]);
+		gchar *tmp;
+		RtNode *tree;
+		g_print ("======= TEST %d =======\n[%s]\n", i, test->in);
+
+		/* parsing test */
+		tree = rt_parse_text (test->in);
+		rt_dump_tree (tree);
+		tmp = rt_dump_to_string (tree);
+		if (strcmp (tmp, test->exp_parsed)) {
+			g_print ("EXP: %s\nGOT: %s\n", test->exp_parsed, tmp);
+			return 1;
+		}
+		g_free (tmp);
+		rt_free_node (tree);
+
+		/* docbook converter */
+		xmlNodePtr node;
+		xmlKeepBlanksDefault(0);
+		node = xmlNewNode (NULL, BAD_CAST "top");
+		parse_rich_text_to_docbook (node, test->in);
+		xmlBufferPtr buf;
+		buf = xmlBufferCreate ();
+		xmlNodeDump (buf, NULL, node, 1, 1);
+		g_print ("XML:\n%s\n", (gchar *) xmlBufferContent (buf));
+		xmlBufferFree (buf);
+
+		buf = xmlBufferCreate ();
+		xmlNodeDump (buf, NULL, node, 1, 0);
+		xmlFreeNode (node);
+		if (strcmp ((gchar *) xmlBufferContent (buf), test->exp_docbook)) {
+			g_print ("EXP: %s\nGOT: %s\n", test->exp_docbook, (gchar *) xmlBufferContent (buf));
+			return 1;
+		}
+		xmlBufferFree (buf);
+		g_print ("Test OK\n");
+	}
+
+	g_print ("All OK!\n");
+	return 0;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0ad04cd..8686417 100755
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -77,6 +77,7 @@ libgda/thread-wrapper/gda-thread-provider.c
 libgda/thread-wrapper/gda-thread-wrapper.c
 libgda-report/DocBook/gda-report-docbook-document.c
 libgda-report/engine/gda-report-engine.c
+libgda-report/engine/rt-parser.c
 libgda-report/gda-report-document.c
 libgda-report/RML/gda-report-rml-document.c
 libgda-ui/data-entries/common-bin.c



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