xslt 3.0 conditional dynamic element creation - dynamic

I have a variable:
<xsl:variable name="courseType" select="Record[1]/course-type"/>
and based on a value I would like to create a dynamic element:
<xsl:if test="$courseType ='B'">
<xsl:element name="newElement">
</xsl:if>
...
other nodes
...
<xsl:if test="$courseType ='B'">
</xsl:element>
</xsl:if>
The problem is that:
The element type "xsl:element" must be terminated by the matching end-tag "".
is there any way to achieve this?

You haven't provided much content but you can certainly use e.g.
<xsl:variable name="other-nodes">
...
</xsl:variable>
<xsl:choose>
<xsl:when test="$course-type = 'B'">
<newElement>
<xsl:sequence select="$other-nodes"/>
</newElement>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$other-nodes"/>
</xsl:otherwise>
</xsl:choose>
Note that I have used a literal result element for newElement instead of using xsl:element as I only consider the use of xsl:element necessary if you want to compute the element name at run-time but it does not matter for the solution of the original problem whether you use a literal result element or xsl:element.

Related

How to multiply numbers with each other in xslt 1.0

I have a number for example 123, this is stored in an element let's say
<Number>123</Number>. I am looking for a solution written in XSLT 1.0 which can do something like: 1*2*3 and provide me the result as 6. The value in Number element can be in any length. I know i can do this through substring function and by storing the values one by one in variables but the problem is, i dont know the length of this field.
I could not write any xslt for this.
Can anyone help or suggest a solution for this?
You can do this by calling a recursive named template:
<xsl:template name="digit-product">
<xsl:param name="digits"/>
<xsl:param name="prev-product" select="1"/>
<xsl:variable name="product" select="$prev-product * substring($digits, 1, 1)" />
<xsl:choose>
<xsl:when test="string-length($digits) > 1">
<!-- recursive call -->
<xsl:call-template name="digit-product">
<xsl:with-param name="digits" select="substring($digits, 2)"/>
<xsl:with-param name="prev-product" select="$product"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$product"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Working example: http://xsltransform.net/3NJ38YJ
Note that this assumes the value of the passed digits parameter is an integer.

xslt replace value of multiple elements

I need to replace values of multiple elements of an input xml. The input file has around 300 elements of which I need to replace around 100 elements. I have used identity template before, but I think it would require me to write 100 different templates each for replacing a single element value. I am not very good at xslts, so, am I thinking it right, or is there a better an elegant approach? Please advice.
Edit
Here is the Link of sample input xml.
The output will have almost the same structure, but different values for some of the elements.
Well, something I did similar before before so I am happy to share ... modified to your example BUT does not do the value mapping, it does name mapping.
First create a simple mapping file like this (NOTE: you would need the namespaces to do this right
<env:map xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<elem old="CardCode" new="CodeCardNEW"/>
<elem old="CardName" new="IAMNew"/>
</env:map>
Then one template will do you with a lookup. There are likely better ways to do the mapping, I am just for-each looping over them all ... a key would be better. But this gets you there.
Output from your file with above:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<GetByKeyResponse>
<BOM>
<BO>
<AdmInfo>
<Object>oBusinessPartners</Object>
</AdmInfo>
<BusinessPartners>
<row>
<CodeCardNEW>CR43WEB</CodeCardNEW>
<IAMNew>Zack Burns</IAMNew>
<CardType>cCustomer</CardType>
...
And here's the XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://www.w3.org/2003/05/soap-envelope"
version="1.0">
<xsl:param name="map" select="document('map.xml')/env:map"></xsl:param>
<xsl:template match="text()" priority="1">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:variable name="newname">
<xsl:call-template name="namesub">
<xsl:with-param name="name" select="name()"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$newname}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template name="namesub">
<xsl:param name="name"/>
<xsl:variable name="newname">
<xsl:for-each select="$map/elem">
<xsl:choose>
<xsl:when test="#old = $name">
<xsl:value-of select="#new"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($newname) > 0">
<xsl:value-of select="$newname"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$name"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You should be able to adapt this as long as you have a match on name ... if the name is the same in different hierarchies of the XML, you would need more work.

XSLT1 error when get first occurence of a tag

This question (and my problem) is similar to XSLT1 Get the first occurence of a specific tag... But I have an "Undefined variable" error.
I have a XML with refpos attribute, that can be make by this first XSLT,
<xsl:template match="p[#class='ref']">
<xsl:copy>
<xsl:attribute name="refpos">
<xsl:value-of select="position()"/>
</xsl:attribute>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()" name="idt">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
Then, I a main (second) XSLT I have,
<xsl:variable name="firstRef">
<xsl:value-of select="(//p[#class='ref'])[1]/#refpos"/>
</xsl:variable>
<xsl:template match="p[#refpos=$firstRef]">
<title><xsl:apply-templates /></title>
</xsl:template>
<xsl:template match="#*|node()" name="idt">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
But here this XSLT not works!!
PS: I also believe that XSLT1 allow us to do everything in one step.
XSLT 1.0 does not allow variable references in template match expressions (XSLT 2.0 does). You'll have to move the check from the predicate inside the template:
<xsl:template match="p">
<xsl:choose>
<xsl:when test="#refpos=$firstRef">
<title><xsl:apply-templates /></title>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="idt" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

