Modifying the XML element value upon condiion by XSLT - xslt-1.0

I have xml
<PhoneNumberDetails>
<PhoneNumber>
<Number>4162880001</Number>
<TimeStamp>2016-08-16T07:07:44-04:00</TimeStamp>
</PhoneNumber>
<PhoneNumber>
<Number>4162880002</Number>
<TimeStamp>2016-08-16T07:07:44-04:00</TimeStamp>
</PhoneNumber>
</PhoneNumberDetails>
This is input to a XSL. In this XSL i retrieve a phone number from context variable stored in one of the previous XSLT and if it matches to any phone number in above xml, I need to update the TimeStamp to new current Timestamp. I need help in this part.
The XSLT Code:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dp="http://www.datapower.com/extensions" xmlns:date="http://exslt.org/dates-and-times" extension-element-prefixes="dp date" exclude-result-prefixes="dp">
<xsl:variable name="currentTimeStamp" select="date:date-time()"/>
<xsl:output method="xml"/>
<xsl:template match="PhoneNumberDetails">
<xsl:variable name="savedPhoneNum" select="dp:variable('var://context/name/phonenum')"/>
<xsl:choose>
<xsl:when test="/*[local-name()='PhoneNumberDetails']/*[local-name()='PhoneNumber']/*[local-name()='Number']=$savedPhoneNum">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="phonenumber"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="phonenumber" match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PhoneNumberDetails">
<PhoneNumberDetails>
<xsl:apply-templates select="#* | *"/>
<PhoneNumber>
<Number>
<xsl:value-of select="dp:variable('var://context/name/phonenum')"/>
</Number>
<TimeStamp>
<xsl:value-of select="$currentTimeStamp"/>
</TimeStamp>
</PhoneNumber>
</PhoneNumberDetails>
</xsl:template>
</xsl:stylesheet>
If the phone number does not matches any number in XML i need to add a new element to the above xml and this part works. For e.g.
<PhoneNumber>
<Number>New number</Number>
<TimeStamp>Current TimeStamp</TimeStamp>
</PhoneNumber>

Why can't you do simply:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="dp date">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="savedPhoneNum" select="dp:variable('var://context/name/phonenum')"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="TimeStamp[../Number=$savedPhoneNum]">
<xsl:copy>
<xsl:value-of select="date:date-time()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PhoneNumberDetails[not(PhoneNumber/Number=$savedPhoneNum)]">
<xsl:copy>
<xsl:apply-templates/>
<PhoneNumber>
<Number>New number</Number>
<TimeStamp>Current TimeStamp</TimeStamp>
</PhoneNumber>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

I suggest you start with something like this. Don't use an absolute path to select the phone number, use it from that context node where the template matches.
<xsl:template match="PhoneNumber">
<xsl:copy>
<xsl:copy-of select="Number"/>
<xsl:variable name="savedPhoneNum" select="dp:variable('var://context/name/phonenum')"/>
<TimeStamp>
<xsl:choose>
<xsl:when test="Number=$savedPhoneNum">
<xsl:value-of select="$currentTimeStamp"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="TimeStamp"/>
</xsl:otherwise>
</xsl:choose>
</TimeStamp>
</xsl:copy>
</xsl:template>
Note that I assume that you don't have a (non-shown) namespace on the elements of your input xml. If yes, keep your *[local-name()=""] construct, but then the namespace would have to be added to the match="PhoneNumber" and to the TimeStamp output as well.

Related

How to do for-each-group group-adjacent in XSLT 1.0

Can someone help me convert the below XSLT 2.0 code to XSLT 1.0? Thanks.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[E1EDL24]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="node()" group-adjacent="boolean(self::E1EDL24)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()">
<xsl:sort select="MATNR" order="descending"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
Try whether the following works:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" mode="sort">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:key name="adjacent-group" match="E1EDL24[preceding-sibling::*[1][self::E1EDL24]]" use="generate-id(preceding-sibling::E1EDL24[not(preceding-sibling::*[1][self::E1EDL24])][1])"/>
<xsl:template match="E1EDL24[not(preceding-sibling::*[1][self::E1EDL24])]">
<xsl:apply-templates select=". | key('adjacent-group', generate-id())" mode="sort">
<xsl:sort select="MATNR" order="descending"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="E1EDL24[preceding-sibling::*[1][self::E1EDL24]]"/>
</xsl:stylesheet>

