2 * Copyright © 2011 Canonical Ltd.
5 * Author: Ryan Lortie <desrt@desrt.ca>
8 #include "gmenumarkup.h"
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().
73 gboolean translatable;
77 boolean_from_string (const gchar *str,
80 if (strcmp (str, "true") == 0 ||
81 strcmp (str, "yes") == 0 ||
82 strcmp (str, "t") == 0 ||
83 strcmp (str, "1") == 0)
85 else if (strcmp (str, "false") == 0 ||
86 strcmp (str, "no") == 0 ||
87 strcmp (str, "f") == 0 ||
88 strcmp (str, "0") == 0)
97 g_menu_markup_push_frame (GMenuMarkupState *state,
103 new = g_slice_new (struct frame);
106 state->frame.menu = menu;
107 state->frame.item = item;
108 state->frame.prev = new;
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);
122 state->frame = *prev;
124 g_slice_free (struct frame, prev);
128 add_string_attributes (GMenuItem *item,
130 const gchar **values)
134 for (i = 0; names[i]; i++)
136 g_menu_item_set_attribute (item, names[i], "s", values[i]);
141 g_menu_markup_start_element (GMarkupParseContext *context,
142 const gchar *element_name,
143 const gchar **attribute_names,
144 const gchar **attribute_values,
148 GMenuMarkupState *state = user_data;
150 #define COLLECT(first, ...) \
151 g_markup_collect_attributes (element_name, \
152 attribute_names, attribute_values, error, \
153 first, __VA_ARGS__, G_MARKUP_COLLECT_INVALID)
154 #define OPTIONAL G_MARKUP_COLLECT_OPTIONAL
155 #define STRDUP G_MARKUP_COLLECT_STRDUP
156 #define STRING G_MARKUP_COLLECT_STRING
157 #define NO_ATTRS() COLLECT (G_MARKUP_COLLECT_INVALID, NULL)
159 if (!(state->frame.menu || state->frame.menu || state->string))
161 /* Can only have <menu> here. */
162 if (g_str_equal (element_name, "menu"))
166 if (COLLECT (STRDUP, "id", &id))
170 menu = g_menu_new ();
171 g_hash_table_insert (state->objects, id, menu);
172 g_menu_markup_push_frame (state, menu, NULL);
179 if (state->frame.menu)
181 /* Can have '<item>', '<submenu>' or '<section>' here. */
182 if (g_str_equal (element_name, "item"))
186 item = g_menu_item_new (NULL, NULL);
187 add_string_attributes (item, attribute_names, attribute_values);
188 g_menu_markup_push_frame (state, NULL, item);
192 else if (g_str_equal (element_name, "submenu"))
197 menu = g_menu_new ();
198 item = g_menu_item_new_submenu (NULL, G_MENU_MODEL (menu));
199 add_string_attributes (item, attribute_names, attribute_values);
200 g_menu_markup_push_frame (state, menu, item);
204 else if (g_str_equal (element_name, "section"))
209 menu = g_menu_new ();
210 item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
211 add_string_attributes (item, attribute_names, attribute_values);
212 g_menu_markup_push_frame (state, menu, item);
217 if (state->frame.item)
219 /* Can have '<attribute>' or '<link>' here. */
220 if (g_str_equal (element_name, "attribute"))
222 const gchar *typestr;
224 const gchar *translatable;
225 const gchar *context;
227 if (COLLECT (STRING, "name", &name,
228 OPTIONAL | STRING, "translatable", &translatable,
229 OPTIONAL | STRING, "context", &context,
230 OPTIONAL | STRING, "comments", NULL, /* ignore, just for translators */
231 OPTIONAL | STRING, "type", &typestr))
233 if (typestr && !g_variant_type_string_is_valid (typestr))
235 g_set_error (error, G_VARIANT_PARSE_ERROR,
236 G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
237 "Invalid GVariant type string '%s'", typestr);
241 state->type = typestr ? g_variant_type_new (typestr) : g_variant_type_copy (G_VARIANT_TYPE_STRING);
242 state->string = g_string_new (NULL);
243 state->attribute = g_quark_from_string (name);
244 state->context = g_strdup (context);
246 state->translatable = FALSE;
247 else if (!boolean_from_string (translatable, &state->translatable))
249 g_set_error (error, G_MARKUP_ERROR,
250 G_MARKUP_ERROR_INVALID_CONTENT,
251 "Invalid boolean attribute: '%s'", translatable);
255 g_menu_markup_push_frame (state, NULL, NULL);
261 if (g_str_equal (element_name, "link"))
266 if (COLLECT (STRING, "name", &name,
267 STRING | OPTIONAL, "id", &id))
271 menu = g_menu_new ();
272 g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu));
273 g_menu_markup_push_frame (state, menu, NULL);
276 g_hash_table_insert (state->objects, g_strdup (id), g_object_ref (menu));
284 const GSList *element_stack;
286 element_stack = g_markup_parse_context_get_element_stack (context);
288 if (element_stack->next)
289 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
290 _("Element <%s> not allowed inside <%s>"),
291 element_name, (const gchar *) element_stack->next->data);
294 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
295 _("Element <%s> not allowed at toplevel"), element_name);
300 g_menu_markup_end_element (GMarkupParseContext *context,
301 const gchar *element_name,
305 GMenuMarkupState *state = user_data;
307 g_menu_markup_pop_frame (state);
314 text = g_string_free (state->string, FALSE);
315 state->string = NULL;
317 /* If error is set here, it will follow us out, ending the parse.
318 * We still need to free everything, though.
320 if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
322 /* Deal with translatable string attributes */
323 if (state->domain && state->translatable &&
324 g_variant_type_equal (state->type, G_VARIANT_TYPE_STRING))
329 msgid = g_variant_get_string (value, NULL);
331 msgstr = g_dpgettext2 (state->domain, state->context, msgid);
333 msgstr = g_dgettext (state->domain, msgid);
337 g_variant_unref (value);
338 value = g_variant_new_string (msgstr);
342 g_menu_item_set_attribute_value (state->frame.item, g_quark_to_string (state->attribute), value);
343 g_variant_unref (value);
346 g_variant_type_free (state->type);
349 g_free (state->context);
350 state->context = NULL;
357 g_menu_markup_text (GMarkupParseContext *context,
363 GMenuMarkupState *state = user_data;
366 for (i = 0; i < text_len; i++)
367 if (!g_ascii_isspace (text[i]))
370 g_string_append_len (state->string, text, text_len);
373 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
374 _("text may not appear inside <%s>"),
375 g_markup_parse_context_get_element (context));
381 g_menu_markup_error (GMarkupParseContext *context,
385 GMenuMarkupState *state = user_data;
387 while (state->frame.prev)
389 struct frame *prev = state->frame.prev;
391 state->frame = *prev;
393 g_slice_free (struct frame, prev);
397 g_string_free (state->string, TRUE);
400 g_variant_type_free (state->type);
403 g_hash_table_unref (state->objects);
405 g_free (state->context);
407 g_slice_free (GMenuMarkupState, state);
410 static GMarkupParser g_menu_subparser =
412 g_menu_markup_start_element,
413 g_menu_markup_end_element,
415 NULL, /* passthrough */
420 * g_menu_markup_parser_start:
421 * @context: a #GMarkupParseContext
422 * @domain: (allow-none): translation domain for labels, or %NULL
423 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
425 * Begin parsing a group of menus in XML form.
427 * If @domain is not %NULL, it will be used to translate attributes
428 * that are marked as translatable, using gettext().
430 * If @objects is specified then it must be a #GHashTable that was
431 * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
432 * g_free() and g_object_unref(). Any named menus that are encountered
433 * while parsing will be added to this table. Each toplevel menu must
436 * If @objects is %NULL then an empty hash table will be created.
438 * This function should be called from the start_element function for
439 * the element representing the group containing the menus. In other
440 * words, the content inside of this element is expected to be a list of
444 g_menu_markup_parser_start (GMarkupParseContext *context,
448 GMenuMarkupState *state;
450 g_return_if_fail (context != NULL);
452 state = g_slice_new0 (GMenuMarkupState);
454 state->domain = g_strdup (domain);
457 state->objects = g_hash_table_ref (objects);
459 state->objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
461 g_markup_parse_context_push (context, &g_menu_subparser, state);
465 * g_menu_markup_parser_end:
466 * @context: a #GMarkupParseContext
468 * Stop the parsing of a set of menus and return the #GHashTable.
470 * The #GHashTable maps strings to #GObject instances. The parser only
471 * adds #GMenu instances to the table, but it may contain other types if
472 * a table was provided to g_menu_markup_parser_start().
474 * This call should be matched with g_menu_markup_parser_start().
475 * See that function for more information
477 * Returns: (transfer full): the #GHashTable containing the objects
480 g_menu_markup_parser_end (GMarkupParseContext *context)
482 GMenuMarkupState *state = g_markup_parse_context_pop (context);
485 objects = state->objects;
487 g_free (state->domain);
489 g_slice_free (GMenuMarkupState, state);
495 * g_menu_markup_parser_start_menu:
496 * @context: a #GMarkupParseContext
497 * @domain: (allow-none): translation domain for labels, or %NULL
498 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
500 * Begin parsing the XML definition of a menu.
502 * This function should be called from the start_element function for
503 * the element representing the menu itself. In other words, the
504 * content inside of this element is expected to be a list of items.
506 * If @domain is not %NULL, it will be used to translate attributes
507 * that are marked as translatable, using gettext().
509 * If @objects is specified then it must be a #GHashTable that was
510 * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
511 * g_free() and g_object_unref(). Any named menus that are encountered
512 * while parsing will be added to this table.
514 * If @object is %NULL then named menus will not be supported.
516 * You should call g_menu_markup_parser_end_menu() from the
517 * corresponding end_element function in order to collect the newly
521 g_menu_markup_parser_start_menu (GMarkupParseContext *context,
525 GMenuMarkupState *state;
527 g_return_if_fail (context != NULL);
529 state = g_slice_new0 (GMenuMarkupState);
532 state->objects = g_hash_table_ref (objects);
534 state->domain = g_strdup (domain);
536 g_markup_parse_context_push (context, &g_menu_subparser, state);
538 state->frame.menu = g_menu_new ();
542 * g_menu_markup_parser_end_menu:
543 * @context: a #GMarkupParseContext
545 * Stop the parsing of a menu and return the newly-created #GMenu.
547 * This call should be matched with g_menu_markup_parser_start_menu().
548 * See that function for more information
550 * Returns: (transfer full): the newly-created #GMenu
553 g_menu_markup_parser_end_menu (GMarkupParseContext *context)
555 GMenuMarkupState *state = g_markup_parse_context_pop (context);
558 menu = state->frame.menu;
561 g_hash_table_unref (state->objects);
562 g_free (state->domain);
563 g_slice_free (GMenuMarkupState, state);
569 indent_string (GString *string,
573 g_string_append_c (string, ' ');
577 * g_menu_markup_print_string:
578 * @string: a #GString
579 * @model: the #GMenuModel to print
580 * @indent: the intentation level to start at
581 * @tabstop: how much to indent each level
583 * Print the contents of @model to @string.
584 * Note that you have to provide the containing
585 * <tag class="starttag">menu</tag> element yourself.
592 g_menu_markup_print_string (GString *string,
597 gboolean need_nl = FALSE;
600 if G_UNLIKELY (string == NULL)
601 string = g_string_new (NULL);
603 n = g_menu_model_get_n_items (model);
605 for (i = 0; i < n; i++)
607 GMenuAttributeIter *attr_iter;
608 GMenuLinkIter *link_iter;
612 attr_iter = g_menu_model_iterate_item_attributes (model, i);
613 link_iter = g_menu_model_iterate_item_links (model, i);
614 contents = g_string_new (NULL);
615 attrs = g_string_new (NULL);
617 while (g_menu_attribute_iter_next (attr_iter))
619 const char *name = g_menu_attribute_iter_get_name (attr_iter);
620 GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
622 if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
625 str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
626 g_string_append (attrs, str);
636 printed = g_variant_print (value, TRUE);
637 type = g_variant_type_peek_string (g_variant_get_type (value));
638 str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
639 indent_string (contents, indent + tabstop);
640 g_string_append (contents, str);
645 g_variant_unref (value);
647 g_object_unref (attr_iter);
649 while (g_menu_link_iter_next (link_iter))
651 const gchar *name = g_menu_link_iter_get_name (link_iter);
652 GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
655 if (contents->str[0])
656 g_string_append_c (contents, '\n');
658 str = g_markup_printf_escaped ("<link name='%s'>\n", name);
659 indent_string (contents, indent + tabstop);
660 g_string_append (contents, str);
663 g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
665 indent_string (contents, indent + tabstop);
666 g_string_append (contents, "</link>\n");
667 g_object_unref (menu);
669 g_object_unref (link_iter);
671 if (contents->str[0])
673 indent_string (string, indent);
674 g_string_append_printf (string, "<item%s>\n", attrs->str);
675 g_string_append (string, contents->str);
676 indent_string (string, indent);
677 g_string_append (string, "</item>\n");
684 g_string_append_c (string, '\n');
686 indent_string (string, indent);
687 g_string_append_printf (string, "<item%s/>\n", attrs->str);
691 g_string_free (contents, TRUE);
692 g_string_free (attrs, TRUE);
699 * g_menu_markup_print_stderr:
700 * @model: a #GMenuModel
702 * Print @model to stderr for debugging purposes.
704 * This debugging function will be removed in the future.
707 g_menu_markup_print_stderr (GMenuModel *model)
711 string = g_string_new ("<menu>\n");
712 g_menu_markup_print_string (string, model, 2, 2);
713 g_printerr ("%s</menu>\n", string->str);
714 g_string_free (string, TRUE);