XSL : How to pass variable to for each

<xsl:for-each select="$script/startWith">
<xsl:variable name = "i" >
<xsl:value-of select="$script/startWith[position()]"/>
</xsl:variable>
<xsl:for-each select="JeuxDeMots/Element">
<xsl:variable name = "A" >
<xsl:value-of select="eName"/>
</xsl:variable>
<xsl:if test="starts-with($A,$i)= true()">
<xsl:variable name="stringReplace">
<xsl:value-of select="substring($i,0,3)"/>
</xsl:variable>
<xsl:value-of select="$stringReplace"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
Problem : variable $i, can not pass in an xsl for-each.
Please help me.
Try replacing the declaration of i with
<xsl:variable name="i" select="string(.)"/>
The context item (i.e. ".") is different for each evaluation of the for-each instruction, but the value of the expression $script/startWith[position()] does not change. (Here, you are making a sequence of startWith elements, and testing each one for the effective boolean value of the expression position(). The expression position() returns a positive integer, so its effective boolean value is always true. So the predicate [position()] is doing no work at all here.
Also, you'll want to replace the declaration of stringReplace with
<xsl:variable name="stringReplace" select="substring($i,1,3)"/>
(String offsets begin with 1 in XPath, not with 0.)
I am guessing that you want to process all of the startWith children of $script, and for each one emit the first three characters of the startWith value once, for each startWith/JeuxDeMots/Element whose eName child begins with the value of startWith.
Actually, the whole thing might be a little easier to read if it were briefer and more direct:
<xsl:for-each select="$script/startWith">
<xsl:variable name = "i" select="string()"/>
<xsl:for-each select="JeuxDeMots/Element">
<xsl:if test="starts-with(eName,$i)">
<xsl:value-of select="substring($i,1,3)"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
It makes perfect sense to create the variables $A and $stringReplace if they are used several times in code you're not showing us, but if not, ...
I think the problem is that you change context in first for-each. Then the element JeuxDeMots is not "visible" for second for-each. You can for example try to store it in variable and use this variable in second for-each (there are also another ways how to figure out this problem).
<xsl:template match="/">
<xsl:variable name="doc" select="JeuxDeMots"/>
<xsl:choose>
<xsl:when test="$script/startWith">
<xsl:for-each select="$script/startWith">
<xsl:variable name="i">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:for-each select="$doc/Element">
<xsl:variable name="A">
<xsl:value-of select="eName"/>
</xsl:variable>
<xsl:if test="starts-with($A,$i) = true()">
<xsl:variable name="stringReplace">
<xsl:value-of select="substring($i,0,3)"/>
</xsl:variable>
<xsl:value-of select="$stringReplace"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:template>
Although I'm not sure what exactly you are dealing with it seems to output desired value AsAt.
You could also consider C.M.Sperberg-McQueen post about string offset in XPath.

Numeric variable/parameter in XSLT

I have this XSLT:
<xsl:template match="/">
<xsl:variable name="errorCount" select="count($orders/*[1]/cm:Error)" />
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="errorCount" select="$errorCount" tunnel="yes" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="status">
<xsl:param name="errorCount" tunnel="yes" />
<xsl:copy>
<xsl:choose>
<xsl:when test="$errorCount > 0">
<xsl:text>ERROR</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>OK</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
The tunneling and all seems to work, but the transformation fails with the following error:
Required item type of first operand of '>' is numeric; supplied value has item type xs:string
I first had the variable declaration in the template where it is it used, and then it worked fine. Moved it because I need to use the same count in other templates too.
How/Where do I declare that this variable/parameter is in fact a number?
Since you are using XSLT 2.0, then you should also add an as attribute to your xsl:param in the template. For example (you may have to use a different as value depending on what you need the resulting number to be, such as if you'll have values that have decimals; you would also need to correct the tunnel value, per Michael Kay's point):
<xsl:param name="errorCount" tunnel="yes" as="xs:integer" />
The transform would fail if it couldn't be converted to the as type (in this case, an integer). Eero's solution may look cleaner because you'll still have to check to see if the value is greater than zero. But, because you are using XSLT 2.0, the best practice is to type your parameters/variables.
You can use number() to convert a string to a number:
<xsl:when test="number($errorCount) > 0">
<xsl:text>ERROR</xsl:text>
</xsl:when>
My suspicion is that because you wrote tunnel="true" rather than tunnel="yes", the fact that you specified tunnel at all is being (incorrectly) ignored, and the parameter is being given its default value, which is a zero-length string.