Inserting a child node when list is empty (XForms) - insert

My problem is the following :
I usually have those data:
<structures>
<structure id="10">
<code>XXX</code>
</structure>
</structures>
so the table I display (single columns : code) is ok.
But in some cases, the data is the result a a query with no content, so the data is:
<structures/>
resulting in my table not displaying + error.
I am trying to insert, in the case of an empty instance, a single node so that the data would look like:
<structures>
<structure id="0"/>
</structures>
I am trying something like that :
<xforms:action ev:event="xforms-submit-done">
<xforms:insert if="0 = count(instance('{./instance-name}')/root/node())" context="instance('{./instance-name}')/root/node()" origin="xforms:element('structure', '')" />
</xforms:action>
but no node inserted when I look at the data in the inspector in the page.
Any obvious thing I am doing wrong?

There seems to be erros in your XPath if and context expressions:
if="0 = count(instance('{./instance-name}')/root/node())"
context="instance('{./instance-name}')/root/node()"
You are a using curly brackets { and }, I assume to have the behavior of attribute value templates (AVTs). But the if and context expressions are already XPath expressions, so you cannot use AVTs in them. Try instead:
if="0 = count(instance(instance-name)/root/node())"
context="instance(instance-name)/root/node()"
Also, the instance-name path is relative to something which might not be clear when reading or writing the expression. I would suggest using an absolute path for example instance('foo')/instance-name to make things clearer.
You don't provide the structure of the other instances, so I can tell for sure, but you'll expression above suppose that they have the form:
<xf:instance id="foo">
<some-root-element>
<root>
<structure/>
</root>
<some-root-element>
</xf:instance>
I don't know if that's what you intend.
Finally, you could replace count(something) = 0, with empty(something).

Related

How to select a specific category value in this Xpath expression

