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 g_menu_markup_push_frame (GMenuMarkupState *state,
83 new = g_slice_new (struct frame);
86 state->frame.menu = menu;
87 state->frame.item = item;
88 state->frame.prev = new;
92 g_menu_markup_pop_frame (GMenuMarkupState *state)
94 struct frame *prev = state->frame.prev;
96 if (state->frame.item)
98 g_assert (prev->menu != NULL);
99 g_menu_append_item (prev->menu, state->frame.item);
100 g_object_unref (state->frame.item);
103 state->frame = *prev;
105 g_slice_free (struct frame, prev);
109 add_string_attributes (GMenuItem *item,
111 const gchar **values)
115 for (i = 0; names[i]; i++)
117 g_menu_item_set_attribute (item, names[i], "s", values[i]);
122 find_id_attribute (const gchar **names,
123 const gchar **values,
128 for (i = 0; names[i]; i++)
130 if (strcmp (names[i], "id") == 0)
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 BOOLEAN G_MARKUP_COLLECT_BOOLEAN
156 #define STRING G_MARKUP_COLLECT_STRING
158 if (!(state->frame.menu || state->frame.item || state->string))
160 /* Can only have <menu> here. */
161 if (g_str_equal (element_name, "menu"))
165 if (COLLECT (STRING, "id", &id))
169 menu = g_menu_new ();
171 g_hash_table_insert (state->objects, g_strdup (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"))
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);
203 if (find_id_attribute (attribute_names, attribute_values, &id))
206 g_hash_table_insert (state->objects, g_strdup (id), g_object_ref (menu));
208 g_object_unref (menu);
213 else if (g_str_equal (element_name, "section"))
219 menu = g_menu_new ();
220 item = g_menu_item_new_section (NULL, G_MENU_MODEL (menu));
221 add_string_attributes (item, attribute_names, attribute_values);
222 g_menu_markup_push_frame (state, menu, item);
224 if (find_id_attribute (attribute_names, attribute_values, &id))
227 g_hash_table_insert (state->objects, g_strdup (id), g_object_ref (menu));
229 g_object_unref (menu);
235 if (state->frame.item)
237 /* Can have '<attribute>' or '<link>' here. */
238 if (g_str_equal (element_name, "attribute"))
240 const gchar *typestr;
242 const gchar *context;
244 if (COLLECT (STRING, "name", &name,
245 OPTIONAL | BOOLEAN, "translatable", &state->translatable,
246 OPTIONAL | STRING, "context", &context,
247 OPTIONAL | STRING, "comments", NULL, /* ignore, just for translators */
248 OPTIONAL | STRING, "type", &typestr))
250 if (typestr && !g_variant_type_string_is_valid (typestr))
252 g_set_error (error, G_VARIANT_PARSE_ERROR,
253 G_VARIANT_PARSE_ERROR_INVALID_TYPE_STRING,
254 "Invalid GVariant type string '%s'", typestr);
258 state->type = typestr ? g_variant_type_new (typestr) : g_variant_type_copy (G_VARIANT_TYPE_STRING);
259 state->string = g_string_new (NULL);
260 state->attribute = g_quark_from_string (name);
261 state->context = g_strdup (context);
263 g_menu_markup_push_frame (state, NULL, NULL);
269 if (g_str_equal (element_name, "link"))
274 if (COLLECT (STRING, "name", &name,
275 STRING | OPTIONAL, "id", &id))
279 menu = g_menu_new ();
280 g_menu_item_set_link (state->frame.item, name, G_MENU_MODEL (menu));
281 g_menu_markup_push_frame (state, menu, NULL);
283 if (id != NULL && state->objects)
284 g_hash_table_insert (state->objects, g_strdup (id), g_object_ref (menu));
285 g_object_unref (menu);
293 const GSList *element_stack;
295 element_stack = g_markup_parse_context_get_element_stack (context);
297 if (element_stack->next)
298 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
299 _("Element <%s> not allowed inside <%s>"),
300 element_name, (const gchar *) element_stack->next->data);
303 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
304 _("Element <%s> not allowed at toplevel"), element_name);
309 g_menu_markup_end_element (GMarkupParseContext *context,
310 const gchar *element_name,
314 GMenuMarkupState *state = user_data;
316 g_menu_markup_pop_frame (state);
323 text = g_string_free (state->string, FALSE);
324 state->string = NULL;
326 /* If error is set here, it will follow us out, ending the parse.
327 * We still need to free everything, though.
329 if ((value = g_variant_parse (state->type, text, NULL, NULL, error)))
331 /* Deal with translatable string attributes */
332 if (state->domain && state->translatable &&
333 g_variant_type_equal (state->type, G_VARIANT_TYPE_STRING))
338 msgid = g_variant_get_string (value, NULL);
340 msgstr = g_dpgettext2 (state->domain, state->context, msgid);
342 msgstr = g_dgettext (state->domain, msgid);
346 g_variant_unref (value);
347 value = g_variant_new_string (msgstr);
351 g_menu_item_set_attribute_value (state->frame.item, g_quark_to_string (state->attribute), value);
352 g_variant_unref (value);
355 g_variant_type_free (state->type);
358 g_free (state->context);
359 state->context = NULL;
366 g_menu_markup_text (GMarkupParseContext *context,
372 GMenuMarkupState *state = user_data;
375 for (i = 0; i < text_len; i++)
376 if (!g_ascii_isspace (text[i]))
379 g_string_append_len (state->string, text, text_len);
382 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
383 _("text may not appear inside <%s>"),
384 g_markup_parse_context_get_element (context));
390 g_menu_markup_error (GMarkupParseContext *context,
394 GMenuMarkupState *state = user_data;
396 while (state->frame.prev)
398 struct frame *prev = state->frame.prev;
400 state->frame = *prev;
402 g_slice_free (struct frame, prev);
406 g_string_free (state->string, TRUE);
409 g_variant_type_free (state->type);
412 g_hash_table_unref (state->objects);
414 g_free (state->context);
416 g_slice_free (GMenuMarkupState, state);
419 static GMarkupParser g_menu_subparser =
421 g_menu_markup_start_element,
422 g_menu_markup_end_element,
424 NULL, /* passthrough */
429 * g_menu_markup_parser_start:
430 * @context: a #GMarkupParseContext
431 * @domain: (allow-none): translation domain for labels, or %NULL
432 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
434 * Begin parsing a group of menus in XML form.
436 * If @domain is not %NULL, it will be used to translate attributes
437 * that are marked as translatable, using gettext().
439 * If @objects is specified then it must be a #GHashTable that was
440 * created using g_hash_table_new_full() with g_str_hash(),
441 * g_str_equal(), g_free() and g_object_unref().
442 * Any named menus (ie: <tag class="starttag">menu</tag>,
443 * <tag class="starttag">submenu</tag>,
444 * <tag class="starttag">section</tag> or <tag class="starttag">link</tag>
445 * elements with an id='' attribute) that are encountered while parsing
446 * will be added to this table. Each toplevel menu must be named.
448 * If @objects is %NULL then an empty hash table will be created.
450 * This function should be called from the start_element function for
451 * the element representing the group containing the menus. In other
452 * words, the content inside of this element is expected to be a list of
458 g_menu_markup_parser_start (GMarkupParseContext *context,
462 GMenuMarkupState *state;
464 g_return_if_fail (context != NULL);
466 state = g_slice_new0 (GMenuMarkupState);
468 state->domain = g_strdup (domain);
471 state->objects = g_hash_table_ref (objects);
473 state->objects = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
475 g_markup_parse_context_push (context, &g_menu_subparser, state);
479 * g_menu_markup_parser_end:
480 * @context: a #GMarkupParseContext
482 * Stop the parsing of a set of menus and return the #GHashTable.
484 * The #GHashTable maps strings to #GObject instances. The parser only
485 * adds #GMenu instances to the table, but it may contain other types if
486 * a table was provided to g_menu_markup_parser_start().
488 * This call should be matched with g_menu_markup_parser_start().
489 * See that function for more information
491 * Returns: (transfer full): the #GHashTable containing the objects
496 g_menu_markup_parser_end (GMarkupParseContext *context)
498 GMenuMarkupState *state = g_markup_parse_context_pop (context);
501 objects = state->objects;
503 g_free (state->domain);
505 g_slice_free (GMenuMarkupState, state);
511 * g_menu_markup_parser_start_menu:
512 * @context: a #GMarkupParseContext
513 * @domain: (allow-none): translation domain for labels, or %NULL
514 * @objects: (allow-none): a #GHashTable for the objects, or %NULL
516 * Begin parsing the XML definition of a menu.
518 * This function should be called from the start_element function for
519 * the element representing the menu itself. In other words, the
520 * content inside of this element is expected to be a list of items.
522 * If @domain is not %NULL, it will be used to translate attributes
523 * that are marked as translatable, using gettext().
525 * If @objects is specified then it must be a #GHashTable that was
526 * created using g_hash_table_new_full() with g_str_hash(),
527 * g_str_equal(), g_free() and g_object_unref().
528 * Any named menus (ie: <tag class="starttag">submenu</tag>,
529 * <tag class="starttag">section</tag> or <tag class="starttag">link</tag>
530 * elements with an id='' attribute) that are encountered while parsing
531 * will be added to this table.
532 * Note that toplevel <tag class="starttag">menu</tag> is not added to
533 * the hash table, even if it has an id attribute.
535 * If @objects is %NULL then named menus will not be supported.
537 * You should call g_menu_markup_parser_end_menu() from the
538 * corresponding end_element function in order to collect the newly
544 g_menu_markup_parser_start_menu (GMarkupParseContext *context,
548 GMenuMarkupState *state;
550 g_return_if_fail (context != NULL);
552 state = g_slice_new0 (GMenuMarkupState);
555 state->objects = g_hash_table_ref (objects);
557 state->domain = g_strdup (domain);
559 g_markup_parse_context_push (context, &g_menu_subparser, state);
561 state->frame.menu = g_menu_new ();
565 * g_menu_markup_parser_end_menu:
566 * @context: a #GMarkupParseContext
568 * Stop the parsing of a menu and return the newly-created #GMenu.
570 * This call should be matched with g_menu_markup_parser_start_menu().
571 * See that function for more information
573 * Returns: (transfer full): the newly-created #GMenu
578 g_menu_markup_parser_end_menu (GMarkupParseContext *context)
580 GMenuMarkupState *state = g_markup_parse_context_pop (context);
583 menu = state->frame.menu;
586 g_hash_table_unref (state->objects);
587 g_free (state->domain);
588 g_slice_free (GMenuMarkupState, state);
594 indent_string (GString *string,
598 g_string_append_c (string, ' ');
602 * g_menu_markup_print_string:
603 * @string: a #GString
604 * @model: the #GMenuModel to print
605 * @indent: the intentation level to start at
606 * @tabstop: how much to indent each level
608 * Print the contents of @model to @string.
609 * Note that you have to provide the containing
610 * <tag class="starttag">menu</tag> element yourself.
617 g_menu_markup_print_string (GString *string,
622 gboolean need_nl = FALSE;
625 if G_UNLIKELY (string == NULL)
626 string = g_string_new (NULL);
628 n = g_menu_model_get_n_items (model);
630 for (i = 0; i < n; i++)
632 GMenuAttributeIter *attr_iter;
633 GMenuLinkIter *link_iter;
637 attr_iter = g_menu_model_iterate_item_attributes (model, i);
638 link_iter = g_menu_model_iterate_item_links (model, i);
639 contents = g_string_new (NULL);
640 attrs = g_string_new (NULL);
642 while (g_menu_attribute_iter_next (attr_iter))
644 const char *name = g_menu_attribute_iter_get_name (attr_iter);
645 GVariant *value = g_menu_attribute_iter_get_value (attr_iter);
647 if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING))
650 str = g_markup_printf_escaped (" %s='%s'", name, g_variant_get_string (value, NULL));
651 g_string_append (attrs, str);
661 printed = g_variant_print (value, TRUE);
662 type = g_variant_type_peek_string (g_variant_get_type (value));
663 str = g_markup_printf_escaped ("<attribute name='%s' type='%s'>%s</attribute>\n", name, type, printed);
664 indent_string (contents, indent + tabstop);
665 g_string_append (contents, str);
670 g_variant_unref (value);
672 g_object_unref (attr_iter);
674 while (g_menu_link_iter_next (link_iter))
676 const gchar *name = g_menu_link_iter_get_name (link_iter);
677 GMenuModel *menu = g_menu_link_iter_get_value (link_iter);
680 if (contents->str[0])
681 g_string_append_c (contents, '\n');
683 str = g_markup_printf_escaped ("<link name='%s'>\n", name);
684 indent_string (contents, indent + tabstop);
685 g_string_append (contents, str);
688 g_menu_markup_print_string (contents, menu, indent + 2 * tabstop, tabstop);
690 indent_string (contents, indent + tabstop);
691 g_string_append (contents, "</link>\n");
692 g_object_unref (menu);
694 g_object_unref (link_iter);
696 if (contents->str[0])
698 indent_string (string, indent);
699 g_string_append_printf (string, "<item%s>\n", attrs->str);
700 g_string_append (string, contents->str);
701 indent_string (string, indent);
702 g_string_append (string, "</item>\n");
709 g_string_append_c (string, '\n');
711 indent_string (string, indent);
712 g_string_append_printf (string, "<item%s/>\n", attrs->str);
716 g_string_free (contents, TRUE);
717 g_string_free (attrs, TRUE);
724 * g_menu_markup_print_stderr:
725 * @model: a #GMenuModel
727 * Print @model to stderr for debugging purposes.
729 * This debugging function will be removed in the future.
732 g_menu_markup_print_stderr (GMenuModel *model)
736 string = g_string_new ("<menu>\n");
737 g_menu_markup_print_string (string, model, 2, 2);
738 g_printerr ("%s</menu>\n", string->str);
739 g_string_free (string, TRUE);