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
|
0.12.0
|
||||||
- Add code coverage support
|
- Add code coverage support
|
||||||
|
- New API:
|
||||||
|
- xmpp_stanza_get_child_by_path()
|
||||||
|
|
||||||
0.11.0
|
0.11.0
|
||||||
- SASL EXTERNAL support (XEP-0178)
|
- 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");
|
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.
|
/** Get the first child of stanza with name.
|
||||||
* This function searches all the immediate children of stanza for a child
|
* This function searches all the immediate children of stanza for a child
|
||||||
* stanza that matches the name. The first matching child is returned.
|
* 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,
|
xmpp_stanza_t *xmpp_stanza_get_child_by_name_and_ns(xmpp_stanza_t *stanza,
|
||||||
const char *name,
|
const char *name,
|
||||||
const char *ns);
|
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);
|
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(xmpp_stanza_t *stanza, xmpp_stanza_t *child);
|
||||||
int xmpp_stanza_add_child_ex(xmpp_stanza_t *stanza,
|
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 *stanza;
|
||||||
xmpp_stanza_t *error;
|
xmpp_stanza_t *error;
|
||||||
xmpp_stanza_t *item;
|
xmpp_stanza_t *item;
|
||||||
|
xmpp_stanza_t *mood;
|
||||||
char *buf;
|
char *buf;
|
||||||
size_t buflen;
|
size_t buflen;
|
||||||
const char *attr[10];
|
const char *attr[10];
|
||||||
@@ -135,12 +136,29 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
|
|||||||
static const char *str_error =
|
static const char *str_error =
|
||||||
"<error type=\"cancel\"><service-unavailable "
|
"<error type=\"cancel\"><service-unavailable "
|
||||||
"xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/></error>";
|
"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);
|
stanza = xmpp_stanza_new_from_string(ctx, str);
|
||||||
assert(stanza != NULL);
|
assert(stanza != NULL);
|
||||||
error =
|
error =
|
||||||
xmpp_stanza_reply_error(stanza, "cancel", "service-unavailable", NULL);
|
xmpp_stanza_reply_error(stanza, "cancel", "service-unavailable", NULL);
|
||||||
assert(error != NULL);
|
assert(error != NULL);
|
||||||
|
mood = xmpp_stanza_new_from_string(ctx, str_mood);
|
||||||
|
assert(stanza != NULL);
|
||||||
|
|
||||||
assert(xmpp_stanza_get_to(error) != NULL);
|
assert(xmpp_stanza_get_to(error) != NULL);
|
||||||
COMPARE("romeo@montague.lit/home", xmpp_stanza_get_to(error));
|
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);
|
assert(xmpp_stanza_get_type(error) != NULL);
|
||||||
COMPARE("error", xmpp_stanza_get_type(error));
|
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);
|
ret = xmpp_stanza_get_attributes(error, attr, attrlen);
|
||||||
/* attr contains both attribute name and value. */
|
/* attr contains both attribute name and value. */
|
||||||
assert(ret == 8);
|
assert(ret == 8);
|
||||||
@@ -163,6 +221,7 @@ static void test_stanza_error(xmpp_ctx_t *ctx)
|
|||||||
COMPARE(str_error, buf);
|
COMPARE(str_error, buf);
|
||||||
|
|
||||||
xmpp_free(ctx, buf);
|
xmpp_free(ctx, buf);
|
||||||
|
xmpp_stanza_release(mood);
|
||||||
xmpp_stanza_release(stanza);
|
xmpp_stanza_release(stanza);
|
||||||
xmpp_stanza_release(error);
|
xmpp_stanza_release(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user