xsl:if or xsl:applytemplate which one has better memroy usage - xslt

Lets say that I have a input like following.
<country>
<name>countryname</name>
<capital>captialname</capital>
<population>19000</population>
</country>
I'm trnsforming element names to lets say upper code using an xsl. Child elements of country may not occur sometimes. So I can write my transformation as follows.
<xsl:template match="country">
<xsl:element name="COUNTRY">
<xsl:apply-templates select="name" />
<xsl:apply-templates select="capital" />
<xsl:apply-templates select="population" />
</xsl:element>
</xsl:template>
<xsl:template match="name">
<xsl:element name="NAME">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="capital">
<xsl:element name="CAPITAL">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
<xsl:template match="population">
<xsl:element name="POPULATION">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
or I can do it as follows.
<xsl:template match="country">
<xsl:element name="COUNTRY">
<xsl:if test="name">
<xsl:element name="NAME">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
<xsl:if test="capital">
<xsl:element name="CAPITAL">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
<xsl:if test="population">
<xsl:element name="POPULATION">
<xsl:value-of select="." />
</xsl:element>
</xsl:if>
</xsl:element>
I'm wondering which way it'd use less memory. The actual code I have goes around seven levels deep inside templates. So what I need to know is if I don't use use templates for simple elements will if improve memory usage.

As per my understanding first one is good. Just change:
<xsl:apply-templates select="name" />
<xsl:apply-templates select="capital" />
<xsl:apply-templates select="population" />
To
<xsl:apply-templates/>
only and don't worry about child element if they are not coming sometimes XSLT would take care it.

I think you are approaching from the wrong way. Your current XSLT templates isn't very flexible. You would have contantly amend it for new elements being added.
Instead you should be using the identity transform, and including a template to match any generic element, outut the name as upper case.
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()[not(self::*)]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{upper-case(local-name())}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note the the following template matches any element and converts the name to upper case
<xsl:template match="*">
Whereas the other template match will match attributes or any other nodes other than elements, and just copies them as-is
<xsl:template match="#*|node()[not(self::*)]">
Note that this solution would not work if the XML had namespaces defined, but it wouldn't take much tweaking to get it to cope.

Related

how to number <images> tags with XSLT

output:
<imgs>
<image>https://testtest.com/_data/products/sku-6287sku-6287.jpg</image>
<image>https://testtest.com/_data/products/sku-6287sku-6287,1.jpg</image>
<image>https://testtest.com/_data/products/sku-6287sku-6287,2.jpg</image>
<image>https://testtest.com/_data/products/sku-6287sku-6287,3.jpg</image>
<image>https://testtest.com/_data/products/sku-6287sku-6287,4.jpg</image>
</imgs>
Version:
XSLT 1.0
Can anyone help us, can we use xslt to convert to number the tags?
Except result:
<imgs>
<image1>https://testtest.com/_data/products/sku-6287sku-6287.jpg</image1>
<image2>https://testtest.com/_data/products/sku-6287sku-6287,1.jpg</image2>
<image3>https://testtest.com/_data/products/sku-6287sku-6287,2.jpg</image3>
<image4>https://testtest.com/_data/products/sku-6287sku-6287,3.jpg</image4>
<image5>https://testtest.com/_data/products/sku-6287sku-6287,4.jpg</image5>
</imgs>
You need this template:
<xsl:template match="imgs">
<xsl:copy>
<xsl:for-each select="image">
<xsl:element name="{name()}{position()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>
Or as #michael.hor257k in comment suggest, even simpeler:
<xsl:template match="imgs">
<xsl:copy>
<xsl:for-each select="image">
<xsl:element name="image{position()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>

Need to perform two transformation for the same content in xsl

