Not IN equivalent in XPath expression - xslt

I am working on an XSL development and I am in need of knowing the NOT IN equivalent in XPATH.
I am presenting the XML and XSL in the simplest format which would be understandable to all.
<?xml-stylesheet type="text/xsl" href="XSL.xsl"?>
<Message>
<Customers>
<Customer pin="06067">1</Customer>
<Customer pin="06068">2</Customer>
<Customer pin="06069">3</Customer>
<Customer pin="06070">4</Customer>
<Customer pin="06072">5</Customer>
</Customers>
<Addresses>
<Address pin1="06067">A</Address>
<Address pin1="06068">B</Address>
<Address pin1="06069">C</Address>
</Addresses>
</Message>
XSL
<xsl:template match="/Message">
<html>
<body>
<h4>Existing Customers</h4>
<table>
<xsl:apply-templates select="//Customers/Customer[#pin = //Addresses/Address/#pin1]"></xsl:apply-templates>
</table>
<h4>New Customers</h4>
<table>
<!--This place need to be filled with new customers-->
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Customer" name="Customer">
<xsl:variable name="pin" select="./#pin"></xsl:variable>
<tr>
<td>
<xsl:value-of select="."/>
<xsl:text> is in </xsl:text>
<xsl:value-of select="//Addresses/Address[#pin1=$pin]"/>
</td>
</tr>
</xsl:template>
In the above XSLT, under the commented area, i need to match and display the customers who's address is not existing in the Addresses/Address node.
Please help find an XPath expression that would match the Customers who are NOT IN the Addresses Node set. (Any alternate could also help)

