ancestor-or-self - xpath

I have the following xml:
<?xml version="1.0" encoding="utf-8" ?>
<ROLES>
<ROLE type="A">
<USER name="w" />
<USER name="x" />
<ROLE type="B">
<USER name="x" />
<USER name="y" />
</ROLE>
<ROLE type="C">
<USER name="x" />
<USER name="y" />
<USER name="z" />
</ROLE>
</ROLE>
<ROLE type ="D">
<USER name="w" />
</ROLE>
</ROLES>
and I want to find all USER nodes with name="x" and which are immediate children of ROLE nodes with attribute "type" equals "C" and their ancestors with name="x" (probably by using ancestor-or-self axis). In this case, the nodeset should
contain two nodes (not three, since the occurrence of x under B should
not count).
What is the correct XPath expression that would do it? Why doesn't the following expression work?
/ROLES//ROLE[#type='C']/USER[#name='x']/ancestor-or-self::USER[#name='x']
(this returns only one node, probably the self axis, and not the ancestors)
Any help will be most appreciated.

I want to find all USER nodes with name="x"…
//USER[#name = 'x']
…which are immediate children of ROLE nodes with attribute "type" equals "C"…
//USER[#name = 'x' and parent::ROLE[#type = 'C']]
…and their ancestors with name="x".
?
I don't see any ancestors that could possibly have the name="x". What do you mean?
EDIT: Ah, I think I understand. What you mean is:
…and their ancestor's children that are USERs with the name="x"
//USER[#name = 'x' and parent::ROLE[#type = 'C']]/ancestor::ROLE/USER[#name = 'x']
And now for the question why your XPath doesn't work:
/ROLES//ROLE[#type='C']/USER[#name='x']/ancestor-or-self::USER[#name='x']
selects
all ROLEs that are descendant of /ROLES ("/ROLES//ROLE")…
…that have #type='C' ("/ROLES//ROLE[#type='C']")…
…of their USER children those that have #name='x' (/USER[#name='x'])
…and going from there all ancestor-or-self::USERs having #name='x'
the last location step breaks it. There are no USER ancestors, only ROLE ancestors.

Related

Freeswitch: mod_xml_curl and call groups

I am currently switching from static configs to using mod_xml_curl and have encountered a problem with setting up call groups.
Inside my dialplan (served dynamically, working as expected) I am bridging to a group:
<action application="bridge" data="${group_call(call-group#domain-a.com)}"/>
Freeswitch is making a request with section=directory&action=group_call to the web server, to which I respond with a chunk of the directory containing the group and all relevant users:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="freeswitch/xml">
<section name="directory">
<domain name="domain-a.com">
<params>
<param name="dial-string" value="{presence_id=${dialed_user}#${dialed_domain}}${sofia_contact(${dialed_user}#${dialed_domain})}" />
</params>
<variables>
<variable name="user_context" value="domain-a.com" />
</variables>
<group name="call-group">
<users>
<user id="john" number-alias="1000">
<params>
<param name="password" value="1234" />
<param name="vm-password" value="1000" />
</params>
<variables>
<variable name="toll_allow" value="domestic,international,local" />
<variable name="accountcode" value="1000" />
<variable name="outbound_caller_id_name" value="John at domain-a.com" />
<variable name="outbound_caller_id_number" value="1234567" />
</variables>
</user>
<user id="lucy" number-alias="1001">
<params>
<param name="password" value="1234" />
<param name="vm-password" value="1000" />
</params>
<variables>
<variable name="toll_allow" value="domestic,international,local" />
<variable name="accountcode" value="1001" />
<variable name="outbound_caller_id_name" value="Lucy" />
<variable name="outbound_caller_id_number" value="12345678" />
</variables>
</user>
</users>
</group>
</domain>
</section>
</document>
However, group_call() seems to fail and in the logs I get ``:
2016-02-24 10:42:14.249534 [DEBUG] mod_dptools.c:1498 SET sofia/internal/michael#domain-a.com [call_timeout]=[15]
2016-02-24 10:42:14.529107 [CONSOLE] mod_xml_curl.c:323 XML response is in /tmp/2f772a8a-4c3a-46f2-834f-b9ba2c735feb.tmp.xml
EXECUTE sofia/internal/michael#domain-a.com bridge(error/NO_ROUTE_DESTINATION)
Perhaps anyone has experience setting up group calls with mod_xml_curl and could explain what exactly Freeswitch is expecting in the response?
After a little gnashing of teeth, I got this working. The clue was here underneath the group_call description:
Please note: If you need to have outgoing user variables set in leg B,
make sure you don't have dial-string and group-dial-string in your
domain or dialed group variables list; instead set dial-string or
group-dial-string in the default group of the user. This way
group_call will return user/101 and user/ would set all your user
variables to the leg B channel.
So, when you receive an action of type group_call, move the dial-string param to the group level, so instead of
<domain name="domain-a.com">
<params>
<param name="dial-string" value="{presence_id=${dialed_user}#${dialed_domain}}${sofia_contact(${dialed_user}#${dialed_domain})}" />
</params>
<variables>
<variable name="user_context" value="domain-a.com" />
</variables>
<group name="call-group">
<users>
<user id="john" number-alias="1000">
...
send this
<domain name="domain-a.com">
<variables>
<variable name="user_context" value="domain-a.com" />
</variables>
<group name="call-group">
<params>
<param name="dial-string" value="{presence_id=${dialed_user}#${dialed_domain}}${sofia_contact(${dialed_user}#${dialed_domain})}" />
</params>
<users>
<user id="john" number-alias="1000">
...
After I made that change, everything was hunky dory. Cheers!
It's related to your dialplan.It's not generated perfectly. just check the domain-a.com context in the dialplan.

