[libgda] Initial support for rich text rendering in reports
- From: Vivien Malerba <vivien src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgda] Initial support for rich text rendering in reports
- Date: Sat, 8 Jan 2011 14:35:55 +0000 (UTC)
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><gda_report_param_value></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]