XPath to find sibling attribute based on sibling condition - xpath

I want to find the postal code based on AddressID in my XML.
<Address City="Londong" County="Greater London" FlatNumber="" HouseNumber="702" PostCode="EH14 3HW" Street="" AddressID="0" isPrimary="1"/>
<Address City="Braintree" County="Essex" FlatNumber="" HouseNumber="586" PostCode="CM79AB" Street="Blocking End" AddressID="1" isPrimary="0"/>
I'm trying different XPath statements and not getting a value or getting null. I'm trying to say, Give me the PostCode where the AddressID = 1.
I've tried the following:
//Address[AddressID = 1]#PostCode
//Address[AddressID = 1]/#PostCode
//Address[AddressID = 1 #PostCode]

Your AddressID is an attribute (same as the PostCode), so you need to use a # for both.
//Address[#AddressID="1"]/#PostCode
The query you were trying, //Address[AddressID = 1]/#PostCode, looks for something like this:
<Address PostCode="ABC"> <!-- PostCode is an attribute (has an #) -->
<AddressID>1</AddressID> <!-- AddressID is a child element (no #) -->
</Address>

Related

SchemaTron rule to find invalid records

I am trying to validate the following XML using the Schematron rule.
XML:
<?xml version="1.0" encoding="utf-8"?>
<Biotic><Maul><Number>1</Number>
<Record><Code IDREF="a1"/>
<Detail><ItemID>1</ItemID></Detail>
<Detail><ItemID>3</ItemID></Detail>
</Record>
<Record><Code IDREF="b1"/>
<Detail><ItemID>3</ItemID></Detail>
<Detail><ItemID>4</ItemID></Detail>
</Record>
<Record><Code IDREF="b1"/>
<Detail><ItemID>4</ItemID></Detail>
<Detail><ItemID>6</ItemID></Detail>
</Record>
<Record><Code IDREF="c1"/>
<Detail><ItemID>5</ItemID></Detail>
<Detail><ItemID>5</ItemID></Detail>
</Record>
</Maul></Biotic>
And the check is "ItemID should be unique for the given Code within the given Maul."
So as per requirement Records with Code b1 is not valid because ItemId 4 exists in both records.
Similarly, record C1 is also not valid because c1 have two nodes with itemId 5.
Record a1 is valid, even ItemID 3 exists in the next record but the code is different.
Schematron rule I tried:
<?xml version="1.0" encoding="utf-8" ?><schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<title>Schematron validation rule</title>
<pattern id="P1">
<rule context="Maul/Record" id="R1">
<let name="a" value="//Detail/[./ItemID, ../Code/#IDREF]"/>
<let name="b" value="current()/Detail/[./ItemID, ../Code/#IDREF]"/>
<assert test="count($a[. = $b]) = count($b)">
ItemID should be unique for the given Code within the given Maul.
</assert>
</rule>
</pattern>
</schema>
The two let values seem problematic. They will each return a Detail element (and all of its content including attributes, child elements, and text nodes). I'm not sure what the code inside the predicates [./ItemID, ../Code/#IDREF] is going to, but I think it will return all Detail elements that have either a child ItemID element or a sibling Code element with an #IDREF attribute, regardless of what the values of ItemID or #IDREF are.
I think I would change the rule/#context to ItemID, so the assert would fail once for each ItemID that violates the constraint.
Here are a rule and assert that work correctly:
<?xml version="1.0" encoding="utf-8" ?><schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<title>Schematron validation rule</title>
<pattern id="P1">
<rule context="Maul/Record/Detail/ItemID" id="R1">
<assert test="count(ancestor::Maul/Record[Code/#IDREF = current()/ancestor::Record/Code/#IDREF]/Detail/ItemID[. = current()]) = 1">
ItemID should be unique for the given Code within the given Maul.
</assert>
</rule>
</pattern>
</schema>
The assert test finds, within the ancestor Maul, any Record that has a Code/#IDREF that equals the Code/#IDREF of the Record that the current ItemID is in. At minimum, it will find one Record (the one that the current ItemID is in). Then it looks for any Detail/ItemID within those Records that is equal to the current ItemID. It will find at least one (the current ItemID). The count function counts how many ItemIDs are found. If more than one is found, the assert fails.
Thanks for the reference to https://www.liquid-technologies.com/online-schematron-validator! I wasn't aware of that tool.

Talend / XPath: Get text of CDATA element in mixed context

I've the following XML data:
<?xml version="1.0"?>
<products>
<product>
<ingredient id="1" weighting="1">
<![CDATA[Name of ingredient 1]]>
<blocked_search_terms><![CDATA[Term A, Term B, Term C]]></blocked_search_terms>
</ingredient>
<ingredient id="2" weighting="2">
<![CDATA[Name of ingredient 2]]>
<blocked_search_terms><![CDATA[Term E, Term F]]></blocked_search_terms>
</ingredient>
</product>
</products>
I am trying to get a list of all ingredient names via a tXmlMap component in Talend. The problem is, that I either get null a concatenated string of the ingredient names and blocked search terms, e.g.
Expression 1:
[xml.products:/products/product/ingredient]
Result 1:
"Name of ingredient 1Term A, Term B, Term C, Name of ingredient 2 Term E, Term F"
Expression 2:
[xml.products:/products/product/ingredient/text()]
Result 2:
"null, null"
The result that I want to achieve is:
"Name of ingredient 1, Name of ingredient 2"
What do I need to use to get it?
Since you cannot dive directly into the CDATA (see what xpath to select CDATA content when some childs exist) you should be able to achieve what you want with a indication of which element you want:
[xml.products:/products/product/ingredient[1]]

Xpath conditioning in datamapper using rule

If the below XML is evaluated using datamapper to map only even numbered id's to output then the condition using xpath(Rule) is : ?
<employees>
<employee>
<id>1</id>
<name>aaa</name>
</employee>
<employee>
<id>2</id>
<name>bbb</name>
</employee>
<employee>
<id>3</id>
<name>ccc</name>
</employee>
<employee>
<id>4</id>
<name>ddd</name>
</employee>
</employees>
I tried using /employee[id mod 2 = 0]/id. It maps if the input has only one even number and fails if it has more than one even numbered id in the input.xml.
The error is
Result of xpath filling field 'id' contains two or more values!
Rules are independent to each Element Mapping configured in DM. Rules are to be created in the element mapping configuration it is intended to be used in.
For the sample xml provided, this rule should be created in Foreach 'employee' -> 'employee' element mapping.
Try this for the rule configuration
Name : {Rule Name}
Type : Boolean
Context : /employees/employee
Xpath : /matches(string(id mod 2), '0')
This creates a rule returning true if id is even and false if id is odd.
Use an if block in DM script view and check for the rule's value. if rule value is true, only then map the id.
It also worked in this way
Name : {Rule Name}
Type : String
Context : /employees/employee
Xpath : /id mod 2 = 0
and used the if condition as below :
if(input.Ruleid.equals("true")){
output.id = input.id;
output.name = input.name;
}
Is there any other way to do this without using if condition.

Selecting a XML node with LINQ, and modifying

I've got the following XML:
<Config>
<Book>
<Name> Book Name #1 </Name>
<Available In>
<Country>US</Country>
<Country>Canada</Country>
</Available In>
</Book>
</Config>
I need to find all instances of Book which are available in a specific country, and then introduce a node underneath "Available In". My selection statement fails anytime I add the where statement:
XElement xmlFile = XElement.Load(xmlFileLocation);
var q = (from c in xmlFile.Elements(“Book”)
where c.Elements(Country).Value == "Canada"
select c;
.Value can't be resolved, and toString give me the entire subnode in stringform. I need to select all books in a particular country so that I can then update them all to include a new locale node, ex:
<Config>
<Book>
<Name> Book Name #1 </Name>
<Available In>
<Country>US</Country>
<Country>Canada</Country>
</Available In>
<LocaleIDs>
<LocalID> 3066 </LocaleID>
<LocaleIDs>
</Book>
</Config>
Thanks for your help!
You're trying to use Value on the result of calling Elements which returns a sequence of elements. That's not going to work - it doesn't make any sense. You want to call it on a single element at a time.
Additionally, you're trying to look for direct children of Book, which ignores the Available In element, which isn't even a valid element name...
I suspect you want something like:
var query = xmlFile.Elements("Book")
.Where(x => x.Descendants("Country")
.Any(x => (string) x == "Canada"));
In other words, find Book elements where any of the descendant Country elements has a text value of "Canada".
You'll still need to fix your XML to use valid element names though...

getting XmlSearch to return siblings only, not children

I'm getting a SOAP response that looks like this:
<Activity>
<Id>A</Id>
<Subject>foo</Subject>
<Activity>Task</Activity>
</Activity>
<Activity>
<Id>B</Id>
<Subject>bar</Subject>
<Activity>Appointment</Activity>
</Activity>
<Activity>
<Id>C</Id>
<Subject>snafu</Subject>
<Activity>Task</Activity>
</Activity>
In Coldfusion, I was trying to parse out the Activity nodes with this:
<cfset arrMainNodes = XmlSearch(soapResponse, "//*[name()='Activity']") />
The problem is, instead if getting an array with three elements, I get an array with six: 3 of the parent, and 3 of the children.
I can't for the life of me figure out the XPath statement the will find siblings only, and not children.
Please Help.
Use:
//*[name()='Activity' and not(ancestor::*[name()='Activity' ])]
This selects all elements in the document, whose name is "Activity" and that do not have an ancestor with name "Activity".

Resources