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
);
120 g_object_unref (state
->frame
.item
);
123 state
->frame
= *prev
;
125 g_slice_free (struct frame
, prev
);
129 add_string_attributes (GMenuItem
*item
,
131 const gchar
**values
)
135 for (i
= 0; names
[i
]; i
++)
137 g_menu_item_set_attribute (item
, names
[i
], "s", values
[i
]);
142 g_menu_markup_start_element (GMarkupParseContext
*context
,
143 const gchar
*element_name
,
144 const gchar
**attribute_names
,
145 const gchar
**attribute_values
,
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"))
167 if (COLLECT (STRDUP
, "id", &id
))
171 menu
= g_menu_new ();
172 g_hash_table_insert (state
->objects
, id
, menu
);
173 g_menu_markup_push_frame (state
, menu
, NULL
);
180 if (state
->frame
.menu
)
182 /* Can have '<item>', '<submenu>' or '<section>' here. */
183 if (g_str_equal (element_name
, "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
);
193 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
);
205 else if (g_str_equal (element_name
, "section"))
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
);
218 if (state
->frame
.item
)
220 /* Can have '<attribute>' or '<link>' here. */
221 if (g_str_equal (element_name
, "attribute"))
223 const gchar
*typestr
;
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
);
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
);
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
);
256 g_menu_markup_push_frame (state
, NULL
, NULL
);
262 if (g_str_equal (element_name
, "link"))
267 if (COLLECT (STRING
, "name", &name
,
268 STRING
| OPTIONAL
, "id", &id
))
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
);
277 g_hash_table_insert (state
->objects
, g_strdup (id
), g_object_ref (menu
));
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
);
295 g_set_error (error
, G_MARKUP_ERROR
, G_MARKUP_ERROR_UNKNOWN_ELEMENT
,
296 _("Element <%s> not allowed at toplevel"), element_name
);
301 g_menu_markup_end_element (GMarkupParseContext
*context
,
302 const gchar
*element_name
,
306 GMenuMarkupState
*state
= user_data
;
308 g_menu_markup_pop_frame (state
);
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
))
330 msgid
= g_variant_get_string (value
, NULL
);
332 msgstr
= g_dpgettext2 (state
->domain
, state
->context
, msgid
);
334 msgstr
= g_dgettext (state
->domain
, 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
);
350 g_free (state
->context
);
351 state
->context
= NULL
;
358 g_menu_markup_text (GMarkupParseContext
*context
,
364 GMenuMarkupState
*state
= user_data
;
367 for (i
= 0; i
< text_len
; i
++)
368 if (!g_ascii_isspace (text
[i
]))
371 g_string_append_len (state
->string
, text
, text_len
);
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
));
382 g_menu_markup_error (GMarkupParseContext
*context
,
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
);
398 g_string_free (state
->string
, TRUE
);
401 g_variant_type_free (state
->type
);
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
,
416 NULL
, /* passthrough */
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
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
445 g_menu_markup_parser_start (GMarkupParseContext
*context
,
449 GMenuMarkupState
*state
;
451 g_return_if_fail (context
!= NULL
);
453 state
= g_slice_new0 (GMenuMarkupState
);
455 state
->domain
= g_strdup (domain
);
458 state
->objects
= g_hash_table_ref (objects
);
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
481 g_menu_markup_parser_end (GMarkupParseContext
*context
)
483 GMenuMarkupState
*state
= g_markup_parse_context_pop (context
);
486 objects
= state
->objects
;
488 g_free (state
->domain
);
490 g_slice_free (GMenuMarkupState
, state
);
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
522 g_menu_markup_parser_start_menu (GMarkupParseContext
*context
,
526 GMenuMarkupState
*state
;
528 g_return_if_fail (context
!= NULL
);
530 state
= g_slice_new0 (GMenuMarkupState
);
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
554 g_menu_markup_parser_end_menu (GMarkupParseContext
*context
)
556 GMenuMarkupState
*state
= g_markup_parse_context_pop (context
);
559 menu
= state
->frame
.menu
;
562 g_hash_table_unref (state
->objects
);
563 g_free (state
->domain
);
564 g_slice_free (GMenuMarkupState
, state
);
570 indent_string (GString
*string
,
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.
593 g_menu_markup_print_string (GString
*string
,
598 gboolean need_nl
= FALSE
;
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
;
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
))
626 str
= g_markup_printf_escaped (" %s='%s'", name
, g_variant_get_string (value
, NULL
));
627 g_string_append (attrs
, str
);
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
);
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
);
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
);
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");
685 g_string_append_c (string
, '\n');
687 indent_string (string
, indent
);
688 g_string_append_printf (string
, "<item%s/>\n", attrs
->str
);
692 g_string_free (contents
, TRUE
);
693 g_string_free (attrs
, TRUE
);
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.
708 g_menu_markup_print_stderr (GMenuModel
*model
)
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
);