Referencing specific element(s) in a RelaxNG schema with externalRef

So I have one RelaxNG schema that references another:
<define name="review">
<element name="review">
<externalRef href="other.rng"/>
</element>
</define>
other.rng:
<start>
<choice>
<ref name="good"/>
<ref name="bad"/>
</choice>
</start>
<define name="good">
<element name="good"/>
</define>
<define name="bad">
<element name="bad"/>
</define>
Is there any way I can import only <good>, but not allow <bad>? The goal being:
<review><good/></review>: valid
<review><bad/></review>: invalid
The grammar you import with externalRef can't be modified. To achieve the kind of validation you're after, I see this method :
<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<include href="other.rng">
<start combine="choice">
<ref name="review"/>
</start>
</include>
<define name="review">
<element name="review">
<ref name="good"/>
</element>
</define>
</grammar>
You include the other schema.
You override the start element in the include (good and bad elements won't be possible root).
The specification says :
If the include element has a start component, then all start
components are removed from the grammar element.
You make a reference to the good element in your review definition.

XDT Config Transforms - ReplaceAll?

I've got a custom section in my web.config file similar to this structure:
<Messages>
<Message id="1'>
<Property Name="foo" value="bar" />
</Message>
<Message id="2'>
<Property Name="foo" value="bar2" />
</Message>
</Messages>
I want to apply a custom transformation on this such that I can change the value of ALL instances of the Property element with Name="foo" - but I cant seem to get it to work.
I've tried:
<Messages>
<Message>
<Property Name="foo" value="updated" xdt:Locator=Match(Name) xdt:Transform="Replace" />
</Message>
</Mesasges>
I can remove all the elements by replacing the Transform=Replace with a Transform=RemoveAll - any ideas how I can achieve something similar to replace all the values?
It seems like Transform:Replace only replaces the first matched element from the documentation on msdn: ...If more than one element is selected, only the first selected element is replaced. I solved this issue by using a combination of Match-Conditions and SetAttributes, something like:
<Messages>
<Message>
<Property value="updated" xdt:Locator=Condition(#Name='foo') xdt:Transform="SetAttributes(value)" />
</Message>
</Messages>

UrlRewriter.net Expression Examples

I need some web.config examples for each expression types below :
$number
The last substring matched by group number number.
$<name>
The last substring matched by group named name matched by (?< name > ).
${property}
The value of the property when the expression is evaluated.
${transform(value)}
The result of calling the transform on the specified value.
${map:value}
The result of mapping the specified value using the map. Replaced with empty string if no mapping exists.
${map:value|default}
The result of mapping the specified value using the map. Replaced with the default if no mapping exists.
Sample:
<rewriter>
<if url="/tags/(.+)" rewrite="/tagcloud.aspx?tag=$1" />
<!-- same thing as <rewrite url="/tags/(.+)" to="/tagcloud.aspx?tag=$1" /> -->
</rewriter>
Thank you very much !
Here's what I found/guessed. Not tested.
$number : http://urlrewriter.net/index.php/support/using
<rewrite url="^(.*)/(\?.+)?$" to="$1/default.aspx$2?" />
$1 matches (.*)
$2 matches (\?.+)
$< name > : this one I'm not so sure on the regex, couldn't find anything in documentation
<rewrite url="^(?<group1>(.*))/(\?.+)?$" to="$<group1>/default.aspx$2?" />
$<group1> matches
${property} : http://urlrewriter.net/index.php/support/reference/actions/set-property
<set property="branch" value="$3" />
<rewrite to="/showbranch.aspx?branch=${branch}" />
${transform(value)} : http://urlrewriter.net/index.php/support/reference/transforms
<set property="transform-name" value="lower" />
<set property="value-to-transform" value="THIS WAS UPPER CASE" />
<redirect to="/WebForm1.aspx?q=${encode(${${transform-name}(${value-to-transform})})}" />
results in "/WebForm1.aspx?q=this+was+upper+case"
${map:value} : http://urlrewriter.net/index.php/support/reference/transforms/static
<mapping name="areas">
<map from="sydney" to="1" />
<map from="melbourne" to="2" />
<map from="brisbane" to="3" />
</mapping>
<rewrite to="/area.aspx?area=${areas:$3}" />
results in "/area.aspx?area=brisbane"
${map:value|default}
<mapping name="areas">
<map from="sydney" to="1" />
<map from="melbourne" to="2" />
<map from="brisbane" to="3" />
</mapping>
<rewrite to="/area.aspx?area=${areas:$4|perth}" />
results in "/area.aspx?area=perth"

Working With Nested XPath Predicates ... Refined

All great answers! But the question deserves refinement ...
I've got the following sample XML ...
<objects>
<object objectId="1123" ... />
<properties refObjectId="1123" ... />
<properties refObjectId="1123" refPropertyId="2311" ... />
<properties refObjectId="1123" refPropertyId="4611" ... />
<object objectId="2123" ... />
<properties refObjectId="2123" refPropertyId="4311" ... />
<properties refObjectId="2123" refPropertyId="8611" ... />
....
</objects>
... and the following XPath query ...
//object[//properties[#refObjectId=#objectId and not(#refPropertyId)]]
I thought this query would return all object nodes where there is a properties node that has a refObjectId attribute that equals the objectId attribute of the object node and no 'refPropertyId' attribute ... namely object 1123 only, not object 2123 ... but it doesn't. It seems the #objectId in the nested predicate does not refer to the objectId attribute of the object node.
Any ideas? I know the XML structure isn't nested as you would expect, but there are reasons for this structure.
Generally you should avoid using // where you can. I'd consider rephrasing:
//object[../properties/#refObjectId=#objectId]
In the expression provided, your nested predicate is actually checking for
//properties/#refObjectId=//properties/#objectId
of which there are none.
I hope this helps!
EDIT: Since the question has been updated here is an updated response:
You added "It seems the #objectId in the nested predicate does not refer to the objectId attribute of the object node." You're absolutely right! So let's fix it!!
//object[../properties[not(#refPropertyId)]/#refObjectId=#objectId]
This should be closer to what you're after!
Try this:
//objects[object/#objectId = properties/#refObjectId]/object
This should work:
//objects/object[#objectId = ../properties/#refObjectId]
I am not sure how your xml is. However, if it is in the following format:
<objects>
<object objectId="1111" />
<properties refObjectId="1111" />
<object objectId="2111" />
<properties refObjectId="3111" />
<object objectId="4111" />
<properties refObjectId="5111" />
<object objectId="6111" />
<properties refObjectId="4111" />
<object objectId="7111" />
<properties refObjectId="7111" />
</objects>
Then you should use the following xpath to get only objects 1111 and 7111. The result should not include 4111 because the properties where refObjectId = 4111 does not immediately follow the object whose objectId=4111.
//objects/properties[#refObjectId = preceding::object[1]/#objectId]/preceding::object[1]
Assuming that all <properties> nodes that belong to a given <object> actually follow that object (your input seems to imply that), you could do:
/objects/properties[
#refObjectId = preceding-sibling::object[1]/#objectId
and
not(#refPropertyId)
]/preceding-sibling::object[1]
This should perform pretty well.
If you happen to be in XSLT, things get a lot simpler:
<xsl:key name="kPropertiesByObjectId" match="properties" use="#refObjectId" />
and
<xsl:template match="object">
<!-- This tests for an empty node-set. Non-empty node sets can only happen
for objects with at least one <properties> node without #refPropertyId -->
<xsl:if test="key('kPropertiesByObjectId', #objectId)[not(#refPropertyId)]">
<xsl:copy-of select="." />
</xsl:if>
</xsl:template>
In the XSLT case, the order of object and proerties nodes becomes irrelevant.

Resources