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) : NULL;
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 && state->type &&
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);
348 g_variant_type_free (state->type);
352 g_free (state->context);
353 state->context = NULL;
360 g_menu_markup_text (GMarkupParseContext *context,
366 GMenuMarkupState *state = user_data;
369 for (i = 0; i < text_len; i++)
370 if (!g_ascii_isspace (text[i]))
373 g_string_append_len (state->string, text, text_len);
376 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
377 _("text may not appear inside <%s>"),
378 g_markup_parse_context_get_element (context));
384 g_menu_markup_error (GMarkupParseContext *context,
388 GMenuMarkupState *state = user_data;
390 while (state->frame.prev)
392 struct frame *prev = state->frame.prev;
394 state->frame = *prev;
396 g_slice_free (struct frame, prev);
400 g_string_free (state->string, TRUE);
403 g_variant_type_free (state->type);
406 g_hash_table_unref (state->objects);
408 g_free (state->context);
410 g_slice_free (GMenuMarkupState, state);
413 static GMarkupParser g_menu_subparser =
415 g_menu_markup_start_element,
416 g_menu_markup_end_element,
418 NULL, /* passthrough */
423 * g_menu_markup_parser_start:
424 * @context: a #GMarkupParseContext
425 * @domain: (allow-none): translation domain for labels, or %NULL
426 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
428 * Begin parsing a group of menus in XML form.
430 * If @domain is not %NULL, it will be used to translate attributes
431 * that are marked as translatable, using gettext().
433 * If @objects is specified then it must be a #GHashTable that was
434 * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
435 * g_free() and g_object_unref(). Any named menus that are encountered
436 * while parsing will be added to this table. Each toplevel menu must
439 * If @objects is %NULL then an empty hash table will be created.
441 * This function should be called from the start_element function for
442 * the element representing the group containing the menus. In other
443 * words, the content inside of this element is expected to be a list of
447 g_menu_markup_parser_start (GMarkupParseContext *context,
451 GMenuMarkupState *state;
453 g_return_if_fail (context != NULL);
455 state = g_slice_new0 (GMenuMarkupState);
457 state->domain = g_strdup (domain);
460 state->objects = g_hash_table_ref (objects);
462 state->objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
464 g_markup_parse_context_push (context, &g_menu_subparser, state);
468 * g_menu_markup_parser_end:
469 * @context: a #GMarkupParseContext
471 * Stop the parsing of a set of menus and return the #GHashTable.
473 * The #GHashTable maps strings to #GObject instances. The parser only
474 * adds #GMenu instances to the table, but it may contain other types if
475 * a table was provided to g_menu_markup_parser_start().
477 * This call should be matched with g_menu_markup_parser_start().
478 * See that function for more information
480 * Returns: (transfer full): the #GHashTable containing the objects
483 g_menu_markup_parser_end (GMarkupParseContext *context)
485 GMenuMarkupState *state = g_markup_parse_context_pop (context);
488 objects = state->objects;
490 g_free (state->domain);
492 g_slice_free (GMenuMarkupState, state);
498 * g_menu_markup_parser_start_menu:
499 * @context: a #GMarkupParseContext
500 * @domain: (allow-none): translation domain for labels, or %NULL
501 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
503 * Begin parsing the XML definition of a menu.
505 * This function should be called from the start_element function for
506 * the element representing the menu itself. In other words, the
507 * content inside of this element is expected to be a list of items.
509 * If @domain is not %NULL, it will be used to translate attributes
510 * that are marked as translatable, using gettext().
512 * If @objects is specified then it must be a #GHashTable that was
513 * created using g_hash_table_new_full() with g_str_hash(), g_str_equal(),
514 * g_free() and g_object_unref(). Any named menus that are encountered
515 * while parsing will be added to this table.
517 * If @object is %NULL then named menus will not be supported.
519 * You should call g_menu_markup_parser_end_menu() from the
520 * corresponding end_element function in order to collect the newly
524 g_menu_markup_parser_start_menu (GMarkupParseContext *context,
528 GMenuMarkupState *state;
530 g_return_if_fail (context != NULL);
532 state = g_slice_new0 (GMenuMarkupState);
535 state->objects = g_hash_table_ref (objects);
537 state->domain = g_strdup (domain);
539 g_markup_parse_context_push (context, &g_menu_subparser, state);
541 state->frame.menu = g_menu_new ();
545 * g_menu_markup_parser_end_menu:
546 * @context: a #GMarkupParseContext
548 * Stop the parsing of a menu and return the newly-created #GMenu.
550 * This call should be matched with g_menu_markup_parser_start_menu().
551 * See that function for more information
553 * Returns: (transfer full): the newly-created #GMenu
556 g_menu_markup_parser_end_menu (GMarkupParseContext *context)
558 GMenuMarkupState *state = g_markup_parse_context_pop (context);
561 menu = state->frame.menu;
564 g_hash_table_unref (state->objects);
565 g_free (state->domain);
566 g_slice_free (GMenuMarkupState, state);
572 indent_string (GString *string,
576 g_string_append_c (string, ' ');
580 * g_menu_markup_print_string:
581 * @string: a #GString
582 * @model: the #GMenuModel to print
583 * @indent: the intentation level to start at
584 * @tabstop: how much to indent each level
586 * Print the contents of @model to @string.
587 * Note that you have to provide the containing
588 * <tag class="starttag">menu</tag> element yourself.
595 g_menu_markup_print_string (GString *string,
600 gboolean need_nl = FALSE;
603 if G_UNLIKELY (string == NULL)
604 string = g_string_new (NULL);
606 n = g_menu_model_get_n_items (model);
608 for (i = 0; i < n; i++)
610 GMenuAttributeIter *attr_iter;
611 GMenuLinkIter *link_iter;
615 attr_iter = g_menu_model_iterate_item_attributes (model, i);
616 link_iter = g_menu_model_iterate_item_links (model, i);
617 contents = g_string_new (NULL);
618 attrs = g_string_new (NULL);
620 while (g_menu_attribute_iter_next (attr_iter))
622 const char *name = g_menu_attribute_iter_get_name (attr_iter);
623 GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
625 if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
628 str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
629 g_string_append (attrs, str);
638 printed = g_variant_print (value, TRUE);
639 str = g_markup_printf_escaped ("<attribute name='%s'>%s</attribute>\n", name, printed);
640 indent_string (contents, indent + tabstop);
641 g_string_append (contents, str);
642 g_variant_unref (value);
647 g_variant_unref (value);
649 g_object_unref (attr_iter);
651 while (g_menu_link_iter_next (link_iter))
653 const gchar *name = g_menu_link_iter_get_name (link_iter);
654 GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
657 if (contents->str[0])
658 g_string_append_c (contents, '\n');
660 str = g_markup_printf_escaped ("<link name='%s'>\n", name);
661 indent_string (contents, indent + tabstop);
662 g_string_append (contents, str);
665 g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
667 indent_string (contents, indent + tabstop);
668 g_string_append (contents, "</link>\n");
669 g_object_unref (menu);
671 g_object_unref (link_iter);
673 if (contents->str[0])
675 indent_string (string, indent);
676 g_string_append_printf (string, "<item%s>\n", attrs->str);
677 g_string_append (string, contents->str);
678 indent_string (string, indent);
679 g_string_append (string, "</item>\n");
686 g_string_append_c (string, '\n');
688 indent_string (string, indent);
689 g_string_append_printf (string, "<item%s/>\n", attrs->str);
693 g_string_free (contents, TRUE);
694 g_string_free (attrs, TRUE);
701 * g_menu_markup_print_stderr:
702 * @model: a #GMenuModel
704 * Print @model to stderr for debugging purposes.
706 * This debugging function will be removed in the future.
709 g_menu_markup_print_stderr (GMenuModel *model)
713 string = g_string_new ("<menu>\n");
714 g_menu_markup_print_string (string, model, 2, 2);
715 g_printerr ("%s</menu>\n", string->str);
716 g_string_free (string, TRUE);