Reputation: 1
I am new to XSLT transformations and here's what I've been trying to do all evening and I hope to get your help: I have an XML structure like this:
<DEV>
<HDR>
<ApID>value1</ApID>
<STAT>value2</STAT>
</HDR>
<DSC>
<Cap>value3</Cap>
</DSC>
</DEV>
basically I need to translate all values of elements without children into attributes with name "V":
<DEV>
<HDR>
<ApID V= "value1" />
<STAT V= "value2" />
</HDR>
<DSC>
<Cap V = "value3" />
</DSC>
</DEV>
Is there any way to write a generic transformation like that with XSLT?
Upvotes: 0
Views: 2256
Reputation: 741
XSLT lets you recursively traverse the node tree. Whilst traversing your tree, you can get XSLT to process your nodes by creating template rules that are more specific than other template rules.
The problem you are trying to solve is in transforming your XML tree with some slight differences. A good start is to take the identity transform:
<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:stylesheet>
This identity transform will simply produce the XML output that is the same as the input XML. It matches and copies every XML node as it is. You then mould your output step-by-step until you get what you want. See documentation for <xsl:copy/>
.
The exception to this copying you want to make is when you come across elements that do not have any child nodes. To match any element, we use *
. to match no elements, we use not(*)
. To match any element with no elements, we thus use *[not(*)]
. In fact, these rules are defined by the XPath query language which XSLT uses. Let's try the following XSLT against your XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="*[not(*)]">
<found-an-element-with-no-children/>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
We get:
<?xml version="1.0" encoding="utf-8"?><DEV>
<HDR>
<found-an-element-with-no-children/>
<found-an-element-with-no-children/>
</HDR>
<DSC>
<found-an-element-with-no-children/>
</DSC>
</DEV>
The nice thing is that any one node can only match against a single template rule at a time. And we look much closer to where we want to be now.
Here's where <xsl:copy/>
comes in. We'll use it now because we want to copy the original element's name.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="*[not(*)]">
<xsl:copy>
<found-an-element-with-no-children/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Now we get to:
<?xml version="1.0" encoding="utf-8"?><DEV>
<HDR>
<ApID><found-an-element-with-no-children/></ApID>
<STAT><found-an-element-with-no-children/></STAT>
</HDR>
<DSC>
<Cap><found-an-element-with-no-children/></Cap>
</DSC>
</DEV
Now add an attribute to our newly created node, and include in it the text contents of the node that we matched just now:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="*[not(*)]">
<xsl:copy>
<xsl:attribute name="V">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And it looks like we're here:
<?xml version="1.0" encoding="utf-8"?><DEV>
<HDR>
<ApID V="value1"/>
<STAT V="value2"/>
</HDR>
<DSC>
<Cap V="value3"/>
</DSC>
</DEV>
Now a couple of pointers:
ApID
, STAT
and Cap
will be able to copy its old attributes. You will want to include <xsl:apply-templates select="@*"/>
to include those attributes.V
attribute?Upvotes: 2
Reputation: 70638
The answer is "yes"! Firstly, you would base your XSLT upon the XSLT identity transform. On its own, this copies all the nodes an attributes in your XML
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
By using this, you only then need to write templates to match the nodes you wish to transform. In your case, you say you want to match elements without children (although strictly speaking, the elements do have children. They have text nodes as children! But I guess you mean they don't have other elements as children). So, your template match would look this this
<xsl:template match="*[not(*)]">
Where *
is the symbol used to match elements.
Then, within this template, you can copy the element, and turn the text into an attribute using xsl:attribute
<xsl:attribute name="V">
<xsl:value-of select="text()" />
</xsl:attribute>
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(*)]">
<xsl:copy>
<xsl:attribute name="V">
<xsl:value-of select="text()" />
</xsl:attribute>
<xsl:apply-templates select="@*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
<DEV>
<HDR>
<ApID V="value1" />
<STAT V="value2" />
</HDR>
<DSC>
<Cap V="value3" />
</DSC>
</DEV>
Upvotes: 1