Apply templates that matchs two conditions

I need to list all the INST names but only if the "onlyTesters" node don´t exists in the "inst/idef" part of XML body above.
I know thats strange but I can´t change the XML I receive.
XML:
<river>
<station num="699">
<inst name="FLU(m)" num="1">
<idef></idef>
</inst>
<inst name="Battery(V)" num="18">
<idef>
<onlyTesters/>
</idef>
</inst>
</station>
<INST name="PLU(mm)" num="0" hasData="1" virtual="0"/>
<INST name="FLU(m)" num="1" hasData="1" virtual="0"/>
<INST name="Q(m3/s)" num="3" hasData="1" virtual="1"/>
<INST name="Battery(V)" num="18" hasData="1" virtual="0"/>
</river>
XSL:
<xsl:template match="/">
<xsl:apply-templates select="//INST[#hasData = 1 and not(//inst[#num=(current()/#num)]/idef/onlyTesters)]/#name"/>
</xsl:template>
<xsl:template match="//INST[#hasData = 1 and not(//inst[#num=(current()/#num)]/idef/onlyTesters)]/#name">
<xsl:value-of select="#name"/>,
</xsl:template>
I´m having no match.
This is the result I expect:
PLU(mm),FLU(m),Q(m3/s)
You can achieve this with only one template:
<xsl:template match="/">
<xsl:for-each select="//INST[#hasData='1' and not(#name=//inst[idef/onlyTesters]/#name)]">
<xsl:value-of select="#name"/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
</xsl:template>
Output is:
PLU(mm), FLU(m), Q(m3/s)
Cross-references are best resolved using a key - for example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" />
<xsl:key name="inst" match="inst" use="#name" />
<xsl:template match="/river">
<xsl:for-each select="INST[#hasData = 1 and not(key('inst', #name)/idef/onlyTesters)]">
<xsl:value-of select="#name"/>
<xsl:if test="position() != last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Or even simpler:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8" />
<xsl:key name="exclude" match="onlyTesters" use="ancestor::inst/#name" />
<xsl:template match="/river">
<xsl:for-each select="INST[#hasData = 1 and not(key('exclude', #name))]">
<xsl:value-of select="#name"/>
<xsl:if test="position() != last()">, </xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSL I need to remove space

I need to remove the white space between "|"
Example:
|| 3.3 | A | 001 | 2017-03-03T16:57:51 | 01 *20001000000200001437 | Only free ||
I need this output:
||3.3|A|001|2017-03-03T16:57:51|01 *20001000000200001437|Only free||
This is rather awkward to do in XSLT 1.0.
Given the following input:
XML
<root>
<item>|| 3.3 | A | 001 | 2017-03-03T16:57:51 | 01 *20001000000200001437 | Only free ||</item>
</root>
the following stylesheet:
XSLT 1.0
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:variable name="remove-left-spaces">
<xsl:call-template name="remove-spaces">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="delimiter" select="' |'"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="remove-spaces">
<xsl:with-param name="text" select="$remove-left-spaces"/>
<xsl:with-param name="delimiter" select="'| '"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="remove-spaces">
<xsl:param name="text"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="remove-spaces">
<xsl:with-param name="text">
<xsl:value-of select="substring-before($text, $delimiter)"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="substring-after($text, $delimiter)"/>
</xsl:with-param>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
will return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>||3.3|A|001|2017-03-03T16:57:51|01 *20001000000200001437|Only free||</item>
</root>

XSLT1.0 Remove repeat nodes

