XSL:Choose query - xslt

I've the code as shown below which gives Stylesheet Compilation error.
<xsl:template match="form">
<xsl:copy>
<xsl:for-each select="#*">
<xsl:variable name="param" select="name(.)" />
<xsl:choose>
<xsl:when test="$param = 'name'">
<xsl:attribute name="name"><xsl:value-of
select="#name" /></xsl:attribute>
</xsl:when>
<xsl:when test="$param = 'action'">
<xsl:attribute name="action"><xsl:value-of
select="java:com.hp.cpp.proxy.util.URLUtils.rewriteAction($response, $baseurl, #action, $scope)" /></xsl:attribute>
</xsl:when>
<xsl:when test="$param = 'method'">
<xsl:attribute name="method">POST</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="$param"><xsl:value-of
select="." /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<input type="hidden" name="httpmethod">
<xsl:attribute name="value"> <xsl:value-of
select="#method" /></xsl:attribute>
</input>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
I'm trying to re-write FORM tag of HTML with quite complex requirement. Hope you'll be able to identify by the code-snap. I'm trying to re-write only few of the attributes of the tag and trying to retain the rest. Is it the right way? Any other way to do it? Any suggestion.
Thanks in advance.
-Rikin

Just a guess. Try to replace the last apply
<xsl:apply-templates select="node()|#*" />
by this
<xsl:apply-templates select="node()" />

About the compilation error, you don't provide enough information to help; Abel's conjecture that the compilation error has to do with your call to an extension function is plausible.
You also ask Is this the right way? to achieve your goal. Maybe. Your first problem here is the logic error that jvverde has already pointed to. The call to apply templates should not select attributes; you have already dealt with all the attributes. So it's unnecessary to process them again. It's also a bad idea: if you you try to handle the form element's attributes again, you'll get a run-time error because you've already written content to the element (namely that input element).
I think some XSLT programmers would write something that looks more like this:
<xsl:template match="form">
<xsl:copy>
<!--* don't use a for-each to handle the
* attributes; use templates. *-->
<xsl:apply-templates select="#*"/>
<!--* you don't need an xsl:attribute constructor
* if you want to use an expression within a
* literal result element; just braces in the
* attribute-value template.
*-->
<input type="hidden"
name="httpmethod"
value="{#method}" />
<!--* change your apply-templates call to
* select children, but not attributes.
*-->
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<!--* now the attributes ... *-->
<xsl:template match="form/#action">
<xsl:attribute name="action">
<xsl:value-of select="java:com.hp.cpp.proxy.util.URLUtils.rewriteAction(
$response, $baseurl, #action, $scope)" />
</xsl:attribute>
</xsl:template>
<xsl:template match="form/#method">
<xsl:attribute name="method">
<xsl:value-of select="'POST'"/>
</xsl:attribute>
</xsl:template>

Related

correct approach for XSL templates

I started on XSL a few weeks ago, and I have developed a pattern for templates.
My templates will handle some of the content of the matched element, then pass off its children to other templates that might match them.
But I exclude elements that it has already dealt with, so as not to process the same elements again.
For example:
<xsl:template name="DEFINITION" match="DEFINITION">
<xsl:element name="body">
<xsl:attribute name="break">before</xsl:attribute>
<xsl:element name="defn">
<xsl:attribute name="id" />
<xsl:attribute name="scope" />
<xsl:value-of select="DEFINEDTERM" />
</xsl:element>
<xsl:apply-templates select="TEXT" />
</xsl:element>
<xsl:apply-templates select="*[not(self::DEFINEDTERM|self::TEXT)]" />
</xsl:template>
Is this the correct approach? Is there a better way?
It seems you don't need any xsl:element or xsl:attribute but could just use literal result elements and attribute value templates e.g. <body break="before"><xsl:apply-templates select="DEFINEDTERM, TEXT"/></body> (that is XSLT 2 or 3, for XSLT 1 use <body break="before"><xsl:apply-templates select="DEFINEDTERM"/> <xsl:apply-templates select="TEXT"/></body>) plus obviously a template
<xsl:template matched="DEFINEDTERM">
<defn id="" scope="">
<xsl:value-of select="."/>
</defn>
</xsl:template>
In XSLT 2/3 you can write <xsl:apply-templates select="*[not(self::DEFINEDTERM|self::TEXT)]" /> as e.g. <xsl:apply-templates select="* except (DEFINEDTERM, TEXT)"/>.

XSLT: How do I use a concatenated string as a variable name with xsl:for-each select?

