xsl:if first and last node not retrieved - xslt

I have an XML as follows:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExistingReservations>
<reservations>
<reservation>
<type>1</type>
<start_time>2019-03-09T16:11:14Z</start_time>
<stop_time>2019-03-09T16:23:23Z</stop_time>
</reservation>
<reservation>
<type>2</type>
<start_time>2019-03-09T11:23:12Z</start_time>
<stop_time>2019-03-09T11:32:18Z</stop_time>
</reservation>
<reservation>
<type>2</type>
<start_time>2019-03-09T12:23:12Z</start_time>
<stop_time>2019-03-09T12:32:18Z</stop_time>
</reservation>
</reservations>
</ExistingReservations>
I want to only look at reservations of type '2' and then get the range of dates. i.e. the first start_time and the last end time.
But i'm struggling with my xsl as I can't seem to get the first and last positions.
My xsl is as follows:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="ExistingReservations">
<ReservationSchedule>
<xsl:for-each select="//reservation">
<xsl:if test="type='2'">
<period>
<xsl:if test="position()=1">
<start_time><xsl:value-of select="start_time"/><start_time>
</xsl:if>
<xsl:if test="position() = last()">
<end_time><xsl:value-of select="stop_time"/></end_time>
</xsl:if>
</period>
</xsl:if>
</xsl:for-each>
</ReservationSchedule>
</xsl:template>
</xsl:stylesheet>
So I want to transform to the following:
<ReservationSchedule>
<period>
<start_time>2019-03-09T11:23:12Z</start_time>
<end_time>2019-03-09T12:32:18Z</end_time>
</period>
</ReservationSchedule>
I think in the <xsl:if test="position()=1"> is not working because it's looking at the first node whichi s type 1.. i.e. it's igoring the <xsl:if test="type='2'"> logic.
Any help appreciated.

Your xsl:for-each selects all reservations, so position() will be based on that selected node set. The xsl:if will not affect the position() function.
What you need to do is change the select statement itself, so only reservations of type 2 are selected in the first place
<xsl:template match="ExistingReservations">
<ReservationSchedule>
<xsl:for-each select="//reservation[type='2']">
<period>
<xsl:if test="position()=1">
<start_time><xsl:value-of select="start_time"/></start_time>
</xsl:if>
<xsl:if test="position() = last()">
<end_time><xsl:value-of select="stop_time"/></end_time>
</xsl:if>
</period>
</xsl:for-each>
</ReservationSchedule>
</xsl:template>
Alternatively, you can do away with the xsl:for-each and write it like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="ExistingReservations">
<ReservationSchedule>
<xsl:variable name="type2s" select="//reservation[type='2']" />
<period>
<start_time><xsl:value-of select="$type2s[1]/start_time"/></start_time>
<end_time><xsl:value-of select="$type2s[last()]/stop_time"/></end_time>
</period>
</ReservationSchedule>
</xsl:template>
</xsl:stylesheet>

Related

Store condition test in variable

I am quite new to XSLT and need to transform an address xml file.
I need to store the result of an condition test in a variable, I want to use it later in the same template.
Here my source xml:
<?xml version="1.0" encoding="UTF-8"?>
<message>
<partner>
<addressInfo>
<addressUsage>
<Code>Default</Code>
<Main>false</Main>
</addressUsage>
<addressUsage>
<Code>BILL_TO</Code>
<Main>false</Main>
</addressUsage>
<Address>...</Address>
</addressInfo>
<addressInfo>
<addressUsage>
<Code>SHIP_TO</Code>
<Main>false</Main>
</addressUsage>
<addressUsage>
<Code>BILL_TO</Code>
<Main>true</Main>
</addressUsage>
<Address>...</Address>
</addressInfo>
</partner>
</message>
Now, I need to know if there is an address with code "BILL_TO" and Main = "true". I need to store this in a variable. But in my test I always fail.
Here my 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" indent="yes" omit-xml-declaration="yes"/>
<!-- copy all nodes and attributes -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="partner">
<partner>
<xsl:variable name="main_bill_to">
<xsl:choose>
<xsl:when test="(./addressInfo/addressUsage/Code = 'BILL_TO' and Main = 'true')">
<xsl:value-of select="'Y'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'N'" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:if test="($main_bill_to = 'Y')">
<result>yes</result>
</xsl:if>
<xsl:if test="($main_bill_to = 'N')">
<result>no</result>
</xsl:if>
</partner>
</xsl:template>
</xsl:stylesheet>
I always get the output "no".
<message>
<partner>
<result>no</result>
</partner>
</message>
Any idea? Many thanks!
This expression is not quite right...
<xsl:when test="(./addressInfo/addressUsage/Code = 'BILL_TO' and Main = 'true')">
You are actually asking "Is there an element addressInfo/addressUsage/Code (under partner) equal to 'BILL_TO', and is there also an element Main (under partner) equal to 'true'".
In other words, it is looking for an element named Main under partner, not under the same addressUsage as the Code element you are checking.
You need to do this...
<xsl:when test="addressInfo/addressUsage[Code = 'BILL_TO' and Main = 'true']">
So, this is now asking "Is there an addressInfo/addressUsage element which has both Code set to 'BILL_TO' and Main set to 'true'"?
Note the ./ is not really necessary, which is why I removed it.
Also note, you don't really need to go to all the trouble of using an xsl:choose to set the variable to "Y" or "N". You can, in this case, simplify the code block to this....
<xsl:template match="partner">
<partner>
<xsl:variable name="main_bill_to" select="addressInfo/addressUsage[Code = 'BILL_TO' and Main = 'true']" />
<xsl:if test="$main_bill_to">
<result>yes</result>
</xsl:if>
<xsl:if test="not($main_bill_to)">
<result>no</result>
</xsl:if>
</partner>
</xsl:template>
So now, you are checking if an element exists to determine what to display.

