How to group two siblings with consecutive days in XSLT - xslt-1.0

I have a requirement to group consecutive days into 1 for the matching ID and am not able to figure out the solution, can someone please guide me on how to approach it.
Here are my input and expected output.
<?xml version='1.0' encoding='UTF-8'?>
<Member>
<Data>
<Id>X0001</Id>
<Date>2022-01-01</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-02</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-03</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-04</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-05</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-08</Date>
</Data>
<Data>
<Id>X0001</Id>
<Date>2022-01-12</Date>
</Data>
<Data>
<Id>X0004</Id>
<Date>2022-01-09</Date>
</Data>
<Data>
<Id>X0005</Id>
<Date>2022-01-10</Date>
</Data>
<Data>
<Id>X0005</Id>
<Date>2022-01-11</Date>
</Data>
</Member>
OUTPUT
----
Out>
<Member>
<Id>X0001</Id
<SDate>2022-01-01</SDate>
<EDate>2022-01-02</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-03</SDate>
<EDate>2022-01-04</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-05</SDate>
<EDate>2022-01-05</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-08</SDate>
<EDate>2022-01-08</EDate
</Member>
<Member>
<Id>X0001</Id
<SDate>2022-01-12</SDate>
<EDate>2022-01-12</EDate
</Member>
<Member>
<Id>X0004</Id
<SDate>2022-01-09</SDate>
<EDate>2022-01-09</EDate
</Member>
<Member>
<Id>X0005</Id
<SDate>2022-01-10</SDate>
<EDate>2022-01-11</EDate
</Member>
</Out>
I need to group if days are consecutive for each ID, if there is no following day then day should be left as is.

Here is one way you could look at it, I think:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/Member">
<Out>
<!-- group by id -->
<xsl:for-each-group select="Data" group-by="Id">
<!-- group contiguous ranges -->
<xsl:for-each-group select="current-group()" group-starting-with="Data[not(xs:date(Date) = xs:date(preceding-sibling::Data[1]/Date) + xs:dayTimeDuration('P1D'))]">
<!-- divide into pairs -->
<xsl:for-each-group select="current-group()" group-by="(position()-1) idiv 2">
<Member>
<xsl:copy-of select="Id"/>
<SDate>
<xsl:value-of select="Date"/>
</SDate>
<EDate>
<xsl:value-of select="current-group()[last()]/Date"/>
</EDate>
</Member>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each-group>
</Out>
</xsl:template>
</xsl:stylesheet>
Note that this assumes that within each Id records are already sorted chronologically.

Related

Division of Nested XML Values with XSLT 1.0 on adjacent element match

I am attempting to divide two values from an xml where the ID and Date match but am not having luck referencing the second record's value. I have the below XML:
<Export>
<Record>
<ID>1000</ID>
<Date>2022-08-15</Date>
<Value>14.09059</Value>
</Record>
<Record>
<ID>1000</ID>
<Date>2022-08-15</Date>
<Value>259.394</Value>
</Record>
<Record>
<ID>2000</ID>
<Date>2022-08-08</Date>
<Value>32.01453</Value>
</Record>
<Record>
<ID>2000</ID>
<Date>2022-08-08</Date>
<Value>467.052</Value>
</Record>
</Export>
And am looking to achieve the below result:
<Export>
<Record>
<ID>1000</ID>
<Date>2022-08-15</Date>
<Value>18.409</Value>
</Record>
<Record>
<ID>2000</ID>
<Date>2022-08-08</Date>
<Value>14.589</Value>
</Record>
</Export>
Is this possible with XLST 1.0?
If they are adjacent, you could do simply:
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:template match="/Export">
<xsl:copy>
<xsl:for-each select="Record[position() mod 2 = 1]">
<xsl:copy>
<xsl:copy-of select="ID | Date"/>
<Value>
<xsl:value-of select="following-sibling::Record[1]/Value div Value" />
</Value>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to loop through all same name parent and child nodes

