Im having issues getting my XML to transform correctly using the EXSLT str:replace template.
Here is my XML:
<XML>
<Item>
<Field>
<Name>ID</Name>
<Value>98765</Value>
</Field>
<Field>
<Name>ParentID</Name>
<Value>10002</Value>
</Field>
<Field>
<Name>Type</Name>
<Value>content</Value>
</Field>
<Field>
<Name>Name</Name>
<Value>TestAPI Course</Value>
</Field>
<Field>
<Name>URL</Name>
<Value></Value>
</Field>
<Field>
<Name>Description</Name>
<Value>a description</Value>
</Field>
<Field>
<Name>DateBegin</Name>
<Value>2012-04-04T00:00:00.000-07:00</Value>
</Field>
<Field>
<Name>DateEnd</Name>
<Value>2012-04-04T00:00:00.000-08:00</Value>
</Field>
<Field>
<Name>DateCreated</Name>
<Value>2012-04-03T00:00:00.000-07:00</Value>
</Field>
<Field>
<Name>DateModified</Name>
<Value>2012-04-04T00:00:00.000-06:00</Value>
</Field>
</Item>
Here is my XSL:
<?xml version="1.0" encoding="utf-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" extension-element-prefixes="str" version="1.0">
<xsl:import href="C:\inetpub\wwwroot\LMSConnector\LMS\XSL\str.xsl"/>
<xsl:template match="/Items">
<xsl:call-template name="str:replace">
<xsl:with-param name="string" select="Field/Name"/>
<xsl:with-param name="search" select="ID"/>
<xsl:with-param name="replace" select="sco-id"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
My problem is that I just get a XML document header and nothing else, but nothing else? I dont think I am far off a working solution and the problems probably lie in the values i am setting for the "match" parameter on the template, and the select parameters within the call-template with-param nodes.
Any help would be appreciated.
Mike
You are trying to match <xsl:template match="/Items">, however your XML contains <Item> elements, not <Items>.
EDIT
Is this what you are trying to do?
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://exslt.org/strings" extension-element-prefixes="str">
<xsl:import href="http://delicious-library-export.googlecode.com/svn/trunk/str/str.xsl"/>
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:template match="/Item/Field/Name/text()">
<xsl:call-template name="str:replace">
<xsl:with-param name="string" select="."/>
<xsl:with-param name="search" select="'ID'"/>
<xsl:with-param name="replace" select="'sco-id'"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="utf-8"?>
<Item>
<Field>
<Name>sco-id</Name>
<Value>98765</Value>
</Field>
<Field>
<Name>Parentsco-id</Name>
<Value>10002</Value>
</Field>
<Field>
<Name>Type</Name>
<Value>content</Value>
</Field>
<Field>
<Name>Name</Name>
<Value>TestAPI Course</Value>
</Field>
<Field>
<Name>URL</Name>
<Value/>
</Field>
<Field>
<Name>Description</Name>
<Value>a description</Value>
</Field>
<Field>
<Name>DateBegin</Name>
<Value>2012-04-04T00:00:00.000-07:00</Value>
</Field>
<Field>
<Name>DateEnd</Name>
<Value>2012-04-04T00:00:00.000-08:00</Value>
</Field>
<Field>
<Name>DateCreated</Name>
<Value>2012-04-03T00:00:00.000-07:00</Value>
</Field>
<Field>
<Name>DateModified</Name>
<Value>2012-04-04T00:00:00.000-06:00</Value>
</Field>
</Item>
Related
I'm trying to transform the following XML file, to remove each <AGGREGATION> node if followed by a <MULTIPLE> node.
<?xml version="1.0" encoding="UTF-8"?>
<RECORD TEMPLATE="PRODUCTS" TRACK="1">
<FIELD NAME="PRODUCT" BASE="CT300" COUNT="2">
<AGGREGATION DOMAIN="4" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<TOKEN TEXT="CT300" BEGIN="11379" END="11384"/>
<AGGREGATION DOMAIN="9" />
<AGGREGATION DOMAIN="4" />
<AGGREGATION DOMAIN="4" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<MULTIPLE TYPE="YES" />
<TOKEN TEXT="CT300" BEGIN="11379" END="11384"/>
</FIELD>
</RECORD>
With the following xslt transformation, I was able to remove only the first occurrence of the <AGGREGATION> node:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//RECORD[#TEMPLATE='PRODUCTS']/FIELD[#NAME='PRODUCT']/AGGREGATION[following-sibling::*[1][self::MULTIPLE]]">
<xsl:choose>
<xsl:when test="//RECORD[#TEMPLATE='PRODUCTS']/FIELD[#NAME='PRODUCT']/AGGREGATION[following-sibling::*[1][self::MULTIPLE]]">
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This is the output I receive:
<?xml version="1.0" encoding="UTF-8"?>
<RECORD TEMPLATE="PRODUCTS" TRACK="1">
<FIELD BASE="CT300" COUNT="2" NAME="PRODUCT">
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="11379" END="11384" TEXT="CT300"/>
<AGGREGATION DOMAIN="9"/>
<AGGREGATION DOMAIN="4"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="11379" END="11384" TEXT="CT300"/>
</FIELD>
</RECORD>
While the desired output is the following:
<?xml version="1.0" encoding="UTF-8"?>
<RECORD TEMPLATE="PRODUCTS" TRACK="1">
<FIELD BASE="CT300" COUNT="2" NAME="PRODUCT">
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="11379" END="11384" TEXT="CT300"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="11379" END="11384" TEXT="CT300"/>
</FIELD>
</RECORD>
How can I implement a recursive deletion of the <AGGREGATION> node?
My solution is as follows:
Write a template matching AGGREGATION.
The decision whether it shoud copy anything is as follows:
Take the first following sibling with name != AGGREGATION.
Check whether its name is MULTIPLE.
If yes, do nothing (skip the current AGGREGATION element).
If not, apply templates for the current element.
You need also the identity template.
So the whole script looks like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="AGGREGATION">
<xsl:if test="not(following-sibling::*[name()!='AGGREGATION'][1]
[name()='MULTIPLE'])">
<AGGREGATION>
<xsl:apply-templates select="#*|node()"/>
</AGGREGATION>
</xsl:if>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
For a working example see http://xsltransform.net/bEJaofQ/1
Just use this empty template plus the identity template to eradicate the unwanted <AGGREGATION> elements conditionally:
<xsl:template match="AGGREGATION[following-sibling::*[1] = (self::MULTIPLE or self::AGGREGATION)]" />
I think you just need to ignore the following sibling AGGREGATION elements...
<xsl:template match="RECORD[#TEMPLATE='PRODUCTS']/FIELD[#NAME='PRODUCT']/AGGREGATION[following-sibling::*[not(self::AGGREGATION)][1][self::MULTIPLE]]"/>
I Have an XML file like the one below. What I need to do is to add a Flag to each <TOKEN> node with a <MULTIPLE TYPE='YES'> node when:
there exist more than one <TOKEN> with the same #BEGIN-#END
at least one of the <TOKEN> #TEXT is different from the others with the same#BEGIN-#END
The xml code is the following:
<?xml version="1.0" encoding="UTF-8"?>
<ALL>
<RECORD TEMPLATE="PRODUCTS" DB="0">
<FIELD NAME="PRODUCT" BASE="AST" >
<TOKEN TEXT="AST" BEGIN="0" END="100"/>
</FIELD>
</RECORD>
<RECORD TEMPLATE="PRODUCTS" DB="1" >
<FIELD NAME="PRODUCT" BASE="BUC" >
<TOKEN TEXT="BUC" BEGIN="0" END="100"/>
<TOKEN TEXT="BUC" BEGIN="0" END="100"/>
<TOKEN TEXT="BUC" BEGIN="0" END="100"/>
<TOKEN TEXT="BUC" BEGIN="0" END="100"/>
<TOKEN TEXT="BUC" BEGIN="102" END="133"/>
<TOKEN TEXT="BUC" BEGIN="102" END="133"/>
</FIELD>
</RECORD>
<RECORD TEMPLATE="PRODUCTS" DB="1" >
<FIELD NAME="PRODUCT" BASE="BUC" >
<TOKEN TEXT="ART" BEGIN="300" END="450"/>
<TOKEN TEXT="ART" BEGIN="300" END="450"/>
</FIELD>
</RECORD>
</ALL>
The desired output is the following:
<ALL>
<RECORD DB="0" TEMPLATE="PRODUCTS">
<FIELD BASE="AST" NAME="PRODUCT">
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="0" END="100" TEXT="AST"/>
</FIELD>
</RECORD>
<RECORD DB="1" TEMPLATE="PRODUCTS">
<FIELD BASE="BUC" NAME="PRODUCT">
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="0" END="100" TEXT="BUC"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="0" END="100" TEXT="BUC"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="0" END="100" TEXT="BUC"/>
<MULTIPLE TYPE="YES"/>
<TOKEN BEGIN="0" END="100" TEXT="BUC"/>
<TOKEN BEGIN="102" END="133" TEXT="BUC"/>
<TOKEN BEGIN="102" END="133" TEXT="BUC"/>
</FIELD>
</RECORD>
<RECORD DB="1" TEMPLATE="PRODUCTS">
<FIELD BASE="BUC" NAME="PRODUCT">
<TOKEN BEGIN="300" END="450" TEXT="ART"/>
<TOKEN BEGIN="300" END="450" TEXT="ART"/>
</FIELD>
</RECORD>
</ALL>
I tried using the following xslt, and try to match either the #BEGIN-#END and the #TEXT, but it didn't work.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" cdata-section-elements="DOCUMENT" method="xml" indent="yes" omit-xml-declaration="no" />
<xsl:key name="token" match="//RECORD[#TEMPLATE='PRODUCTS']/FIELD[#NAME='PRODUCT']/TOKEN" use="concat(#BEGIN, '|', #END)"/>
<xsl:key name="text" match="//RECORD[#TEMPLATE='PRODUCTS']/FIELD[#NAME='PRODUCT']/TOKEN" use="#TEXT"/>
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//RECORD[#TEMPLATE='PRODUCTS']/FIELD[#NAME='PRODUCT']/TOKEN [key('token', concat(#BEGIN, '|', #END))[2]] [(key('word', #TEXT)[2])] ">
<xsl:element name="MULTIPLE">
<xsl:attribute name="TYPE">YES</xsl:attribute>
</xsl:element>
<xsl:call-template name="identity" />
</xsl:template>
</xsl:stylesheet>
Removing form the xslt above the [(key('word', #TEXT)[2])] part of code, the <MULTIPLE TYPE='YES'> node is added, but on every <TOKEN> with the same #BEGIN-#END, not checking if exists a #TEXT node different from the others.
Can you check this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" method="xml" indent="yes" omit-xml-declaration="no" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="TOKEN">
<xsl:variable name="current" select="concat(#BEGIN,#END)"/>
<xsl:variable name="text" select="#TEXT"/>
<xsl:for-each select="ancestor::RECORD/following-sibling::RECORD/FIELD/TOKEN|ancestor::RECORD/preceding-sibling::RECORD/FIELD/TOKEN">
<xsl:if test="(concat(#BEGIN,#END) = $current) and (#TEXT!=$text)">
<MULTIPLE TYPE="YES"/>
</xsl:if>
</xsl:for-each>
<TOKEN>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</TOKEN>
</xsl:template>
</xsl:stylesheet>
silly question but hoping I can get some help here, I need to access specific node in XSL, the XML I am provided with looks like the below, can someone give me an idea what my XSL should look like in order to access the content of the node 'Value' I cannot no matter how i try reach that node!- any help appreciated!!!! :
<?xml version="1.0" encoding="UTF-8" ?>
<CrystalReport xmlns="urn:crystal-reports:schemas:report-detail"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:crystal-reports:schemas:report-detail http://www.businessobjects.com/products/xml/CR2008Schema.xsd">
<Group Level="1">
<Group Level="2">
<Details Level="3">
<Section SectionNumber="1">
<Field Name="Field5" FieldName="{ARTransaction.Transactions}">
<FormattedValue>0.00</FormattedValue>
<Value>0.00</Value>
</Field>
<Field Name="Field15" FieldName="{ARTransaction.PostingDate}">
<FormattedValue>8/1/2016</FormattedValue>
<Value>2016-08-01</Value>
</Field>
<Field Name="Field14" FieldName="{ARTransaction.AuditTrail}">
<FormattedValue>2016083100154</FormattedValue>
<Value>2016083100154</Value>
</Field>
<Field Name="Field13" FieldName="{ARTransaction.JobN}">
<FormattedValue>-25043</FormattedValue>
<Value>-25043</Value>
</Field>
<Field Name="Field11" FieldName="{Customer.CustomerName}">
<FormattedValue>First Church of Christ</FormattedValue>
<Value>First Church of Christ</Value>
</Field>
<Field Name="Field7" FieldName="{ARTransaction.CustomerN}">
<FormattedValue>13157</FormattedValue>
<Value>13157</Value>
</Field>
<Field Name="Field6" FieldName="{ARTransaction.InvoiceN}">
<FormattedValue>25043</FormattedValue>
<Value>25043</Value>
</Field>
<Field Name="SalesmanN1" FieldName="{ARTransaction.SalesmanN}">
<FormattedValue>22</FormattedValue>
<Value>22</Value>
</Field>
</Section>
</Details>
</Group>
</Group>
</Group>
<ReportFooter> </ReportFooter>
XSL :
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:element name="CrystalReport">
<xsl:attribute name="xsi:schemaLocation">http://www.w3.org/2001/XMLSchema-instance</xsl:attribute>
<xsl:attribute name="version">1.2</xsl:attribute>
</xsl:element>
<xsl:element name="DR">
<xsl:value-of select="Group/Group/Details/Section/Field[#Name='Field13']/Value"/>
</xsl:element>
</xsl:template>
The main problem with your attempt is that it ignores the default namespace of the source XML.
You need to declare the namespace in your stylesheet, assign it a prefix, and use that prefix when addressing the elements in the source XML. For example, the following stylesheet:
XSL 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns1="urn:crystal-reports:schemas:report-detail"
exclude-result-prefixes="ns1">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/ns1:CrystalReport">
<DR>
<xsl:value-of select="ns1:Group/ns1:Group/ns1:Details/ns1:Section/ns1:Field[#Name='Field13']/ns1:Value"/>
</DR>
</xsl:template>
</xsl:stylesheet>
when applied to a well-formed input such as:
XML
<CrystalReport xmlns="urn:crystal-reports:schemas:report-detail" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:crystal-reports:schemas:report-detail http://www.businessobjects.com/products/xml/CR2008Schema.xsd">
<Group Level="1">
<Group Level="2">
<Details Level="3">
<Section SectionNumber="1">
<Field Name="Field5" FieldName="{ARTransaction.Transactions}">
<FormattedValue>0.00</FormattedValue>
<Value>0.00</Value>
</Field>
<Field Name="Field15" FieldName="{ARTransaction.PostingDate}">
<FormattedValue>8/1/2016</FormattedValue>
<Value>2016-08-01</Value>
</Field>
<Field Name="Field14" FieldName="{ARTransaction.AuditTrail}">
<FormattedValue>2016083100154</FormattedValue>
<Value>2016083100154</Value>
</Field>
<Field Name="Field13" FieldName="{ARTransaction.JobN}">
<FormattedValue>-25043</FormattedValue>
<Value>-25043</Value>
</Field>
<Field Name="Field11" FieldName="{Customer.CustomerName}">
<FormattedValue>First Church of Christ</FormattedValue>
<Value>First Church of Christ</Value>
</Field>
<Field Name="Field7" FieldName="{ARTransaction.CustomerN}">
<FormattedValue>13157</FormattedValue>
<Value>13157</Value>
</Field>
<Field Name="Field6" FieldName="{ARTransaction.InvoiceN}">
<FormattedValue>25043</FormattedValue>
<Value>25043</Value>
</Field>
<Field Name="SalesmanN1" FieldName="{ARTransaction.SalesmanN}">
<FormattedValue>22</FormattedValue>
<Value>22</Value>
</Field>
</Section>
</Details>
</Group>
</Group>
</CrystalReport>
will return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<DR>-25043</DR>
Note: If you're using an XSLT 2.0 processor, you can declare a xpath-default-namespace attribute and do away with the prefix.
I am trying to re-structure xml data into groups and sub-group. I was able to get it to work, but my code has to include something, that looks (at least to me) like a workaround. Here are my sample files:
Data.xml:
<data>
<record Group="g1" SubGroup="sg1">Record 1</record>
<record Group="g2" SubGroup="sg1">Record 2</record>
<record Group="g1" SubGroup="sg1">Record 3</record>
<record Group="g2" SubGroup="sg1">Record 4</record>
<record Group="g2" SubGroup="sg2">Record 5</record>
<record Group="g1" SubGroup="sg2">Record 6</record>
</data>
Stylesheet.xsl:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" indent="yes" encoding="UTF-8"/>
<xsl:key name="Group" match="record" use="#Group" />
<xsl:key name="SubGroup" match="record" use="#SubGroup" />
<xsl:template match="/data">
<xsl:variable name="Records" select="record"/>
<data>
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('Group',#Group)[1])]">
<xsl:sort select="#Group"/>
<xsl:variable name="Group" select="#Group"/>
<xsl:call-template name="Group">
<xsl:with-param name="Records" select="$Records[#Group = $Group]"/>
<xsl:with-param name="Group" select="$Group"/>
</xsl:call-template>
</xsl:for-each>
</data>
</xsl:template>
<xsl:template name="Group">
<xsl:param name="Records"/>
<xsl:param name="Group"/>
<group name="{$Group}">
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',#SubGroup)[1])]">
<!-- this works: <xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',#SubGroup)[#Group = $Group][1])]"> -->
<xsl:sort select="#SubGroup"/>
<xsl:variable name="SubGroup" select="#SubGroup"/>
<xsl:call-template name="SubGroup">
<xsl:with-param name="Records" select="$Records[#SubGroup = $SubGroup]"/>
<xsl:with-param name="Group" select="$Group"/>
<xsl:with-param name="SubGroup" select="$SubGroup"/>
</xsl:call-template>
</xsl:for-each>
</group>
</xsl:template>
<xsl:template name="SubGroup">
<xsl:param name="Records"/>
<xsl:param name="Group"/>
<xsl:param name="SubGroup"/>
<subgroup name="{$SubGroup}">
<xsl:for-each select="$Records">
<xsl:copy-of select="."/>
</xsl:for-each>
</subgroup>
</xsl:template>
</xsl:stylesheet>
This is the output generated:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<group name="g1">
<subgroup name="sg1">
<record Group="g1" SubGroup="sg1">Record 1</record>
<record Group="g1" SubGroup="sg1">Record 3</record>
</subgroup>
</group>
<group name="g2">
<subgroup name="sg2">
<record Group="g2" SubGroup="sg2">Record 5</record>
</subgroup>
</group>
</data>
but this is the output, I want to have:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<group name="g1">
<subgroup name="sg1">
<record Group="g1" SubGroup="sg1">Record 1</record>
<record Group="g1" SubGroup="sg1">Record 3</record>
</subgroup>
<subgroup name="sg2">
<record Group="g1" SubGroup="sg2">Record 6</record>
</subgroup>
</group>
<group name="g2">
<subgroup name="sg1">
<record Group="g2" SubGroup="sg1">Record 2</record>
<record Group="g2" SubGroup="sg1">Record 4</record>
</subgroup>
<subgroup name="sg2">
<record Group="g2" SubGroup="sg2">Record 5</record>
</subgroup>
</group>
</data>
The problem is the for-each loop in the tempalte named "Group". It seems, that the key()-function is not working on the nodes contained in $Records but on the entire input XML file.
I get identical results with xsltproc and with saxon, so I do not think, it is a bug in my xslt processor. It seems, that I did not completely understand, how key() works.
If I add an additional selector [#Group = $Group] to key()'s output, I get the expected result.
Can somebody explain what is going on and why the additional selector [#Group = $Group] is needed.
Mario
When you want to do sub-grouping, you need to use a concatenated key of both the main group and the sub-group
<xsl:key name="SubGroup" match="record" use="concat(#Group,'|', #SubGroup)" />
Then, just use it in the same way as before, with the concatenation
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',concat(#Group,'|', #SubGroup))[1])]">
Try this XSLT (which I have also simplified to utilise the key when calling your named templates with the records)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" version="1.0" indent="yes" encoding="UTF-8"/>
<xsl:key name="Group" match="record" use="#Group" />
<xsl:key name="SubGroup" match="record" use="concat(#Group,'|', #SubGroup)" />
<xsl:template match="/data">
<data>
<xsl:for-each select="record[generate-id(.)=generate-id(key('Group',#Group)[1])]">
<xsl:sort select="#Group"/>
<xsl:call-template name="Group">
<xsl:with-param name="Records" select="key('Group',#Group)"/>
</xsl:call-template>
</xsl:for-each>
</data>
</xsl:template>
<xsl:template name="Group">
<xsl:param name="Records"/>
<group name="{#Group}">
<xsl:for-each select="$Records[generate-id(.)=generate-id(key('SubGroup',concat(#Group,'|', #SubGroup))[1])]">
<xsl:sort select="#SubGroup"/>
<xsl:call-template name="SubGroup">
<xsl:with-param name="Records" select="key('SubGroup',concat(#Group,'|', #SubGroup))"/>
</xsl:call-template>
</xsl:for-each>
</group>
</xsl:template>
<xsl:template name="SubGroup">
<xsl:param name="Records"/>
<subgroup name="{#SubGroup}">
<xsl:for-each select="$Records">
<xsl:copy-of select="."/>
</xsl:for-each>
</subgroup>
</xsl:template>
</xsl:stylesheet>
I have a strange transformation that I'm trying to do.
XML looks like this:
<?xml version="1.0" standalone="yes"?>
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
And this is what it needs to look like:
<?xml version="1.0" standalone="yes"?>
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="2">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="3">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="4">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
Is something like this even possible with XSLT or should I rather just handle this in code?
Here's another way using XSLT 2.0...
XML Input
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="RecordCount">
<xsl:variable name="record" select="../Record"/>
<xsl:copy-of select="."/>
<xsl:for-each select="1 to .">
<xsl:apply-templates select="$record" mode="replicate">
<xsl:with-param name="cnt" select="."/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="Record" mode="replicate">
<xsl:param name="cnt"/>
<Record name="{$cnt}">
<xsl:apply-templates select="#* except #name|node()"/>
</Record>
</xsl:template>
<xsl:template match="Record"/>
</xsl:stylesheet>
Output
<Parent>
<RecordCount>4</RecordCount>
<Record name="1">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="2">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="3">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
<Record name="4">
<Child1>Value 1</Child1>
<Child2>Value 2</Child2>
</Record>
</Parent>
Try following xlst
<?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="/Parent">
<Parent>
<xsl:variable name="count" select="RecordCount" />
<xsl:call-template name="multiply">
<xsl:with-param name="maxCount" select="$count" />
<xsl:with-param name="nodeToCopy" select="Record" />
</xsl:call-template>
</Parent>
</xsl:template>
<xsl:template name="multiply">
<xsl:param name="maxCount" />
<xsl:param name="i" select="1" />
<xsl:param name="nodeToCopy" />
<xsl:choose>
<xsl:when test="$i <= $maxCount">
<xsl:element name="{name($nodeToCopy)}">
<xsl:attribute name="name">
<xsl:value-of select="$i" />
</xsl:attribute>
<xsl:copy-of select="$nodeToCopy/child::*" />
</xsl:element>
<xsl:call-template name="multiply">
<xsl:with-param name="maxCount" select="$maxCount" />
<xsl:with-param name="nodeToCopy" select="$nodeToCopy" />
<xsl:with-param name="i" select="$i+1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
It is based on recursive calling of named template with increasing of "iterating" value. If would be something unclear just write a comment.