I have the following solution in 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:template match="/rows">
<xsl:copy>
<xsl:for-each-group select="row" group-starting-with="row[item='****************']">
<xsl:variable name="title-item" select="preceding-sibling::row[1]/item" />
<xsl:for-each select="current-group()[starts-with(item[1], '#')]">
<row>
<xsl:copy-of select="$title-item, *"/>
</row>
</xsl:for-each>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
as for-each-group is not supported in XSLT 1.0
I want to convert it into XSLT 1.0 version.
Declare e.g.
<xsl:key name="group" match="row[not(item = '****************')]" use="generate-id(preceding-sibling::row[item='****************'][1])"/>
as a top-level element, then instead of <xsl:for-each-group select="row" group-starting-with="row[item='****************']"> try
<xsl:for-each select="row[item='****************']">
<xsl:variable name="group" select="key('group', generate-id())"/>
<xsl:variable name="title-item" select="preceding-sibling::row[1]/item" />
<xsl:for-each select="$group[starts-with(item[1], '#')]">
<row>
<xsl:copy-of select="$title-item | *"/>
</row>
</xsl:for-each>
</xsl:for-each>
But we really need to see some input sample structure plus the wanted result. And I might be overlooking some XSLT/XPath 2 only stuff I have used despite trying to write XSLT 1.
Related
I have the following XML:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Matches>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Matches>
</Row>
</Document>
I want to use XSLT to return the nested /Matches/Row/Period when the Document/Row/Period is undefined (as it is in the second Row of the XML)
So I have the following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:variable name="period" select="/Period" />
<xsl:choose>
<xsl:when test="$period = null">
<xsl:copy>
<xsl:copy-of select="KEY | /Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
But it returns the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
</Row>
</Document>
(Note how it is not returning the nested /Matches/Row/Period in the second /Row.
I expect to get the following output:
<Document>
<Row>
<KEY>NIKE|JB|APPAREL|MENS</KEY>
<Period>Nov-21</Period>
</Row>
<Row>
<KEY>FASCINATE|JB|ACCESSORIES|LADIES</KEY>
<Period>Nov-22</Period>
</Row>
</Document>
What am I doing wrong?
undefined or null are not checked in XSLT/XPath using expression = null, you would rather use (for node-sets) <xsl:when test="expression"> e.g. <xsl:when test="Period"> that there is at least one Period child element for the context node or test="not(Period)" to check there is no Period child.
In the end I would suggest to use template matching based on the identity transformation template and put any conditions into match pattern (predicates), but that is a different issue.
Got it working with this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns="http://exampleincludednamespace.com/"
exclude-result-prefixes="ns">
<xsl:output method="xml" omit-xml-declaration="yes" />
<xsl:template match="/">
<Document>
<xsl:for-each select="/Document/Row">
<xsl:choose>
<xsl:when test="not(Period)">
<xsl:copy>
<xsl:copy-of select="KEY | Matches/Row/Period" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="KEY | Period" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Document>
</xsl:template>
</xsl:stylesheet>
Thanks to #MartinHonnen's guidance.
I believe it could be 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="/Document">
<xsl:copy>
<xsl:for-each select="Row">
<xsl:copy>
<xsl:copy-of select="KEY | descendant::Period[1]" />
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I am new to XSLT, and I am trying to create new group based on node value eventType so if eventType is alert, create new group event.
I am checking for last sibling
Input XML
<?xml version="1.0" encoding="UTF-8"?><Rowsets >
<Row>
<eventId>2</eventId>
<plantId>1020</plantId>
<workCenter>WC1</workCenter>
<eventType>alert</eventType>
<eventText>Downtime</eventText>
<eventDesc>WorkcenterDown</eventDesc>
</Row>
<Row>
<eventId>3</eventId>
<plantId>1021</plantId>
<workCenter>WC1</workCenter>
<eventType>alert</eventType>
<eventText>Downtime</eventText>
<eventDesc>WorkcenterDown</eventDesc>
</Row>
<Row>
<eventId>4</eventId>
<plantId>1020</plantId>
<workCenter>WC2</workCenter>
<eventType>incident</eventType>
<eventText>eventtext</eventText>
<eventDesc>failed</eventDesc>
</Row>
<Row>
<plantId>1020</plantId>
<workCenter>WC2</workCenter>
<eventType>incident</eventType>
<eventText>Text</eventText>
<eventDesc>failed</eventDesc>
</Row>
</Rowsets>
Expected output:
<?xml version="1.0" encoding="UTF-8"?>
<Rowsets>
<Alert>
<element>
<Title>Downtime:DIA01</Title>
<eventDesc>WorkcenterDown</eventDesc>
</element>
<element>
<Title>Downtime:DIA01</Title>
<eventDesc>WorkcenterDown</eventDesc>
</element>
</Alert>
<Incident>
<element>
<Title>YAT < 60%:DIA01</Title>
<eventDesc>7 Parts in 60 minutes have failed</eventDesc>
</element>
<element>
<Title>YAT < 60%:DIA01</Title>
<eventDesc>7 Parts in 60 minutes have failed</eventDesc>
</element>
</Incident>
</Rowsets>
Based on eventType, I want to generate group.
I am using this 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" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="/">
<Rowsets>
<Rowset>
<xsl:variable name="Type" select="'alert'"/>
<xsl:for-each select="/Rowsets/Rowset/Row">
<xsl:choose>
<xsl:when test="$Type = eventType">
<element>
<xsl:variable name="text" select="eventText"/>
<xsl:variable name="WC" select="workCenter"/>
<Title><xsl:value-of select="concat($text,':',$WC)" /></Title>
<xsl:copy-of select="eventDesc"/>
</element>
</xsl:when>
<xsl:otherwise>
<element>
<xsl:variable name="text" select="eventText"/>
<xsl:variable name="WC" select="workCenter"/>
<Title><xsl:value-of select="concat($text,':',$WC)" /></Title>
<xsl:copy-of select="eventDesc"/>
</element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Rowset>
</Rowsets>
</xsl:template>
</xsl:stylesheet>
Need help in generating id and key based on eventType
This is a grouping problem - and a rather trivial one at that. In XSLT 2.0 you could do simply:
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:template match="Rowsets">
<Rowsets>
<xsl:for-each-group select="Row" group-by="eventType">
<xsl:element name="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<element>
<Title>
<xsl:value-of select="eventText, workCenter" separator=":"/>
</Title>
<xsl:copy-of select="eventDesc"/>
</element>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</Rowsets>
</xsl:template>
</xsl:stylesheet>
Alternatively, with only two possible types, you could do:
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="Rowsets">
<Rowsets>
<Alert>
<xsl:apply-templates select="Row[eventType='alert']"/>
</Alert>
<Incident>
<xsl:apply-templates select="Row[eventType='incident']"/>
</Incident>
</Rowsets>
</xsl:template>
<xsl:template match="Row">
<element>
<Title>
<xsl:value-of select="eventText"/>
<xsl:text>:</xsl:text>
<xsl:value-of select="workCenter"/>
</Title>
<xsl:copy-of select="eventDesc"/>
</element>
</xsl:template>
</xsl:stylesheet>
This is assuming you don't mind creating a group even if it is empty. Otherwise you would do:
<xsl:template match="Rowsets">
<Rowsets>
<xsl:variable name="alerts" select="Row[eventType='alert']"/>
<xsl:variable name="incidents" select="Row[eventType='incident']"/>
<xsl:if test="$alerts">
<Alert>
<xsl:apply-templates select="Row[eventType='alert']"/>
</Alert>
</xsl:if>
<xsl:if test="$incidents">
<Incident>
<xsl:apply-templates select="Row[eventType='incident']"/>
</Incident>
</xsl:if>
</Rowsets>
</xsl:template>
P.S. Note that XML is case-sensitive: <Alert> is not the same as <alert>.
I have a file called ori.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<container>
<elA>
<el1>value1</el1>
<el2>value2</el2>
</elA>
<elB>
<el3>value3</el3>
<el4>value4</el4>
<el5>value5</el5>
</elB>
<elC>
<el6>value5</el6>
</elC>
</container>
</root>
and another one called modifs.xml:
<?xml version="1.0" encoding="UTF-8"?>
<els>
<el2>newvalue2</el2>
<el5>newvalue5</el5>
</els>
and I would like to obtain result.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<container>
<elA>
<el1>value1</el1>
<el2>newvalue2</el2>
</elA>
<elB>
<el3>value3</el3>
<el4>value4</el4>
<el5>newvalue5</el5>
</elB>
<elC>
<el6>value5</el6>
</elC>
</container>
</root>
I'm a beginner in XSLT.
So I started to write a stylesheet with which I'm able to change value2 into newvalue2:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="fileName" select="'modifs.xml'" />
<xsl:param name="modifs" select="document($fileName)" />
<xsl:param name="updateEl" >
<xsl:value-of select="$modifs/els/el2" />
</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//elA/el2">
<xsl:copy>
<xsl:apply-templates select="$updateEl" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But now I have to modify this stylesheet to be able to know which elements are in modifs.xml and find them in ori.xml. I don't know how to do that. Could you help please ?
I would use a key:
<xsl:key name="ref-change" match="els/*" use="local-name()"/>
<xsl:template match="*[key('ref-change', local-name(), $modifs)]">
<xsl:copy-of select="key('ref-change', local-name(), $modifs)"/>
</xsl:template>
However, using the third argument for the key function is only supported in XSLT 2 and later thus if you use an XSLT 1 processor you need to move the logic into the template, that requires using for-each to "switch" the context document
<xsl:template match="*">
<xsl:variable name="this" select="."/>
<xsl:for-each select="$modifs">
<xsl:choose>
<xsl:when test="key('ref-change', local-name($this))">
<xsl:copy-of select="key('ref-change', local-name($this))"/>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$this">
<xsl:call-template name="identity"/>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
Put name="identity" on your identity transformation template.
My knowledge to XSLT is limited but always eager to learn. I am currently working on a template that requires to transform the XML input. I've been trying to group the InvoiceNum fields and not getting anywhere.
I am getting an error: Envision.Utilities.XsltEngine-Object reference not set to an instance of an object.
Here's the input XML for reference:
<?xml version='1.0' ?>
<Request>
<Information>
<ImageID>987456321</ImageID>
<Contract>123456789</Contract>
<Lastname>MICKEYMOUSE</Lastname>
</Information>
<Document>
<InvoiceNum>123456823</InvoiceNum>
<Reference>AD20985224</Reference>
<InvoiceNum>100000123</InvoiceNum>
<Reference>AS20101387</Reference>
<InvoiceNum>858511825</InvoiceNum>
<Reference>GF96844</Reference>
<InvoiceNum>885154145</InvoiceNum>
<Reference>FGFD2018</Reference>
<InvoiceNum>25241111</InvoiceNum>
<Reference>SD88888</Reference>
<InvoiceNum>8571414</InvoiceNum>
<Reference>DF864841254</Reference>
</Document>
</Request>
Here's my XSLT format for reference:
What am I missing? Is there better way to format the XSLT template I have currently below? Any help is greatly appreciated.
<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:param name="cols" select="3" />
<xsl:template match="Request">
<table border="1">
<xsl:apply-templates select="InvoiceNum[position() mod $cols = 1]"/>
</table>
</xsl:template>
<xsl:template match="Document">
<xsl:variable name="group" select=". | following-sibling::InvoiceNum
[position() < $cols]" />
<xsl:for-each select="*">
<xsl:variable name="i" select="position()" />
<Invoice>
<InvoiceNumber>
<xsl:value-of select="InvoiceNum()"/>
</InvoiceNumber>
<xsl:for-each select="$group">
<xsl:value-of select="*[$i]"/>
</xsl:for-each>
</Invoice>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here's the XML output I'd like to have:
<InvCase>
<Invoices>
<InvoicemNumber>InvoiceNum1</InvoicemNumber>
<InvoicemNumber>InvoiceNum2</InvoicemNumber>
<InvoicemNumber>InvoiceNum3</InvoicemNumber>
</Invoices>
</InvCase>
It looks like you are trying to group in the InvoiceNum into groups of 3. The first issue you have is that in your template matching Request you do this...
<xsl:apply-templates select="InvoiceNum[position() mod $cols = 1]"/>
But InvoiceNum is not a child of Request, and so that selects nothing. You probably need to do this...
Additionally, you have a template matching Document, but this probably needs to match InvoiceNum (Doing following-sibling::InvoiceNum would not return anything if you were matching Document as the InvoiceNum elements are children of Document not following siblings).
Try this XSLT
<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:param name="cols" select="3" />
<xsl:template match="Request">
<InvCases>
<xsl:apply-templates select="Document/InvoiceNum[position() mod $cols = 1]"/>
</InvCases>
</xsl:template>
<xsl:template match="InvoiceNum">
<xsl:variable name="group" select=". | following-sibling::InvoiceNum[position() < $cols]" />
<Invoice>
<xsl:for-each select="$group">
<InvoiceNumber>
<xsl:value-of select="."/>
</InvoiceNumber>
</xsl:for-each>
</Invoice>
</xsl:template>
</xsl:stylesheet>
I have got a source XML
<Records>
<Data>
<RecordType>New</RecordType>
<Number>4734122946</Number>
<DateOfBirth>20160506</DateOfBirth>
<Title>Mr</Title>
<ChangeTimeStamp>20160101010001</ChangeTimeStamp>
<SerialChangeNumber>01</SerialChangeNumber>
</Data>
<Data>
<RecordType>New</RecordType>
<Number>4734122946</Number>
<LastName>Potter</LastName>
<DateOfBirth>20160506</DateOfBirth>
<ChangeTimeStamp>20160101010002</ChangeTimeStamp>
<SerialChangeNumber>01</SerialChangeNumber>
</Data>
</Records>
I want to use XSLT 1.0 to produce the below output
<Contact>
<Number>4734122946</Number>
<Title>Mr</Title>
<LastName>Potter</LastName>
<BirthDate>20160506</BirthDate>
<ChangeTimeStamp>20160101010002</ChangeTimeStamp>
</Contact>
The XSLT has to group and merge the child nodes of Data records into one based on the Number field. Also if there are same elements present, then it should use the ChangeTimeStamp element to figure out the latest change and use that element.
I tried the below XSLT. But I am nowhere close to the output.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes" />
<xsl:key name="groups" match="Data" use="Number"/>
<xsl:key name="sortGroup" match="Data" use ="ChangeTimeStamp"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Records">
<xsl:for-each select="Data[generate-id() = generate-id(key('groups',Number))]">
<Contact>
<Number>
<xsl:value-of select="Number"/>
</Number>
<xsl:for-each select="key('groups',Number)">
<xsl:for-each select="key('sortGroup',ChangeTimeStamp)">
<xsl:sort select="sortGroup" order="ascending"/>
<xsl:if test="Title">
<Title>
<xsl:value-of select ="Title"/>
</Title>
</xsl:if>
<xsl:if test="LastName">
<LastName>
<xsl:value-of select="LastName"/>
</LastName>
</xsl:if>
<BirthDate>
<xsl:value-of select="DateOfBirth"/>
</BirthDate>
</xsl:for-each>
</xsl:for-each>
</Contact>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Appreciate your help.
The XSLT has to group and merge the child nodes of Data records into
one based on the Number field. Also if there are same elements
present, then it should use the ChangeTimeStamp element to figure out
the latest change and use that element.
For that, I believe you would want to do something like:
<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="group" match="Data" use="Number"/>
<xsl:key name="item" match="Data/*" use="concat(../Number, '|', name())"/>
<xsl:template match="/Records">
<root>
<xsl:for-each select="Data[generate-id() = generate-id(key('group', Number)[1])]">
<Contact>
<xsl:for-each select="key('group', Number)/*[generate-id() = generate-id(key('item', concat(../Number, '|', name()))[1])]">
<xsl:for-each select="key('item', concat(../Number, '|', name()))">
<xsl:sort select="../ChangeTimeStamp" data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</Contact>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
You will have to make some adjustments if you want to include only some data items and/or if you want them to appear in particular order. If you have a list of all possible data item names (e.g. Title, LastName, DateOfBirth, etc.) then this could be simpler.