I have an XML file where I have same name nodes: Parent and child, for example:
<Root>
<Field name="ID">
<description>Test 1</description>
</Field>
<Field name="Period">
<description>Test 2</description>
<Field name="Name">
<description>Test 3</description>
</Field>
<Field name="Name2">
<description>Test 4</description>
<Field name="address">
<description>Test 5</description>
</Field>
<Field name="partyID">
<description>Test 6</description>
<Field name="E-ID">
<Field name="address">
<description>Test 7</description>
</Field>
</Field>
</Field>
</Field>
</Field>
</Root>
The problem is, I am not sure how deep we can have child elements with the same name:
I used template-match on top parent node:
<xsl:template match="Field">
<xsl:value-of select="description"/>
<xsl:for-each select="Field">
<xsl:value-of select="description"/>
</xsl:for-each>
</xsl:template>
This code is not providing me all the child node values. I am looking for a code which can loop through all same name nodes and provide value of description element.
I cannot add multiple for-each because as I said, this is unknown how many times we will have Field node inside another Field node.
Please help me to solve this problem.
You could use recursion to process all Field elements from the root of the tree to the leaves - for example (you did not post the expected result, so I made something up):
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="*"/>
<xsl:template match="/Root">
<output>
<xsl:apply-templates select="Field"/>
</output>
</xsl:template>
<xsl:template match="Field">
<desc>
<xsl:value-of select="description"/>
</desc>
<xsl:apply-templates select="Field"/>
</xsl:template>
</xsl:stylesheet>
Alternatively, you could just select all Field elements regardless of their position in the tree hierarchy:
<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:template match="/Root">
<output>
<xsl:for-each select="//Field">
<desc>
<xsl:value-of select="description"/>
</desc>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>

Grouping XSLT elements by value (XSLT 1.0)

I am having a really hard time trying to group different elements by a common value using XSLT 1.0.
Using the following XML:
<root>
<segment>
<id>ABCD123</id>
</segment>
<segment>
<contact>
<field1>ABCD123</field1>
<field2>(111)345-7890</field2>
</contact>
</segment>
<segment>
<details>
<field1>ABCD123</field1>
<field5>More Details for ABCD123</field5>
</details>
</segment>
<segment>
<id>XZX098</id>
</segment>
<segment>
<contact>
<field1>XZX098</field1>
<field2>(111)443-9999</field2>
</contact>
</segment>
<segment>
<details>
<field1>XZX098</field1>
<field5>More Details for XZX098</field5>
</details>
</segment>
</root>
Transform into this:
<File>
<Record>
<id>ABCD123</id>
<phone>(111)345-7890</phone>
<details>More Details for ABCD123</details>
</Record>
<Record>
<id>XZX098</id>
<phone>(111)443-9999</phone>
<details>More Details for XZX098</details>
</Record>
</File>
I'm trying to group records by the 'id', and then get the contact, and details information that matches that 'id'.
Any help is greatly appreciated.
I don't see any grouping per se required here - just a simple lookup of cross-referenced data:
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:key name="contact-details" match="contact|details" use="field1" />
<xsl:template match="root">
<File>
<xsl:for-each select="segment/id">
<Record>
<xsl:copy-of select="."/>
<phone>
<xsl:value-of select="key('contact-details', .)/field2"/>
</phone>
<details>
<xsl:value-of select="key('contact-details', .)/field5"/>
</details>
</Record>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>

Filter keys out of response

I have an xslt sheet in which I have 2 response objects. $response1 contains a list of ids something like:
<response>
<idlist>
<id>1</id>
<id>2</id>
</idlist>
</response>
And $response2 contains a number of objects:
<response2>
<obj id="1" name="obj1"/>
<obj id="2" name="obj2"/>
<obj id="3" name="obj3"/>
<obj id="4" name="obj4"/>
</response2>
I want to make a copy of response2 but filtering out any objects where the id matches thos contained in response 1
<xsl:variable name="copy">
<xsl:copy-of select="$response2/*[not contains($response1, id)]"/>
</xsl:variable>
any ideas greatly appreciated
C
Given a well-formed input such as:
<root>
<response>
<idlist>
<id>1</id>
<id>2</id>
</idlist>
</response>
<response2>
<obj id="1" name="obj1"/>
<obj id="2" name="obj2"/>
<obj id="3" name="obj3"/>
<obj id="4" name="obj4"/>
</response2>
</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:template match="/root">
<xsl:variable name="ids" select="response/idlist/id" />
<output>
<xsl:copy-of select="response2/obj[not(#id=$ids)]"/>
</output>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<obj id="3" name="obj3"/>
<obj id="4" name="obj4"/>
</output>
A better solution is to use a key to link the nodes by matching id:
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:key name="id" match="id" use="." />
<xsl:template match="/root">
<output>
<xsl:copy-of select="response2/obj[not(key('id', #id))]"/>
</output>
</xsl:template>
</xsl:stylesheet>

Using XSLT to traverse muliple nodes and to select by attribute name

I have the following XML format below, where my main repeating node is
<portfolioSummary>...</portfolioSummary>
One of the descendent nodes is
<keys>
<key>...</key>
<key>...</key>
</keys>
and as you can see, there are multiple <key> child nodes.
I would like to set up an XSLT template whereby I'm selecting all <portfolioSummary> nodes where keys/key[displayValue='HSVaR'] .
My XML input sample set is :
<?xml version="1.0" encoding="UTF-8"?>
<outBound>
<body>
<portfolioSummary portfolioId="61">
<portfolio id="42">
<currency>USD</currency>
<keys>
<key displayValue="My Company Inc" sequenceValue="My Company Inc" sequenceNumber="30" value="My Company Inc" />
<key displayValue="COUNTERPARTY IRS" sequenceValue="COUNTERPARTY IRS" sequenceNumber="50" value="COUNTERPARTY IRS" />
<key displayValue="Historical VaR" sequenceNumber="330" value="HSVaR" />
</keys>
</portfolio>
<exposureProfile>
<node date="2008-08-08">
<tag>HSVaR 5D 99.7 ES</tag>
<exposure>16250079</exposure>
</node>
</exposureProfile>
<summary>
<util>33000000</util>
</summary>
</portfolio>
</portfolioSummary>
</body>
</outBound>
My desired OUTPUT with sample is (where <extIA>..</extIA> may be repeated many times over) :
<?xml version="1.0" encoding="UTF-8"?>
<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extIA>
<legal_id>My Company Inc</legal_id>
<AMOUNT>16250079</AMOUNT>
<CURRENCY>USD</CURRENCY>
<ValuationDate>2008-08-08</ValuationDate>
<externalSystem>myExternalSystem123</externalSystem>
</extIA>
<extIA>
<legal_id>My Company Inc</legal_id>
<AMOUNT>100000</AMOUNT>
<externalSystem>myExternalSystem123</externalSystem>
</extIA>
</collection>
and my XSLT starter code is the following (just some starter ideas):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
version="1.0"
indent="yes">
<xsl:template match="/*">
<collection>
<xsl:apply-templates select="/outbound/body/portfolioSummary"/>
</collection>
</xsl:template>
<!-- Pull portfolioSummary nodes -->
<xsl:template match="portfolio/keys/key[#value='HSVAR']">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*">
<extIA>
<legal_id><xsl:value-of select="#portfolioId"></xsl:value-of></legal_id>
<PRODUCT><xsl:value-of select="summary"/></PRODUCT>
<AMOUNT><xsl:value-of select="exposureProfile"></xsl:value-of></AMOUNT>
<CURRENCY>USD</CURRENCY>
<ValuationDate>2012-05-15</ValuationDate>
<externalSystem>My External System</externalSystem>
</extIA>
</xsl:template>
</xsl:stylesheet>
Again, I would be needing to pull all of those <portfolioSummary> nodes, but ONLY where portfolio/keys/key[#value='HSVAR'] .
Your advice would be greatly appreciated.
I would like to take a guess here. Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<outBound>
<body>
<portfolioSummary portfolioId="42">
<portfolio id="42">
<currency>USD</currency>
<keys>
<key displayValue="My Company Inc" sequenceValue="My Company Inc" sequenceNumber="30" value="My Company Inc" />
<key displayValue="COUNTERPARTY IRS" sequenceValue="COUNTERPARTY IRS" sequenceNumber="50" value="COUNTERPARTY IRS" />
<key displayValue="Historical VaR" sequenceNumber="330" value="HSVaR" />
</keys>
<exposureProfile>
<node date="2008-08-08">100000</node>
</exposureProfile>
<summary>
<util>33000000</util>
</summary>
</portfolio>
</portfolioSummary>
</body>
</outBound>
and the following stylesheet:
<?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="/">
<collection>
<xsl:apply-templates select="/outBound/body/portfolioSummary[descendant::key/#value[.='HSVaR']]"/>
</collection>
</xsl:template>
<xsl:template match="portfolioSummary">
<extIA>
<legal_id><xsl:value-of select="#portfolioId"/></legal_id>
<PRODUCT><xsl:value-of select="descendant::summary/util"/></PRODUCT>
<AMOUNT><xsl:value-of select="descendant::exposureProfile/node"/></AMOUNT>
<CURRENCY><xsl:value-of select="descendant::currency"/></CURRENCY>
<ValuationDate><xsl:value-of select="descendant::exposureProfile/node/#date"/></ValuationDate>
<externalSystem>RAZOR</externalSystem>
</extIA>
</xsl:template>
</xsl:stylesheet>
it outputs:
<?xml version="1.0" encoding="utf-8"?>
<collection>
<extIA>
<legal_id>42</legal_id>
<PRODUCT>33000000</PRODUCT>
<AMOUNT>100000</AMOUNT>
<CURRENCY>USD</CURRENCY>
<ValuationDate>2008-08-08</ValuationDate>
<externalSystem>RAZOR</externalSystem>
</extIA>
</collection>