In XSLT, how can I insert matching tags that apply to different templates
I'm very new to XSLT and just haven't been able to find a solution yet to my problem. I have an xml file that looks like (and I can't change the way this xml looks, realizing it's a bit odd):
<account>
<name>accountA</name>
</account>
<period>
<type>priormonth</type>
</period>
<period>
<type>currentmonth</type>
</period>
<account>
<name>accountB</name>
</account>
<period>
<type>priormonth</type>
</period>
<period>
<type>currentmonth</type>
</period>
The xml file can have a variable number of account/period/period datasets but they are always in that order.
I've got an xsl file which looks like:
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="account">
<name> <xsl:value-of select="name"/> </name>
</xsl:template>
&开发者_StackOverflow社区lt;xsl:template match="period">
<type> <xsl:value-of select="type"/> </type>
</xsl:template>
The above works great because it deals with the multiple account/period/period occurrences and my output comes out like:
<name>accountA</name>
<type>priormonth</type>
<type>currentmonth</type>
<name>accountB</name>
<type>priormonth</type>
<type>currentmonth</type>
However, I'm wanting to have some additional tags inserted so that the output actually looks like:
<account>
<name>accountA</name>
<period>
<type>priormonth</type>
<type>currentmonth</type>
</period>
</account>
<account>
<name>accountB</name>
<period>
<type>priormonth</type>
<type>currentmonth</type>
</period>
</account>
Is there a way of doing this? Apologies if my terminology is not quite right. Thanks
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="account">
<xsl:copy>
<xsl:apply-templates />
<period>
<xsl:apply-templates select="following-sibling::period[generate-id(preceding-sibling::account[1]) = generate-id(current())]/*" />
</period>
</xsl:copy>
</xsl:template>
<xsl:template match="period" />
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The select statement is a bit convoluted, but what it basically does it select all period
siblings following each account
element, where the first account
element that precedes it is the current one.
The following XSLT assumes that the xml source is exactly as presented in your question (apart the missing root which makes the source document not well formed). In this case you do not need identity transform. Moreover, if really your account
needs only the first next two period
you can use a simpler XPath.
XSLT 1.0 tested under Saxon-HE 9.2.1.1J
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="account"/>
</xsl:copy>
</xsl:template>
<xsl:template match="account">
<xsl:copy>
<xsl:copy-of select="name" />
<period>
<xsl:copy-of select="following-sibling::period[position()<=2]/type" />
</period>
</xsl:copy>
</xsl:template>
<xsl:template match="period" />
</xsl:stylesheet>
Applied on this input:
<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
<account>
<name>accountA</name>
</account>
<period>
<type>march</type>
</period>
<period>
<type>currentmonth</type>
</period>
<account>
<name>accountB</name>
</account>
<period>
<type>priormonth</type>
</period>
<period>
<type>currentmonth</type>
</period>
</root>
Gives:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<account>
<name>accountA</name>
<period>
<type>march</type>
<type>currentmonth</type>
</period>
</account>
<account>
<name>accountB</name>
<period>
<type>priormonth</type>
<type>currentmonth</type>
</period>
</account>
</root>
精彩评论