BizTalk Mapper Check and Replace Default Values - xslt

I have created a number of functoids on the map to validate the source node 'AdoptedDate' for default values (Equals defaultvalues into an Logical OR through to a Value functoid with a new default value. Then an Logical Not for if neither of the Equals are not true to map the source node 'AdoptedDate' across to the destination schema (xslt enclosed).
What I would like to do is implement this for all date fields in the source schema, can this be done.
Current Generated XSLT (Adopted Only)
<xsl:for-each select="Form/SDetails">
<xsl:variable name="var:v17" select="userCSharp:LogicalEq(string(NameDetails/AdoptedDate/text()) , "1900-09-09")" />
<xsl:variable name="var:v18" select="string(NameDetails/AdoptedDate/text())" />
<xsl:variable name="var:v19" select="userCSharp:LogicalEq($var:v18 , "1800-09-09")" />
<xsl:variable name="var:v20" select="userCSharp:LogicalOr(string($var:v17) , string($var:v19))" />
<xsl:variable name="var:v22" select="userCSharp:LogicalNot(string($var:v20))" />
<xsl:if test="string($var:v20)='true'">
<xsl:variable name="var:v21" select=""1901-01-01"" />
<p:AdoptedDate>
<xsl:value-of select="$var:v21" />
</p:AdoptedDate>
</xsl:if>
<xsl:if test="string($var:v22)='true'">
<xsl:variable name="var:v23" select="NameDetails/AdoptedDate/text()" />
<p:AdoptedDate>
<xsl:value-of select="$var:v23" />
</p:AdoptedDate>
</xsl:if>

Your options are
Use the same set of functoids on each field (this is what I take it you are trying to avoid)
Create an external class implements the logic that you can then call from the Scripting functoid.
Create an in-line script to do the logic. You can then have subsequent scripting fuctoids containing the same function name and parameters and it will only have a single script in the XSLT that they all call.
In all situations you will have to have to have a functoid linked to the source and destination fields.

Related

xslt select="." vs select="<tag name>"

I am finally beginning to understand how xslt works.
Since I will be creating several more xslts in the future I would like to write them well.
I am wondering whether there is a preferred way to get the data of an xml tag.
Is it better to use select="." select=" tag name " or is it irrelevant?
For example:
<xsl:value-of select="." />
or
<xsl:value-of select="Vert_Prism" />
To get the data enclosed in the Vert_Prism tag.
<Vert_Prism>1.5</Vert_Prism>
Thanks,
It depends on your context. If your current node is Vert_Prism, then you would use <xsl:value-of select="." /> to get the text value of the current node.
OTOH, <xsl:value-of select="Vert_Prism" /> is an abbreviation of <xsl:value-of select="child::Vert_Prism" /> - so this would not return anything, unless the current Vert_Prism has a child element with the same name. However, it would work fine from the context of the parent node of Vert_Prism.

Umbraco - Creating variable of nodes - how to check for missing value

I am using Umbraco 4.5 (yes, I know I should upgrade to 7 now!)
I have an XSLT transform which builds up a list of products which match user filters.
I am making an XSL:variable which is a collection of products from the CMS database.
Each product has several Yes/No properties (radio buttons). Some of these haven't been populated however.
As a result, the following code breaks occasionally if the dataset includes products which don't have one of the options populated with an answer.
The error I get when it transforms the XSLT is "Value was either too large or too small for an Int32". I assume this is the value being passed into the GetPreValueAsString method.
How do I check to see if ./option1 is empty and if so, use a specific integer, otherwise use ./option1
<xsl:variable name="nodes"
select="umbraco.library:GetXmlNodeById(1098)/*
[#isDoc and string(umbracoNaviHide) != '1' and
($option1= '' or $option1=umbraco.library:GetPreValueAsString(./option1)) and
($option2= '' or $option2=umbraco.library:GetPreValueAsString(./option2)) and
($option3= '' or $option3=umbraco.library:GetPreValueAsString(./option3)) and
($option4= '' or $option4=umbraco.library:GetPreValueAsString(./option4))
]" />
Note: you tagged your question as XSLT 2.0, but Umbraco does not use XSLT 2.0, it is (presently) stuck with XSLT 1.0.
$option1= '' or $option1=umbraco.library:GetPreValueAsString(./option1)
There can be multiple causes for your error. A processor is not required to process the or-expression left-to-right or right-to-left, and it is even allowed to always evaluate both expressions, even if the first is true (this is comparable with bit-wise operators (unordered) in other languages, whereas boolean operators (ordered) in those languages typically use early breakout).
Another error can be that your option value in the context node is not empty and is not an integer or empty, in which case your code will always return an error.
You could expand your expression by testing ./optionX, but then you still have the problem of order of evaluation.
That said, how can you resolve it and prevent the error from arising? In XSLT 1.0, this is a bit clumsy (i.e., you cannot define functions and cannot use sequences), but here's one way to do it:
<xsl:variable name="pre-default-option">
<default>1</default>
<default>2</default>
<default>3</default>
<default>4</default>
</xsl:variable>
<xsl:variable name="default-option"
select="exslt:node-set($pre-default-option)" />
<xsl:variable name="pre-selected-option">
<option><xsl:value-of select="$option1" /></option>
<option><xsl:value-of select="$option2" /></option>
<option><xsl:value-of select="$option3" /></option>
<option><xsl:value-of select="$option4" /></option>
</xsl:variable>
<xsl:variable name="selected-option" select="exslt:node-set($pre-selected-option)" />
<xsl:variable name="pre-process-nodes">
<xsl:variable name="selection">
<xsl:apply-templates
select="umbraco.library:GetXmlNodeById(1098)/*"
mode="pre">
<xsl:with-param name="opt-no" select="1" />
</xsl:apply-templates>
</xsl:variable>
<!-- your original code uses 'and', so is only true if all
conditions are met, hence there must be four found nodes,
otherwise it is false (i.e., this node set will be empty) -->
<xsl:if test="count($selection) = 4">
<xsl:copy-of select="$selection" />
</xsl:if>
</xsl:variable>
<!-- your original variable, should now contain correct set, no errors -->
<xsl:variable name="nodes" select="exslt:node-set($pre-process-nodes)"/>
<xsl:template match="*[#isDoc and string(umbracoNaviHide) != '1']" mode="pre">
<xsl:param name="opt-no" />
<xsl:variable name="option"
select="$selected-option[. = string($opt-no)]" />
<!-- gets the child node 'option1', 'option2' etc -->
<xsl:variable
name="pre-ctx-option"
select="*[local-name() = concat('option', $opt-no)]" />
<xsl:variable name="ctx-option">
<xsl:choose>
<!-- empty option param always allowed -->
<xsl:when test="$option = ''">
<xsl:value-of select="$option"/>
</xsl:when>
<!-- if NaN or 0, this will return false -->
<xsl:when test="number($pre-ctx-option)">
<xsl:value-of select="$default-option[$opt-no]"/>
</xsl:when>
<!-- valid number (though you could add a range check as well) -->
<xsl:otherwise>
<xsl:value-of select="umbraco.library:GetPreValueAsString($pre-ctx-option)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- prevent eternal recursion -->
<xsl:if test="4 >= $opt-no">
<xsl:apply-templates select="self::*" mode="pre">
<xsl:with-param name="opt-no" select="$opt-no + 1" />
</xsl:apply-templates>
<!-- the predicate is now ctx-independent and just true/false
this copies nothing if the conditions are not met -->
<xsl:copy-of select="self::*[$option = $ctx-option]" />
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="pre" />
Note (1): I have written the above code by hand, tested only for syntax errors, I couldn't test it because you didn't provide an input document to test it against. If you find errors, by all means, edit my response so that it becomes correct.
Note (2): the above code generalizes working with the numbered parameters. By generalizing it, the code becomes a bit more complicated, but it becomes easier to maintain and to extend, and less error-prone for copy/paste errors.

XSLT: Is it possible to select unique IDs into variable ordered by a criteria other than the ID itself?

Imagine the following xml
<elements>
<element>
<elementID>0x1000</elementID>
<elementSort>1</elementSort>
<elementName>Master Joda</elementName>
<modifyDate>1979-01-01</modifyDate>
</element>
<element>
<elementID>0x1000</elementID>
<elementSort>1</elementSort>
<elementName>Master Yoda</elementName>
<modifyDate>1979-01-05</modifyDate>
</element>
<element>
<elementID>0x2000</elementID>
<elementSort>2</elementSort>
<elementName>Luke Skywalker</elementName>
<modifyDate>1979-01-08</modifyDate>
</element>
</elements>
I use the following xslt to select a list of unique IDs into a variable
<xsl:variable name="ids"
select="elements/element/elementID[not(.=following::elementID)]" />
Then i let xslt build some html for each ID (the output will be a horizontal list of elements per ID)
<xsl:for-each select="$ids">
<xsl:variable name="elementID" select="." />
<div class="itemContainer clear" style="width:{$containerWidth}">
<xsl:for-each select="/elements/element[elementID=$elementID]">
<xsl:sort select="modifyDate" />
<xsl:call-template name="elementTemplate" />
</xsl:for-each>
</div>
</xsl:for-each>
The problem is: how can i sort the elements in the first level of the for-each nesting (the IDs) without having the Element by which i want to sort in the list itself (the ID list).
In practical terms: how can i sort by Jedi hierarchy (master -> pupil), if elementSort 1 means master and elementSort 2 means pupil, having multiple elements per hierarchy in each row, which are then ordered by modifyDate.
Instead of:
<xsl:for-each select="/elements/element/elementID[elementID=$elementID]">
<xsl:sort select="modifyDate" />
<xsl:call-template name="elementTemplate" />
</xsl:for-each>
BTW, the above is obviously incorrect, because an elementID element doesn't have any elementID child at all.
use:
<xsl:for-each select="/elements/element[elementID=$elementID]">
<xsl:sort select="elementSort" data-type="number" />
<xsl:sort select="modifyDate" />
<xsl:call-template name="elementTemplate" />
</xsl:for-each>
I have found a solution though it is probably not very good "xslt-design".
I store list of the unique elementSorts in an additional variable and put another for-each around the original first one. Then i combine the conditions (uniqueness and sortID) while setting the variable holding the unique element IDs.
<xsl:variable name="ids"
select="elements/element/elementID[not(.=following::elementID) and ../elementSort=$sort]" />
edit:
probably even better:
<xsl:variable name="ids"
select="elements/element[elementSort=$sort]/elementID[not(.=following::elementID)]" />
another note
if there are other nodes in the xml document that can contain elementID nodes, than you would have to specify the following:: clause as follows to avoid unwanted behaviour:
<xsl:variable name="ids"
select="elements/element[elementSort=$sort]/elementID[not(.=following::element/elementID)]" />
That way only elementIDs inside of element nodes are taken into consideration for evaluating the uniqueness, comes in handy if there were nodes that relate to the element nodes by elementID.

XSLT for-each counter - how to access data

for performance testing purposes I want to take a small XML file and create a bigger one from it - using XSLT. Here I plan to take each entity (Campaign node in the example below) in the original XML and copy it n times, just changing its ID.
The only way I can think of to realize this, is a xsl:for-each select "1 to n". But when I do this I do not seem to be able to access the entity node anymore (xsl:for-each select="campaigns/campaign" does not work in my case). I am getting a processor error: "cannot be used here: the context item is an atomic value".
It seems that by using the "1 to n" loop, I am loosing the access to my actual entity. Is there any XPath expression that gets me access back or does anyone have a completely different idea how to realize this?
Here is what I do:
Original XML
<campaigns>
<campaign id="1" name="test">
<campaign id="2" name="another name">
</cmpaigns>
XSLT I try to use
<xsl:template match="/">
<xsl:element name="campaigns">
<xsl:for-each select="1 to 10">
<xsl:for-each select="campaigns/campaign">
<xsl:element name="campaign">
<xsl:copy-of select="#*[local-name() != 'id']" />
<xsl:attribute name="id"><xsl:value-of select="#id" /></xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
Define a variable as the first thing in the match, like so:
<xsl:variable name="foo" select="."/>
This defines a variable $foo of type nodeset. Then access it like this
<xsl:for-each select="$foo/campaigns/campaign">
...
</xsl:for-each>

XSL: How best to store a node in a variable and then use it in future xpath expressions?

I need to be able to store a node set in variable and then perform more filting/sorting on it afterward. All the examples I've seen of this involve either using XSL2 or extensions neither of which are really an option.
I've a list of hotels in my XML doc that can be sorted/filtered and then paged through 5 at a time. I'm finding though I'm repeating alot of the logic as currently I've not found a good way to store node-sets in xsl variable and then use xpath on them for further filtering/sorting.
This is the sort of thing I'm after (excuse the code written of the top of my head so might not be 100%):
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="3_star_or_less" select="/results/hotels[number(rating) <= 3]" />
<xsl:for-each select="3_star_or_less">
<xsl:sort select="rating" />
</xsl:for-each>
Has anyone got an example of how best to do this sort of thing?
Try this example:
<xsl:variable name="hotels" select="/results/hotels[active='true']" />
<xsl:variable name="three_star_or_less"
select="$hotels[number(rating) <= 3]" />
<xsl:for-each select="$three_star_or_less">
<xsl:sort select="rating" />
<xsl:value-of select="rating" />
</xsl:for-each>
There is no problem storing a node-set in a variable in XSLT 1.0, and no extensions are needed. If you just use an XPath expression in select attribute of xsl:variable, you'll end up doing just that.
The problem is only when you want to store the nodes that you yourself had generated in a variable, and even then only if you want to query over them later. The problem here is that nodes you output don't have type "node-set" - instead, they're what is called a "result tree fragment". You can store that to a variable, and you can use that variable to insert the fragment into output (or another variable) later on, but you cannot use XPath to query over it. That's when you need either EXSLT node-set() function (which converts a result tree fragment to a node-set), or XSLT 2.0 (in which there are no result tree fragments, only sequences of nodes, regardless of where they come from).
For your example as given, this doesn't seem to be a problem. Rubens' answer gives the exact syntax.
Another note, if you want to be able to use the variable as part of an XPath statement, you need to select into the variable with <xsl:copy-of select="."/> instead of <xsl:value-of select="."/>
value-of will only take the text of the node and you wont be able to use the node-set function to return anything meaningful.
<xsl:variable name="myStringVar">
<xsl:value-of select="."/>
</xsl:variable>
<!-- This won't work: -->
<Output>
<xsl:value-of select="node-set($myStringVar)/SubNode" />
</Output>
<xsl:variable name="myNodeSetVar">
<xsl:copy-of select="."/>
</xsl:variable>
<!-- This will work: -->
<Output>
<xsl:value-of select="node-set($myNodeSetVar)/SubNode" />
</Output>