I have a feed here. I'm trying to create an XPath expression that returns items that have a category equal to Bananas. Due to the limitations in my XML parser, I can't use namespaces directly to select items.
The expression /rss/channel/item//*[name()='itunes:category'] returns this:
Element='<itunes:category
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
text="Apples"/>'
Element='<itunes:category
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
text="Bananas"/>'
...
And /rss/channel/item//*[name()='itunes:category']/#text returns this:
Attribute='text=Apples'
Attribute='text=Bananas'
...
But I can't figure out how to limit the response to just a single category (e.g., Bananas)?
I want some kind of expression like this:
/rss/channel/item//*[name()='itunes:category' and contains(., 'Bananas')]
But this doesn't work. It's not syntactically valid. What would be the right XPath expression syntax to just return Bananas?
Do you just mean to filter by attributes of item child, but still return item node?
/rss/channel/item/*[name()='itunes:category' and contains(#text,'Apples')]/parent::item
or simplier
/rss/channel/item[*[name()='itunes:category' and #text='Apples']]
I used Apples in example because using your example xml file there is 0 results for Bananas.

XPath Syntax - XSL 1.0

I'm trying to select all elements using XSL and XPath syntax where there is more than one pickup. I'd like to return the counter_name for each. Can someone please help me with the syntax? In this example there is only one counter_name with pickup locations, but there could be multiple locations where there are pickup counters.
XPATH
<xsl:value-of select="results/unique_locations/partner_location_ids[count(pickup) > 0]/counter_name" /><br/>
XML
<results>
<unique_locations>
<counter_name>Lake Buena Vista, FL</counter_name>
<is_airport>N</is_airport>
<partner_location_ids>
<pickup>
</pickup>
<dropoff>
<container>ZR-ORLS001</container>
<container>ET-ORLR062</container>
<container>HZ-ORLS011</container>
<container>HZ-ORLW015</container>
<container>AV-ORLR004</container>
</dropoff>
</partner_location_ids>
<counter_name>Orlando, FL</counter_name>
<is_airport>N</is_airport>
<partner_location_ids>
<pickup>
<container>ET-ORLC037</container>
<container>AV-ORLC021</container>
<container>ET-ORLC033</container>
<container>ET-ORLC035</container>
<container>HZ-ORLS007</container>
<container>HZ-ORLC004</container>
<container>HZ-ORLC002</container>
<container>ZR-ORLS002</container>
<container>BU-ORLE002</container>
<container>AV-ORLC019</container>
<container>ET-ORLR064</container>
<container>ET-ORLC001</container>
<container>ET-ORLR063</container>
<container>ET-ORLR061</container>
<container>HZ-ORLC011</container>
<container>HZ-ORLC054</container>
<container>HZ-ORLN003</container>
<container>HZ-ORLC007</container>
<container>HZ-ORLC005</container>
<container>ZA-ORLC002</container>
<container>ZA-ORLC003</container>
<container>ZA-ORLC001</container>
<container>AV-ORLC002</container>
<container>AV-ORLC001</container>
<container>BU-ORLS001</container>
<container>ET-ORLC012</container>
<container>AL-ORLR071</container>
<container>HZ-ORLC022</container>
<container>ET-ORLC051</container>
<container>HZ-ORLC025</container>
<container>HZ-ORLN018</container>
<container>HZ-ORLC017</container>
<container>AV-ORLN003</container>
<container>BU-ORLC002</container>
<container>BU-ORLC003</container>
<container>BU-ORLS006</container>
<container>ET-ORLC027</container>
<container>ET-ORLC022</container>
<container>AL-ORLR081</container>
<container>BU-ORLC005</container>
<container>HZ-ORLR029</container>
<container>HZ-ORLC032</container>
<container>HZ-ORLC031</container>
<container>HZ-ORLC030</container>
<container>ET-ORLC021</container>
</pickup>
<dropoff>
<container>HZ-ORLC003</container>
<container>ZA-ORLC004</container>
<container>BU-ORLW002</container>
<container>HZ-ORLC026</container>
<container>ZR-ORLC010</container>
<container>AL-ORLR073</container>
</dropoff>
</partner_location_ids>
</unique_locations>
Your XML structure is non-ideal, in that it appears to contain elements that are associated with each other by sequence, rather than exclusively by containment within the same element. But XPath can deal with that.
Supposing that the context node for evaluation of the XPath is the parent node of the <results> whose contents you are examining, it appears you want something along these lines:
results/unique_locations/partner_location_ids[pickup/*]/preceding-sibling::counter_name
Note in the first place the predicate: [pickup/*]. The expression within, interpreted in boolean context, evaluates to true if the expression matches any nodes. That's why we need pickup/*, not just pickup, to distinguish between <pickup> elements that contain child nodes and those that don't.
Additionally, observe the use of the preceding-sibling axis instead of the default child axis to step from each matching <partner_location_ids> to its corresponding (I think) <counter_name>.

How to refer to another instance in the iterate of the XForms action element?

I am using an XForms action along with iterate. The iterate selects a set (using XPath) of nodes and repeats the action for it.The problem is I have multiple conditions for selecting the node set.
There should not be a readOnly node.
Should not be part of the ignoreProperties list (this list is in another instance).
Code:
<xf:action ev:event="setValues" iterate="
instance('allProps')/props/prop[
not(readOnly) and
not(instance('ignoreProperties')/ignoredProperties/property[text() = name]
]
">
The first condition not(readOnly) works. But the second condition does not work. I feel there is some problem with the context of the XPath nodes.
How should I replace the second condition to achieve the result ?
The target XML is a simple ignoredProperties document:
<ignoredProperties>
<property>c_name</property>
<property>c_tel_no</property>
</ignoredProperties>
This should work:
<xf:action ev:event="setValues" iterate="
instance('allProps')/props/prop[
not(readOnly) and
not(name = instance('ignoreProperties')/ignoredProperties/property)
]
">
The = operator works against multiple nodes, returning all the ones that match. With not() you can express that you don't want a match.
Explicitly selecting .../property/text() will not be necessary.
There seems to be something wrong with your calls to instance(). If you have:
<xf:instance id="ignoredProperties">
<ignoredProperties>
<property>c_name</property>
<property>c_tel_no</property>
</ignoredProperties>
</xf:instance>
Then instance('ignoredProperties') returns the <ignoredProperties> element. So you should write:
<xf:action ev:event="setValues" iterate="
instance('allProps')/prop[
not(readOnly) and
not(instance('ignoreProperties')/property[text() = name])
]
">
This also assumes your allProps instance has a <props> root element.
Further, the second condition appears wrong, as already shown in another answer. Write instead:
not(name = instance('ignoreProperties')/property)
In XPath 2, you could clarify that your not() are testing on node existence by using empty() instead:
<xf:action ev:event="setValues" iterate="
instance('allProps')/prop[
empty(readOnly) and
not(name = instance('ignoreProperties')/property)
]
">

XPath HTML finding nodes

I am using HtmlAgilityPack to try to find HTML 'A' nodes that have a href attribute that contains a certain string, in my case the string '/groups/':
HtmlNodeCollection groups = source.DocumentNode.SelectNodes("//a[contains(#href, '/groups/')]");
Although the source code contains about 20 such nodes my code above is returning none which leads me to believe maybe I'm doing it incorrectly.
Is what I'm doing correct, and if not how can I select nodes that have a certain attribute that has a value that contains a certain string?
Your expression is seems to be correct as for me.
You don't post your source document (or at least a part of it). So, I'll be guessing.
The thing is, xpath is not cool for case insensitive comparison. If you have an <a> tag with href attribute that contains e.g. /Groups/ or /GROUPS/, it won't be matched. There is a workaround for this:
//a[contains(translate(#href, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '/groups/')]
As another option you could use LINQ with StringComparison.OrdinalIgnoreCase:
source.DocumentNode.Descendants("a")
.Where(a => a.GetAttributeValue("href", string.Empty)
.IndexOf("/groups/", StringComparison.OrdinalIgnoreCase) != -1
);

How to get H1,H2,H3,... using a single xpath expression

How can I get H1,H2,H3 contents in one single xpath expression?
I know I could do this.
//html/body/h1/text()
//html/body/h2/text()
//html/body/h3/text()
and so on.
Use:
/html/body/*[self::h1 or self::h2 or self::h3]/text()
The following expression is incorrect:
//html/body/*[local-name() = "h1"
or local-name() = "h2"
or local-name() = "h3"]/text()
because it may select text nodes that are children of unwanted:h1, different:h2, someWeirdNamespace:h3.
Another recommendation: Always avoid using // when the structure of the XML document is statically known. Using // most often results in significant inefficiencies because it causes the complete document (sub)tree roted in the context node to be traversed.

Resources