I have two different transformation.After first transformation get over second need to be applied but not able to do.I have referred other stackoverflow post but its not working for me.
Sample Input:
<Para>1<AAA>2<BBB>3</BBB>4</AAA>5</Para>
Requirement is like BBB may available outside of AAA as well.We need to remove all AAA and put the value in comma separated format.
Expected output after First:
<Para>12<BBB>3</BBB>45</Para>
Expected output on the Second:
<Para>12,3,45</Para>
First:
Here i am just removing tag AAA from the content.And retriving its content and child.
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match ="*/AAA">
<xsl:apply-templates/>
</xsl:template>
Second:
Here I am just appending comma between the node element by removing all the tags inside para and retrieving its content.
<xsl:param name="Para"></xsl:param>
<xsl:template match="Para">
<xsl:copy>
<xsl:variable name="BBB-element-exists">
<xsl:for-each select="node()[self::BBB ][normalize-space(.)!='']">
<xsl:if test="(self::Change and (child::BBB)) or (self::BBB)">
<xsl:value-of select="'BBBexists'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$BBB-element-exists = 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:if test="position() >1 and not(parent::Change) ">
<xsl:value-of select="','" />
</xsl:if>
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
<xsl:if test="$BBB-element-exists != 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You will need to use at least one mode to separate processing of the second step from the first:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/AAA">
<xsl:apply-templates/>
</xsl:template>
<xsl:variable name="first-pass">
<xsl:apply-templates select="node()"/>
</xsl:variable>
<xsl:template match="/">
<xsl:apply-templates select="$first-pass/node()" mode="step2"/>
</xsl:template>
<xsl:template match="Para" mode="step2">
<xsl:copy>
<xsl:variable name="BBB-element-exists">
<xsl:for-each select="node()[self::BBB ][normalize-space(.)!='']">
<xsl:if test="(self::Change and (child::BBB)) or (self::BBB)">
<xsl:value-of select="'BBBexists'"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:if test="$BBB-element-exists = 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:if test="position() >1 and not(parent::Change) ">
<xsl:value-of select="','" />
</xsl:if>
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
<xsl:if test="$BBB-element-exists != 'BBBexists'">
<xsl:for-each select=".//text()">
<xsl:value-of select="normalize-space()" />
</xsl:for-each>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:transform>

XSL: match two template with same XPath-expression but different code inside other template

I know, that I can call xsl:apply-templates inside another template, when I specify the XPath-expression of that subtemplate.
In my xsl-file I got an
<xsl:template match="/">
<xsl:apply-templates select="root/values" />
</xsl:template>
<xsl:template match="root/values>
<xsl:value-of select="value/key" />
</xsl:template>
Now I want to do something with subnodes of root/values again in another context - how do I match this template in my main template?
<xsl:template match="root/values>
<xsl:for-each select="value">
<xsl:value-of select="key" />
</xsl:for-each>
</xsl:template>
I think you want to use a mode:
<xsl:template match="/">
<xsl:apply-templates select="root/values" />
<xsl:apply-templates select="root/values" mode="m1" />
</xsl:template>
<xsl:template match="root/values>
<xsl:value-of select="value/key" />
</xsl:template>
<xsl:template match="root/values" mode="m1">
<xsl:for-each select="value">
<xsl:value-of select="key" />
</xsl:for-each>
</xsl:template>

XSLT Gouping, for-each and implicated loop. How to eliminate duplicates?