Compare data of 2 xmls in xslt

I am new to XSL. Hence please help me with the below.
I have 2 xmls. I have to do the following in XSL transformation.
if Employee/EmployeeInfo/FirstName = EmployeeSegment/EmployeeSummary/GivenName and Employee/EmployeeInfo/LastName = EmployeeSegment/EmployeeSummary/Surname
employeeId = EmployeeSegment/EmployeeSummary/EmpId
XML1
<Employee>
<EmployeeInfo>
<FirstName>ABC</FirstName>
<LastName>DEF</LastName>
</EmployeeInfo>
</Employee>
XML2
<EmployeeSegment>
<EmployeeSummary>
<EmpId>1234</EmpId>
<GivenName>ABC</GivenName>
<Surname>DEF</Surname>
</EmployeeSummary>
</EmployeeSegment>
I have tried the following. It is not working.
<xsl:param name="cjEmployeeSegment" select="document('CJ_Response.xml')"/>
<xsl:for-each select="/ns3:Employee/ns3:EmployeeInfo">
<xsl:variable name="empFirstName">
<xsl:value-of select="ns1:FirstName"/>
</xsl:variable>
<xsl:variable name="empLastName">
<xsl:value-of select="ns1:LastName"/>
</xsl:variable>
<xsl:for-each select="$cjEmployeeSegment/v32:EmployeeSegment/v31:EmployeeSummary">
<xsl:if test="$empFirstName=v31:GivenName and $empLastName=v31:Surname">
<ns12:EmployeeIdentifier>
<ns12:EmployeeID>
<xsl:value-of select="v31:EmpId"/>
</ns12:EmployeeID>
</ns12:EmployeeIdentifier>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Assuming you are processing the following input:
XML
<Employee>
<EmployeeInfo>
<FirstName>ABC</FirstName>
<LastName>DEF</LastName>
</EmployeeInfo>
</Employee>
and there is another XML document named CJ_Response.xml:
<EmployeeSegment>
<EmployeeSummary>
<EmpId>1234</EmpId>
<GivenName>ABC</GivenName>
<Surname>DEF</Surname>
</EmployeeSummary>
</EmployeeSegment>
you can use 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:param name="cj_Response" select="document('CJ_Response.xml')"/>
<xsl:template match="/Employee">
<root>
<xsl:for-each select="EmployeeInfo">
<xsl:variable name="lookup" select="$cj_Response/EmployeeSegment/EmployeeSummary[GivenName = current()/FirstName and Surname = current()/LastName]" />
<xsl:if test="$lookup">
<EmployeeIdentifier>
<EmployeeID>
<xsl:value-of select="$lookup/EmpId"/>
</EmployeeID>
</EmployeeIdentifier>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
to return:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<EmployeeIdentifier>
<EmployeeID>1234</EmployeeID>
</EmployeeIdentifier>
</root>
Of course, this will fail miserably if there are two or more employees with the same name.

Applying template more than once on an element

