with-param not working in apply-templates - xslt

I am currently trying to generate the creation SQL for my tables based off of a Visio diagram. I am doing this using the approach found here.
http://www.dougboude.com/blog/1/2008/11/SQL-Forward-Engineering-with-Visio-2003-Professional.cfm
I am attempting to modify the xslt file found there to better model the syntax that we use in our office. Unfortunately, I cannot get the part that involves passing the table name into the template for the table columns to work. The template gets called, but it seems to ignore my parameters.
<xsl:template match="Entity" mode="table">
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name = '<xsl:value-of select="#PhysicalName"/>')
<br />
CREATE TABLE dbo.[<xsl:value-of select="#PhysicalName"/>]
(
<br />
<xsl:for-each select="EntityAttributes/EntityAttribute">
<span style="padding-left: 20px;">
<xsl:apply-templates select="../../../../Attributes/Attribute[#AttributeID = current()/#EntityAttributeID]" mode="table">
<xsl:with-param name="EntityName" select="#PhysicalName" />
</xsl:apply-templates>
</span>
<xsl:if test="count(../../EntityAttributes/EntityAttribute) != position()">,</xsl:if>
<br />
</xsl:for-each>
)
<br />
GO
<p />
<xsl:apply-templates select="EntityAnnotations/EntityAnnotation[#AnnotationType='Primary Key']" mode="pk"/>
<xsl:apply-templates select="EntityAnnotations/EntityAnnotation[#AnnotationType='Alternate Key']" mode="ak"/>
<xsl:apply-templates select="EntityAnnotations/EntityAnnotation[#AnnotationType='Index']" mode="idx"/>
</xsl:template>
<!-- Create column for each EntityAttribute -->
<xsl:template match="Attribute" mode="table">
<xsl:param name="EntityName"></xsl:param>
<xsl:variable name="nullability">
<xsl:choose>
<xsl:when test='#AllowNulls = "false"'>NOT NULL CONSTRAINT DF_<xsl:value-of select="$EntityName" />_<xsl:value-of select="#PhysicalName"/>
</xsl:when>
<xsl:otherwise> NULL</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="incremental">
<xsl:choose>
<xsl:when test='#PhysicalDatatype = "int identity"'> INT IDENTITY(1,1)</xsl:when>
<xsl:otherwise><xsl:value-of select="#PhysicalDatatype"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
[<xsl:value-of select="#PhysicalName"/>] <span style="text-transform:uppercase;"> <xsl:value-of select="$incremental"/></span> <xsl:value-of select="$nullability"/>
</xsl:template>

The parameter is not ignored, but I guess it is empty. You call:
<xsl:with-param name="EntityName" select="#PhysicalName" />
where #PhysicalName must be an attribute of EntityAttributes/EntityAttribute element from the for-each. The fact that you use #PhysicalName earlier in
CREATE TABLE dbo.[<xsl:value-of select="#PhysicalName"/>]
makes me think in reality it is an attribute of the Entity element the template matches. You need to store its value in a variable first (before the for-each):
<xsl:variable name="PhysicalName" select="#PhysicalName" />
and then use it like this:
<xsl:with-param name="EntityName" select="$PhysicalName" />
<!-- -------------------------------------^ -->
The for-each resets the context node with every iteration, I guess this is where it goes wrong for you.

Related

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.

XSL:Choose query

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>

XSL counter on specific condition for SharePoint 2010 CQWP