I'm being put on a system migration project, and my task is to receive a SOAP XML request from System A, then convert 6 parameter names inside the request into new names readable by System B, and output a file with just the parameters changed. (all param names changed)
Request:
<q0:Command>
<q0:Transaction>
<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
<q0:ParameterList>
<q0:StringParameter name="Action">Create</q0:StringParameter>
<q0:StructParameter name="Create">
<q0:StringParameter name="UserID">1234567X</q0:StringParameter>
<q0:StringParameter name="Device1">abcd567890</q0:StringParameter>
<q0:StringParameter name="Contract1">Postpaid</q0:StringParameter>
<q0:StringParameter name="Line1">990108024011900</q0:StringParameter>
<q0:StringParameter name="Device2">efgh567890</q0:StringParameter>
<q0:StringParameter name="Contract2">Postpaid</q0:StringParameter>
<q0:StringParameter name="Line2">990104562499105</q0:StringParameter>
</q0:StructParameter>
</q0:ParameterList>
</q0:Operation>
</q0:Transaction>
</q0:Command>
First, I just manually recreated each param with the new name in a for-each select block, and it worked. The situation is, there is now a requirement that the values shouldn't be hardcoded one-by-one, but instead checked in a loop (Just in case the number of devices per user increases).
So I thought of declaring a $LoopIndex variable, and concatenating it to strings that match the parameter names, like so:
So I implemented Tim's answer below into my for-each section, like so:
<xsl:for-each select="//q0:CommandRequestData/q0:Command/q0:Transaction/q0:Operation/q0:ParameterList/q0:StructParameter[#name='Create']">
<xsl:for-each select="q0:StringParameter">
<xsl:choose>
<xsl:when test="#name = 'UserID'">
<ins:Parameter name="USER_ID" value="{string()}" />
</xsl:when>
<xsl:when test="starts-with(#name, 'Device')">
<ins:Parameter name="concat('DEVICE_ID_', substring(., 7))" value="{string()}" />
</xsl:when>
<xsl:when test="starts-with(#name, 'Contract')">
<ins:Parameter name="concat('CONTRACT_TYPE_', substring(., 9))" value="{string()}" />
</xsl:when>
<xsl:when test="starts-with(#name, 'Line')">
<ins:Parameter name="concat('LINE_ID_', substring(., 5))" value="{string()}" />
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
And the above code isn't working. The page isn't understanding that I mean "#name='Device1'" when I say "#name=concat('Device', $LoopIndex)". Is this kind of thing possible with XSLT or will I need to stick to hardcoding the parameter assignments?
Unfortunately, it's only semi-successful. The page is literally taking the "concat('DEVICE_ID_', substring(., 7))" as a string for the parameter name, instead of performing the concatenation.
Update: Got inspired by Tim's answer and found a 'simple' way of using position() to just increment the numbers:
<xsl:for-each select="//q0:CommandRequestData/q0:Command/q0:Transaction/q0:Operation/q0:ParameterList/q0:StructParameter[#name='Create']">
<xsl:for-each select="q0:StringParameter[#name='UserID']">
<ins:Parameter name="USER_ID" value="{string()}" />
</xsl:for-each>
<xsl:for-each select="q0:StringParameter[starts-with(#name, 'Device')]">
<xsl:variable name="DeviceIndex" select="position()" />
<ins:Parameter name="DEVICE_ID_{$DeviceIndex}" value="{string()}" />
</xsl:for-each>
<xsl:for-each select="q0:StringParameter[starts-with(#name, 'Contract')]">
<xsl:variable name="ContractIndex" select="position()" />
<ins:Parameter name="CONTRACT_TYPE_{$ContractIndex}" value="{string()}" />
</xsl:for-each>
<xsl:for-each select="q0:StringParameter[starts-with(#name, 'Line')]">
<xsl:variable name="LineIndex" select="position()" />
<ins:Parameter name="LINE_ID_{$LineIndex}" value="{string()}" />
</xsl:for-each>
</xsl:for-each>
The resulting SOAP XML request passed to System B now successfully looks like below. It's not arranged like the SOAP request...but I'll take it! :)
<q0:Command>
<q0:Transaction>
<q0:Operation namespace="Provisioning" name="Create" modifier="Data">
<q0:ParameterList>
<q0:StringParameter name="Action">Create</q0:StringParameter>
<q0:StructParameter name="Create">
<ins:Parameter name="USER_ID" value="1234567X"/>
<ins:Parameter name="DEVICE_ID_1" value="abcd567890"/>
<ins:Parameter name="DEVICE_ID_2" value="efgh567890"/>
<ins:Parameter name="CONTRACT_TYPE_1" value="Postpaid"/>
<ins:Parameter name="CONTRACT_TYPE_2" value="Postpaid"/>
<ins:Parameter name="LINE_ID_1" value="990108024011900"/>
<ins:Parameter name="LINE_ID_2" value="990104562499105"/>
</q0:StructParameter>
</q0:ParameterList>
</q0:Operation>
</q0:Transaction>
</q0:Command>
I would use template matching here on the attribute, along with the identity template, rather than xsl:for-each and xsl:choose, just to keep the code cleaner (although it is not strictly necessary to solve your problem).
But to solve your problem you can use string functions to extract the current number from the #name attribute.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:q0="q0" >
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="q0:StructParameter[#name='Create']/q0:StringParameter/#name">
<xsl:attribute name="name">
<xsl:choose>
<xsl:when test=". = 'UserID'">
<xsl:text>USER_ID</xsl:text>
</xsl:when>
<xsl:when test="starts-with(., 'Device')">
<xsl:value-of select="concat('DEVICE_ID_', substring(., 7))" />
</xsl:when>
<xsl:when test="starts-with(., 'Contract')">
<xsl:value-of select="concat('CONTRACT_TYPE_', substring(., 9))" />
</xsl:when>
<xsl:when test="starts-with(., 'Line')">
<xsl:value-of select="concat('LINE_ID_', substring(., 5))" />
</xsl:when>
<xsl:otherwise>NA</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Note that you would have to change the namespace URI for q0 to match your actual one accordingly.

