/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "XbSilo"
#include "config.h"
#include <gio/gio.h>
#include <string.h>
#include "xb-builder-node-private.h"
#include "xb-silo-private.h"
#include "xb-string-private.h"
typedef struct {
GObject parent_instance;
guint32 offset;
gint priority;
XbBuilderNodeFlags flags;
gchar *element;
guint32 element_idx;
gchar *text;
guint32 text_idx;
gchar *tail;
guint32 tail_idx;
XbBuilderNode *parent; /* noref */
GPtrArray *children; /* of XbBuilderNode */
GPtrArray *attrs; /* of XbBuilderNodeAttr */
} XbBuilderNodePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (XbBuilderNode, xb_builder_node, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (xb_builder_node_get_instance_private (o))
/**
* xb_builder_node_has_flag:
* @self: a #XbBuilderNode
* @flag: a #XbBuilderNodeFlags
*
* Checks a flag on the builder node.
*
* Returns: %TRUE if @flag is set
*
* Since: 0.1.0
**/
gboolean
xb_builder_node_has_flag (XbBuilderNode *self, XbBuilderNodeFlags flag)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), FALSE);
return (priv->flags & flag) > 0;
}
/**
* xb_builder_node_add_flag:
* @self: a #XbBuilderNode
* @flag: a #XbBuilderNodeFlags
*
* Adds a flag to the builder node.
*
* Since: 0.1.0
**/
void
xb_builder_node_add_flag (XbBuilderNode *self, XbBuilderNodeFlags flag)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
priv->flags |= flag;
for (guint i = 0; i < priv->children->len; i++) {
XbBuilderNode *c = g_ptr_array_index (priv->children, i);
xb_builder_node_add_flag (c, flag);
}
}
/**
* xb_builder_node_get_element:
* @self: a #XbBuilderNode
*
* Gets the element from the builder node.
*
* Returns: string, or %NULL if unset
*
* Since: 0.1.0
**/
const gchar *
xb_builder_node_get_element (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
return priv->element;
}
/**
* xb_builder_node_set_element:
* @self: a #XbBuilderNode
* @element: a string element
*
* Sets the element name on the builder node.
*
* Since: 0.1.0
**/
void
xb_builder_node_set_element (XbBuilderNode *self, const gchar *element)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_free (priv->element);
priv->element = g_strdup (element);
}
/**
* xb_builder_node_get_attr:
* @self: a #XbBuilderNode
* @name: attribute name, e.g. `type`
*
* Gets an attribute from the builder node.
*
* Returns: string, or %NULL if unset
*
* Since: 0.1.0
**/
const gchar *
xb_builder_node_get_attr (XbBuilderNode *self, const gchar *name)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
g_return_val_if_fail (name != NULL, NULL);
for (guint i = 0; i < priv->attrs->len; i++) {
XbBuilderNodeAttr *a = g_ptr_array_index (priv->attrs, i);
if (g_strcmp0 (a->name, name) == 0)
return a->value;
}
return NULL;
}
/**
* xb_builder_node_get_attr_as_uint:
* @self: a #XbBuilderNode
* @name: attribute name, e.g. `priority`
*
* Gets an attribute from the builder node.
*
* Returns: integer, or 0 if unset
*
* Since: 0.1.3
**/
guint64
xb_builder_node_get_attr_as_uint (XbBuilderNode *self, const gchar *name)
{
const gchar *tmp = xb_builder_node_get_attr (self, name);
if (tmp == NULL)
return 0;
if (g_str_has_prefix (tmp, "0x"))
return g_ascii_strtoull (tmp + 2, NULL, 16);
return g_ascii_strtoll (tmp, NULL, 10);
}
/**
* xb_builder_node_get_text:
* @self: a #XbBuilderNode
*
* Gets the text from the builder node.
*
* Returns: string, or %NULL if unset
*
* Since: 0.1.0
**/
const gchar *
xb_builder_node_get_text (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
return priv->text;
}
/**
* xb_builder_node_get_text_as_uint:
* @self: a #XbBuilderNode
*
* Gets the text from the builder node.
*
* Returns: integer, or 0 if unset
*
* Since: 0.1.3
**/
guint64
xb_builder_node_get_text_as_uint (XbBuilderNode *self)
{
const gchar *tmp = xb_builder_node_get_text (self);
if (tmp == NULL)
return 0;
if (g_str_has_prefix (tmp, "0x"))
return g_ascii_strtoull (tmp + 2, NULL, 16);
return g_ascii_strtoll (tmp, NULL, 10);
}
/**
* xb_builder_node_get_tail:
* @self: a #XbBuilderNode
*
* Gets the tail from the builder node.
*
* Returns: string, or %NULL if unset
*
* Since: 0.1.12
**/
const gchar *
xb_builder_node_get_tail (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
return priv->tail;
}
/* private */
GPtrArray *
xb_builder_node_get_attrs (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
return priv->attrs;
}
static gchar *
xb_builder_node_parse_literal_text (XbBuilderNode *self, const gchar *text, gssize text_len)
{
GString *tmp;
guint newline_count = 0;
g_auto(GStrv) split = NULL;
gsize text_len_safe;
/* we know this has been pre-fixed */
text_len_safe = text_len >= 0 ? (gsize) text_len : strlen (text);
if (xb_builder_node_has_flag (self, XB_BUILDER_NODE_FLAG_LITERAL_TEXT))
return g_strndup (text, text_len_safe);
/* all whitespace? */
if (xb_string_isspace (text, text_len_safe))
return NULL;
/* all on one line, no trailing or leading whitespace */
if (g_strstr_len (text, text_len, "\n") == NULL)
return g_strndup (text, text_len_safe);
/* split the text into lines */
tmp = g_string_sized_new ((gsize) text_len_safe + 1);
split = g_strsplit (text, "\n", -1);
for (guint i = 0; split[i] != NULL; i++) {
/* remove leading and trailing whitespace */
g_strstrip (split[i]);
/* if this is a blank line we end the paragraph mode
* and swallow the newline. If we see exactly two
* newlines in sequence then do a paragraph break */
if (split[i][0] == '\0') {
newline_count++;
continue;
}
/* if the line just before this one was not a newline
* then seporate the words with a space */
if (newline_count == 1 && tmp->len > 0)
g_string_append (tmp, " ");
/* if we had more than one newline in sequence add a paragraph
* break */
if (newline_count > 1)
g_string_append (tmp, "\n\n");
/* add the actual stripped text */
g_string_append (tmp, split[i]);
/* this last section was paragraph */
newline_count = 1;
}
/* success */
return g_string_free (tmp, FALSE);
}
/**
* xb_builder_node_set_text:
* @self: a #XbBuilderNode
* @text: a string
* @text_len: length of @text, or -1 if @text is NUL terminated
*
* Sets the text on the builder node.
*
* Since: 0.1.0
**/
void
xb_builder_node_set_text (XbBuilderNode *self, const gchar *text, gssize text_len)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_return_if_fail (text != NULL);
/* old data */
g_free (priv->text);
priv->text = xb_builder_node_parse_literal_text (self, text, text_len);
priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TEXT;
}
/**
* xb_builder_node_set_tail:
* @self: a #XbBuilderNode
* @tail: a string
* @tail_len: length of @tail, or -1 if @tail is NUL terminated
*
* Sets the tail on the builder node.
*
* Since: 0.1.12
**/
void
xb_builder_node_set_tail (XbBuilderNode *self, const gchar *tail, gssize tail_len)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_return_if_fail (tail != NULL);
/* old data */
g_free (priv->tail);
priv->tail = xb_builder_node_parse_literal_text (self, tail, tail_len);
priv->flags |= XB_BUILDER_NODE_FLAG_HAS_TAIL;
}
/**
* xb_builder_node_set_attr:
* @self: a #XbBuilderNode
* @name: attribute name, e.g. `type`
* @value: attribute value, e.g. `desktop`
*
* Adds an attribute to the builder node.
*
* Since: 0.1.0
**/
void
xb_builder_node_set_attr (XbBuilderNode *self, const gchar *name, const gchar *value)
{
XbBuilderNodeAttr *a;
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_return_if_fail (name != NULL);
/* check for existing name */
for (guint i = 0; i < priv->attrs->len; i++) {
a = g_ptr_array_index (priv->attrs, i);
if (g_strcmp0 (a->name, name) == 0) {
g_free (a->value);
a->value = g_strdup (value);
return;
}
}
/* create new */
a = g_slice_new0 (XbBuilderNodeAttr);
a->name = g_strdup (name);
a->name_idx = XB_SILO_UNSET;
a->value = g_strdup (value);
a->value_idx = XB_SILO_UNSET;
g_ptr_array_add (priv->attrs, a);
}
/**
* xb_builder_node_remove_attr:
* @self: a #XbBuilderNode
* @name: attribute name, e.g. `type`
*
* Removes an attribute from the builder node.
*
* Since: 0.1.0
**/
void
xb_builder_node_remove_attr (XbBuilderNode *self, const gchar *name)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_return_if_fail (name != NULL);
for (guint i = 0; i < priv->attrs->len; i++) {
XbBuilderNodeAttr *a = g_ptr_array_index (priv->attrs, i);
if (g_strcmp0 (a->name, name) == 0) {
g_ptr_array_remove_index (priv->attrs, i);
break;
}
}
}
/**
* xb_builder_node_depth:
* @self: a #XbBuilderNode
*
* Gets the depth of the node tree, where 0 is the root node.
*
* Since: 0.1.1
**/
guint
xb_builder_node_depth (XbBuilderNode *self)
{
for (guint i = 0; ; i++) {
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
if (priv->parent == NULL)
return i;
self = priv->parent;
}
return 0;
}
/**
* xb_builder_node_add_child:
* @self: A XbBuilderNode
* @child: A XbBuilderNode
*
* Adds a child builder node.
*
* Since: 0.1.0
**/
void
xb_builder_node_add_child (XbBuilderNode *self, XbBuilderNode *child)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
XbBuilderNodePrivate *priv_child = GET_PRIVATE (child);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_return_if_fail (XB_IS_BUILDER_NODE (child));
g_return_if_fail (priv_child->parent == NULL);
/* no refcount */
priv_child->parent = self;
g_object_add_weak_pointer (G_OBJECT (self), (gpointer *) &priv_child->parent);
g_ptr_array_add (priv->children, g_object_ref (child));
}
/**
* xb_builder_node_remove_child:
* @self: A XbBuilderNode
* @child: A XbBuilderNode
*
* Removes a child builder node.
*
* Since: 0.1.1
**/
void
xb_builder_node_remove_child (XbBuilderNode *self, XbBuilderNode *child)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
XbBuilderNodePrivate *priv_child = GET_PRIVATE (child);
/* no refcount */
g_object_remove_weak_pointer (G_OBJECT (self), (gpointer *) &priv_child->parent);
priv_child->parent = NULL;
g_ptr_array_remove (priv->children, child);
}
/**
* xb_builder_node_unlink:
* @self: a #XbBuilderNode
*
* Unlinks a #XbBuilderNode from a tree, resulting in two separate trees.
*
* This should not be used from the function called by xb_builder_node_traverse()
* otherwise the entire tree will not be traversed.
*
* Instead use xb_builder_node_add_flag(bn,XB_BUILDER_NODE_FLAG_IGNORE);
*
* Since: 0.1.1
**/
void
xb_builder_node_unlink (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
if (priv->parent == NULL)
return;
xb_builder_node_remove_child (priv->parent, self);
}
/**
* xb_builder_node_get_parent:
* @self: a #XbBuilderNode
*
* Gets the parent node for the current node.
*
* Returns: (transfer full): a new #XbBuilderNode, or %NULL no parent exists.
*
* Since: 0.1.1
**/
XbBuilderNode *
xb_builder_node_get_parent (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
if (priv->parent == NULL)
return NULL;
return g_object_ref (priv->parent);
}
/**
* xb_builder_node_get_children:
* @self: a #XbBuilderNode
*
* Gets the children of the builder node.
*
* Returns: (transfer none) (element-type XbBuilderNode): children
*
* Since: 0.1.0
**/
GPtrArray *
xb_builder_node_get_children (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
return priv->children;
}
/**
* xb_builder_node_get_first_child:
* @self: a #XbBuilderNode
*
* Gets the first child of the builder node.
*
* Returns: (transfer none): a #XbBuilderNode, or %NULL
*
* Since: 0.1.12
**/
XbBuilderNode *
xb_builder_node_get_first_child (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
if (priv->children->len == 0)
return NULL;
return g_ptr_array_index (priv->children, 0);
}
/**
* xb_builder_node_get_last_child:
* @self: a #XbBuilderNode
*
* Gets the last child of the builder node.
*
* Returns: (transfer none): a #XbBuilderNode, or %NULL
*
* Since: 0.1.12
**/
XbBuilderNode *
xb_builder_node_get_last_child (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
if (priv->children->len == 0)
return NULL;
return g_ptr_array_index (priv->children, priv->children->len - 1);
}
/**
* xb_builder_node_get_child:
* @self: a #XbBuilderNode
* @element: An element name, e.g. "url"
* @text: (allow-none): node text, e.g. "gimp.desktop"
*
* Finds a child builder node by the element name, and optionally text value.
*
* Returns: (transfer full): a new #XbBuilderNode, or %NULL if not found
*
* Since: 0.1.1
**/
XbBuilderNode *
xb_builder_node_get_child (XbBuilderNode *self, const gchar *element, const gchar *text)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
g_return_val_if_fail (element != NULL, NULL);
for (guint i = 0; i < priv->children->len; i++) {
XbBuilderNode *child = g_ptr_array_index (priv->children, i);
if (g_strcmp0 (xb_builder_node_get_element (child), element) != 0)
continue;
if (text != NULL && g_strcmp0 (xb_builder_node_get_text (child), text) != 0)
continue;
return g_object_ref (child);
}
return NULL;
}
typedef struct {
gint max_depth;
XbBuilderNodeTraverseFunc func;
gpointer user_data;
GTraverseFlags flags;
GTraverseType order;
} XbBuilderNodeTraverseHelper;
static void
xb_builder_node_traverse_cb (XbBuilderNodeTraverseHelper *helper,
XbBuilderNode *bn,
gint depth)
{
GPtrArray *children = xb_builder_node_get_children (bn);
/* only leaves */
if (helper->flags == G_TRAVERSE_LEAVES &&
children->len > 0)
return;
/* only non-leaves */
if (helper->flags == G_TRAVERSE_NON_LEAVES &&
children->len == 0)
return;
/* recurse */
if (helper->order == G_PRE_ORDER) {
if (helper->func (bn, helper->user_data))
return;
}
if (helper->max_depth < 0 || depth < helper->max_depth) {
for (guint i = 0; i < children->len; i++) {
XbBuilderNode *bc = g_ptr_array_index (children, i);
xb_builder_node_traverse_cb (helper, bc, depth + 1);
}
}
if (helper->order == G_POST_ORDER) {
if (helper->func (bn, helper->user_data))
return;
}
}
/**
* xb_builder_node_traverse:
* @self: a #XbBuilderNode
* @order: a #GTraverseType, e.g. %G_PRE_ORDER
* @flags: a #GTraverseFlags, e.g. %G_TRAVERSE_ALL
* @max_depth: the maximum depth of the traversal, or -1 for no limit
* @func: (scope call): a #XbBuilderNodeTraverseFunc
* @user_data: user pointer to pass to @func, or %NULL
*
* Traverses a tree starting from @self. It calls the given function for each
* node visited.
*
* The traversal can be halted at any point by returning TRUE from @func.
*
* Since: 0.1.1
**/
void
xb_builder_node_traverse (XbBuilderNode *self,
GTraverseType order,
GTraverseFlags flags,
gint max_depth,
XbBuilderNodeTraverseFunc func,
gpointer user_data)
{
XbBuilderNodeTraverseHelper helper = {
.max_depth = max_depth,
.order = order,
.flags = flags,
.func = func,
.user_data = user_data,
};
if (order == G_PRE_ORDER || order == G_POST_ORDER) {
xb_builder_node_traverse_cb (&helper, self, 0);
return;
}
g_critical ("order %u not supported", order);
}
typedef struct {
XbBuilderNodeSortFunc func;
gpointer user_data;
} XbBuilderNodeSortHelper;
static gint
xb_builder_node_sort_children_cb (gconstpointer a, gconstpointer b, gpointer user_data)
{
XbBuilderNodeSortHelper *helper = (XbBuilderNodeSortHelper *) user_data;
XbBuilderNode *bn1 = *((XbBuilderNode **) a);
XbBuilderNode *bn2 = *((XbBuilderNode **) b);
return helper->func (bn1, bn2, helper->user_data);
}
/**
* xb_builder_node_sort_children:
* @self: a #XbBuilderNode
* @func: (scope call): a #XbBuilderNodeSortFunc
* @user_data: user pointer to pass to @func, or %NULL
*
* Sorts the node children using a custom sort function.
*
* Since: 0.1.3
**/
void
xb_builder_node_sort_children (XbBuilderNode *self,
XbBuilderNodeSortFunc func,
gpointer user_data)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
XbBuilderNodeSortHelper helper = {
.func = func,
.user_data = user_data,
};
g_return_if_fail (XB_IS_BUILDER_NODE (self));
g_return_if_fail (func != NULL);
g_ptr_array_sort_with_data (priv->children,
xb_builder_node_sort_children_cb,
&helper);
}
/* private */
guint32
xb_builder_node_get_offset (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), 0);
return priv->offset;
}
/* private */
void
xb_builder_node_set_offset (XbBuilderNode *self, guint32 offset)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
priv->offset = offset;
}
/* private */
gint
xb_builder_node_get_priority (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), 0);
return priv->priority;
}
/* private */
void
xb_builder_node_set_priority (XbBuilderNode *self, gint priority)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
priv->priority = priority;
}
/* private */
guint32
xb_builder_node_get_element_idx (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), 0);
return priv->element_idx;
}
/* private */
void
xb_builder_node_set_element_idx (XbBuilderNode *self, guint32 element_idx)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
priv->element_idx = element_idx;
}
/* private */
guint32
xb_builder_node_get_text_idx (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), 0);
return priv->text_idx;
}
/* private */
void
xb_builder_node_set_text_idx (XbBuilderNode *self, guint32 text_idx)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
priv->text_idx = text_idx;
}
/* private */
guint32
xb_builder_node_get_tail_idx (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), 0);
return priv->tail_idx;
}
/* private */
void
xb_builder_node_set_tail_idx (XbBuilderNode *self, guint32 tail_idx)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_BUILDER_NODE (self));
priv->tail_idx = tail_idx;
}
/* private */
guint32
xb_builder_node_size (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
guint32 sz = sizeof(XbSiloNode);
return sz + priv->attrs->len * sizeof(XbSiloAttr);
}
static void
xb_builder_node_attr_free (XbBuilderNodeAttr *attr)
{
g_free (attr->name);
g_free (attr->value);
g_slice_free (XbBuilderNodeAttr, attr);
}
static void
xb_builder_node_init (XbBuilderNode *self)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
priv->element_idx = XB_SILO_UNSET;
priv->text_idx = XB_SILO_UNSET;
priv->tail_idx = XB_SILO_UNSET;
priv->attrs = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_builder_node_attr_free);
priv->children = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
}
static void
xb_builder_node_finalize (GObject *obj)
{
XbBuilderNode *self = XB_BUILDER_NODE (obj);
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
g_free (priv->element);
g_free (priv->text);
g_free (priv->tail);
g_ptr_array_unref (priv->attrs);
g_ptr_array_unref (priv->children);
G_OBJECT_CLASS (xb_builder_node_parent_class)->finalize (obj);
}
static void
xb_builder_node_class_init (XbBuilderNodeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = xb_builder_node_finalize;
}
/**
* xb_builder_node_new:
* @element: An element name, e.g. "component"
*
* Creates a new builder node.
*
* Returns: (transfer full): a new #XbBuilderNode
*
* Since: 0.1.0
**/
XbBuilderNode *
xb_builder_node_new (const gchar *element)
{
XbBuilderNode *self = g_object_new (XB_TYPE_BUILDER_NODE, NULL);
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
priv->element = g_strdup (element);
return self;
}
/**
* xb_builder_node_insert: (skip)
* @parent: A XbBuilderNode, or %NULL
* @element: An element name, e.g. "component"
* @...: any attributes to add to the node, terminated by %NULL
*
* Creates a new builder node.
*
* Returns: (transfer full): a new #XbBuilderNode
*
* Since: 0.1.0
**/
XbBuilderNode *
xb_builder_node_insert (XbBuilderNode *parent, const gchar *element, ...)
{
XbBuilderNode *self = xb_builder_node_new (element);
va_list args;
const gchar *key;
const gchar *value;
/* add this node to the parent */
if (parent != NULL)
xb_builder_node_add_child (parent, self);
/* process the attrs valist */
va_start (args, element);
for (guint i = 0;; i++) {
key = va_arg (args, const gchar *);
if (key == NULL)
break;
value = va_arg (args, const gchar *);
if (value == NULL)
break;
xb_builder_node_set_attr (self, key, value);
}
va_end (args);
return self;
}
/**
* xb_builder_node_insert_text: (skip)
* @parent: A XbBuilderNode, or %NULL
* @element: An element name, e.g. "id"
* @text: (allow-none): node text, e.g. "gimp.desktop"
* @...: any attributes to add to the node, terminated by %NULL
*
* Creates a new builder node with optional node text.
*
* Since: 0.1.0
**/
void
xb_builder_node_insert_text (XbBuilderNode *parent,
const gchar *element,
const gchar *text,
...)
{
g_autoptr(XbBuilderNode) self = xb_builder_node_new (element);
va_list args;
const gchar *key;
const gchar *value;
g_return_if_fail (parent != NULL);
/* add this node to the parent */
xb_builder_node_add_child (parent, self);
if (text != NULL)
xb_builder_node_set_text (self, text, -1);
/* process the attrs valist */
va_start (args, text);
for (guint i = 0;; i++) {
key = va_arg (args, const gchar *);
if (key == NULL)
break;
value = va_arg (args, const gchar *);
if (value == NULL)
break;
xb_builder_node_set_attr (self, key, value);
}
va_end (args);
}
typedef struct {
GString *xml;
XbNodeExportFlags flags;
guint level;
} XbBuilderNodeExportHelper;
static gboolean
xb_builder_node_export_helper (XbBuilderNode *self,
XbBuilderNodeExportHelper *helper,
GError **error)
{
XbBuilderNodePrivate *priv = GET_PRIVATE (self);
/* add start of opening tag */
if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) {
for (guint i = 0; i < helper->level; i++)
g_string_append (helper->xml, " ");
}
g_string_append_printf (helper->xml, "<%s", priv->element);
/* add any attributes */
for (guint i = 0; i < priv->attrs->len; i++) {
XbBuilderNodeAttr *a = g_ptr_array_index (priv->attrs, i);
g_autofree gchar *key = xb_string_xml_escape (a->name);
g_autofree gchar *val = xb_string_xml_escape (a->value);
g_string_append_printf (helper->xml, " %s=\"%s\"", key, val);
}
/* finish the opening tag and add any text if it exists */
if (priv->text != NULL) {
g_autofree gchar *text = xb_string_xml_escape (priv->text);
g_string_append (helper->xml, ">");
g_string_append (helper->xml, text);
} else {
g_string_append (helper->xml, ">");
if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE)
g_string_append (helper->xml, "\n");
}
/* recurse deeper */
for (guint i = 0; i < priv->children->len; i++) {
XbBuilderNode *child = g_ptr_array_index (priv->children, i);
helper->level++;
if (!xb_builder_node_export_helper (child, helper, error))
return FALSE;
helper->level--;
}
/* add any tail if it exists */
if (priv->tail != NULL) {
g_autofree gchar *tail = xb_string_xml_escape (priv->tail);
g_string_append (helper->xml, tail);
}
/* add closing tag */
if ((helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_INDENT) > 0 &&
priv->text == NULL) {
for (guint i = 0; i < helper->level; i++)
g_string_append (helper->xml, " ");
}
g_string_append_printf (helper->xml, "</%s>", priv->element);
if (helper->flags & XB_NODE_EXPORT_FLAG_FORMAT_MULTILINE)
g_string_append (helper->xml, "\n");
return TRUE;
}
/**
* xb_builder_node_export:
* @self: a #XbBuilderNode
* @flags: some #XbNodeExportFlags, e.g. #XB_NODE_EXPORT_FLAG_NONE
* @error: the #GError, or %NULL
*
* Exports the node to XML.
*
* Returns: XML data, or %NULL for an error
*
* Since: 0.1.5
**/
gchar *
xb_builder_node_export (XbBuilderNode *self, XbNodeExportFlags flags, GError **error)
{
g_autoptr(GString) xml = g_string_new (NULL);
XbBuilderNodeExportHelper helper = {
.flags = flags,
.level = 0,
.xml = xml,
};
g_return_val_if_fail (XB_IS_BUILDER_NODE (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if ((flags & XB_NODE_EXPORT_FLAG_ADD_HEADER) > 0)
g_string_append (xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
if (!xb_builder_node_export_helper (self, &helper, error))
return NULL;
return g_string_free (g_steal_pointer (&xml), FALSE);
}