GMenuModel exporter: remove workaround
[glib.git] / gio / gmenumarkup.c
blob24a2dfb1db19650d087b4832110af8c6010c12e7
1 /*
2 * Copyright © 2011 Canonical Ltd.
3 * All rights reserved.
5 * Author: Ryan Lortie <desrt@desrt.ca>
6 */
8 #include "gmenumarkup.h"
10 #include <gi18n.h>
12 /**
13 * SECTION:gmenumarkup
14 * @title: GMenu Markup
15 * @short_description: parsing and printing GMenuModel XML
17 * The functions here allow to instantiate #GMenuModels by parsing
18 * fragments of an XML document.
19 * * The XML format for #GMenuModel consists of a toplevel
20 * <tag class="starttag">menu</tag> element, which contains one or more
21 * <tag class="starttag">item</tag> elements. Each <tag class="starttag">item</tag>
22 * element contains <tag class="starttag">attribute</tag> and <tag class="starttag">link</tag>
23 * elements with a mandatory name attribute.
24 * <tag class="starttag">link</tag> elements have the same content
25 * model as <tag class="starttag">menu</tag>.
27 * Here is the XML for <xref linkend="menu-example"/>:
28 * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/menumarkup2.xml"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
30 * The parser also understands a somewhat less verbose format, in which
31 * attributes are encoded as actual XML attributes of <tag class="starttag">item</tag>
32 * elements, and <tag class="starttag">link</tag> elements are replaced by
33 * <tag class="starttag">section</tag> and <tag class="starttag">submenu</tag> elements.
35 * Here is how the example looks in this format:
36 * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/menumarkup.xml"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
38 * The parser can obtaing translations for attribute values using gettext.
39 * To make use of this, the <tag class="starttag">menu</tag> element must
40 * have a domain attribute which specifies the gettext domain to use, and
41 * <tag class="starttag">attribute</tag> elements can be marked for translation
42 * with a <literal>translatable="yes"</literal> attribute. It is also possible
43 * to specify message context and translator comments, using the context
44 * and comments attributes.
46 * The following DTD describes the XML format approximately:
47 * |[<xi:include xmlns:xi="http://www.w3.org/2001/XInclude" parse="text" href="../../../../gio/menumarkup.dtd"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include>]|
49 * To serialize a #GMenuModel into an XML fragment, use
50 * g_menu_markup_print_string().
53 struct frame
55 GMenu *menu;
56 GMenuItem *item;
57 struct frame *prev;
60 typedef struct
62 GHashTable *objects;
63 struct frame frame;
65 /* attributes */
66 GQuark attribute;
67 GVariantType *type;
68 GString *string;
70 /* translation */
71 gchar *domain;
72 gchar *context;
73 gboolean translatable;
74 } GMenuMarkupState;
76 static gboolean
77 boolean_from_string (const gchar *str,
78 gboolean *val)
80 if (strcmp (str, "true") == 0 ||
81 strcmp (str, "yes") == 0 ||
82 strcmp (str, "t") == 0 ||
83 strcmp (str, "1") == 0)
84 *val = TRUE;
85 else if (strcmp (str, "false") == 0 ||
86 strcmp (str, "no") == 0 ||
87 strcmp (str, "f") == 0 ||
88 strcmp (str, "0") == 0)
89 *val = FALSE;
90 else
91 return FALSE;
93 return TRUE;
96 static void
97 g_menu_markup_push_frame (GMenuMarkupState *state,
98 GMenu *menu,
99 GMenuItem *item)
101 struct frame *new;
103 new = g_slice_new (struct frame);
104 *new = state->frame;
106 state->frame.menu = menu;
107 state->frame.item = item;
108 state->frame.prev = new;
111 static void
112 g_menu_markup_pop_frame (GMenuMarkupState *state)
114 struct frame *prev = state->frame.prev;
116 if (state->frame.item)
118 g_assert (prev->menu != NULL);
119 g_menu_append_item (prev->menu, state->frame.item);
120 g_object_unref (state->frame.item);
123 state->frame = *prev;
125 g_slice_free (struct frame, prev);
128 static void
129 add_string_attributes (GMenuItem *item,
130 const gchar **names,
131 const gchar **values)
133 gint i;
135 for (i = 0; names[i]; i++)
137 g_menu_item_set_attribute (item, names[i], "s", values[i]);
141 static void
142 g_menu_markup_start_element (GMarkupParseContext *context,
143 const gchar *element_name,
144 const gchar **attribute_names,
145 const gchar **attribute_values,
146 gpointer user_data,
147 GError **error)
149 GMenuMarkupState *state = user_data;
151 #define COLLECT(first, ...) \
152 g_markup_collect_attributes (element_name, \
153 attribute_names, attribute_values, error, \
154 first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
155 #define OPTIONAL G_MARKUP_COLLECT_OPTIONAL
156 #define STRDUP G_MARKUP_COLLECT_STRDUP
157 #define STRING G_MARKUP_COLLECT_STRING
158 #define NO_ATTRS() COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
160 if (!(state->frame.menu || state->frame.menu || state->string))
162 /* Can only have <menu> here. */
163 if (g_str_equal (element_name, "menu"))
165 gchar *id;
167 if (COLLECT (STRDUP, "id", &id))
169 GMenu *menu;
171 menu = g_menu_new ();
172 g_hash_table_insert (state->objects, id, menu);
173 g_menu_markup_push_frame (state, menu, NULL);
176 return;
180 if (state->frame.menu)
182 /* Can have '<item>', '<submenu>' or '<section>' here. */
183 if (g_str_equal (element_name, "item"))
185 GMenuItem *item;
187 item = g_menu_item_new (NULL, NULL);
188 add_string_attributes (item, attribute_names, attribute_values);
189 g_menu_markup_push_frame (state, NULL, item);
190 return;
193 else if (g_str_equal (element_name, "submenu"))
195 GMenuItem *item;
196 GMenu *menu;
198 menu = g_menu_new ();
199 item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu));
200 add_string_attributes (item, attribute_names, attribute_values);
201 g_menu_markup_push_frame (state, menu, item);
202 return;
205 else if (g_str_equal (element_name, "section"))
207 GMenuItem *item;
208 GMenu *menu;
210 menu = g_menu_new ();
211 item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
212 add_string_attributes (item, attribute_names, attribute_values);
213 g_menu_markup_push_frame (state, menu, item);
214 return;
218 if (state->frame.item)
220 /* Can have '<attribute>' or '<link>' here. */
221 if (g_str_equal (element_name, "attribute"))
223 const gchar *typestr;
224 const gchar *name;
225 const gchar *translatable;
226 const gchar *context;
228 if (COLLECT (STRING, "name", &name,
229 OPTIONAL | STRING, "translatable", &translatable,
230 OPTIONAL | STRING, "context", &context,
231 OPTIONAL | STRING, "comments", NULL, /* ignore, just for translators */
232 OPTIONAL | STRING, "type", &typestr))
234 if (typestr && !g_variant_type_string_is_valid (typestr))
236 g_set_error (error, G_VARIANT_PARSE_ERROR,
237 G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
238 "Invalid GVariant type string '%s'", typestr);
239 return;
242 state->type = typestr ? g_variant_type_new (typestr) : g_variant_type_copy (G_VARIANT_TYPE_STRING);
243 state->string = g_string_new (NULL);
244 state->attribute = g_quark_from_string (name);
245 state->context = g_strdup (context);
246 if (!translatable)
247 state->translatable = FALSE;
248 else if (!boolean_from_string (translatable, &state->translatable))
250 g_set_error (error, G_MARKUP_ERROR,
251 G_MARKUP_ERROR_INVALID_CONTENT,
252 "Invalid boolean attribute: '%s'", translatable);
253 return;
256 g_menu_markup_push_frame (state, NULL, NULL);
259 return;
262 if (g_str_equal (element_name, "link"))
264 const gchar *name;
265 const gchar *id;
267 if (COLLECT (STRING, "name", &name,
268 STRING | OPTIONAL, "id", &id))
270 GMenu *menu;
272 menu = g_menu_new ();
273 g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu));
274 g_menu_markup_push_frame (state, menu, NULL);
276 if (id != NULL)
277 g_hash_table_insert (state->objects, g_strdup (id), g_object_ref (menu));
280 return;
285 const GSList *element_stack;
287 element_stack = g_markup_parse_context_get_element_stack (context);
289 if (element_stack->next)
290 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
291 _("Element <%s> not allowed inside <%s>"),
292 element_name, (const gchar *) element_stack->next->data);
294 else
295 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
296 _("Element <%s> not allowed at toplevel"), element_name);
300 static void
301 g_menu_markup_end_element (GMarkupParseContext *context,
302 const gchar *element_name,
303 gpointer user_data,
304 GError **error)
306 GMenuMarkupState *state = user_data;
308 g_menu_markup_pop_frame (state);
310 if (state->string)
312 GVariant *value;
313 gchar *text;
315 text = g_string_free (state->string, FALSE);
316 state->string = NULL;
318 /* If error is set here, it will follow us out, ending the parse.
319 * We still need to free everything, though.
321 if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
323 /* Deal with translatable string attributes */
324 if (state->domain && state->translatable &&
325 g_variant_type_equal (state->type, G_VARIANT_TYPE_STRING))
327 const gchar *msgid;
328 const gchar *msgstr;
330 msgid = g_variant_get_string (value, NULL);
331 if (state->context)
332 msgstr = g_dpgettext2 (state->domain, state->context, msgid);
333 else
334 msgstr = g_dgettext (state->domain, msgid);
336 if (msgstr != msgid)
338 g_variant_unref (value);
339 value = g_variant_new_string (msgstr);
343 g_menu_item_set_attribute_value (state->frame.item, g_quark_to_string (state->attribute), value);
344 g_variant_unref (value);
347 g_variant_type_free (state->type);
348 state->type = NULL;
350 g_free (state->context);
351 state->context = NULL;
353 g_free (text);
357 static void
358 g_menu_markup_text (GMarkupParseContext *context,
359 const gchar *text,
360 gsize text_len,
361 gpointer user_data,
362 GError **error)
364 GMenuMarkupState *state = user_data;
365 gint i;
367 for (i = 0; i < text_len; i++)
368 if (!g_ascii_isspace (text[i]))
370 if (state->string)
371 g_string_append_len (state->string, text, text_len);
373 else
374 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
375 _("text may not appear inside <%s>"),
376 g_markup_parse_context_get_element (context));
377 break;
381 static void
382 g_menu_markup_error (GMarkupParseContext *context,
383 GError *error,
384 gpointer user_data)
386 GMenuMarkupState *state = user_data;
388 while (state->frame.prev)
390 struct frame *prev = state->frame.prev;
392 state->frame = *prev;
394 g_slice_free (struct frame, prev);
397 if (state->string)
398 g_string_free (state->string, TRUE);
400 if (state->type)
401 g_variant_type_free (state->type);
403 if (state->objects)
404 g_hash_table_unref (state->objects);
406 g_free (state->context);
408 g_slice_free (GMenuMarkupState, state);
411 static GMarkupParser g_menu_subparser =
413 g_menu_markup_start_element,
414 g_menu_markup_end_element,
415 g_menu_markup_text,
416 NULL, /* passthrough */
417 g_menu_markup_error
421 * g_menu_markup_parser_start:
422 * @context: a #GMarkupParseContext
423 * @domain: (allow-none): translation domain for labels, or %NULL
424 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
426 * Begin parsing a group of menus in XML form.
428 * If @domain is not %NULL, it will be used to translate attributes
429 * that are marked as translatable, using gettext().
431 * If @objects is specified then it must be a #GHashTable that was
432 * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
433 * g_free() and g_object_unref(). Any named menus that are encountered
434 * while parsing will be added to this table. Each toplevel menu must
435 * be named.
437 * If @objects is %NULL then an empty hash table will be created.
439 * This function should be called from the start_element function for
440 * the element representing the group containing the menus. In other
441 * words, the content inside of this element is expected to be a list of
442 * menus.
444 void
445 g_menu_markup_parser_start (GMarkupParseContext *context,
446 const gchar *domain,
447 GHashTable *objects)
449 GMenuMarkupState *state;
451 g_return_if_fail (context != NULL);
453 state = g_slice_new0 (GMenuMarkupState);
455 state->domain = g_strdup (domain);
457 if (objects != NULL)
458 state->objects = g_hash_table_ref (objects);
459 else
460 state->objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
462 g_markup_parse_context_push (context, &g_menu_subparser, state);
466 * g_menu_markup_parser_end:
467 * @context: a #GMarkupParseContext
469 * Stop the parsing of a set of menus and return the #GHashTable.
471 * The #GHashTable maps strings to #GObject instances. The parser only
472 * adds #GMenu instances to the table, but it may contain other types if
473 * a table was provided to g_menu_markup_parser_start().
475 * This call should be matched with g_menu_markup_parser_start().
476 * See that function for more information
478 * Returns: (transfer full): the #GHashTable containing the objects
480 GHashTable *
481 g_menu_markup_parser_end (GMarkupParseContext *context)
483 GMenuMarkupState *state = g_markup_parse_context_pop (context);
484 GHashTable *objects;
486 objects = state->objects;
488 g_free (state->domain);
490 g_slice_free (GMenuMarkupState, state);
492 return objects;
496 * g_menu_markup_parser_start_menu:
497 * @context: a #GMarkupParseContext
498 * @domain: (allow-none): translation domain for labels, or %NULL
499 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
501 * Begin parsing the XML definition of a menu.
503 * This function should be called from the start_element function for
504 * the element representing the menu itself. In other words, the
505 * content inside of this element is expected to be a list of items.
507 * If @domain is not %NULL, it will be used to translate attributes
508 * that are marked as translatable, using gettext().
510 * If @objects is specified then it must be a #GHashTable that was
511 * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
512 * g_free() and g_object_unref(). Any named menus that are encountered
513 * while parsing will be added to this table.
515 * If @object is %NULL then named menus will not be supported.
517 * You should call g_menu_markup_parser_end_menu() from the
518 * corresponding end_element function in order to collect the newly
519 * parsed menu.
521 void
522 g_menu_markup_parser_start_menu (GMarkupParseContext *context,
523 const gchar *domain,
524 GHashTable *objects)
526 GMenuMarkupState *state;
528 g_return_if_fail (context != NULL);
530 state = g_slice_new0 (GMenuMarkupState);
532 if (objects)
533 state->objects = g_hash_table_ref (objects);
535 state->domain = g_strdup (domain);
537 g_markup_parse_context_push (context, &g_menu_subparser, state);
539 state->frame.menu = g_menu_new ();
543 * g_menu_markup_parser_end_menu:
544 * @context: a #GMarkupParseContext
546 * Stop the parsing of a menu and return the newly-created #GMenu.
548 * This call should be matched with g_menu_markup_parser_start_menu().
549 * See that function for more information
551 * Returns: (transfer full): the newly-created #GMenu
553 GMenu *
554 g_menu_markup_parser_end_menu (GMarkupParseContext *context)
556 GMenuMarkupState *state = g_markup_parse_context_pop (context);
557 GMenu *menu;
559 menu = state->frame.menu;
561 if (state->objects)
562 g_hash_table_unref (state->objects);
563 g_free (state->domain);
564 g_slice_free (GMenuMarkupState, state);
566 return menu;
569 static void
570 indent_string (GString *string,
571 gint indent)
573 while (indent--)
574 g_string_append_c (string, ' ');
578 * g_menu_markup_print_string:
579 * @string: a #GString
580 * @model: the #GMenuModel to print
581 * @indent: the intentation level to start at
582 * @tabstop: how much to indent each level
584 * Print the contents of @model to @string.
585 * Note that you have to provide the containing
586 * <tag class="starttag">menu</tag> element yourself.
588 * Returns: @string
590 * Since: 2.32
592 GString *
593 g_menu_markup_print_string (GString *string,
594 GMenuModel *model,
595 gint indent,
596 gint tabstop)
598 gboolean need_nl = FALSE;
599 gint i, n;
601 if G_UNLIKELY (string == NULL)
602 string = g_string_new (NULL);
604 n = g_menu_model_get_n_items (model);
606 for (i = 0; i < n; i++)
608 GMenuAttributeIter *attr_iter;
609 GMenuLinkIter *link_iter;
610 GString *contents;
611 GString *attrs;
613 attr_iter = g_menu_model_iterate_item_attributes (model, i);
614 link_iter = g_menu_model_iterate_item_links (model, i);
615 contents = g_string_new (NULL);
616 attrs = g_string_new (NULL);
618 while (g_menu_attribute_iter_next (attr_iter))
620 const char *name = g_menu_attribute_iter_get_name (attr_iter);
621 GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
623 if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
625 gchar *str;
626 str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
627 g_string_append (attrs, str);
628 g_free (str);
631 else
633 gchar *printed;
634 gchar *str;
635 const gchar *type;
637 printed = g_variant_print (value, TRUE);
638 type = g_variant_type_peek_string (g_variant_get_type (value));
639 str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
640 indent_string (contents, indent + tabstop);
641 g_string_append (contents, str);
642 g_free (printed);
643 g_free (str);
646 g_variant_unref (value);
648 g_object_unref (attr_iter);
650 while (g_menu_link_iter_next (link_iter))
652 const gchar *name = g_menu_link_iter_get_name (link_iter);
653 GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
654 gchar *str;
656 if (contents->str[0])
657 g_string_append_c (contents, '\n');
659 str = g_markup_printf_escaped ("<link name='%s'>\n", name);
660 indent_string (contents, indent + tabstop);
661 g_string_append (contents, str);
662 g_free (str);
664 g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
666 indent_string (contents, indent + tabstop);
667 g_string_append (contents, "</link>\n");
668 g_object_unref (menu);
670 g_object_unref (link_iter);
672 if (contents->str[0])
674 indent_string (string, indent);
675 g_string_append_printf (string, "<item%s>\n", attrs->str);
676 g_string_append (string, contents->str);
677 indent_string (string, indent);
678 g_string_append (string, "</item>\n");
679 need_nl = TRUE;
682 else
684 if (need_nl)
685 g_string_append_c (string, '\n');
687 indent_string (string, indent);
688 g_string_append_printf (string, "<item%s/>\n", attrs->str);
689 need_nl = FALSE;
692 g_string_free (contents, TRUE);
693 g_string_free (attrs, TRUE);
696 return string;
700 * g_menu_markup_print_stderr:
701 * @model: a #GMenuModel
703 * Print @model to stderr for debugging purposes.
705 * This debugging function will be removed in the future.
707 void
708 g_menu_markup_print_stderr (GMenuModel *model)
710 GString *string;
712 string = g_string_new ("<menu>\n");
713 g_menu_markup_print_string (string, model, 2, 2);
714 g_printerr ("%s</menu>\n", string->str);
715 g_string_free (string, TRUE);