How can I insert a Diazo theme parameter into some theme's class attribute?

I want to replace a class tag attribute in my static theme on-the-fly, based on a theme parameter.
I tried this:
<replace attributes="class" css:theme=".conteudo">conteudo-$section</replace>
And this:
<replace css:theme=".conteudo">
<xsl:attribute name="class">conteudo-$section</xsl:attribute>
<xsl:value-of select="."/>
</replace>
And even this:
<xsl:template match="//div[contains(concat(' ', normalize-space(#class), ' '), ' conteudo ')]">
<xsl:attribute name="class">
<xsl:value-of select="substring((body/#class), 'section-', 0)" />
</xsl:attribute>
</xsl:template>
Since I also have other rules referencing .conteudo element, it'd be also nice to get to know best practices on how to deal with those (after the desired transformation occurs), ie:
<replace
css:content-children="#portal-column-content"
css:theme-children=".conteudo" />
You can't reference a variable just anywhere, but need to do so from an XPath expression.
You can avoid interfering with your replacement of the children nodes by inserting the attribute "before" the children.
Here's what I would try:
<before css:theme-children=".conteudo">
<xsl:attribute name="class">conteudo-<xsl:value-of select="$section" /></xsl:attribute>
</before>
As David said, is the right way to do it.
And to set the attributes before the child elements, you have to do that:
<xsl:template match="//*[contains(#class, 'conteudo')]">
<xsl:copy>
<xsl:attribute name="class"><xsl:value-of select="$section" /></xsl:attribute>
<xsl:apply-templates select="#*[not(name()='class')]|node()" />
</xsl:copy>
</xsl:template>
<!--Identity template copies content forward -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>

Sort attributes in specific order for output

