add xmpp_stanza_get_child_by_path()
... to provide an easier way to find long paths. After looking at some code in profanity-im/profanity#1605 I thought this could help to make libstrophe-user code easier to write (and read). Initially it started off as `xmpp_stanza_get_child_by_xpath()` but then I was annoyed when it came to handling all those potential corner cases like e.g. escaping URL's in the ns ... so here we go with a vararg approach :) Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
0.12.0
|
||||
- Add code coverage support
|
||||
- New API:
|
||||
- xmpp_stanza_get_child_by_path()
|
||||
|
||||
0.11.0
|
||||
- SASL EXTERNAL support (XEP-0178)
|
||||
|
||||
76
src/stanza.c
76
src/stanza.c
@@ -844,6 +844,82 @@ const char *xmpp_stanza_get_from(xmpp_stanza_t *stanza)
|
||||
return xmpp_stanza_get_attribute(stanza, "from");
|
||||
}
|
||||
|
||||
/** Get the first child of stanza following a path-like list of names.
|
||||
* This function searches the children and their children that match
|
||||
* the given path.
|
||||
*
|
||||
* * "name" - Search 'name'
|
||||
*
|
||||
* * "name[@ns='foo']" - Search 'name' which is in the namespace 'foo'
|
||||
*
|
||||
* The Syntax to pass namespaces is inspired by the XPATH way of passing
|
||||
* attributes.
|
||||
*
|
||||
* The namespace syntax only supports single quotes `'`.
|
||||
*
|
||||
* The \ref XMPP_STANZA_NAME_IN_NS macro is provided as a helper for names
|
||||
* in namespaces.
|
||||
*
|
||||
* @param stanza a Strophe stanza object
|
||||
* @param ... a var-args list that must be terminated by a NULL entry
|
||||
*
|
||||
* @return the matching child stanza object or NULL if no match was found
|
||||
*
|
||||
* @ingroup Stanza
|
||||
*/
|
||||
xmpp_stanza_t *xmpp_stanza_get_child_by_path(xmpp_stanza_t *stanza, ...)
|
||||
{
|
||||
xmpp_stanza_t *child = NULL;
|
||||
char *p, *tok, *attr, *saveattr, *ns = NULL;
|
||||
const char *xmlns;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, stanza);
|
||||
|
||||
while ((p = va_arg(ap, char *)) != NULL) {
|
||||
tok = xmpp_strdup(stanza->ctx, p);
|
||||
if (!tok) {
|
||||
child = NULL;
|
||||
break;
|
||||
}
|
||||
saveattr = ns = NULL;
|
||||
attr = xmpp_strtok_r(tok, "[", &saveattr);
|
||||
if (attr) {
|
||||
attr = xmpp_strtok_r(NULL, "]", &saveattr);
|
||||
if (attr) {
|
||||
if (!strncmp(attr, "@ns='", 5)) {
|
||||
ns = attr + 5;
|
||||
xmpp_strtok_r(ns, "'", &saveattr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!child) {
|
||||
if (strcmp(xmpp_stanza_get_name(stanza), tok))
|
||||
goto error_out;
|
||||
|
||||
if (ns) {
|
||||
xmlns = xmpp_stanza_get_ns(stanza);
|
||||
if (!xmlns || strcmp(xmlns, ns))
|
||||
goto error_out;
|
||||
}
|
||||
child = stanza;
|
||||
} else {
|
||||
if (!ns)
|
||||
child = xmpp_stanza_get_child_by_name(child, tok);
|
||||
else
|
||||
child = xmpp_stanza_get_child_by_name_and_ns(child, tok, ns);
|
||||
}
|
||||
error_out:
|
||||
xmpp_free(stanza->ctx, tok);
|
||||
if (!child)
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
|
||||
return p == NULL ? child : NULL;
|
||||
}
|
||||
|
||||
/** Get the first child of stanza with name.
|
||||
* This function searches all the immediate children of stanza for a child
|
||||
* stanza that matches the name. The first matching child is returned.
|
||||
|
||||
@@ -406,6 +406,9 @@ xmpp_stanza_t *xmpp_stanza_get_child_by_ns(xmpp_stanza_t *stanza,
|
||||
xmpp_stanza_t *xmpp_stanza_get_child_by_name_and_ns(xmpp_stanza_t *stanza,
|
||||
const char *name,
|
||||
const char *ns);
|
||||
/* helper macro for names with a namespace */
|
||||
#define XMPP_STANZA_NAME_IN_NS(name, ns) name "[@ns='" ns "']"
|
||||
xmpp_stanza_t *xmpp_stanza_get_child_by_path(xmpp_stanza_t *stanza, ...);
|
||||
xmpp_stanza_t *xmpp_stanza_get_next(xmpp_stanza_t *stanza);
|
||||
int xmpp_stanza_add_child(xmpp_stanza_t *stanza, xmpp_stanza_t *child);
|
||||
int xmpp_stanza_add_child_ex(xmpp_stanza_t *stanza,
|
||||
|
||||
@@ -123,6 +123,7 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
|
||||
xmpp_stanza_t *stanza;
|
||||
xmpp_stanza_t *error;
|
||||
xmpp_stanza_t *item;
|
||||
xmpp_stanza_t *mood;
|
||||
char *buf;
|
||||
size_t buflen;
|
||||
const char *attr[10];
|
||||
@@ -135,12 +136,29 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
|
||||
static const char *str_error =
|
||||
"<error type=\"cancel\"><service-unavailable "
|
||||
"xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/></error>";
|
||||
// clang-format off
|
||||
static const char *str_mood =
|
||||
"<iq from='juliet@capulet.lit/balcony' id='publish1' type='set'>"
|
||||
"<pubsub xmlns='http://jabber.org/protocol/pubsub'>"
|
||||
"<publish node='http://jabber.org/protocol/mood'>"
|
||||
"<item>"
|
||||
"<mood xmlns='http://jabber.org/protocol/mood'>"
|
||||
"<annoyed/>"
|
||||
"<text>curse my nurse!</text>"
|
||||
"</mood>"
|
||||
"</item>"
|
||||
"</publish>"
|
||||
"</pubsub>"
|
||||
"</iq>";
|
||||
// clang-format on
|
||||
|
||||
stanza = xmpp_stanza_new_from_string(ctx, str);
|
||||
assert(stanza != NULL);
|
||||
error =
|
||||
xmpp_stanza_reply_error(stanza, "cancel", "service-unavailable", NULL);
|
||||
assert(error != NULL);
|
||||
mood = xmpp_stanza_new_from_string(ctx, str_mood);
|
||||
assert(stanza != NULL);
|
||||
|
||||
assert(xmpp_stanza_get_to(error) != NULL);
|
||||
COMPARE("romeo@montague.lit/home", xmpp_stanza_get_to(error));
|
||||
@@ -151,6 +169,46 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
|
||||
assert(xmpp_stanza_get_type(error) != NULL);
|
||||
COMPARE("error", xmpp_stanza_get_type(error));
|
||||
|
||||
/* FAIL - no list given */
|
||||
item = xmpp_stanza_get_child_by_path(mood, NULL);
|
||||
assert(item == NULL);
|
||||
|
||||
/* FAIL - first entry doesn't match */
|
||||
item = xmpp_stanza_get_child_by_path(mood, "foo", NULL);
|
||||
assert(item == NULL);
|
||||
|
||||
/* FAIL - 'iq' has no namespace */
|
||||
item = xmpp_stanza_get_child_by_path(
|
||||
mood, XMPP_STANZA_NAME_IN_NS("iq", "foobar"),
|
||||
XMPP_STANZA_NAME_IN_NS("pubsub", "http://jabber.org/protocol/pubsub"),
|
||||
"publish", "item", "mood", NULL);
|
||||
assert(item == NULL);
|
||||
|
||||
/* FAIL - 'pubsub' is in another namespace */
|
||||
item = xmpp_stanza_get_child_by_path(
|
||||
mood, "iq",
|
||||
XMPP_STANZA_NAME_IN_NS("pubsub", "http://jabber.org/protocol/foobar"),
|
||||
"publish", "item", "mood", NULL);
|
||||
assert(item == NULL);
|
||||
|
||||
item = xmpp_stanza_get_child_by_path(mood, "iq", "pubsub", "publish",
|
||||
"item", "mood", NULL);
|
||||
assert(item != NULL);
|
||||
assert(xmpp_stanza_get_children(item) != NULL);
|
||||
assert(xmpp_stanza_get_name(xmpp_stanza_get_children(item)) != NULL);
|
||||
COMPARE("annoyed", xmpp_stanza_get_name(xmpp_stanza_get_children(item)));
|
||||
|
||||
item = xmpp_stanza_get_child_by_path(
|
||||
mood, "iq",
|
||||
XMPP_STANZA_NAME_IN_NS("pubsub", "http://jabber.org/protocol/pubsub"),
|
||||
"publish", "item",
|
||||
XMPP_STANZA_NAME_IN_NS("mood", "http://jabber.org/protocol/mood"),
|
||||
NULL);
|
||||
assert(item != NULL);
|
||||
assert(xmpp_stanza_get_children(item) != NULL);
|
||||
assert(xmpp_stanza_get_name(xmpp_stanza_get_children(item)) != NULL);
|
||||
COMPARE("annoyed", xmpp_stanza_get_name(xmpp_stanza_get_children(item)));
|
||||
|
||||
ret = xmpp_stanza_get_attributes(error, attr, attrlen);
|
||||
/* attr contains both attribute name and value. */
|
||||
assert(ret == 8);
|
||||
@@ -163,6 +221,7 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
|
||||
COMPARE(str_error, buf);
|
||||
|
||||
xmpp_free(ctx, buf);
|
||||
xmpp_stanza_release(mood);
|
||||
xmpp_stanza_release(stanza);
|
||||
xmpp_stanza_release(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user