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
Related
I am trying to implement an at first looking simple transformation but whatever I have tried has been failed.
The XML is generated from a fixed length record and have the below format.
<?xml version="1.0" encoding="UTF-8"?>
<record>
<no_of_records>30</no_of_records>
<cust_lastname_1>Smith</cust_lastname_1>
<cust_name_1>John</cust_name_1>
<cust_id_1>X45</cust_id_1>
<cust_lastname_2>George</cust_lastname_2>
<cust_name_2>Michael</cust_name_2>
<cust_id_2>X76</cust_id_2>
<cust_lastname_3>Ria</cust_lastname_3>
<cust_name_3>Chris</cust_name_3>
<cust_id_3>C87</cust_id_3>
...
</record>
The no_of_records indicates how many _X suffixed elements contains each record and because of its fix length origin has a defined maximum.
I want to transform it to a “verticalized” form resempling the below.
<record>
<customer num="1">
<lastname>Smith</lastname>
<name>John</name>
<id>X45</id>
</customer>
<customer num="2">
<lastname>George</lastname>
<name>Michael</name>
<id>X76</id>
</customer>
<customer num="3">
<lastname>Ria</lastname>
<name>Chris</name>
<id>C87</id>
...
</customer>
</record>
Any help would greatly appreciated.
In XSLT 2.0, you want something like
<xsl:for-each-group select="*" group-starting-with="*[starts-with(local-name(), 'cust_lastname']">
<customer num="{position()}">
<xsl:apply-templates select="current-group()"/>
</customer>
</xsl:for-each-group>
....
<xsl:template match="*[starts-with(local-name(), 'cust')]">
<xsl:element name="{replace(local-name(), 'cust_(.*?)_[0-9]+', '$1')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
The solution from #Michael Kay works fine. Thank you !
XML
<?xml version="1.0" encoding="UTF-8"?>
<record>
<no_of_records>3</no_of_records>
<cust_lastname_1>Smith</cust_lastname_1>
<cust_name_1>John</cust_name_1>
<cust_id_1>X45</cust_id_1>
<cust_lastname_2>George</cust_lastname_2>
<cust_name_2>Michael</cust_name_2>
<cust_id_2>X76</cust_id_2>
<cust_lastname_3>Ria</cust_lastname_3>
<cust_name_3>Chris</cust_name_3>
<cust_id_3>C87</cust_id_3>
</record>
XSLT
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="record">
<records>
<xsl:for-each-group select="*[starts-with(local-name(), 'cust_')]"
group-starting-with="*[starts-with(local-name(), 'cust_lastname')]">
<customer num="{position()}">
<xsl:apply-templates select="current-group()"/>
</customer>
</xsl:for-each-group>
</records>
</xsl:template>
<xsl:template match="*[starts-with(local-name(), 'cust')]">
<xsl:element name="{replace(local-name(), 'cust_(.*?)_[0-9]+', '$1')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<records>
<customer num="1">
<lastname>Smith</lastname>
<name>John</name>
<id>X45</id>
</customer>
<customer num="2">
<lastname>George</lastname>
<name>Michael</name>
<id>X76</id>
</customer>
<customer num="3">
<lastname>Ria</lastname>
<name>Chris</name>
<id>C87</id>
</customer>
</records>
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>
I have the following xml:
<Envelope>
<ABC>
<Hierarchy>
<Family>
<History> ... </History>
<Plan>
<Mom Name="Parent1">
<Child Name = "Child11">
<GrandChild Name="GC111" Label="" Sequence = "1">
<Attributes>... </Attributes>
</GrandChild>
</Child>
</Mom>
<Child Name = "ChildIndependent1">
<GrandChild Name="GCIndep12" Label="<Requested><Item1>68</Item1><Item2>69</Item2&g t;</Requested>" Sequence = "2" >
<Attributes>... </Attributes>
</GrandChild> </Child>
</Plan> </Family>
</Hierarchy> </ABC>
</Envelope>
Here is my current xsl i have on the xml to create dummy mom tags around independent child tags. It also converts the & lt; and & gt; to < and >
<xsl:stylesheet version="2.0" >
<xsl:output method="xml" indent="yes" use-character-maps="angle-brackets"/>
<xsl:character-map name="angle-brackets">
<xsl:output-character character="<" string="<"/>
<xsl:output-character character=">" string=">"/>
</xsl:character-map>
<xsl:template match="node()|#*">
<xsl:copy><xsl:apply-templates select="node()|#*"/></xsl:copy>
</xsl:template>
<xsl:template match="//ABC/Hierarchy/Family">
<xsl:element name="Plan">
<xsl:for-each select="//ABC/Hierarchy/Family/Plan/*">
<xsl:if test="self::Mom">
<xsl:copy-of select="."/>
</xsl:if>
<xsl:if test="self::Child">
<xsl:element name="Mom">
<xsl:attribute name="Name">dummy</xsl:attribute>
<xsl:copy-of select="."/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I need to modify the above xsl to create the LabelRequested tag with the content of the label attribute as a child to the GrandChild and remove the Label attribute itself as below above xsl already converts the & lt; and & gt; to < and >.
<Child Name = "ChildIndependent1">
<GrandChild Name="GCIndep12" Sequence = "2" >
<LabelRequested>
<Requested><Item1>68</Item1><Item2>69</Item2> </Requested>
</LabelRequested>
<Attributes>... </Attributes>
</GrandChild>
Any idea how I could change the above xsl to do this?
To minimize the example to the problem at hand, consider the following stylesheet:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="GrandChild">
<xsl:copy>
<xsl:apply-templates select="#* except #Label"/>
<LabelRequested>
<xsl:value-of select="#Label" disable-output-escaping="yes"/>
</LabelRequested>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Envelope>
<ABC>
<Hierarchy>
<Family>
<History> ... </History>
<Plan>
<Mom Name="Parent1">
<Child Name="Child11">
<GrandChild Name="GC111" Sequence="1">
<LabelRequested/>
<Attributes>... </Attributes>
</GrandChild>
</Child>
</Mom>
<Child Name="ChildIndependent1">
<GrandChild Name="GCIndep12" Sequence="2">
<LabelRequested><Requested><Item1>68</Item1><Item2>69</Item2></Requested></LabelRequested>
<Attributes>... </Attributes>
</GrandChild>
</Child>
</Plan>
</Family>
</Hierarchy>
</ABC>
</Envelope>
Note that this assumes your processor supports disable-output-escaping and that the processing chain allows it to execute it. Otherwise you would have to use either XSLT 3.0 or an extension function to parse the markup contained in the Label attribute.
I want to update one xml values with other xml.
Suppose i have a xml having root node
<Customer>
<Fname>John</Fname>
<Lname>Smith<Lname>
</Customer>
The other xml is having
<Customer>
<Lname>Smith<Lname>
</Customer>
I want to transfer <Fname>John</Fname> from 1st to 2nd xml if that information is not present in 2nd xml.
Is it possible by using xslt in .net?
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kElementByAncestors" match="*"
use="concat(name(../..),'+',name(..))"/>
<xsl:key name="kAttributeByAncestors" match="#*"
use="concat(name(../..),'+',name(..))"/>
<xsl:param name="pSource2" select="'source2.xml'"/>
<xsl:variable name="vSource2" select="document($pSource2,/)"/>
<xsl:template match="*">
<xsl:variable name="vKey" select="concat(name(..),'+',name())"/>
<xsl:variable name="vCurrent" select="."/>
<xsl:copy>
<xsl:for-each select="$vSource2">
<xsl:variable name="vNames">
<xsl:text>|</xsl:text>
<xsl:for-each select="$vCurrent/*">
<xsl:value-of select="concat(name(),'|')"/>
</xsl:for-each>
</xsl:variable>
<xsl:copy-of select="key('kAttributeByAncestors',$vKey)"/>
<xsl:copy-of select="$vCurrent/#*"/>
<xsl:copy-of
select="key('kElementByAncestors',
$vKey)[not(contains($vNames,
concat('|',
name(),
'|')))]"/>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<Customer>
<Lname>Smith</Lname>
<Data>Data</Data>
</Customer>
And "source2.xml":
<Customer test="test">
<Fname>John</Fname>
<Lname>Smith</Lname>
</Customer>
Output:
<Customer test="test">
<Fname>John</Fname>
<Lname>Smith</Lname>
<Data>Data</Data>
</Customer>
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>