How do you write element attributes in a specific order without writing it explicitly?
Consider:
<xsl:template match="Element/#1|#2|#3|#4">
<xsl:if test="string(.)">
<span>
<xsl:value-of select="."/><br/>
</span>
</xsl:if>
</xsl:template>
The attributes should appear in the order 1, 2, 3, 4. Unfortunately, you can't guarantee the order of attributes in XML, it could be <Element 2="2" 4="4" 3="3" 1="1">
So the template above will produce the following:
<span>2</span>
<span>4</span>
<span>3</span>
<span>1</span>
Ideally I don't want to test each attribute if it has got a value. I was wondering if I can somehow set an order of my display? Or will I need to do it explicitly and repeating the if test as in:
<xsl:template match="Element">
<xsl:if test="string(./#1)>
<span>
<xsl:value-of select="./#1"/><br/>
</span>
</xsl:if>
...
<xsl:if test="string(./#4)>
<span>
<xsl:value-of select="./#4"/><br/>
</span>
</xsl:if>
</xsl:template>
What can be done in this case?
In an earlier question you seemed to use XSLT 2.0 so I hope this time too an XSLT 2.0 solution is possible.
The order is not determined in the match pattern of a template, rather it is determined when you do xsl:apply-templates. So (with XSLT 2.0) you can simply write a sequence of the attributes in the order you want e.g. <xsl:apply-templates select="#att2, #att1, #att3"/> will process the attributes in that order.
XSLT 1.0 doesn't have sequences, only node-sets. To produce the same result, use xsl:apply-templates in the required order, such as:
<xsl:apply-templates select="#att2"/>
<xsl:apply-templates select="#att1"/>
<xsl:apply-templates select="#att3"/>
Do not produce XML that relies on the order of the attributes. This is very brittle and I would consider it bad style, to say the least. XML was not designed in that way; <elem a="1" b="2" /> and <elem a="1" b="2" /> are explicitly equivalent.
If you want ordered output, order your output (instead of relying on ordered input).
Furthermore, match="Element/#1|#2|#3|#4" is not equivalent to match="Element/#1|Element/#2|Element/#3|Element/#4", but I'm sure you mean the latter.
That being said, you can do:
<xsl:template match="Element/#1|Element/#2|Element/#3|Element/#4">
<xsl:if test="string(.)">
<span>
<xsl:value-of select="."/><br/>
</span>
</xsl:if>
</xsl:template>
<xsl:template match="Element">
<xsl:apply-templates select="#1|#2|#3|#4">
<!-- order your output... -->
<xsl:sort select="name()" />
</xsl:apply-templates>
</xsl:template>
EDIT: I'll take it as read that #1 etc are just examples, because names cannot actually start with a number in XML.
I'd use xsl:sort on the local-name of the attribute to get the result you want. I'd also use a different mode so the results don't get called by accident somewhere else.
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Element">
<xsl:apply-templates select="#*" mode="sorted">
<xsl:sort select="local-name()" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Element/#a|#b|#c|#d" mode="sorted">
<xsl:if test="string(.)">
<span>
<xsl:value-of select="."/><br/>
</span>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The clue was is the answer by Martin Honnen
To copy attributes and conditionally add a new attribute to the end of the list of attributes.
Add rel="noopener noreferrer" to all external links.
<xsl:template match="a">
<xsl:copy>
<xsl:if test="starts-with(./#href,'http')">
<xsl:apply-templates select="./#*"/>
<!-- Insert rel as last node -->
<xsl:attribute name="rel">noopener noreferrer</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a/#href|a/#target|a/#rel">
<!--
Allowed attribute on anchor
-->
<xsl:attribute name="{name()}">
<xsl:value-of select="."></xsl:value-of>
</xsl:attribute>
</xsl:template>
You can also specify the attribute sequence by calling apply templates with each select in the order you want.
<xsl:template match="a">
<xsl:copy>
<xsl:if test="starts-with(./#href,'http')">
<xsl:apply-templates select="./#id"/>
<xsl:apply-templates select="./#href"/>
<xsl:apply-templates select="./#target"/>
<!-- Insert rel as last node -->
<xsl:attribute name="rel">noopener noreferrer</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>

XSLT: xsl:function won't work

The following works for me:
<xsl:variable name="core" select="document('CoreMain_v1.4.0.xsd')" />
<xsl:variable name="AcRec" select="document('AcademicRecord_v1.3.0.xsd')" />
<xsl:template match="xs:element">
<xsl:variable name="prefix" select="substring-before(#type, ':')" />
<xsl:variable name="name" select="substring-after(#type, ':')" />
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
But I use the same logic to handle the lookup of elements in the current or other documents based on the prefix, matching the node name in numerous places within the stylesheet. So, after changing the stylesheet version to 2.0, I tried:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
<xsl:function name="my:lookup">
<xsl:param name="attribute" />
<!-- parse the attribute for the prefix & name values -->
<xsl:variable name="prefix" select="substring-before($attribute, ':')" />
<xsl:variable name="name" select="substring-after($attribute, ':')" />
<!-- Switch statement based on the prefix value -->
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:function>
In my reading, I have only found examples of functions that return text - none call templates. I have the impression that an xsl:function should always return text/output...
After more investigation, it is entering the my:lookup function and the variables (prefix & name) are getting populated. So it does enter the xsl:choose statement, and the hits the appropriate when test. The issue appears to be with the apply-templates - value-of is displaying the child values; copy-of does as well, which I think is odd (shouldn't the output include the xml element declarations?). Why would there be a difference if code that works in a template declaration is moved to an xsl:function?
It's been a while since I did any serious XSLT, but IIRC your problem is not in the function, but in your template:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
The value-of statement won't inline the result tree returned by your function. Instead, it's going to try and reduce that result tree down into some kind of string, and inline that instead. This is why you're seeing the child values and not the elements themselves.
To inline the result tree returned by your function, you'll need to use some templates to copy the result tree into place.
So, your main template will need to change to this:
<xsl:template match="xs:element">
<xsl:apply-templates select="my:lookup(#type)" />
</xsl:template>
and you'll need some templates to do the recursive call. A quick google found a good discussion of the identity template that should do what you need.
(Please forgive any syntax errors, as I said, it's been a while ...)