[glib/wip/gutils-splitup: 1/6] gmarkup: add utility functions for text



commit c28469faa031ed38cc07419303add1b477bd2a1c
Author: Ryan Lortie <desrt desrt ca>
Date:   Mon Feb 4 01:19:52 2013 +0100

    gmarkup: add utility functions for text
    
    WIP patch.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=689035

 docs/reference/glib/glib-sections.txt |    3 +
 glib/gmarkup.c                        |  359 +++++++++++++++++++++++++++++++++
 glib/gmarkup.h                        |   17 ++
 3 files changed, 379 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index 4153a29..6a14cec 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -1140,6 +1140,9 @@ g_markup_parse_context_unref
 <SUBSECTION>
 GMarkupCollectType
 g_markup_collect_attributes
+g_markup_string_parser_start
+g_markup_string_parser_end
+g_markup_parser_reject_text
 <SUBSECTION Private>
 g_markup_error_quark
 </SECTION>
diff --git a/glib/gmarkup.c b/glib/gmarkup.c
index 552773f..558b6c4 100644
--- a/glib/gmarkup.c
+++ b/glib/gmarkup.c
@@ -37,6 +37,7 @@
 #include "gtestutils.h"
 #include "glibintl.h"
 #include "gthread.h"
+#include "ggettext.h"
 
 /**
  * SECTION:markup
@@ -2865,3 +2866,361 @@ failure:
 
   return FALSE;
 }
+
+static void
+g_markup_string_parser_start_element (GMarkupParseContext  *context,
+                                      const gchar          *element_name,
+                                      const gchar         **attribute_names,
+                                      const gchar         **attribute_values,
+                                      gpointer              user_data,
+                                      GError              **error)
+{
+  g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+               "Only text may appear inside element <%s> (not <%s>)",
+               g_markup_parse_context_get_element (context), element_name);
+}
+
+typedef struct
+{
+  GString *str;
+  gboolean translatable;
+  gchar *context;
+  gchar *domain;
+} StringParserState;
+
+static void
+g_markup_string_parser_text (GMarkupParseContext  *context,
+                             const gchar          *text,
+                             gsize                 text_len,
+                             gpointer              user_data,
+                             GError              **error)
+{
+  StringParserState *state = user_data;
+
+  g_string_append_len (state->str, text, text_len);
+}
+
+static void
+g_markup_string_parser_error (GMarkupParseContext *context,
+                              GError              *error,
+                              gpointer             user_data)
+{
+  StringParserState *state = user_data;
+
+  g_string_free (state->str, TRUE);
+  g_free (state->context);
+  g_free (state->domain);
+
+  g_slice_free (StringParserState, state);
+}
+
+/**
+ * g_markup_string_parser_start:
+ * @context: a #GMarkupParseContext
+ * @translatable: if the text is meant to be translatable
+ * @gettext_domain: the gettext domain for translation
+ * @gettext_context: the gettext context for translation
+ * @error: the #GError passed into your 'start_element'.
+ *
+ * Starts reading an all-text section inside of an element from an
+ * invocation of a #GMarkupParser 'start_element' function.
+ *
+ * In many cases you probably want to parse the arguments of the tag
+ * starting the text section to determine appropriate values for
+ * @translatable, @gettext_domain and particularly @gettext_context.
+ *
+ * If @translatable is true then the text will be translated according
+ * to @gettext_domain and @gettext_context.  Whitespace is normalised
+ * according to (FIXME: some rules... probably the ones intltool already
+ * knows about) before translation (if any) occurs.
+ *
+ * There may not be nested tags in the text section.  If any are
+ * encountered then an error is generated (and the next and final call
+ * that your #GMarkupParser will receive is to its 'error' handler, if
+ * any).
+ *
+ * You should call g_markup_string_parser_end() in the corresponding
+ * 'end_element' invocation (ie: the one called immediately after, with
+ * the same @element_name).
+ *
+ * If you intend to parse all of the text sections in your document type
+ * using this function then you should probably use
+ * g_markup_parser_reject_text() as the 'text' handler in your
+ * #GMarkupParser vtable.
+ *
+ * Consider an example parser to parse a file of the following form:
+ *
+ * |[
+ * <![CDATA[
+ *  <attrlist gettext-domain='myapp'>
+ *    <attribute name='identifier'>
+ *      myapp
+ *    </attribute>
+ *    <attribute name='label' translatable='yes'
+ *               comment='This is the text in the main window'>
+ *      Hello world!
+ *    </attribute>
+ *    <attribute name='24-hour-time' context='24 hour time'>
+ *      false
+ *    </attribute>
+ *  </attrlist>
+ * ]]>
+ * ]|
+ *
+ * with the corresponding <literal>myapp.po</literal> fragment:
+ * |[
+ *  # This is the text in the main window
+ *  #: file.xml:7
+ *  msgid "Hello world!"
+ *  msgstr ""
+ *
+ *  #: file.xml:10
+ *  msgctxt "24 hour time"
+ *  msgid "false"
+ *  msgstr ""
+ * ]|
+ *
+ * The code for the parser follows:
+ *
+ * |[
+ *  typedef struct
+ *  {
+ *    // For the file
+ *    gchar *gettext_domain;
+ *
+ *    // For the current <attribute/>
+ *    gchar *name;
+ *  } ParserState;
+ *
+ *  void
+ *  start_element (GMarkupParseContext  *context,
+ *                 const gchar          *element_name,
+ *                 const gchar         **attribute_names,
+ *                 const gchar         **attribute_values,
+ *                 gpointer              user_data,
+ *                 GError              **error)
+ *  {
+ *    ParserState *state = user_data;
+ *    const GSList *element_stack;
+ *    const gchar *container;
+ *
+ *    element_stack = g_markup_parse_context_get_element_stack (context);
+ *    container = element_stack->next ? element_stack->next->data : NULL;
+ *
+ *    if (container == NULL)
+ *      {
+ *        if (g_str_equal (element_name, "attrlist"))
+ *          {
+ *            g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
+ *                                         G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL,
+ *                                           "gettext-domain", &state->gettext_domain,
+ *                                         G_MARKUP_COLLECT_INVALID);
+ *            return;
+ *          }
+ *      }
+ *
+ *    else if (g_str_equal (container, "attrlist"))
+ *      {
+ *        if (g_str_equal (element_name, "attribute"))
+ *          {
+ *            const gchar *gettext_context;
+ *            gboolean translatable;
+ *
+ *            if (g_markup_collect_attributes (element_name, attribute_names, attribute_values, error,
+ *                                             G_MARKUP_COLLECT_STRDUP,
+ *                                               "name", &state->name,
+ *                                             G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_BOOLEAN,
+ *                                               "translatable", &translatable,
+ *                                             G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING,
+ *                                               "context", &gettext_context,
+ *                                             G_MARKUP_COLLECT_OPTIONAL | G_MARKUP_COLLECT_STRING,
+ *                                               "comment", NULL, // just for translators; ignore here
+ *                                             G_MARKUP_COLLECT_INVALID))
+ *              {
+ *                g_markup_string_parser_start (context, translatable, state->gettext_domain, gettext_context, error);
+ *              }
+ *            return;
+ *          }
+ *      }
+ *
+ *    if (container)
+ *      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ *                   _("Element <%s> not allowed inside <%s>"),
+ *                   element_name, container);
+ *    else
+ *      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ *                   _("Element <%s> not allowed at the top level"), element_name);
+ *  }
+ *
+ *  void
+ *  end_element (GMarkupParseContext  *context,
+ *               const gchar          *element_name,
+ *               gpointer              user_data,
+ *               GError              **error)
+ *  {
+ *    ParserState *state = user_data;
+ *
+ *    if (g_str_equal (element_name, "attrlist"))
+ *      {
+ *        g_clear_pointer (&state->gettext_domain, g_free);
+ *      }
+ *
+ *    else if (g_str_equal (element_name, "attribute"))
+ *      {
+ *        gchar *value;
+ *
+ *        value = g_markup_string_parser_end (context);
+ *        g_print ("Got '%s'='%s'\n", state->name, value);
+ *        g_clear_pointer (&state->name, g_free);
+ *        g_free (value);
+ *      }
+ *  }
+ *
+ *  static void
+ *  error (GMarkupParseContext *context,
+ *         GError              *error,
+ *         gpointer             user_data)
+ *  {
+ *    ParserState *state = user_data;
+ *
+ *    g_clear_pointer (&state->gettext_domain, g_free);
+ *    g_clear_pointer (&state->name, g_free);
+ *  }
+ *
+ * static GMarkupParser parser_vtable = {
+ *   start_element,
+ *   end_element,
+ *   g_markup_parser_reject_text,
+ *   NULL, // passthrough
+ *   error
+ * };
+ * ]|
+ *
+ * Since: 2.36
+ **/
+void
+g_markup_string_parser_start (GMarkupParseContext  *context,
+                              gboolean              translatable,
+                              const gchar          *gettext_domain,
+                              const gchar          *gettext_context,
+                              GError              **error)
+{
+  static const GMarkupParser parser = {
+    g_markup_string_parser_start_element,
+    NULL,
+    g_markup_string_parser_text,
+    NULL,
+    g_markup_string_parser_error
+  };
+  StringParserState *state;
+
+  if (translatable && gettext_domain == NULL)
+    {
+      g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE,
+                   "translation was requested for <%s> but no gettext domain was given",
+                   g_markup_parse_context_get_element (context));
+      return;
+    }
+
+  state = g_slice_new (StringParserState);
+  state->str = g_string_new (NULL);
+  state->domain = g_strdup (gettext_domain);
+  state->context = g_strdup (gettext_context);
+  state->translatable = translatable;
+
+  g_markup_parse_context_push (context, &parser, state);
+}
+
+/**
+ * g_markup_string_parser_end:
+ * @context: the #GMarkupParseContext
+ *
+ * Collect the result of an instance of the built-in #GMarkup string
+ * subparser.
+ *
+ * This should be called from the 'end_element' invocation that is
+ * run immediately after the 'start_element' invocation that called
+ * g_markup_string_parser_start().
+ *
+ * The resulting string will have been whitespace normalised and
+ * translated, if applicable.
+ *
+ * Returns: the string that was read from the markup
+ *
+ * Since: 2.36
+ **/
+gchar *
+g_markup_string_parser_end (GMarkupParseContext *context)
+{
+  StringParserState *state = g_markup_parse_context_pop (context);
+  const gchar *translated;
+  gchar *result;
+
+  /* TODO: whitespace normalisation before translation? */
+
+  if (state->translatable)
+    {
+      if (state->context)
+        translated = g_dpgettext2 (state->domain, state->context, state->str->str);
+      else
+        translated = g_dgettext (state->domain, state->str->str);
+    }
+  else
+    translated = state->str->str;
+
+  if (translated != state->str->str)
+    {
+      g_string_free (state->str, TRUE);
+      result = g_strdup (translated);
+    }
+  else
+    result = g_string_free (state->str, FALSE);
+
+  g_free (state->context);
+  g_free (state->domain);
+
+  g_slice_free (StringParserState, state);
+
+  return result;
+}
+
+/**
+ * g_markup_parser_reject_text:
+ * @context: do not call this function directly
+ * @text: do not call this function directly
+ * @text_len: do not call this function directly
+ * @user_data: do not call this function directly
+ * @error: do not call this function directly
+ *
+ * This function is not designed to be used directly.  Rather, it should
+ * be used as the 'text' callback in the #GMarkupParser vtable if your
+ * parser is not interested in parsing any text.
+ *
+ * This is useful for document types where no text is valid (ie: only
+ * elements are allowed) or for parsers that want to parse all text
+ * sections using subparsers (with g_markup_string_parser_text() for
+ * example).
+ *
+ * The function will reject any text that does not consist entirely of
+ * whitespace characters, issuing an appropriate warning via #GError.
+ *
+ * Since: 2.36
+ **/
+void
+g_markup_parser_reject_text (GMarkupParseContext  *context,
+                             const gchar          *text,
+                             gsize                 text_len,
+                             gpointer              user_data,
+                             GError              **error)
+{
+  gsize i;
+
+  for (i = 0; i < text_len; i++)
+    if (!g_ascii_isspace (text[i]))
+      {
+        g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
+                     "text may not appear inside <%s>",
+                     g_markup_parse_context_get_element (context));
+        break;
+      }
+}
diff --git a/glib/gmarkup.h b/glib/gmarkup.h
index eb7214d..0230fe7 100644
--- a/glib/gmarkup.h
+++ b/glib/gmarkup.h
@@ -252,6 +252,23 @@ gboolean   g_markup_collect_attributes (const gchar         *element_name,
                                         const gchar         *first_attr,
                                         ...);
 
+GLIB_AVAILABLE_IN_2_36
+void            g_markup_string_parser_start            (GMarkupParseContext  *context,
+                                                         gboolean              translatable,
+                                                         const gchar          *gettext_domain,
+                                                         const gchar          *gettext_context,
+                                                         GError              **error);
+GLIB_AVAILABLE_IN_2_36
+gchar *         g_markup_string_parser_end              (GMarkupParseContext  *context);
+
+
+GLIB_AVAILABLE_IN_2_36
+void            g_markup_parser_reject_text             (GMarkupParseContext  *context,
+                                                         const gchar          *text,
+                                                         gsize                 text_len,
+                                                         gpointer              user_data,
+                                                         GError              **error);
+
 G_END_DECLS
 
 #endif /* __G_MARKUP_H__ */



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