Xpath Select values from one node that doesn't exist in another - xslt

<my-courses>
<courses-reqd>
<details>
<subject>Math</subject>
</details>
<details>
<subject>Econ</subject>
</details>
<details>
<subject>Geog</subject>
</details>
<details>
<subject>Phys</subject>
</details>
<detail>
<subject>Zool</subject>
</details>
</courses-reqd>
<courses-taken>
<details>
<subject>Math</subject>
</details>
<details>
<subject>Econ</subject>
</details>
<detail>
<subject>Zool</subject>
</details>
</courses-taken>
I have provided sample XML and want to know how to get the values the are not in the courses-taken but are in the courses-reqd by using XPATH. In the case provided, the answer would be Geog and Phys as they do not exist in course-taken.
<xsl:apply-templates select="my-courses\courses-reqd | not(my-courses\courses-taken)"/>
Is this possible and if so, how would I go abouts doing this. Any help would be appreciated.
Thanks

The simple answer here is to do this:
<xsl:apply-templates
select="/my-courses/courses-reqd/detail
[not(subject = /my-courses/courses/taken/details/subject)]" />
A better (more efficient) approach is to use keys.
This would be added towards the top of your XSLT:
<xsl:key name="kTaken" match="courses-taken//subject" use="."/>
And then this would be used in one of your templates:
<xsl:apply-templates
select="/my-courses/courses-reqd/detail[not(key('kTaken', subject))]" />
When this XSLT is run on your sample input (after the sample input has been corrected to consistently use elements named "details" and not "detail":
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="kTaken" match="courses-taken//subject" use="."/>
<xsl:template match="/">
<root>
<xsl:apply-templates
select="/my-courses/courses-reqd/details[not(key('kTaken', subject))]" />
</root>
</xsl:template>
<xsl:template match="details">
<course>
<xsl:value-of select="subject"/>
</course>
</xsl:template>
</xsl:stylesheet>
The result is:
<root>
<course>Geog</course>
<course>Phys</course>
</root>

Related

XSL to Parse and Re-order XML Nodes

I need assistance with creating a XSL stylesheet to parse data and re-order based on values within certain nodes. My original XML is being exported by a roster program in a undesirable structure which is causing issues when converting to JSON.
This is a Fire Department roster that will be converted into JSON to be processed by Station Status Boards. I'm looking to format the XML so that when converted into JSON each Station has a crew list. I've attempted to create a XSL without success. I have zero background in XSL (Fire Fighter).
Section of Original XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Data>
<Date>2019-05-07-07:00</Date>
<Headers></Headers>
<Records>
<Record>
<RscPayrollIDCh>12345678</RscPayrollIDCh>
<RscEmployeeIDCh>12345678</RscEmployeeIDCh>
<RscMasterNameCh>Smith, Mike A.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>B19</StationAbrvCh>
<PUnitAbrvCh>BAT19</PUnitAbrvCh>
<PosJobAbrvCh>BC-S</PosJobAbrvCh>
</Record>
<Record>
<RscPayrollIDCh>12345</RscPayrollIDCh>
<RscEmployeeIDCh>12345</RscEmployeeIDCh>
<RscMasterNameCh>Smith, John A.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>S15</StationAbrvCh>
<PUnitAbrvCh>E15</PUnitAbrvCh>
<PosJobAbrvCh>CAPT</PosJobAbrvCh>
</Record>
<Record>
<RscPayrollIDCh>123456</RscPayrollIDCh>
<RscEmployeeIDCh>123456</RscEmployeeIDCh>
<RscMasterNameCh>Smith, Bob R.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>S15</StationAbrvCh>
<PUnitAbrvCh>E15</PUnitAbrvCh>
<PosJobAbrvCh>ENG</PosJobAbrvCh>
</Record>
</Records>
</Data>
I would like to format the XML so that it looks something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Data>
<Date>2019-05-07-07:00</Date>
<Headers></Headers>
<Records>
<Record>
<StationAbrvCh>B19</StationAbrvCh>
<RscMasterNameCh>Smith, Mike A.</RscMasterNameCh>
</Record>
<Record>
<StationAbrvCh>S15</StationAbrvCh>
<RscMasterNameCh>Smith, John A.</RscMasterNameCh>
<RscMasterNameCh>Smith, Bob R.</RscMasterNameCh>
</Record>
</Records>
I would like my roster to list each crew member under the Station they are assigned to for the day.
If you are using XSLT 1.0, Muenchian grouping is the best approach to achieve it as below:
<?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" />
<xsl:key name="groups" match="/Data/Records/Record" use="StationAbrvCh" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/Data/Records">
<xsl:copy>
<xsl:for-each select="Record[generate-id() = generate-id(key('groups', StationAbrvCh)[1])]">
<xsl:copy>
<StationAbrvCh><xsl:value-of select="StationAbrvCh" /></StationAbrvCh>
<xsl:for-each select="key('groups', StationAbrvCh)">
<RscMasterNameCh><xsl:value-of select="RscMasterNameCh" /></RscMasterNameCh>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See demo here: https://xsltfiddle.liberty-development.net/pPzifpM
Using XSLT 2.0 it is quite easy.
In the template maching Records, you should use for-each-group
selecting Record elements and grouping them by StationAbrvCh.
Within each group you should:
Generate StationAbrvCh element, filled with the current grouping key
(also StationAbrvCh).
Run a for-each loop for the current group, copying to the output the
current RscMasterNameCh.
The script should contain also the identity template.
Below you have an example script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Records">
<xsl:copy>
<xsl:for-each-group select="Record" group-by="StationAbrvCh">
<xsl:copy>
<StationAbrvCh><xsl:value-of select="current-grouping-key()"/></StationAbrvCh>
<xsl:for-each select="current-group()">
<xsl:sequence select="RscMasterNameCh"/>
</xsl:for-each>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
Maybe, to fully understand each detail of the above solution,
you should search the Web for description of for-each-group
and related functions (current-grouping-key() and current-group).

How to compare siblings from the same group?

I have a piece of code that looks like this:
<root>
<applicant>
<id>XYZ</id>
<group>
<start_date>2019-04-01</start_date>
<end_date>2019-04-01</end_date>
</group>
<group>
<start_date>2019-04-02</start_date>
<end_date>2019-04-02</end_date>
</group>
<group>
<start_date>2019-04-03</start_date>
<end_date>2019-04-03</end_date>
</group>
</applicant>
<applicant>
<id>ABC</id>
<group>
<start_date>2019-05-01</start_date>
<end_date>2019-05-01</end_date>
</group>
<group>
<start_date>2019-05-02</start_date>
<end_date>2019-05-02</end_date>
</group>
<group>
<start_date>2019-05-03</start_date>
<end_date>2019-05-03</end_date>
</group>
</applicant>
</root>
and I need to group it by applicant and merge into one node with single start date and end date if date from from following sibling is 1 day different (date difference in days is 1)
so for above code to achieve something like:
<root>
<applicant>
<id>XYZ</id>
<start_date>2019-04-01</start_date>
<end_date>2019-04-03</end_date>
</applicant>
<applicant>
<id>ABC</id>
<start_date>2019-05-01</start_date>
<end_date>2019-05-03</end_date>
</applicant>
</root>
I was thinking about using following-sibling:: or some sort of recurrence.
Assuming you can indeed use XSLT 2.0 or above, you could use xsl:for-each-group here, and group starting with elements whose start_date - 1 doesn't match the end_date of the previous group.
Try this XSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes" />
<xsl:template match="applicant">
<xsl:copy>
<xsl:copy-of select="id" />
<xsl:for-each-group select="group" group-starting-with="group[not(xs:date(start_date) - xs:dayTimeDuration('P1D') = xs:date(preceding-sibling::group[1]/end_date))]">
<group>
<xsl:copy-of select="start_date, current-group()[last()]/end_date" />
</group>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
(This uses XSLT 3.0. In XSLT 2.0, all you would need to do is replace the xsl:mode with the Identity Template)

Transform to output xml based on the value of an element in the input xml

I am trying to learn the basics of XSLT, but am stuck on a particular use case. What I want to achieve is to transform one xml file into another xml (I am using XSLT 2.0), but a condition is that the grouping of elements in the output xml is decided by the value of one particular element in the input xml.
I will try to exemplify my question through a made-up example.
Lets say this is an input xml:
<products>
<shoes>
<shoe>
<name>Ecco City</name>
<category>Urban</category>
</shoe>
<shoe>
<name>Timberland Forest</name>
<category>Wildlife</category>
</shoe>
<shoe>
<name>Asics Gel-Kayano</name>
<category>Running</category>
</shoe>
</shoes>
<clothes>
<shorts>
<name>North Face</name>
<category>Wildlife</category>
</shorts>
<shorts>
<name>Adidas Running Shorts</name>
<category>Running</category>
</shorts>
</clothes>
Based on the value of the category element I want to, for each product, list similar products, that is, other products having the same category in the input xml, like this:
<output>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
</output>
This doesn't seem to be a grouping problem as such. If I understand correctly, you want to do something like:
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="*"/>
<xsl:key name="product-by-category" match="*" use="category" />
<xsl:template match="/products">
<output>
<xsl:for-each select="*/*">
<forSale>
<item>
<xsl:value-of select="name" />
</item>
<xsl:for-each select="key('product-by-category', category) except .">
<similarItem>
<xsl:value-of select="name" />
</similarItem>
</xsl:for-each>
</forSale>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<forSale>
<item>Ecco City</item>
</forSale>
<forSale>
<item>Timberland Forest</item>
<similarItem>North Face</similarItem>
</forSale>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
<forSale>
<item>North Face</item>
<similarItem>Timberland Forest</similarItem>
</forSale>
<forSale>
<item>Adidas Running Shorts</item>
<similarItem>Asics Gel-Kayano</similarItem>
</forSale>
</output>

XSLT field validation

I have a requirement to validate a field content which is within a 1..N structure, so the data come in pairs of RESULT_ID and RESULT_VALUE.
If "<" is found in Result.Min_Limit characteristic, its value must be put in the Max_Limit characteristic target field and Min_Limit must be cleared out in target structure.
Sample of source structure with data:
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<RESULTS>
<RESULT>
<D_RESULT>
<D_RESULT_ID>Result.Derived</D_RESULT_ID>
<D_RESULT_VALUE>59W</D_RESULT_VALUE>
</D_RESULT>
<D_RESULT>
<D_RESULT_ID>Result.Min_Limit</D_RESULT_ID>
<D_RESULT_VALUE><=600.0000</D_RESULT_VALUE>
</D_RESULT>
<D_RESULT>
<D_RESULT_ID>Result.Max_Limit</D_RESULT_ID>
<D_RESULT_VALUE/>
</D_RESULT>
<D_RESULT>
<D_RESULT_ID>Result.FailedCriticalLvl</D_RESULT_ID>
<D_RESULT_VALUE>false</D_RESULT_VALUE>
</D_RESULT>
</RESULT>
</RESULTS>
</TEST>
Expected target structure should be as follow:
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<RESULTS>
<RESULT>
<D_RESULT>
<D_RESULT_ID>Result.Derived</D_RESULT_ID>
<D_RESULT_VALUE>59W</D_RESULT_VALUE>
</D_RESULT>
<D_RESULT>
<D_RESULT_ID>Result.Min_Limit</D_RESULT_ID>
<D_RESULT_VALUE/>
</D_RESULT>
<D_RESULT>
<D_RESULT_ID>Result.Max_Limit</D_RESULT_ID>
<D_RESULT_VALUE><=600.0000</D_RESULT_VALUE>
</D_RESULT>
<D_RESULT>
<D_RESULT_ID>Result.FailedCriticalLvl</D_RESULT_ID>
<D_RESULT_VALUE>false</D_RESULT_VALUE>
</D_RESULT>
</RESULT>
</RESULTS>
</TEST>
I've created a variable to store Min_Limit value if it contains the "<" character, and it works fine when the pointer is still in Min_Limit characteristic.
When it flips to next characteristic (Max_Limit), the variable seems to loose its value and therefore I have no visibility of Min_Limit value anymore to assign the value to Max_Limit characteristic.
Currently, what I have is like the following:
......
<xsl:for-each select="a:D_RESULT">
<D_RESULT>
<D_RESULT_ID>
<xsl:value-of select="a:D_RESULT_ID"/>
</D_RESULT_ID>
<D_RESULT_VALUE>
<xsl:value-of select="a:D_RESULT_VALUE"/>
</D_RESULT_VALUE>
</D_RESULT>
</xsl:for-each>
</RESULT>
... other fields
I've found some interesting conversations here with some approaches I believe would help solving this, but I'm not sure I understood enough to apply to my scenario.
This is one of those XSLT: nested for-each and dynamic variable
Would anyone suggests something to solve this?
Many thanks in advance!
Rafael.
Here's one way you could look at it:
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="D_RESULT_VALUE[../D_RESULT_ID='Result.Min_Limit' and contains(., '<')] ">
<xsl:copy/>
</xsl:template>
<xsl:template match="D_RESULT_VALUE[../D_RESULT_ID='Result.Max_Limit' and contains(../../D_RESULT[D_RESULT_ID='Result.Min_Limit']/D_RESULT_VALUE, '<')] ">
<xsl:copy>
<xsl:value-of select="../../D_RESULT[D_RESULT_ID='Result.Min_Limit']/D_RESULT_VALUE"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Demo: http://xsltransform.net/3NzcBtA

Filter and Group in XSLT

I am trying to filter data and then group using XSLT. Here is my XML
<?xml version="1.0" encoding="UTF-8"?>
<AccumulatedOutput>
<root>
<Header>
<Add>true</Add>
<Name>Subscriber</Name>
<Value>SAC</Value>
</Header>
</root>
<root>
<Header>
<Add>true</Add>
<Name>System</Name>
<Value>CBP</Value>
</Header>
</root>
<root>
<Header>
<Add>false</Add>
<Name>Subscriber</Name>
<Value>SAC</Value>
</Header>
</root>
</AccumulatedOutput>
What I want to do is that group based on Header/Name and but remove the group in which Header/Add is false.
So in above example I there will be two groups created (one for Name=Subscriber and other for Name=System) but since the first group(with Name=Subscriber) contains Add=false , I want to ignore that and my output should only have one node in it , like below
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<Header>
<Name>System</Name>
<Value>CBP</Value>
<Add>true</Add>
</Header>
</root>
I tried using group by method but I can't figure out a way to filter it.
It will be a great help if someone can give me some pointers
Thanks
In XSLT 2.0, you could do:
<xsl:template match="/AccumulatedOutput">
<root>
<xsl:for-each-group select="root/Header" group-by="Name">
<xsl:if test="not(current-group()/Add='false')">
<xsl:copy-of select="current-group()"/>
</xsl:if>
</xsl:for-each-group>
</root>
</xsl:template>
No explicit XSLT conditional instructions:
<xsl:template match="/*">
<root>
<xsl:for-each-group group-by="Name" select=
"root/Header[for $n in string(Name)
return every $h in /*/root/Header[Name eq $n]
satisfies not($h/Add eq 'false')]">
<xsl:sequence select="current-group()"/>
</xsl:for-each-group>
</root>
</xsl:template>