This is the first time I have ever posted a question so apologese in advance if I jibber here.
I am trying to put together a CQWP with jQuery tabs slider functionality. The HTML I want to output should be in the form of 2 UL's. The first with li anchor tags with #associated-ul-id
The second ul's should have ids that associate with the list items in the first. Eg
<div id="tabs" class="news">
<div class="news-pagination">
« Prev
<ul id="carouseltext" class="horizontal-text order">
<li>System</li>
<li>School</li>
</ul>
» Next
<div class="clear"> </div>
</div>
<ul id="tabs-1" class="feed order">
<li>title 1</li>
<li>title 2</li>
</ul>
<ul id="tabs-2" class="feed order">
<li>title 3</li>
</ul>
</div>
The original XML starts off in the form
My XSL goes through the XML twice to fill the 2 ul's. The first time it just adds a new list item when the __begincolumn and __begingroup variables are true. I striped down the functionality here to just output the header. Here's a stripped down version of the XSL
<xsl:template match="/">
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
<xsl:variable name="RowCount" select="count($Rows)" />
<xsl:variable name="FirstRow" select="1" />
<xsl:param name="ColNumber" select="1" />
<xsl:for-each select="$Rows" >
<xsl:variable name="CurPosition" select="position()" />
<xsl:variable name="BeginNewsItemsList1" select="string('<ul id="tabs-')" />
<xsl:variable name="BeginNewsItemsList2" select="string('"class="feed order">')" />
<xsl:variable name="BeginNewsItemsList" select="concat($BeginNewsItemsList1, $ColNumber, $BeginNewsItemsList2)" />
<xsl:if test="($CurPosition >= $FirstRow and $CurPosition <= $LastRow)">
<xsl:variable name="StartNewGroup" select="#__begingroup = 'True'" />
<xsl:variable name="StartNewColumn" select="#__begincolumn = 'True'" />
<xsl:when test="$StartNewGroup and $StartNewColumn">
<xsl:choose>
<xsl:when test="$CurPosition = $FirstRow">
<xsl:value-of disable-output-escaping="yes" select="$BeginNewsItemsList" />
</xsl:when>
<xsl:otherwise>
<!-- other instructions -->
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$StartNewGroup">
<xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
<xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginNewsItemsList)" />
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="OuterTemplate.Count">
<xsl:param name="ColNumber" />
<xsl:value-of select="$ColNumber + 1" />
</xsl:template>
For the second for-each loop I'm having trouble setting up a counter so that I can add the number to the end of the id for each new list id="tabs-1", id="tabs-2", etc.
In theory I think I should set a parameter outside my for-each loop and then in the loop call a template that gets the parameter value and increments it. That would mean it would increment only when the template is called.
I can't use position() for this as it doesn't correspond to the values I want. I've tried to follow a couple a few blogs about recursive programming with xsl, but I can't seem to find anything that works. I'm sure I'm just writing the XSL wrong, but I'm having a bit of a brain dump now.
If anybody could point me in the right direction that would be awesome. Thanks very much.
You can't change variable's values after declaration. You can use them in expressions and/or pass as parameters. Thus, you can't use outside variable as counter explicitly. One available trick is recursive cycle like:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<HTML>
<BODY>
<xsl:call-template name="for">
<xsl:with-param name="i" select="1"/>
<xsl:with-param name="n" select="5"/>
</xsl:call-template>
</BODY>
</HTML>
</xsl:template>
<xsl:template name="for">
<xsl:param name="i"/>
<xsl:param name="n"/>
<xsl:value-of select="$i"/>
<xsl:if test="$i < $n">
<xsl:text>, </xsl:text>
<xsl:call-template name="for">
<xsl:with-param name="i" select="$i+1"/>
<xsl:with-param name="n" select="$n"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
result:
1, 2, 3, 4, 5

XSLT xsl:apply-templates Conditional Syntax

I've got the following XSLT code which lists out the folders and their file items from a specified node.
This all works fine but I'd like to parameterise the page and optionally filter its output by a tag value.
Being an XLST numpty I'm stumped with the syntax for the conditional I should be putting in under the <xsl:when test="$tag"> clause - can someone please help ?
<xsl:variable name="tag" select="umbraco.library:Request('tag')" />
<xsl:template match="/">
<!-- Root folder in Media that holds the folders to output -->
<xsl:variable name="mediaRootFolderId" select="5948" />
<!-- Pass in true() to get XML for all nodes below -->
<xsl:variable name="mediaRootNode" select="umbraco.library:GetMedia($mediaRootFolderId, true())" />
<xsl:choose>
<xsl:when test="$tag">
</xsl:when>
<xsl:otherwise>
<!-- If we didn't get an error, output Folder elements that contain Image elements -->
<xsl:apply-templates select="$mediaRootNode[not(error)]/Folder[File]" >
<xsl:sort select="#nodeName"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Template for folders -->
<xsl:template match="Folder">
<div class="folder">
<h2>Folder: <xsl:value-of select="#nodeName" /></h2>
<div class="images">
<xsl:apply-templates select="File">
<xsl:sort select="#nodeName"/>
</xsl:apply-templates>
</div>
</div>
</xsl:template>
<!-- Template for files -->
<xsl:template match="File">
File: <a href="{umbracoFile}" alt="{#nodeName}" ><xsl:value-of select="#nodeName" /></a> <br/>
</xsl:template>
Instead of the long <xsl:choose> instruction, use:
<xsl:apply-templates select=
"$mediaRootNode[not($tag)][not(error)]
/Folder[File]" >
Explanation: For the XPath expression in the select attribute above to select a non-empty set of nodes it is necessary that boolean($tag) is true(). Thus the above single <xsl:apply-templates> instruction is equivalent to the long <xsl:choose> in the question.
you can test if $tag is set like this.
<xsl:param name="tag">
<xsl:message terminate="yes">
$tag has not been set
</xsl:message>
</xsl:param>
This isn't standard though, it'll work on most XSLT processors though.
If you wanted to be absolutely save, you could set the value to an illegal value (such as 1 div 0) and test for it in the body of the template:
<xsl:param name="tag" select="1 div 0" />
<xsl:if test="$tag = 1 div 0">
<xsl:message terminate="yes">
$tag has not been set, or has been set to Infinity, which is invalid.
</xsl:message>
</xsl:if>
Source: O'Reilly XSLT Cookbook

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 ...)