Why does this xslt transformation replace all occurences? - java

Using http://xslt.online-toolz.com/tools/xslt-transformation.php
.xml
<?xml version="1.0"?>
<my:project xmlns:my="http://myns">
<my:properties>
<my:property>
<my:name>customerId</my:name>
<my:value>1</my:value>
</my:property>
<my:property>
<my:name>userId</my:name>
<my:value>20</my:value>
</my:property>
</my:properties>
</my:project>
I now want to look for the name customerId and want to replace the value.
It almost works, but it replaces ALL values in the document. What am I doing wrong to just replace the value where the name mached?
.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://myns" xmlns:saxon="http://saxon.sf.net">
<xsl:param name="name" select="'customerId'"/>
<xsl:param name="value" select="'0'"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/my:properties/my:property/my:value/text()" >
<xsl:choose>
<xsl:when test="/*/my:properties/my:property/my:name = $name">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise><xsl:copy-of select="saxon:parse(.)" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

The test for /*/my:properties/my:property/my:name = $name always succeed because it uses an absolute path, and so the result is independent of the surrounding template context. A test with a relative xpath should work.
XSL:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="http://myns" xmlns:saxon="http://saxon.sf.net">
<xsl:param name="name" select="'customerId'"/>
<xsl:param name="value" select="'0'"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/my:properties/my:property/my:value/text()" >
<xsl:choose>
<xsl:when test="../../my:name = $name">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>otherwise</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
XML:
<my:project xmlns:my="http://myns">
<my:properties>
<my:property>
<my:name>customerId</my:name>
<my:value>1</my:value>
</my:property>
<my:property>
<my:name>userId</my:name>
<my:value>20</my:value>
</my:property>
</my:properties>
</my:project>
Result of saxonb-xslt -s:test.xml -xsl:test.xsl
<my:project xmlns:my="http://myns">
<my:properties>
<my:property>
<my:name>customerId</my:name>
<my:value>0</my:value>
</my:property>
<my:property>
<my:name>userId</my:name>
<my:value>otherwise</my:value>
</my:property>
</my:properties>
</my:project>

Related

Can you declare / generate xslt params dynamically?

I'm trying to generate an xml with the help of xslt templates and I have a tricky thing to do:
I have to generate a certain number of elements that have same tags but with different values inside them. Example of xslt stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="NbOfBatches"/>
<xsl:param name="wholeTag"/>
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<Document xmlns="urn:something" xmlns:file="someUrn>
<FileAppHdr>
<file:NbOfBatches><xsl:value-of select="$NbOfBatches"></xsl:value-of></file:NbOfBatches>
<xsl:call-template name="selects">
<xsl:with-param name="i">1</xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$NbOfBatches"/></xsl:with-param>
</xsl:call-template>
</FileAppHdr>
</Document>
</xsl:template>
<xsl:template name="selects">
<xsl:param name="i" />
<xsl:param name="count" />
<xsl:if test="$i <= $count">
<xsl:call-template name="credit">
<xsl:with-param name="param0"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="$i <= $count">
<xsl:call-template name="selects">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="credit">
<xsl:param name="param0"/>
<CreditTransfer>
<xsl:value-of select="$param0"/>
</CreditTransfer>
</xsl:template>
</xsl:stylesheet>
In this way, I can use Saxon or JAXP and call .setParameter() for NbOfBatches, it will generate that number of tags. However is there a way to generate / modify name of param0 inside CreditTransfer so that I can loop through data in Java and use .setParameter("param1", value1) and so on ?
Thank you
Rather than trying to pass structured data as a stylesheet parameter, why not generate that structured data as XML and pass that XML document as the source document of the transformation?
As an example, imagine a source XML document with param0 in each Batch:
<Batches>
<Batch>
<param0>it was the best of times</param0>
</Batch>
<Batch>
<param0>it was the worst of times</param0>
</Batch>
<Batch>
<param0>it was the age of wisdom</param0>
</Batch>
<Batch>
<param0>it was the age of foolishness</param0>
</Batch>
</Batches>
with a simplified version of your stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<Document xmlns:file="someUrn">
<FileAppHdr>
<file:NbOfBatches><xsl:value-of select="count(//Batch)"/></file:NbOfBatches>
<xsl:for-each select="//Batch">
<xsl:call-template name="selects">
<xsl:with-param name="param0" select="param0" />
</xsl:call-template>
</xsl:for-each>
</FileAppHdr>
</Document>
</xsl:template>
<xsl:template name="selects">
<xsl:param name="param0" />
<xsl:call-template name="credit">
<xsl:with-param name="param0" select="$param0" />
</xsl:call-template>
</xsl:template>
<xsl:template name="credit">
<xsl:param name="param0"/>
<CreditTransfer>
<xsl:value-of select="$param0"/>
</CreditTransfer>
</xsl:template>
</xsl:stylesheet>
produces this output:
<Document xmlns:file="someUrn">
<FileAppHdr>
<file:NbOfBatches>4</file:NbOfBatches>
<CreditTransfer>it was the best of times</CreditTransfer>
<CreditTransfer>it was the worst of times</CreditTransfer>
<CreditTransfer>it was the age of wisdom</CreditTransfer>
<CreditTransfer>it was the age of foolishness</CreditTransfer>
</FileAppHdr>
</Document>
It's fairly trivial to generate XML from structured data in Java using the Saxon sapling classes. https://www.saxonica.com/html/documentation10/javadoc/net/sf/saxon/sapling/package-summary.html

XSLT 1.0: how to wrap XML element around other templates?

I am using xsltproc to process XSLT 1.0 transform on OS X Yosemite; input is HTML, and output is XML.
The idea is that the templates below matching h1[#class='page-header'] and div[#class='mixins'] actually work, but the problem is wrapping them in a custom parent XML element (here called dye).
I realize my template matching * is broken; it's there simply to illustrate the kind of structure I would like to output.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|style|script"/>
<xsl:template match="*">
<xsl:element name="dye">
<xsl:apply-templates select="h1[#class='page-header']"/>
<xsl:apply-templates select="div[#class='mixins']"/>
</xsl:element>
</xsl:template>
<xsl:template match="h1[#class='page-header']">
<xsl:element name="color">
<xsl:value-of select="text()"/>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="div[#class='mixins']">
<xsl:element name="tone">
<xsl:value-of select="p/a[#class='tone']/#href"/>
</xsl:element>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Thanks for your interest!
I believe this should work:
<xsl:template match="*">
<dye>
<xsl:apply-templates select="h1[#class='page-header']"/>
<xsl:apply-templates select="div[#class='mixins']"/>
</dye>
</xsl:template>

Generate xpath of XML using XSLT for all Nodes

I was trying to find a way to convert the XML tags to their respective unique address like xPath. Nd I found an XSLT, where XML is processed and the unique address is created only for the nodes which doesnt have child elements and with attributes. [link]:Generate/get xpath from XML node java
XSLT :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vApos">'</xsl:variable>
<xsl:template match="*[#* or not(*)] ">
<xsl:if test="not(*)">
<xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
<xsl:value-of select="concat('=',$vApos,.,$vApos)"/>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:apply-templates select="#*|*"/>
</xsl:template>
<xsl:template match="*" mode="path">
<xsl:value-of select="concat('/',name())"/>
<xsl:variable name="vnumPrecSiblings" select=
"count(preceding-sibling::*[name()=name(current())])"/>
<xsl:if test="$vnumPrecSiblings">
<xsl:value-of select="concat('[', $vnumPrecSiblings +1, ']')"/>
</xsl:if>
</xsl:template>
<xsl:template match="#*">
<xsl:apply-templates select="../ancestor-or-self::*" mode="path"/>
<xsl:value-of select="concat('[#',name(), '=',$vApos,.,$vApos,']')"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when passed to this
<?xml version="1.0" encoding="UTF-8"?>
<root>
<main>
<tag1>001</tag1>
<tag2>002</tag2>
<tag3>
<tag4>004</tag4>
</tag3>
<tag2>002</tag2>
<tag5>005</tag5>
</main>
</root>
produces
/root/main/tag1='001' /root/main/tag2='002' /root/main/tag3/tag4='004' /root/main/tag2[2]='002' /root/main/tag5='005'
So I need the xslt to generate in the following way
/root
/root/main
/root/main/tag1
/root/main/tag2
/root/main/tag3
/root/main/tag3/tag4
/root/main/tag2[2]
/root/main/tag5
Also I dont need values. So please help me with this
Your result could be produced rather simply by:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="*">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="name()" />
<xsl:variable name="i" select="count(preceding-sibling::*[name()=name(current())])"/>
<xsl:if test="$i">
<xsl:value-of select="concat('[', $i + 1, ']')"/>
</xsl:if>
<xsl:if test="position()!=last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="*"/>
</xsl:template>
</xsl:stylesheet>
Note that this does not process attributes (or any other type of nodes other than elements).

XSLT: How to change the tag name if it's parent or child node?

I'm having this xml as my input :
<unidad num="2.">
<tag></tag>
<tag2></tag2>
<unidad num="2.1">
<tag></tag>
<tag2></tag2>
<unidad num="2.1.1">
<tag></tag>
<tag2></tag2>
<unidad num="2.1.1.1">
<tag></tag>
<tag2></tag2>
</unidad>
</unidad>
</unidad>
</unidad>
My output should be :
<sub>
<tag></tag>
<tag2></tag2>
<sub2>
<tag></tag>
<tag2></tag2>
<sub3>
<tag></tag>
<tag2></tag2>
<sub4>
<tag></tag>
<tag2></tag2>
</sub4>
</sub3>
</sub2>
</sub>
I can't find the proper way to do this. I'm working with templates, i'm having this :
<xsl:for-each select="unidad">
<xsl:call-template name="unidades1"/>
</xsl:for-each>
<xsl:template name="unidades1">
<xsl:element name="sub1">
<xsl:text></xsl:text>
</xsl:element>
<xsl:if test="position() != last()">
<xsl:apply-templates select="child::*"/>
</xsl:if>
</xsl:template>
<xsl:template match="unidad">
<xsl:call-template name="unidades2"/>
</xsl:template>
<xsl:template name="unidades2">
<xsl:element name="sub2">
<xsl:text></xsl:text>
</xsl:element>
<xsl:if test="position() != last()">
<xsl:apply-templates select="child::*"/>
</xsl:if>
</xsl:template>
With this XSLT, every child of unidad matches the second condition, so it's written as sub2, and I don't know how to consider if it is a child of another unidad element. Any ideas how to reach this? Thanks!
This stylesheet produces the desired output. It uses a modified identity transform with a specialized template for the <unidad> elements.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<!--standard identity template-->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="unidad">
<!--count the number of ancestors that are unidad elements-->
<xsl:variable name="unidad-ancestor-count" select="count(ancestor::unidad)"/>
<!--If there are at least one unidad ancestors then
set the suffix to be the count()+1-->
<xsl:variable name="suffix">
<xsl:if test="$unidad-ancestor-count>0">
<xsl:value-of select="$unidad-ancestor-count+1"/>
</xsl:if>
</xsl:variable>
<!--create a new element using a base name of "sub" and the suffix value -->
<xsl:element name="sub{$suffix}">
<!--not pushing the #num attribute through the identity template,
just descendant nodes-->
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>

XSLT Apply Template for Specific list of value

I am beginner in XSLT.
My Source XML is as below
<Options>
<Option>
<Data>Data1</Data>
<Type>A</Type>
</Option>
<Option>
<Data>Data2</Data>
<Type>B</Type>
</Option>
<Option>
<Data>Data3</Data>
<Type>C</Type>
</Option>
<Option>
<Data>Data4</Data>
<Type>D</Type>
</Option>
...
</Options>
I have parameter which is used to filter the result from above method and it is as below
<xsl:param name="filterType" select="'A,C'"/>
The output should be as below:
<Result>
<Data Type="A">Data1<Data>
<Data Type="C">Data3<Data>
</Result>
Below is the XSLT i have created:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="filterType" select="'A,C'"/>
<xsl:template match="Options">
<xsl:element name="Result">
<xsl:apply-templates select="Option"/>
</xsl:element>
</xsl:template?
<xsl:template match="Option">
<xsl:element name="Data">
<xsl:attribute name="Type">
<xsl:value-of select="Type"/>
</xsl:attribute>
<xsl:value-of select="Data"/>
</xsl:element>
</xsl:template?
</xsl:stylesheet>
While applying template for 'Option' tag i need to use filterType.
How can i do that? Please Help.
I think you simply want
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="filterType" select="'A,C'"/>
<xsl:variable name="filter" select="concat(',', $filterType, ',')"/>
<xsl:template match="Options">
<Result>
<xsl:apply-templates select="Option[contains($filter, concat(',', Type, ','))]"/>
</Result>
</xsl:template>
<xsl:template match="Option">
<Data Type="{Type}">
<xsl:value-of select="Data"/>
</Data>
</xsl:template>
</xsl:stylesheet>

Categories

Resources