mirror of
https://github.com/labwc/labwc.git
synced 2025-10-29 05:40:24 -04:00
rcxml: convert dotted properties into nested nodes before processing
For example, the following node:
<keybind name.action="ShowMenu" menu.action="root-menu"
x.position.action="1" y.position.action="2" />
is converted to:
<keybind>
<action>
<name>ShowMenu</name>
<menu>root-menu</menu>
<position>
<x>1</x>
<y>2</y>
</position>
</action>
</keybind>
...before processing the entire xml tree. This is a preparation to prevent
breaking changes when we refactor rcxml.c to use recursion instead of
encoding nodes into dotted strings.
This commit is contained in:
parent
330c55e1b2
commit
503af10505
6 changed files with 302 additions and 3 deletions
29
include/common/xml.h
Normal file
29
include/common/xml.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
#ifndef LABWC_XML_H
|
||||||
|
#define LABWC_XML_H
|
||||||
|
|
||||||
|
#include <libxml/tree.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts dotted attributes into nested nodes.
|
||||||
|
* For example, the following node:
|
||||||
|
*
|
||||||
|
* <keybind name.action="ShowMenu" menu.action="root-menu"
|
||||||
|
* x.position.action="1" y.position.action="2" />
|
||||||
|
*
|
||||||
|
* is converted to:
|
||||||
|
*
|
||||||
|
* <keybind>
|
||||||
|
* <action>
|
||||||
|
* <name>ShowMenu</name>
|
||||||
|
* <menu>root-menu</menu>
|
||||||
|
* <position>
|
||||||
|
* <x>1</x>
|
||||||
|
* <y>2</y>
|
||||||
|
* </position>
|
||||||
|
* </action>
|
||||||
|
* </keybind>
|
||||||
|
*/
|
||||||
|
void lab_xml_expand_dotted_attributes(xmlNode *root);
|
||||||
|
|
||||||
|
#endif /* LABWC_XML_H */
|
||||||
|
|
@ -23,4 +23,5 @@ labwc_sources += files(
|
||||||
'surface-helpers.c',
|
'surface-helpers.c',
|
||||||
'spawn.c',
|
'spawn.c',
|
||||||
'string-helpers.c',
|
'string-helpers.c',
|
||||||
|
'xml.c',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
131
src/common/xml.c
Normal file
131
src/common/xml.c
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#include <assert.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include "common/xml.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Converts an attribute A.B.C="X" into <C><B><A>X</A></B></C>
|
||||||
|
*/
|
||||||
|
static xmlNode *
|
||||||
|
create_attribute_tree(const xmlAttr *attr)
|
||||||
|
{
|
||||||
|
gchar **parts = g_strsplit((char *)attr->name, ".", -1);
|
||||||
|
int length = g_strv_length(parts);
|
||||||
|
xmlNode *root_node = NULL;
|
||||||
|
xmlNode *parent_node = NULL;
|
||||||
|
xmlNode *current_node = NULL;
|
||||||
|
|
||||||
|
for (int i = length - 1; i >= 0; i--) {
|
||||||
|
gchar *part = parts[i];
|
||||||
|
if (!*part) {
|
||||||
|
/* Ignore empty string */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
current_node = xmlNewNode(NULL, (xmlChar *)part);
|
||||||
|
if (parent_node) {
|
||||||
|
xmlAddChild(parent_node, current_node);
|
||||||
|
} else {
|
||||||
|
root_node = current_node;
|
||||||
|
}
|
||||||
|
parent_node = current_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: empty attributes or attributes with only dots are forbidden
|
||||||
|
* and root_node becomes never NULL here.
|
||||||
|
*/
|
||||||
|
assert(root_node);
|
||||||
|
|
||||||
|
xmlChar *content = xmlNodeGetContent(attr->children);
|
||||||
|
xmlNodeSetContent(current_node, content);
|
||||||
|
xmlFree(content);
|
||||||
|
|
||||||
|
g_strfreev(parts);
|
||||||
|
return root_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Consider <keybind name.action="ShowMenu" x.position.action="1" y.position="2" />.
|
||||||
|
* These three attributes are represented by following trees.
|
||||||
|
* action(dst)---name
|
||||||
|
* action(src)---position---x
|
||||||
|
* action--------position---y
|
||||||
|
* When we call merge_two_trees(dst, src) for the first 2 trees above, we walk
|
||||||
|
* over the trees from their roots towards leaves, and merge the identical
|
||||||
|
* node 'action' like:
|
||||||
|
* action(dst)---name
|
||||||
|
* \--------position---x
|
||||||
|
* action(src)---position---y
|
||||||
|
* And when we call merge_two_trees(dst, src) again, we walk over the dst tree
|
||||||
|
* like 'action'->'position'->'x' and the src tree like 'action'->'position'->'y'.
|
||||||
|
* First, we merge the identical node 'action' again like:
|
||||||
|
* action---name
|
||||||
|
* \---position(dst)---x
|
||||||
|
* \--position(src)---y
|
||||||
|
* Next, we merge the identical node 'position' like:
|
||||||
|
* action---name
|
||||||
|
* \---position---x
|
||||||
|
* \----y
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
merge_two_trees(xmlNode *dst, xmlNode *src)
|
||||||
|
{
|
||||||
|
bool merged = false;
|
||||||
|
|
||||||
|
while (dst && src && src->children
|
||||||
|
&& !strcasecmp((char *)dst->name, (char *)src->name)) {
|
||||||
|
xmlNode *next_dst = dst->last;
|
||||||
|
xmlNode *next_src = src->children;
|
||||||
|
xmlAddChild(dst, src->children);
|
||||||
|
xmlUnlinkNode(src);
|
||||||
|
xmlFreeNode(src);
|
||||||
|
src = next_src;
|
||||||
|
dst = next_dst;
|
||||||
|
merged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
lab_xml_expand_dotted_attributes(xmlNode *parent)
|
||||||
|
{
|
||||||
|
xmlNode *old_first_child = parent->children;
|
||||||
|
xmlNode *prev_tree = NULL;
|
||||||
|
|
||||||
|
if (parent->type != XML_ELEMENT_NODE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (xmlAttr *attr = parent->properties; attr;) {
|
||||||
|
/* Convert the attribute with dots into an xml tree */
|
||||||
|
xmlNode *tree = create_attribute_tree(attr);
|
||||||
|
if (!tree) {
|
||||||
|
/* The attribute doesn't contain dots */
|
||||||
|
prev_tree = NULL;
|
||||||
|
attr = attr->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to merge the tree with the previous one */
|
||||||
|
if (!merge_two_trees(prev_tree, tree)) {
|
||||||
|
/* If not merged, add the tree as a new child */
|
||||||
|
if (old_first_child) {
|
||||||
|
xmlAddPrevSibling(old_first_child, tree);
|
||||||
|
} else {
|
||||||
|
xmlAddChild(parent, tree);
|
||||||
|
}
|
||||||
|
prev_tree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlAttr *next_attr = attr->next;
|
||||||
|
xmlRemoveProp(attr);
|
||||||
|
attr = next_attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (xmlNode *node = parent->children; node; node = node->next) {
|
||||||
|
lab_xml_expand_dotted_attributes(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
#include "common/parse-double.h"
|
#include "common/parse-double.h"
|
||||||
#include "common/string-helpers.h"
|
#include "common/string-helpers.h"
|
||||||
#include "common/three-state.h"
|
#include "common/three-state.h"
|
||||||
|
#include "common/xml.h"
|
||||||
#include "config/default-bindings.h"
|
#include "config/default-bindings.h"
|
||||||
#include "config/keybind.h"
|
#include "config/keybind.h"
|
||||||
#include "config/libinput.h"
|
#include "config/libinput.h"
|
||||||
|
|
@ -1509,7 +1510,9 @@ rcxml_parse_xml(struct buf *b)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
struct parser_state init_state = {0};
|
struct parser_state init_state = {0};
|
||||||
xml_tree_walk(xmlDocGetRootElement(d), &init_state);
|
xmlNode *root = xmlDocGetRootElement(d);
|
||||||
|
lab_xml_expand_dotted_attributes(root);
|
||||||
|
xml_tree_walk(root, &init_state);
|
||||||
xmlFreeDoc(d);
|
xmlFreeDoc(d);
|
||||||
xmlCleanupParser();
|
xmlCleanupParser();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,21 @@ test_lib = static_library(
|
||||||
sources: files(
|
sources: files(
|
||||||
'../src/common/buf.c',
|
'../src/common/buf.c',
|
||||||
'../src/common/mem.c',
|
'../src/common/mem.c',
|
||||||
'../src/common/string-helpers.c'
|
'../src/common/string-helpers.c',
|
||||||
|
'../src/common/xml.c',
|
||||||
),
|
),
|
||||||
include_directories: [labwc_inc],
|
include_directories: [labwc_inc],
|
||||||
dependencies: [dep_cmocka],
|
dependencies: [
|
||||||
|
dep_cmocka,
|
||||||
|
glib,
|
||||||
|
xml2,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
tests = [
|
tests = [
|
||||||
'buf-simple',
|
'buf-simple',
|
||||||
'str',
|
'str',
|
||||||
|
'xml',
|
||||||
]
|
]
|
||||||
|
|
||||||
foreach t : tests
|
foreach t : tests
|
||||||
|
|
@ -22,6 +28,7 @@ foreach t : tests
|
||||||
sources: '@0@.c'.format(t),
|
sources: '@0@.c'.format(t),
|
||||||
include_directories: [labwc_inc],
|
include_directories: [labwc_inc],
|
||||||
link_with: [test_lib],
|
link_with: [test_lib],
|
||||||
|
dependencies: [xml2],
|
||||||
),
|
),
|
||||||
is_parallel: false,
|
is_parallel: false,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
128
t/xml.c
Normal file
128
t/xml.c
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <cmocka.h>
|
||||||
|
#include "common/macros.h"
|
||||||
|
#include "common/xml.h"
|
||||||
|
|
||||||
|
struct test_case {
|
||||||
|
const char *before, *after;
|
||||||
|
} test_cases[] = {{
|
||||||
|
"<keybind name.action='ShowMenu' menu.action='root-menu' "
|
||||||
|
"x.position.action='1' y.position.action='2'/>",
|
||||||
|
|
||||||
|
"<keybind>"
|
||||||
|
"<action>"
|
||||||
|
"<name>ShowMenu</name>"
|
||||||
|
"<menu>root-menu</menu>"
|
||||||
|
"<position>"
|
||||||
|
"<x>1</x>"
|
||||||
|
"<y>2</y>"
|
||||||
|
"</position>"
|
||||||
|
"</action>"
|
||||||
|
"</keybind>"
|
||||||
|
}, {
|
||||||
|
"<AAA aaa='111' bbb='222'/>",
|
||||||
|
|
||||||
|
"<AAA>"
|
||||||
|
"<aaa>111</aaa>"
|
||||||
|
"<bbb>222</bbb>"
|
||||||
|
"</AAA>"
|
||||||
|
}, {
|
||||||
|
"<AAA aaa.bbb.ccc='111' ddd.ccc='222' eee.bbb.ccc='333'/>",
|
||||||
|
|
||||||
|
"<AAA><ccc>"
|
||||||
|
"<bbb><aaa>111</aaa></bbb>"
|
||||||
|
"<ddd>222</ddd>"
|
||||||
|
"<bbb><eee>333</eee></bbb>"
|
||||||
|
"</ccc></AAA>"
|
||||||
|
}, {
|
||||||
|
"<AAA aaa.bbb.ccc='111' bbb.ccc='222' ddd.bbb.ccc='333'/>",
|
||||||
|
|
||||||
|
"<AAA><ccc><bbb>"
|
||||||
|
"<aaa>111</aaa>"
|
||||||
|
"222"
|
||||||
|
"<ddd>333</ddd>"
|
||||||
|
"</bbb></ccc></AAA>"
|
||||||
|
}, {
|
||||||
|
"<AAA aaa.bbb='111' aaa.ddd='222'/>",
|
||||||
|
|
||||||
|
"<AAA>"
|
||||||
|
"<bbb><aaa>111</aaa></bbb>"
|
||||||
|
"<ddd><aaa>222</aaa></ddd>"
|
||||||
|
"</AAA>"
|
||||||
|
}, {
|
||||||
|
"<AAA aaa.bbb='111' bbb='222' ccc.bbb='333'/>",
|
||||||
|
|
||||||
|
"<AAA><bbb>"
|
||||||
|
"<aaa>111</aaa>"
|
||||||
|
"222"
|
||||||
|
"<ccc>333</ccc>"
|
||||||
|
"</bbb></AAA>",
|
||||||
|
}, {
|
||||||
|
"<AAA>"
|
||||||
|
"<BBB aaa.bbb='111'/>"
|
||||||
|
"<BBB aaa.bbb='111'/>"
|
||||||
|
"</AAA>",
|
||||||
|
|
||||||
|
"<AAA>"
|
||||||
|
"<BBB><bbb><aaa>111</aaa></bbb></BBB>"
|
||||||
|
"<BBB><bbb><aaa>111</aaa></bbb></BBB>"
|
||||||
|
"</AAA>",
|
||||||
|
}, {
|
||||||
|
"<AAA bbb.ccc='111'>"
|
||||||
|
"<BBB>222</BBB>"
|
||||||
|
"</AAA>",
|
||||||
|
|
||||||
|
"<AAA>"
|
||||||
|
"<ccc><bbb>111</bbb></ccc>"
|
||||||
|
"<BBB>222</BBB>"
|
||||||
|
"</AAA>",
|
||||||
|
}, {
|
||||||
|
"<AAA>"
|
||||||
|
"<BBB><CCC>111</CCC></BBB>"
|
||||||
|
"<BBB><CCC>111</CCC></BBB>"
|
||||||
|
"</AAA>",
|
||||||
|
|
||||||
|
"<AAA>"
|
||||||
|
"<BBB><CCC>111</CCC></BBB>"
|
||||||
|
"<BBB><CCC>111</CCC></BBB>"
|
||||||
|
"</AAA>",
|
||||||
|
}, {
|
||||||
|
"<AAA aaa..bbb.ccc.='111' />",
|
||||||
|
|
||||||
|
"<AAA><ccc><bbb><aaa>111</aaa></bbb></ccc></AAA>"
|
||||||
|
}};
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_lab_xml_expand_dotted_attributes(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(test_cases); i++) {
|
||||||
|
xmlDoc *doc = xmlReadDoc((xmlChar *)test_cases[i].before,
|
||||||
|
NULL, NULL, 0);
|
||||||
|
xmlNode *root = xmlDocGetRootElement(doc);
|
||||||
|
|
||||||
|
lab_xml_expand_dotted_attributes(root);
|
||||||
|
|
||||||
|
xmlBuffer *buf = xmlBufferCreate();
|
||||||
|
xmlNodeDump(buf, root->doc, root, 0, 0);
|
||||||
|
assert_string_equal(test_cases[i].after, (char *)buf->content);
|
||||||
|
xmlBufferFree(buf);
|
||||||
|
|
||||||
|
xmlFreeDoc(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_lab_xml_expand_dotted_attributes),
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue