I am working on Grouping using XSLT 1.0 but output is not as expected.
I need to sum the quantity in MovementLine for respective Movement, but instead the sum is happening for all Movement instead.
Here the key should work for each Movement but not for all
Source XML :
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<MovementFileFlowRequest>
<Movement>
<MovementLine>
<ArticleNumber>355</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
<MovementLine>
<ArticleNumber>129</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
<MovementLine>
<ArticleNumber>355</ArticleNumber>
<Quantity>50</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
</Movement>
<Movement>
<MovementLine>
<ArticleNumber>359</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
</Movement>
<Movement>
<MovementLine>
<ArticleNumber>359</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
</Movement>
</MovementFileFlowRequest>`
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"/>
<xsl:key name="Movements" match="MovementLine" use="concat(ArticleNumber, '|', LogisticVariety, '|' , Status)"/>
<xsl:template match="/">
<xsl:for-each select="MovementFileFlowRequest">
<MovementFileFlowRequest >
<xsl:for-each select="Movement">
<!-- Apply "group" template to the first Record Line in group -->
<Movement>
<xsl:for-each select="MovementLine[generate-id() = generate-id(key('Movements', concat(ArticleNumber, '|', LogisticVariety, '|' , Status))[1])]">
<xsl:variable name="Quantity" select="sum(key('Movements', concat(ArticleNumber, '|', LogisticVariety, '|' , Status))/Quantity)"/>
<MovementLine>
<xsl:copy-of select="./*[not(name()='Quantity')]"/>
<Quantity>
<xsl:value-of select="$Quantity" />
</Quantity>
</MovementLine>
</xsl:for-each>
</Movement>
</xsl:for-each>
</MovementFileFlowRequest>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>`
Current Output :
`<?xml version="1.0" encoding="UTF-8"?>
<MovementFileFlowRequest>
<Movement>
<MovementLine>
<ArticleNumber>355</ArticleNumber>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
<Quantity>60</Quantity>
</MovementLine>
<MovementLine>
<ArticleNumber>129</ArticleNumber>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
<Quantity>10</Quantity>
</MovementLine>
</Movement>
<Movement>
<MovementLine>
<ArticleNumber>359</ArticleNumber>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
<Quantity>20</Quantity>
</MovementLine>
</Movement>
<Movement/>
</MovementFileFlowRequest>
`
Desired Output :
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<MovementFileFlowRequest>
<Movement>
<MovementLine>
<ArticleNumber>355</ArticleNumber>
<Quantity>60</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
<MovementLine>
<ArticleNumber>129</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
</Movement>
<Movement>
<MovementLine>
<ArticleNumber>359</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
</Movement>
<Movement>
<MovementLine>
<ArticleNumber>359</ArticleNumber>
<Quantity>10</Quantity>
<LogisticVariety>2</LogisticVariety>
<Status>12</Status>
<DateFormatted>20190516</DateFormatted>
<Content/>
<LineReference/>
</MovementLine>
</Movement>
</MovementFileFlowRequest>`
Change the key use or selection expression to include the generated id of the parent element e.g. use="concat(generated-id(..), '|', ArticleNumber, '|', LogisticVariety, '|' , Status)", make sure you adapt all expressions in the XSLT code where you use the key function as well to adapt the expression the same way.
Related
I have a large XML file to transform using XSLT to append the integer position of sibling node . I’m using XSLT3 streaming and accumulators. I did get desired output. However, my code looks so lengthy that I’m unable to simplify my code. I also need to group same sibling nodes as sibling nodes in the source xml is not grouped always. Could someone help me here please?
Requirement: Sibling nodes such as Positions, Payments etc.. need to be appended with their corresponding integer position such as <Locations1>, <Locations2>etc.<Payments1>,< Payments2> etc..
Now that I have declared two accumulators, each for each sibling nodes. However, my source XML has many sibling nodes.. I’m not sure if I need to use as many accumulators and template match as my sibling nodes.
Input XML
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1000</amount>
<currency>USD</currency>
</Payments>
<Locations>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations>
<Locations>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations>
<Locations>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations>
<Payments>
<amount>1500</amount>
<currency>USD</currency>
</Payments>
<Payments>
<amount>1800</amount>
<currency>USD</currency>
</Payments>
</Member>
</Members>
``
Expected Output
``
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
``
Current code
``
<?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" exclude-result-prefixes="xs" version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:accumulator name="loc-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Locations" select="$value + 1"/>
</xsl:accumulator>
<xsl:accumulator name="pay-count" as="xs:integer" initial-value="0" streamable="yes">
<xsl:accumulator-rule match="Member" select="0"/>
<xsl:accumulator-rule match="Member/Payments" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="Locations">
<xsl:element name="Locations_{accumulator-before('loc-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="Payments">
<xsl:element name="Payments_{accumulator-before('pay-count')}">
<xsl:copy-of select="#* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
``
Current Output
<?xml version="1.0" encoding="UTF-8"?>
<Members>
<Member>
<Name>
<fname>Fred</fname>
<id>1234</id>
</Name>
<Locations_1>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1000</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1000</amount>
<currency>USD</currency>
</Payments_2>
<Locations_3>
<name>New York</name>
<days>5</days>
<hours>40</hours>
</Locations_3>
<Locations_4>
<name>Boston</name>
<days>4</days>
<hours>32</hours>
</Locations_4>
</Member>
<Member>
<Name>
<fname>Jack</fname>
<id>4567</id>
</Name>
<Locations_1>
<name>New York</name>
<days>5</days>
<hours>30</hours>
</Locations_1>
<Locations_2>
<name>Chicago</name>
<days>3</days>
<hours>24</hours>
</Locations_2>
<Payments_1>
<amount>1500</amount>
<currency>USD</currency>
</Payments_1>
<Payments_2>
<amount>1800</amount>
<currency>USD</currency>
</Payments_2>
</Member>
</Members>
If you want to group the Member child elements by node-name() then I think you need to wrap the xsl:for-each-group into xsl:fork:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="counters"/>
<xsl:accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*"
select="map:put($value, node-name(), if (map:contains($value, node-name())) then map:get($value, node-name()) + 1 else 1)"/>
</xsl:accumulator>
<xsl:template match="Member">
<xsl:copy>
<xsl:fork>
<xsl:for-each-group select="*" group-by="node-name()">
<xsl:apply-templates select="current-group()"/>
</xsl:for-each-group>
</xsl:fork>
</xsl:copy>
</xsl:template>
<xsl:template match="Member/*">
<xsl:element name="{node-name()}_{accumulator-before('counters')(node-name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This approach only shows the grouping, it doesn't try to special case Name elements or some other way to not output an index if there is only one such element.
Firstly, my sympathy. XML that uses names like Payments_1 and Payments_2 is really bad news, someone is going to hate you for generating it like this. But if that's the kind of XML you've been told to produce, I guess it's not your job to question it.
As far as the requirements are concerned, you haven't made it clear whether the various kinds of sibling nodes are always grouped as in your example (all Locations, then all Payments, etc), or whether they can be interleaved.
One way you might be able to reduce the volume of code is by having a single accumulator holding a map. The map would use element names as the key and the current sibling count for that element as the value.
<accumulator name="counters" as="map(xs:QName, xs:integer)" initial-value="map{}">
<xsl:accumulator-rule match="Member" select="map{}"/>
<xsl:accumulator-rule match="Member/*" select="map:put($value, node-name(.), if (map:contains($value, node-name(.)) then map:get($value, node-name(.))+1 else 1"/>
</accumulator>
<xsl:template match="Members/*">
<xsl:element name="{name()}_{accumulator-before('counters')(node-name(.))}">
....
Another way to do the conditional map:put is
map:put($value, node-name(.), ($value(node-name(.)), 0)[1] + 1)
I am pulling clob data from a JDBC server and the below is the sample format of the xml
1
<?xml version="1.0" encoding="utf-8"?>
<Sales_Posting>
<row>
<ORGANIZATION_ID>1</ORGANIZATION_ID>
<RTL_LOC_ID>269</RTL_LOC_ID>
<POSLOG_DATA><?xml version="1.0" encoding="UTF-8"?>
<POSLog xmlns="http://www.nrf-arts.org/IXRetail/namespace/"
xmlns:dtv="http://www.datavantagecorp.com/xstore/"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://www.nrf-arts.org/IXRetail/namespace/ POSLog.xsd" >
<Transaction xmlns:dtv="http://www.datavantagecorp.com/xstore/" CancelFlag="true" OfflineFlag="false" TrainingModeFlag="false" dtv:AppVersion="17.0.0.0.716 - 0.0.0 - 0.0" dtv:TransactionType="RETAIL_SALE" >
<dtv:OrganizationID><![CDATA[1]]></dtv:OrganizationID>
<RetailStoreID><![CDATA[269]]></RetailStoreID>
<WorkstationID><![CDATA[2]]></WorkstationID>
</Transaction>
</POSLog></POSLOG_DATA>
<CREATE_DATE>2019-07-17 20:57:56.536</CREATE_DATE>
</row>
<row>
<ORGANIZATION_ID>1</ORGANIZATION_ID>
<RTL_LOC_ID>269</RTL_LOC_ID>
<POSLOG_DATA><?xml version="1.0" encoding="UTF-8"?>
<POSLog xmlns="http://www.nrf-arts.org/IXRetail/namespace/"
xmlns:dtv="http://www.datavantagecorp.com/xstore/"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://www.nrf-arts.org/IXRetail/namespace/ POSLog.xsd" >
<Transaction xmlns:dtv="http://www.datavantagecorp.com/xstore/" CancelFlag="false" OfflineFlag="false" TrainingModeFlag="false" dtv:AppVersion="17.0.0.0.716 - 0.0.0 - 0.0" dtv:TransactionType="RETAIL_SALE" >
<dtv:OrganizationID><![CDATA[1]]></dtv:OrganizationID>
<RetailStoreID><![CDATA[269]]></RetailStoreID>
<WorkstationID><![CDATA[2]]></WorkstationID>
</Transaction>
</POSLog></POSLOG_DATA>
<CREATE_DATE>2019-07-18 06:20:38.014</CREATE_DATE>
</row>
</Sales_Posting>
I need to pull the xml inside the tag .
For a single record I am able to pull the data using the below code
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select="/Sales_Posting/row/POSLOG_DATA"/>
</xsl:template>
</xsl:stylesheet>
But I require it for multiple records.
XSLT 1.0 is preferable. If the result is possible via multiple xslt mappings then that is also fine for me.
Expected Output is :-
2
<?xml version="1.0" encoding="utf-8"?>
<Sales_Posting>
<row>
<POSLog xmlns="http://www.nrf-arts.org/IXRetail/namespace/"
xmlns:dtv="http://www.datavantagecorp.com/xstore/"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://www.nrf-arts.org/IXRetail/namespace/ POSLog.xsd">
<Transaction CancelFlag="true"
OfflineFlag="false"
TrainingModeFlag="false"
dtv:AppVersion="17.0.0.0.716 - 0.0.0 - 0.0"
dtv:TransactionType="RETAIL_SALE">
<dtv:OrganizationID>1</dtv:OrganizationID>
<RetailStoreID>269</RetailStoreID>
<WorkstationID>2</WorkstationID>
</Transaction>
</POSLog>
</row>
<row>
<POSLog xmlns="http://www.nrf-arts.org/IXRetail/namespace/"
xmlns:dtv="http://www.datavantagecorp.com/xstore/"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://www.nrf-arts.org/IXRetail/namespace/ POSLog.xsd">
<Transaction CancelFlag="true"
OfflineFlag="false"
TrainingModeFlag="false"
dtv:AppVersion="17.0.0.0.716 - 0.0.0 - 0.0"
dtv:TransactionType="RETAIL_SALE">
<dtv:OrganizationID>1</dtv:OrganizationID>
<RetailStoreID>269</RetailStoreID>
<WorkstationID>2</WorkstationID>
</Transaction>
</POSLog>
</row>
</Sales_Posting>
I am sharing the image of source structure as well as the expected target structure. Kindly help.
Try:
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="/Sales_Posting">
<xsl:copy>
<xsl:for-each select="row">
<xsl:copy>
<xsl:value-of select="substring-after(POSLOG_DATA, '?>')" disable-output-escaping="yes"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The result must be saved to file before processed further. This is assuming your processor supports disable-output-escaping.
Team,
Need your help in grouping data in XSLT.
Requirements:
Group data based on report_number
Group step 1 output data based on program_name
Iterate through each of grouped data and create XML as in expected output
I am able to execute first two steps. I am not able to arrive at a logic
for step 3.
Input:
<?xml version="1.0" ?>
<MTR>
<program>
<program_row>
<report_number>1</report_number>
<program_id>PMP</program_id>
<program_name>Portfolio Manager Program</program_name>
<ssn_tin>1111111111</ssn_tin>
<acct_number>1111111111</acct_number>
<total_value/>
</program_row>
<program_row>
<report_number>1</report_number>
<program_id>PMP</program_id>
<program_name>Portfolio Manager Program</program_name>
<ssn_tin>2222222222</ssn_tin>
<acct_number>2222222222</acct_number>
<total_value/>
</program_row>
<program_row>
<report_number>1</report_number>
<program_id>PMP</program_id>
<program_name>Customer Manager Program</program_name>
<ssn_tin>3333333333</ssn_tin>
<acct_number>3333333333</acct_number>
<total_value/>
</program_row>
<program_row>
<report_number>1</report_number>
<program_id>PMP</program_id>
<program_name>Portfolio Manager Program</program_name>
<ssn_tin>4444444444</ssn_tin>
<acct_number>4444444444</acct_number>
<total_value/>
</program_row>
<program_row>
<report_number>1</report_number>
<program_id>PMP</program_id>
<program_name>Relationship Manager Program</program_name>
<ssn_tin>55555555555</ssn_tin>
<acct_number>55555555555</acct_number>
<total_value/>
</program_row>
<program_row>
<report_number>2</report_number>
<program_id>PMP</program_id>
<program_name>Ringo Manager Program</program_name>
<ssn_tin>6666666666</ssn_tin>
<acct_number>6666666666</acct_number>
<total_value/>
</program_row>
</program>
</MTR>
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<MTR>
<reports>
<report>
<report_number>1</report_number>
<headers>
<header>
<prog_name>Portfolio Manager Program</prog_name>
<acct_no>1111111111,2222222222,55555555555</acct_no>
</header>
<header>
<prog_name>Customer Manager Program</prog_name>
<acct_no>3333333333</acct_no>
</header>
<header>
<prog_name>Relationship Manager Program</prog_name>
<acct_no>4444444444</acct_no>
</header>
</headers>
</report>
<report>
<report_number>2</report_number>
<headers>
<header>
<prog_name>Ringo Manager Program</prog_name>
<acct_no>6666666666</acct_no>
</header>
</headers>
</report>
</reports>
</MTR>
Incomplete XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="kGroup" match="program_row" use="report_number"/>
<xsl:key name="progNameGroup" match="program_row" use="program_name"/>
<xsl:template match="MTR">
<MTR>
<xsl:copy-of select="emb_disc" />
<xsl:copy-of select="emb_foot_note" />
<reports>
<xsl:for-each select="program/program_row[generate-id(.) = generate-id(key('kGroup', report_number)[1])]">
<report>
<headers>
<xsl:for-each select="key('kGroup', report_number)[generate-id() = generate-id(key('progNameGroup', program_name)[1])]">
<xsl:for-each select="key('progNameGroup', program_name)">
<header>
<prog_name></prog_name>
<acct_no></acct_no>
</header>
</xsl:for-each>
</xsl:for-each>
</headers>
</report>
</xsl:for-each>
</reports>
</MTR>
</xsl:template>
</xsl:stylesheet>
Here is a correction, you need to make sure the second-level key includes the value of the first-level key and then you need to output the items:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="kGroup" match="program_row" use="report_number"/>
<xsl:key name="progNameGroup" match="program_row" use="concat(report_number, '|', program_name)"/>
<xsl:template match="MTR">
<MTR>
<xsl:copy-of select="emb_disc" />
<xsl:copy-of select="emb_foot_note" />
<reports>
<xsl:for-each select="program/program_row[generate-id(.) = generate-id(key('kGroup', report_number)[1])]">
<report>
<xsl:copy-of select="report_number"/>
<headers>
<xsl:for-each select="key('kGroup', report_number)[generate-id() = generate-id(key('progNameGroup', concat(report_number, '|', program_name))[1])]">
<header>
<prog_name><xsl:value-of select="program_name"/></prog_name>
<acct_no>
<xsl:for-each select="key('progNameGroup', concat(report_number, '|', program_name))/acct_number">
<xsl:if test="position() > 1">,</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</acct_no>
</header>
</xsl:for-each>
</headers>
</report>
</xsl:for-each>
</reports>
</MTR>
</xsl:template>
</xsl:stylesheet>
Online sample at http://xsltransform.net/6qVRKx9.
Source xml:
<?xml version="1.0" encoding="UTF-8"?>
<Emp>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ1</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM1</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>1</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>100.00</Value>
</Amount>
</AmountDetails>
</EmpDetail>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ1</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM1</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>1</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>5000</Value>
</Amount>
</AmountDetails>
</EmpDetail>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>2</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>200.00</Value>
</Amount>
</AmountDetails>
</EmpDetail>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>3</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>500</Value>
</Amount>
</AmountDetails>
</EmpDetail>
</Emp>
Target xml:
<?xml version="1.0" encoding="UTF-8"?>
<Emp>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ1</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM1</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>1</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>5100.00</Value>
</Amount>
</AmountDetails>
</EmpDetail>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>2</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>200.00</Value>
</Amount>
</AmountDetails>
</EmpDetail>
<EmpDetail>
<ProjectDetails>
<Code>Project</Code>
<ProjectReference>
<Reference>
<RefCode>PROJ2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<ProjectDetails>
<Code>Element</Code>
<ProjectReference>
<Reference>
<RefCode>ELEM2</RefCode>
</Reference>
</ProjectReference>
</ProjectDetails>
<Period>
<PeriodNo>3</PeriodNo>
</Period>
<AmountDetails>
<Currency>EUR</Currency>
<Amount>
<Value>500</Value>
</Amount>
</AmountDetails>
</EmpDetail>
</Emp>
Query: If the PeriodNo, Project's RefCode and Element's RefCode are same then I have to sum the amount value and should generate only one record. In my source file, the first two line item's periodno, project's refcode and element's refcode are same, so would like to get only one record in the output and the amount value should be (100+5000) = 5100.
I have idea if I have to check for one value and do the sum of the lineitem, but in this case I have to check 3 values in each record and I have sum it. Could you please tell me, how to proceed with it using xslt. I have XSLT 1.0 version.
I do not know if you already find a solution at this point, but because there is no solution posted here I am posting mine which is based in Muenchian Grouping.
I have tried to explain the code using comments, hope it helps.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Copy by default attributes, elements and text -->
<xsl:template match="*|#*">
<xsl:copy><xsl:apply-templates select="node()|#*" /></xsl:copy>
</xsl:template>
<!-- Key which is going to allow us to identify each EmpDetail by the
following id (which is a string): ProjectRefCode-ElementRefCode-PeriodNo
Using the key function we are going to be able to select the set of
EmpDetail nodes which have the described structure. So elements with
the same id (i.e. same ProjectRefCode, ElementRefCode and PeriodNo)
are going to be matched using: key('detail-key', $id), where $id holds
the id to be matched. -->
<xsl:key name="detail-key"
match="Emp/EmpDetail"
use="concat(ProjectDetails[Code = 'Project']/ProjectReference/Reference/RefCode, '-',
ProjectDetails[Code = 'Element']/ProjectReference/Reference/RefCode, '-',
Period/PeriodNo)" />
<xsl:template match="Emp">
<!-- Copy the current node and process its children EmpDetail elements -->
<xsl:copy>
<!-- We use key('detail-key', $id)[1] where the $id is the concat
expression to select one element per group, i.e. we iterate
over a set of unique EmpDetail elements -->
<xsl:apply-templates select="EmpDetail[generate-id(.) = generate-id(key('detail-key',
concat(ProjectDetails[Code = 'Project']/ProjectReference/Reference/RefCode, '-',
ProjectDetails[Code = 'Element']/ProjectReference/Reference/RefCode, '-',
Period/PeriodNo))[1])]" />
</xsl:copy>
</xsl:template>
<!-- When the Value element is found, we perform the sum of all the Value elements
which are contained in the group matched by the id described above-->
<xsl:template match="Value">
<!-- Cache parent node to avoid repeated operations -->
<xsl:variable name="current-detail"
select="../../.." />
<!-- Generate the id for the current EmpDetail element as described
above -->
<xsl:variable name="detail-id"
select="concat($current-detail/ProjectDetails[Code = 'Project']/ProjectReference/Reference/RefCode, '-',
$current-detail/ProjectDetails[Code = 'Element']/ProjectReference/Reference/RefCode, '-',
$current-detail/Period/PeriodNo)" />
<!-- Wrap the sum with the Value element -->
<xsl:copy>
<!-- We use the key function to fetch all the EmpDetail elements with the
same id as the current one and sum them -->
<xsl:value-of select="sum(key('detail-key', $detail-id)/AmountDetails/Amount/Value)" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I am using XSLT to generate my HTML.
I am having below xml and I want to write condition if there is only one city node inside a country node I want to write some condition, please see the below xml.
There are two xmls.
1) destinations.xml
<?xml version="1.0"?>
<list type="Destinations">
<resources location="include/xml/locations.xml">
<publication>232</publication>
</resources>
<destination id="594904" title="Maldives" url="/destinations_offers/destinations/asiapacific/maldives/maldives.aspx" thumbnail="/99/english/images/square_tcm481-594879.jpg" FeaturedDestination="true">
<city id="192513" />
</destination>
<destination id="594089" title="New Delhi" url="/destinations_offers/destinations/asiapacific/india/newdelhi.aspx" thumbnail="/99/english/images/sydney_tcm481-594346.jpg" FeaturedDestination="true" NewestDestination="true">
<city id="192460" />
</destination>
</list>
For eample In the above xml there is city id = 192513 for maldives and it is alone node in locations.xml this will be checked in below locations.xml and if that id is alone in that country node then I need to call specific condition.
<?xml version="1.0"?>
<list type="Locations">
<region id="192393" code="ASIA" name="Asia & the Pacific" shortname="Asia & the Pacific">
<country id="192395" code="AU" name="Australia" shortname="Australia">
<city id="192397" code="BNE" name="Brisbane" shortname="Brisbane">
<airport id="192399" code="BNE" name="Brisbane International Airport" shortname="Brisbane"></airport>
</city>
<city id="192409" code="SYD" name="Sydney" shortname="Sydney">
<airport id="192411" code="SYD" name="Kingsford Smith Airport" shortname="Sydney"></airport>
</city>
</country>
<country id="192511" code="MV" name="Maldives" shortname="Maldives">
<city id="192513" code="MLE" name="Male" shortname="Male">
<airport id="192515" code="MLE" name="Male International Airport" shortname="Male"></airport>
</city>
</country>
</region>
</list>
Please suggest!
Thanks.
Use:
count($vLocations/*/*/country[city[#id = $vDestCity/#id]]/city) = 1
In this expression $vLocations is the XML document with top element <list type="Locations"> and $vDestCity is the <city> element we are interested in from the XML document with top element <list type="Destinations">
To see this in action:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:locations>
<list type="Locations">
<region id="192393" code="ASIA"
name="Asia & the Pacific"
shortname="Asia & the Pacific">
<country id="192395" code="AU" name="Australia"
shortname="Australia">
<city id="192397" code="BNE" name="Brisbane"
shortname="Brisbane">
<airport id="192399" code="BNE"
name="Brisbane International Airport"
shortname="Brisbane">
</airport>
</city>
<city id="192409" code="SYD" name="Sydney"
shortname="Sydney">
<airport id="192411" code="SYD"
name="Kingsford Smith Airport"
shortname="Sydney">
</airport>
</city>
</country>
<country id="192511" code="MV" name="Maldives"
shortname="Maldives">
<city id="192513" code="MLE" name="Male"
shortname="Male">
<airport id="192515" code="MLE"
name="Male International Airport"
shortname="Male">
</airport>
</city>
</country>
</region>
</list>
</my:locations>
<xsl:variable name="vLocations"
select="document('')/*/my:locations"/>
<xsl:variable name="vDestCity1"
select="/*/destination/city[#id=192513]"/>
<xsl:variable name="vDestCity2"
select="/*/destination/city[#id=192397]"/>
<xsl:template match="/">
<xsl:value-of select=
"count($vLocations/*/*/country
[city[#id = $vDestCity1/#id]]/city
) = 1
"/>
:
<xsl:text/>
<xsl:value-of select=
"count($vLocations/*/*/country
[city[#id = $vDestCity2/#id]]/city
) = 1
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided destinations.xml:
<list type="Destinations">
<resources location="include/xml/locations.xml">
<publication>232</publication>
</resources>
<destination id="594904" title="Maldives" url="/destinations_offers/destinations/asiapacific/maldives/maldives.aspx" thumbnail="/99/english/images/square_tcm481-594879.jpg" FeaturedDestination="true">
<city id="192513" />
</destination>
<destination id="594089" title="New Delhi" url="/destinations_offers/destinations/asiapacific/india/newdelhi.aspx" thumbnail="/99/english/images/sydney_tcm481-594346.jpg" FeaturedDestination="true" NewestDestination="true">
<city id="192460" />
</destination>
</list>
The wanted, correct result is produced:
true
:
false
for example Australia: count(//country[#id='192395']/city)