Im struggling a bit with the following. Given that items may contain several part and spec pairs, i want to process each pair, or apply the template to the item more than once.
Currently, each item is processed once and I'm missing the second part.
<figure>
<list>
<item>
<part>
<p>74174</p>
</part>
<spec>
<u>a1</u>
</spec>
<part>
<p>75375</p>
</part>
<spec>
<u>a4</u>
</spec>
</item>
</list>
</figure>
Stylesheet:
<xsl:if test="$a = 'abc'">
<xsl:apply-templates mode="pt" select="/figure/list/item" />
</xsl:if>
<xsl:template mode="pt" match="item[./part]">
<xsl:call-template name="ptt">
<xsl:with-param name="p"><xsl:value-of select="part/p"/>
</xsl:with-param>
<xsl:with-param name="pr">
<xsl:if test="spec/u">
<xsl:element name="pr">
<xsl:element name="rpn">
<xsl:value-of select="spec/u"/>
</xsl:element>
<xsl:element name="rtn">Alt</xsl:element>
</xsl:element>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
I simplified and cropped the code a bit since it goes on and on and on..
Edit: This next one is generating my new elements based solely on the input params
<xsl:template name="ptt">
<xsl:param name="p"/>
<xsl:param name=u"/>
</xsl:template>
It seems that you are unwilling to show a self-contained and complete example (for your future questions, do not make it quite so hard for people to help you), so I am not sure whether the following is helpful to you.
Assuming the XML input you have shown, the stylesheet below demonstrates a way to call a named template for each pair of spec and part elements, as you requested.
It uses xsl:for-each-group (which is exclusive to XSLT 2.0, but you did not tell which version of XSLT you use) to define groups that start with a part element, resulting in the said pairs. Then, for each group, the named template ptt is invoked with p and u as parameters.
Stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="item">
<xsl:for-each-group select="*" group-starting-with="part">
<xsl:call-template name="ptt">
<xsl:with-param name="p" select="current-group()[1]/p"/>
<xsl:with-param name="u" select="current-group()[2]/u"/>
</xsl:call-template>
</xsl:for-each-group>
</xsl:template>
<xsl:template name="ptt">
<xsl:param name="p"/>
<xsl:param name="u"/>
<result>
<part><xsl:value-of select="$p"/></part>
<spec><xsl:value-of select="$u"/></spec>
</result>
</xsl:template>
</xsl:transform>
XML Output
<?xml version="1.0" encoding="UTF-8"?>
<result>
<part>74174</part>
<spec>a1</spec>
</result>
<result>
<part>75375</part>
<spec>a4</spec>
</result>
Well with call-template and a parameter <xsl:with-param name="p"><xsl:value-of select="part/p"/></xsl:with-param> you are making it harder than it needs to be, assuming XSLT 1.0 the <xsl:with-param name="p"><xsl:value-of select="part/p"/></xsl:with-param> fills the parameter p with a text node of the string value of the first element selected by part/p. So at least use simply <xsl:with-param name="p-elements" select="part/p"/>, then the parameter value is a node-set with all p elements.
Better yet, simply use template matching and apply-templates consistently, then you don't have to struggle with call-template and with-param.
Based on your comments you could just use
<xsl:template mode="pt" match="item[part]">
<xsl:apply-templates select="part" mode="pt"/>
</xsl:template>
<xsl:template match="part" mode="pt">
<xsl:variable name="spec" select="following-sibling::*[1][self::spec]"/>
...
</xsl:template>

I want to sort parent node CommlCoverage , based on child's name of From Tag