Hope find a guru's help to figure out the next problem.
I have two xml files. Firts one here (text.xml):
<text>
<ref>Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref>Author2, Title2, Date2</ref>
<ref>Author3, Title3, Date3</ref>
<text>
And the second one like this (list.xml):
<list>
<bibl xml:id="1"><author>Author1</author><date>Date1</date></bibl>
<bibl xml:id="2"><author>Author2</author><date>Date2</date></bibl>
<bibl xml:id="3"><author>Author3</author><date>Date3</date></bibl>
</list>
I want to query text.xml and check against list.xml to add #xml:id (from list.xml) to <ref> (from text.xml) wich contain same Author and Date. If not, then just copy original <ref>.
So I want to obtain:
<ref xml:id="1">Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref xml:id="2>Author2, Title2, Date2</ref>
etc.
My XSLT identify well all correpondence:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="ref" select="."/>
<xsl:for-each select="document('list.xml')//bibl">
<xsl:variable name="bibl" select="."/>
<xsl:variable name="author" select="author"/>
<xsl:variable name="date" select="date"/>
<xsl:choose>
<xsl:when test="contains($ref, $author) and contains($ref, $date)">
<ref>
<xsl:attribute name="xml:id">
<xsl:value-of select="$bibl/#xml:id"/>
</xsl:attribute>
<xsl:value-of select="$ref"/>
</ref>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$ref"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
But, then there aren't correpondence it's not just copy right <ref>, but copy all <ref> the number of time I have <bibl> nodes in the second file.
So problem is in <xsl:otherwise><xsl:copy-of select="$ref"/></xsl:otherwise>.
Any ideas how I can obtain only this distinct value I need? I know it's must be very simple actually and I try key, generate-id, for-each-group, distinct-values, but can't figure it out.
The problem is that you are creating a ref element for each iteration of the for-each loop whether there is a match or not.
What you need to do in this case is create the ref element outside of the for-each and then only create the id attribute for the matching element inside the loop
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="ref" select="."/>
<ref>
<xsl:apply-templates select="#* "/>
<xsl:for-each select="document('list.xml')//bibl">
<xsl:variable name="bibl" select="."/>
<xsl:variable name="author" select="author"/>
<xsl:variable name="date" select="date"/>
<xsl:choose>
<xsl:when test="contains($ref, $author) and contains($ref, $date)">
<xsl:attribute name="xml:id">
<xsl:value-of select="$bibl/#xml:id"/>
</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</ref>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<text>
<ref xml:id="1">Author1, Title1, Date1</ref>
<ref>Author75, Title75, Date2</ref>
<ref xml:id="2">Author2, Title2, Date2</ref>
<ref xml:id="3">Author3, Title3, Date3</ref>
</text>
However, your current method is not very efficient, as for each ref element you are iterating over all bibl elements. Another approach would be to extract the author and date from the ref elements, and then look up the bibl element directly
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ref">
<xsl:variable name="author" select="normalize-space(substring-before(., ','))"/>
<xsl:variable name="date" select="normalize-space(substring-after(substring-after(., ','), ','))"/>
<ref>
<xsl:apply-templates select="#* "/>
<xsl:apply-templates select="document('list.xml')//bibl[author=$author][date=$date]"/>
<xsl:apply-templates select="node()"/>
</ref>
</xsl:template>
<xsl:template match="bibl">
<xsl:attribute name="xml:id">
<xsl:value-of select="#xml:id"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
This should also give the same results.
EDIT:
humm...too much focus on xsl syntax, i should have seen that earlier...
you have an implicated outer loop over each ref and an inner loop over each bibl. You generate one element for every bibl for every ref regardless of match or no match.
So, instead of the xsl:otherwise you need a check after the for-each loop to see if there was no match and do the copy-of if neccessary.
Not sure how to do the check, though..maybe using position() and count() of the generated <ref>s, sorry...don't have any more time to think about this right now.
not really an explanation, but a workaround:
<xsl:otherwise>
<ref>
<xsl:value-of select="$ref"/>
</ref>
</xsl:otherwise>
My guess is that the problem lies in $ref, which is not a xpath expression at that moment (if i remember xsl-t correctly)

Optimisation <xsl:apply-templates/> for a set of tags

How it is possible to reduce this record?
<xsl:template match="BR">
<br/>
</xsl:template>
<xsl:template match="B">
<strong><xsl:apply-templates /></strong>
</xsl:template>
<xsl:template match="STRONG">
<strong><xsl:apply-templates /></strong>
</xsl:template>
<xsl:template match="I">
<em><xsl:apply-templates /></em>
</xsl:template>
<xsl:template match="EM">
<em><xsl:apply-templates /></em>
</xsl:template>
<xsl:template match="OL">
<ol><xsl:apply-templates /></ol>
</xsl:template>
<xsl:template match="UL">
<ul><xsl:apply-templates /></ul>
</xsl:template>
<xsl:template match="LI">
<li><xsl:apply-templates /></li>
</xsl:template>
<xsl:template match="SUB">
<sub><xsl:apply-templates /></sub>
</xsl:template>
<xsl:template match="SUP">
<sup><xsl:apply-templates /></sup>
</xsl:template>
<xsl:template match="NOBR">
<nobr><xsl:apply-templates /></nobr>
</xsl:template>
Maybe something like:
<xsl:template match="LI|SUB|...">
<xsl:element name="{translate(name(),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
I don't think, there's a tolower function in XSLT (at least not in 1.0)
If the elements to be created are not known in advance and only a few known elements needs to be processed in another, more specific way, here's a more dynamic solution:
<xsl:template match="*">
<xsl:element name="{translate(name(), $vUpper, $vLower)}">
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
where $vUpper and $vLower are defined as:
<xsl:variable name="vUpper" select=
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
"/>
<xsl:variable name="vLower" select=
"'abcdefghijklmnopqrstuvwxyz'
"/>
There must be templates matching the few known elements that should not be processed in the above way. These more specific templates will override the more general template above. For example:
<xsl:template match="specificName">
<!-- Specific processing here -->
</xsl:template>
Also, the generic template above, matching elements should be overriding the "identity rule" (template).