In XPath 1.0:
/Message/Customers/Customer[not(#pin=/Message/Addresses/Address/#pin1)]

An alternative to the good answer by #Alejandro, which I upvoted, is the following transformation, which uses keys and will be more efficient if the number of existing customers is big:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kexistingByPin"
match="Address" use="#pin1"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select=
"*/*/Customer[not(key('kexistingByPin', #pin))]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<Message>
<Customers>
<Customer pin="06067">1</Customer>
<Customer pin="06068">2</Customer>
<Customer pin="06069">3</Customer>
<Customer pin="06070">4</Customer>
<Customer pin="06072">5</Customer>
</Customers>
<Addresses>
<Address pin1="06067">A</Address>
<Address pin1="06068">B</Address>
<Address pin1="06069">C</Address>
</Addresses>
</Message>
the wanted, correct answer is produced:
<Customer pin="06070">4</Customer>
<Customer pin="06072">5</Customer>

Related

XSLT Muenchian Grouping on different elements based on a common attribute

I am given XML similar to the following that I need to process.
<root>
<Header/>
<Customer id="1" date="13/04/2014"/>
<Account id="1" date="14/04/2014"/>
<Account id="1" date="01/06/2015"/>
<Address id="1" date="14/04/2014"/>
<Customer id="2" date="12/08/2015"/>
<Account id="2" date="13/08/2015"/>
<Address id="2" date="13/08/2015"/>
<Address id="2" date="03/09/2015"/>
<Address id="2" date="27/01/2017"/>
<Customer id="3" date="04/10/2015"/>
<Customer id="3" date="01/02/2017"/>
<Account id="3" date="05/10/2015"/>
<Address id="3" date="08/10/2015"/>
<Address id="3" date="03/09/2016"/>
</root>
All of the nodes have more attributes but I stripped them off. Each element has an id and a date.If there are duplicate elements that have the same id then the one with the most recent date is considered valid and the older one should be ignored.
If the older ones can be stripped out at the same time I would like to output it into something like this.
<Customers>
<Customer id="1">
<Account/>
<Address/>
</Customer>
<Customer id="2">
<Account/>
<Address/>
</Customer>
<Customer id="3">
<Account/>
<Address/>
</Customer>
</Customers>
If not then it is fine to process the file in two transforms (one to group them by customer id and each customer have multiple Account/Address fields, then in the other transform remove the older entries)
The source XML has close to a million entries so performance is an issue. The transform taking a few minutes is fine, but any more than 15 will not work.
I currently have the following XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="nodes-by-id" match="//root/*" use="#id"/>
<xsl:template match="root">
<Customers>
<xsl:for-each select="*[count(. | key('nodes-by-id', #id)[1]) = 1]">
<xsl:variable name="current-grouping-key" select="#id"/>
<xsl:variable name="current-group" select="key('nodes-by-id', $current-grouping-key)"/>
<Customer>
<xsl:attribute name="id">
<xsl:value-of select="$current-grouping-key"/>
</xsl:attribute>
<CustomerElements>
<xsl:for-each select="$current-group/Customer">
<CustomerElement>
<xsl:attribute name="date">
<xsl:value-of select="#date"/>
</xsl:attribute>
</CustomerElement>
</xsl:for-each>
</CustomerElements>
<xsl:apply-templates select="$current-group"/>
</Customer>
</xsl:for-each>
</Customers>
</xsl:template>
</xsl:stylesheet>
Currently this just tries to group all of the elements by their id, then output all of the Customer elements. I get the following:
<Customers>
<Customer id="">
<CustomerElements/>
</Customer>
<Customer id="1">
<CustomerElements/>
</Customer>
<Customer id="2">
<CustomerElements/>
</Customer>
<Customer id="3">
<CustomerElements/>
</Customer>
</Customers>
I get the customer with the blank ID because I don't ignore the header row. My real question is why does the $current-group variable not contain any elements?
Also any tips on how to ignore the header row, and to filter out entries with the older dates.
I got everything sorted. This is a segment of the XSLT I used. More info in the XML comments.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="nodes-by-id" match="//root/*" use="#id"/>
<xsl:template match="PR-030">
<CustomerMeters>
<!-- Using select="Customer[cou.... instead of select="*[cou... will couse it to ignore the header. However it requres
the Customer element to be the first element for the icp in the xml. -->
<xsl:for-each select="Customer[count(. | key('nodes-by-id', #id)[1]) = 1]">
<xsl:variable name="current-grouping-key" select="#id"/>
<xsl:variable name="current-group" select="key('nodes-by-id', $current-grouping-key)"/>
<xsl:variable name="current-group-sorted">
<!-- If we sort all nodes by date order, then we can fetch the first Address/Customer/etc... from this group and we will have the latest-->
<xsl:for-each select="$current-group">
<!-- year -->
<xsl:sort select="substring(#date, 7, 4)" order="descending" data-type="number"/>
<!-- month -->
<xsl:sort select="substring(#date, 4, 2)" order="descending" data-type="number"/>
<!-- day -->
<xsl:sort select="substring(#date, 1, 2)" order="descending" data-type="number"/>
<xsl:copy-of select="current()" />
</xsl:for-each>
</xsl:variable>
<Customer>
<!-- In here I can get what I want from the current-group-sorted varaible-->
<!-- Because they are in date order I can just get the first occurance and it will be the most recent-->
<someField>
<xsl:value-of select="$current-group-sorted/*[self::Account][1]/#someAttribute"/>
</someField>
</Customer>
</xsl:for-each>
</CustomerMeters>
</xsl:template>
</xsl:stylesheet>

Insert list of elements inside XML tags

I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

how to remove duplicate elements

I need to extract customer names with "Name" and save it in a variable by removing duplicate names as follows. Input is any dummy xml
like response variable should have only this
<customer name="Name">John; Kevin; Leon; Adam</customer>
used this stylesheet
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:variable name="request">
<customers>
<customer name="Address">1 Doe Place</customer>
<customer name="State">OH</customer>
<customer name="Name">John</customer>
<customer name="Name">Kevin</customer>
<customer name="Name">Leon</customer>
<customer name="Name">Adam</customer>
<customer name="Name">Leon</customer>
<customer name="Name">Adam</customer>
<customer name="Name">John</customer>
<customer name="city">Columbus</customer>
</customers>
</xsl:variable>
<xsl:variable name="response" >
<xsl:for-each select="$request/customers/customer[#name = 'Name']">
<xsl:copy-of select="./text()"/>
<xsl:if test="position()!=last()">; </xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="$response"/>
</xsl:template>
</xsl:stylesheet>
This generates
<customer name="Name">John; Kevin; Leon; Adam; Leon; Adam; John</customer>
but is not removing duplicate names

XML use XSL to transform a list of records

Excuse my ignorance. I am just beginning XSL and XML transformations.
I receive xml data from a vendor.
I only need to include certain "ids" in my transformation.
I also need to add a "display name" based on the ID to the final output.
I would be able to manual add the ID and Display names necessary into the XSL.
XML ex.
<root>
<DATA>
<ID>rd_bl</ID>
<travel>15</travel<
<delay>7</delay>
</DATA>
<DATA>
<ID>yl_gr</ID>
<travel>18</travel<
<delay>9</delay>
</DATA>
<DATA>
<ID>pu_gr</ID>
<travel>17</travel<
<delay>6</delay>
</DATA>
</root>
I would like to write a list of IDs and "display names" in the xsl - only the records with the listed IDs would be included.
ID - Display Name
rd_bl - Red to Blue
pu_gr - Purple to Green
In this example the data from yl_gr would be ignored and not show up in the transformation.
Any help is greatly appreciated.
Thanks!
Here’s a simple stylesheet that checks whether an ID is within an approved list of IDs and uses a “display name” for it in the output.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="desired-ids">
<id name="rd_bl">Red to Blue</id>
<id name="pu_gr">Purple to Green</id>
</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="DATA">
<xsl:variable name="current-id" select="ID/text()" />
<xsl:if test="$desired-ids/id[#name=$current-id]">
<entry>
<name>
<xsl:value-of select="$desired-ids/id[#name=$current-id]" />
</name>
<travel>
<xsl:value-of select="travel" />
</travel>
<delay>
<xsl:value-of select="delay" />
</delay>
</entry>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output using your example XML after correcting the closing tag errors:
<root>
<entry>
<name>Red to Blue</name>
<travel>15</travel>
<delay>7</delay>
</entry>
<entry>
<name>Purple to Green</name>
<travel>17</travel>
<delay>6</delay>
</entry>
</root>
EDIT: in case you’re stuck with XSL 1.0:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="desired-ids">rd_bl="Red to Blue" pu_gr="Purple to Green"</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="DATA">
<xsl:variable name="current-id" select="ID/text()" />
<xsl:variable name="id-with-equals" select="concat($current-id, '=')" />
<xsl:if test="contains($desired-ids, $id-with-equals)">
<xsl:variable name="id-with-open-quote" select="concat($id-with-equals, '"')" />
<xsl:variable name="display-name" select="substring-before(substring-after($desired-ids, $id-with-open-quote), '"')" />
<entry>
<name>
<xsl:value-of select="$display-name" />
</name>
<travel>
<xsl:value-of select="travel" />
</travel>
<delay>
<xsl:value-of select="delay" />
</delay>
</entry>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can see this is much less elegant, it uses awkward string-matching to check for a valid ID and extract the display name.
Will this stylesheet help?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<table>
<thead>
<th>ID</th>
<th>Display Name</th>
</thead>
<tbody>
<xsl:apply-templates select="root/DATA"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="DATA">
<xsl:choose>
<xsl:when test="ID='rd_bl'">
<tr>
<td><xsl:value-of select="ID"/></td>
<td>Red to Blue</td>
</tr>
</xsl:when>
<xsl:when test="ID='pu_gr'">
<tr>
<td><xsl:value-of select="ID"/></td>
<td>Purple to Green</td>
</tr>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

limit records display using xslt

I have a problem. I get the data from xml then transform it with xslt.
Let us say I have a xml file:
<?xml version="1.0"?>
<root>
<row id="1" fname="Dan" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1234 LottaWork Ave.</street>
<city>AnyTown</city>
<zip>85786</zip>
</address>
</row>
<row id="2" fname="Elaine" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1233 Books Way</street>
<city>AnyTown</city>
<zip>85784</zip>
</address>
</row>
</root>
And this stylesheet:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" omit-xml-declaration="no"/>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
<xsl:attribute name="fname">
<xsl:value-of select="name/fname"/>
</xsl:attribute>
<xsl:attribute name="lname">
<xsl:value-of select="name/lname"/>
</xsl:attribute>
<xsl:for-each select="address">
<xsl:copy-of select="."/>
</xsl:for-each> </row>
</xsl:template>
</xsl:stylesheet
How can limit this to 3 records, then after 3 records it create a tr tag?
For example:
<table>
<tr>
<td>Address1</td>
<td>Address2</td>
<td>Address3</td>
</tr>
<tr>
<td>Address4</td>
<td>Address5</td>
<td>Address6</td>
</tr>
</table
try something like
<xsl:for-each select="PATH">
<xsl:variable name="pos" select="position() mod 3" />
</xsl:for-each>
then you can work with
<xsl:if test="$pos = 0">
</xsl:if>
and
<xsl:if test="$pos != 0">
</xsl:if>
if $pos = 0 means that you reached the 3rd row
Here are some good resources to learn more about XSLT and XPath
http://w3schools.com/xsl/default.asp
http://w3schools.com/xpath/default.asp