we want to xsl-sort of from element in
<?xml version="1.0"?>
<General><CommlCoverage>
<Audit>
<AuditMtc>
<Term>
<From>2013-07-05</From>
<To>2014-06-02</To>
</Term>
</AuditMtc>
</Audit>
<Supplement>
<Audit>
<AuditMtc>
<Term>
<From>2013-07-02</From>
<To>2014-06-02</To>
</Term>
</AuditMtc>
</Audit>
</Supplement>
<Supplement>
<Audit>
<AuditMtc>
<Term>
<From>2013-01-02</From>
<To>2014-06-02</To>
</Term>
</AuditMtc>
</Audit>
</Supplement>
</CommlCoverage>
<CommlCoverage>
<Audit>
<AuditMtc><Term><From>2013-07-05</From><To>2014-06-02</To></Term></AuditMtc></Audit>
<Supplement><Audit><AuditMtc><Term><From>2013-07-02</From><To>2014-06-02</To></Term></AuditMtc></Audit></Supplement>
</CommlCoverage></General>
need to sorting based on date first tag Audit having different structure with date second Supplement tag having different structure with different date
My code:
<xsl:for-each select="CommlCoverage">
<xsl:sort select="From"/>
<xsl:for-each select="Audit">
<xsl:value-of select="AuditMtc/Term/From"/>
</xsl:for-each>
<xsl:for-each select="Supplement/Audit">
<xsl:value-of select="AuditMtc/Term/From"/>
</xsl:for-each>
</xsl:for-each>
We have update the desired output:
[2013-01-02 To 2014-06-02]
[2013-01-02 To 2014-06-02]
[2013-07-02 To 2014-06-02] [2013-07-02 To 2014-06-02] [2013-07-05 To 2014-06-02] [2013-07-05 To 2014-06-02]
You would probably start off by having a template to match the root element
<xsl:template match="/*">
Within this, you then just need to select the Term children in order of their From date, which could be at different levels. (This assumes the From element is always a direct child of Term
<xsl:apply-templates select=".//Term">
<xsl:sort select="From" />
</xsl:apply-templates>
Note that the syntax .//Term will search for Term elements at any level in the hierarchy below the current node.
Then, you just need a template to match the Term elements, and output the From and To values, like so
<xsl:template match="Term">
[<xsl:value-of select="From"/> to <xsl:value-of select="To" />]
</xsl:template>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/*">
<xsl:apply-templates select=".//Term">
<xsl:sort select="From" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Term">
[<xsl:value-of select="From"/> to <xsl:value-of select="To" />]
</xsl:template>
</xsl:stylesheet>
This outputs the following:
[2013-01-02 to 2014-06-02]
[2013-07-02 to 2014-06-02]
[2013-07-02 to 2014-06-02]
[2013-07-05 to 2014-06-02]
[2013-07-05 to 2014-06-02]
(This doesn't quite match your expected output, because your expected output shows six lots of From and To times, but only five are in your input).
Not sure if I'm right about the requirement, but, this is what the following code does:
Sorts "CommlCoverage" based on any //Term/From and then writes all //Term/From in sorted order.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" indent="yes"/>
<xsl:template match="/General">
<xsl:for-each select="CommlCoverage">
<xsl:sort>
<xsl:for-each select="current()//Term/From">
<xsl:sort select="."/>
<xsl:if test="position() = 1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:sort>
<xsl:apply-templates select="Audit | Supplement/Audit">
<xsl:sort select="AuditMtc/Term/From"/>
</xsl:apply-templates>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="Audit">
<xsl:value-of select="concat('[', AuditMtc/Term/From, ' To ', AuditMtc/Term/To, ']')"/>
</xsl:template>
</xsl:stylesheet>

XSLT: Replace string with Abbreviations

I would like to know how to replace the string with the abbreviations.
My XML looks like below
<concept reltype="CONTAINS" name="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" type="NUM">
<code meaning="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" value="18074-5" schema="LN" />
<measurement value="5.7585187646">
<code value="cm" schema="UCUM" />
</measurement>
<content>
<concept reltype="HAS ACQ CONTEXT" name="Image Mode" type="CODE">
<code meaning="Image Mode" value="G-0373" schema="SRT" />
<code meaning="2D mode" value="G-03A2" schema="SRT" />
</concept>
</content>
</concept>
and I am selecting some value from the xml like,
<xsl:value-of select="concept/measurement/code/#value"/>
Now what I want is, I have to replace cm with centimeter. I have so many words like this. I would like to have a xml for abbreviations and replace from them.
I saw one similar example here.
Using a Map in XSL for expanding abbreviations
But it replaces node text, but I have text as attribute. Also, it would be better for me If I can find and replace when I select text using xsl:valueof select instead of having a separate xsl:template. Please help. I am new to xslt.
I have created XSLT v "1.1". For abbreviations I have created XML file as you have mentioned:
Abbreviation.xml:
<Abbreviations>
<Abbreviation>
<Short>cm</Short>
<Full>centimeter</Full>
</Abbreviation>
<Abbreviation>
<Short>m</Short>
<Full>meter</Full>
</Abbreviation>
</Abbreviations>
XSLT:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" />
<xsl:param name="AbbreviationDoc" select="document('Abbreviation.xml')"/>
<xsl:template match="/">
<xsl:call-template name="Convert">
<xsl:with-param name="present" select="concept/measurement/code/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Convert">
<xsl:param name="present"/>
<xsl:choose>
<xsl:when test="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]">
<xsl:value-of select="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]/Full"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$present"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
INPUT:
as you have given <xsl:value-of select="concept/measurement/code/#value"/>
OUTPUT:
centimeter
You just need to enhance this Abbreviation.xml to keep short and full value of abbreviation and call 'Convert' template with passing current value to get desired output.
Here a little shorter version:
- with abbreviations in xslt file
- make use of apply-templates with mode to make usage shorter.
But with xslt 1.0 node-set extension is required.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="abbreviations_txt">
<abbreviation abbrev="cm" >centimeter</abbreviation>
<abbreviation abbrev="m" >meter</abbreviation>
</xsl:variable>
<xsl:variable name="abbreviations" select="exsl:node-set($abbreviations_txt)" />
<xsl:template match="/">
<xsl:apply-templates select="concept/measurement/code/#value" mode="abbrev_to_text"/>
</xsl:template>
<xsl:template match="* | #*" mode="abbrev_to_text">
<xsl:variable name="abbrev" select="." />
<xsl:variable name="long_text" select="$abbreviations//abbreviation[#abbrev = $abbrev]/text()" />
<xsl:value-of select="$long_text"/>
<xsl:if test="not ($long_text)">
<xsl:value-of select="$abbrev"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>