I am trying to apply Muenchian grouping to eliminate duplicate nodes, but I seem not to be able to make it work.
Input:
<GetUsersByRoleRspMsg>
<UserList>
<User>
<UserId>PPAUSER1</UserId>
<Email>xyz#gmail.com</Email>
</User>
<User>
<UserId>PPAUSER1</UserId>
<Email>xyz#gmail.com</Email>
</User>
</UserList>
</GetUsersByRoleRspMsg>
Required:
<GetUsersByRoleRspMsg>
<UserList>
<User>
<UserId>PPAUSER1</UserId>
<Email>xyz#gmail.com</Email>
</User>
</UserList>
</GetUsersByRoleRspMsg>
The critical part of my code is
<xsl:key name="userIdEmailPair"
match="User"
use="concat(UserId,' ',Email)"/>
<xsl:copy>
<xsl:for-each select="User[
count(. | key('userIdEmailPair',
concat(UserId,' ',Email))[1])
= 1]">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
I don't see any problem with your grouping code. If you try:
<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:strip-space elements="*"/>
<xsl:key name="userIdEmailPair" match="User" use="concat(UserId,' ',Email)"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="UserList">
<xsl:copy>
<xsl:for-each select="User[count(. | key('userIdEmailPair',concat(UserId,' ',Email))[1]) = 1]">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
you will get the result you're looking for.
Of course, you could shorten the last template to:
<xsl:template match="UserList">
<xsl:copy>
<xsl:copy-of select="User[count(. | key('userIdEmailPair',concat(UserId,' ',Email))[1]) = 1]"/>
</xsl:copy>
</xsl:template>

XSLT code for converting attributes to child elements and putting the value of current element into its another new child element

I am unaware of XSLT,Please help:
I have the following XML:
<OLifE>
<Holding id="1234">
<HoldingKey>1397650618090</HoldingKey>
<HoldingTypeCode tc="2">Policy</HoldingTypeCode>
<HoldingStatus tc="2">Inactive</HoldingStatus>
<CarrierAdminSystem>PAS</CarrierAdminSystem>
</Holding>
</OLifE>
I want the output like this:
<OLifE>
<Holding>
<id>1234</id>
<HoldingKey>1397650618090</HoldingKey>
<HoldingTypeCode>
<tc>2</tc>
<value>Policy</value>
</HoldingTypeCode>
<HoldingStatus>
<tc>2</tc>
<value>Inactive</value>
</HoldingStatus>
<CarrierAdminSystem>PAS</CarrierAdminSystem>
</Holding>
</OLifE>
please note that, all attributes are needed to be converted to child elements but, for elements that have a tc attribute specified:
<HoldingTypeCode tc="2">Policy</HoldingTypeCode>
need to be handled carefully.
I DO NOT want the output like:
<HoldingTypeCode>
<tc>2</tc>Policy</HoldingTypeCode>
I have the following XSLT code which needs the slight modification:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml"/>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:for-each select="#*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
Please help.
Assuming you want a generic solution, how about:
XSLT 1.0
<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:strip-space elements="*"/>
<!-- modified identity transform -->
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- attributes to elements -->
<xsl:template match="#*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- avoid mixed content -->
<xsl:template match="text()[../#*]">
<value>
<xsl:value-of select="."/>
</value>
</xsl:template>
</xsl:stylesheet>
Edit
However, the root element in my original XML has got xmlns="some web
address" attribute.
In such case, change the first template to:
<!-- modified identity transform -->
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
This is assuming you want all the output nodes to be in no namespace.
I think you can define a template like this to be called where you want to output the attributes :
<xsl:template name="transform.attrs">
<xsl:for-each select="#*">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:template>
For the most general case you use almost the same template as before, just call the new template for attributes:
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:call-template name="transform.attrs">
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
And for the specific elements, bearing the #tc attribute:
<xsl:template match="*[#tc]">
<xsl:element name="{name()}">
<xsl:call-template name="transform.attrs" />
<value>
<xsl:apply-templates select="*|text()"/>
</value>
</xsl:element>
</xsl:template>