Unable to split and merge in XSLT - xslt

I have an Xml
<input Inputxml="&ltOrder..<LinePPlineNO="1#quot;Line/> >" >
How do i remove some part of string using xsl .. for eg I need to remove a whole string from < to > for a PPline 1
I need to splie the string in 3 parts remove the string from lt to gt and merge the part 1na dpart 3 of string
<Test Attrib1="b" Attrib2="C" Inputxml="
<OrderLine OrderedQty="1" PrimeLineNo="1" ShipNode="ABC" $gt;
</OrderLine $gt;
<OrderLine OrderedQty="1" PrimeLineNo="2" ShipNode="ABC" $gt;
</OrderLine $gt;" />
For example I may have 100 Order lines but I need to find the one with Prime line 1 and remove it .. So if I have to remeove a line I have to remove from lt; to gt;

Your example is confusing. If you have an XML input such as:
<input Inputxml="<order><Line PPlineNO="1">Bingo</Line></order>"/>
where the Inputxml attribute holds the escaped XML:
<order><Line PPlineNO="1">Bingo</Line></order>
you can use:
<xsl:template match="input">
<result>
<xsl:value-of select="substring-before(substring-after(#Inputxml, 'PPlineNO="1">'), '</Line>')" />
</result>
</xsl:template>
to get:
<result>Bingo</result>
Note that is not a good way to parse XML (or rather what used to be XML). It would be much smarter to unescape it first, then parse it as XML. In XSLT 3.0, you can use the parse-xml() function for this. In XSLT 1.0/2.0, you can do:
<xsl:value-of select="#Inputxml" disable-output-escaping="yes"/>
save the result to a file, and process the resulting file using another XSLT stylesheet.

Related

In XSLT nested foreach loop always return first element

XLST receiving the date from apache-camel in below formate.
data format
<list>
<linked-hash-map>
<entry key="NAME">test1</entry>
</linked-hash-map>
<linked-hash-map>
<entry key="NAME">test2</entry>
</linked-hash-map>
</list>
My XSLT:
<xsl:stylesheet>
<xsl:template match="*">
<xsl:for-each select="//*[local-name()='linked-hash-map']">
<tag1>
<xsl:value-of select="string(//*[local-name()='entry'][#key='NAME'])"/>
</tag1t>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT always returns the first element.
<tag1>test1<tag1>
<tag1>test1<tag1>
What is wrong in above xslt and help generate xml with all elements.
Because path expressions starting with "//" select from the root of the document tree, you are selecting the same nodes every time in your xsl:value-of; and in XSLT 1.0, if you select multiple nodes, only the first one gets displayed.
Methinks you're using "//" because you've seen it in example code and don't actually understand what it means...
Within xsl:for-each, you normally want a relative path that selects from the node currently being processed by the for-each.
You've also probably picked up this *[local-name()='linked-hash-map'] habit from other people's code. With no namespaces involved, you can safely replace it with linked-hash-map.

How can you apply concat(...) in a value-of directive in case of multiple nodes?

I am outputting the name node of each property node in a ; delimited string as following:
<xsl:value-of select="properties/property/name" separator=";" />
I want to alter this such that each element is prefixed with _. An example output should be:
_alpha;_beta;_gamma
I tried the following:
<xsl:value-of select="concat('_', properties/property/name)" separator=";" />
I want to use this to create an output node containing that string:
<my_node>
<xsl:value-of select="concat('_', properties/property/name)" separator=";" />
</my_node>
This gives an error when there are multiple properties:
XPTY0004: A sequence of more than one item is not allowed
as the second argument of fn:concat() (<name>, <name>)
Is there a way to get this working in XSLT 2.0/3.0?
I could resort to the XSLT 1.0 for-each solution as given in https://stackoverflow.com/a/57856287/12042211 (in which we are manually adding the separator), but I am wondering if something elegant in XSLT 2.0/3.0 is possible.
The answer is yes. XSLT 2.0 allows you to write expressions like this...
<xsl:value-of select="properties/property/concat('_', name)" separator=";" />
So, for each property it selects the concatenation of "_" with the name element.
Such syntax is not valid in XSLT 1.0 though.
In XSLT 3.0 I would tend to write this as
<xsl:value-of select="properties/property ! ('_' || name)" separator=";" />
and perhaps use string-join() instead of xsl:value-of. You haven't shown the context, but try to use xsl:value-of only when you really want a text node, not when you just want a string.

XSLT to pass value with XML tag

I need to get the value of a node with an xml format but all i'm getting is the values inside those tags.
Body:
<input>
<first>one</first>
<second>two</second>
<third>three</third>
</input>
XQuery
<PayloadAsMessage>
<xsl:value-of select="/input"/>
</PayloadAsMessage>
Expected output:
<PayloadAsMessage>
<first>one</first>
<second>two</second>
<third>three</third>
</PayloadAsMessage>
What i'm getting:
<PayloadAsMessage>
onetwothree
</PayloadAsMessage>
xsl:value-of takes the string value of an element (it's also XSLT, not XQuery). To copy the XML exactly, use either xsl:copy-of (XSLT 1) or xsl:sequence (XSLT2).
<xsl:copy-of select="/input/*"/>
or
<xsl:sequence select="/input/*"/>

Manage flags in xslt?

All,
i am searching in a list of fields those who has the type clob and i am writing it separed by a comma like this [field1, field2, ... fieldn]
my problem is how to identify the first matched field to write it without comma ( i can't use position() because the first field matched can be the first of the list or the last of the list)
I want to make this algorithm in xslt,
variable is_first = TRUE;
if(is_first) {
do smthng;
isfirst = False;
}
Actually it is not possible to make something like this in xslt since variable are immutable. There probably could be workarounds but you have to specify your need in more details.
edit:
If your input is string with values separated by commas...
<xsl:variable name="inputString" select="'field1,field2,field3a,field4,field3b'" />
... you could use tokenize() functions...
<xsl:variable name="tokenized" select="tokenize($inputString, ',')" />
... and then select items corresponding to your condition
<!-- Select item corresponding to condition (e.g. it contains 3). Take first one if there are several such items -->
<xsl:value-of select="$tokenized[contains(., '3')][1]" />
Edit2:
You can use separator attribute of xsl:value-of (xslt 2.0) for output of delimited values.
Assuming following variable
<xsl:variable name="list">
<item>first</item>
<item>second</item>
<item>third</item>
</xsl:variable>
this <xsl:value-of select="$list/item" separator="," /> makes desired output first,second,third
You need to write this using functional code rather than procedural code. It's not possible to do the conversion without seeing the context (it's much easier to work from the problem rather than from the solution in a lower-level language).
But the most common equivalent in XSLT would take the form
<xsl:for-each select=".....">
<xsl:if test="position() = 1"><!-- first time code --></xsl:if>
....
</xsl:for-each>

Biztalk explode in XSLT transformations

I'm receiving XML into BizTalk. One part is element and the value is ids separated by comma
<Stores>15,34</Stores>
I need to transform this into
<Stores>
<Store>Store 1</Store>
<Store>Store 4</Store>
</Stores>
What I need to do is to explode the value by comma, take each value and get value from database (15 -> Store 1, 34 -> Store 2).
How can I make the explode in xslt, how ca I get value from database for each exploded value. I already have procedure in db for that, just need to know how to call it.
Here is an XSLT 1.0 compatible solution that does the explode:
<!-- straightforward -->
<xsl:template match="Stores">
<xsl:copy>
<xsl:call-template name="explode">
<xsl:with-param name="str" select="text()" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<!-- string processing through recursion -->
<xsl:template name="explode">
<xsl:param name="str" select="''" />
<xsl:variable name="temp" select="concat($str, ',')" />
<xsl:variable name="head" select="substring-before($temp, ',')" />
<xsl:variable name="tail" select="substring-after($temp, ',')" />
<xsl:if test="$head != ''">
<Store>
<xsl:value-of select="$head" />
</Store>
<xsl:call-template name="explode">
<xsl:with-param name="str" select="$tail" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Output for <Stores>15,34</Stores> would be:
<Stores>
<Store>Store 15</Store>
<Store>Store 34</Store>
</Stores>
David Hall's solution seems to contain a pointer how to use an XSLT extension function to make calls to that database from XSLT.
The BizTalk Mapper does not support XSLT 2.0 (see MSDN Documentation http://msdn.microsoft.com/en-us/library/aa559261(BTS.10).aspx) so you will need to use the EXSLT extensions if you want to use the mapper.
There is a great post here by Richard Hallgren that covers how to use EXSLT within the BizTalk Mapper.
One additional thought is about an alternative solution. It is not clear if you absolutely must make your database calls one by one - would making a single call work?
It is possible to provide a stored procedure a delimited string as a parameter and to then use a function to break this string up. I've included an example of such a function below, the example being a table function. You'll be able find lots of other implementations on the web.
With the table function you can join against this in you store lookup procedure.
If this meets your needs it should be a lot faster since you now perform just a single database hit and can perform set operations to get back your list of stores.
CREATE function fn_ParseCSVString
(
#INPUTCSV varchar(MAX)
)
RETURNS #TBL TABLE
(
COL1 INT
)
AS
BEGIN
DECLARE #NUM_STR NVARCHAR(MAX)
SET #NUM_STR = #INPUTCSV
SET #NUM_STR = REPLACE(#NUM_STR,' ','')
-- this will trim any intermediate spaces
WHILE LEN(#NUM_STR) >= 0
BEGIN
DECLARE ##SUBSTR VARCHAR(100)
IF CHARINDEX(',',#NUM_STR,0) <> 0
BEGIN
SET ##SUBSTR = SUBSTRING(#NUM_STR,0,CHARINDEX(',',#NUM_STR,0))
INSERT INTO #TBL VALUES(##SUBSTR)
END
ELSE
BEGIN
INSERT INTO #TBL VALUES(#NUM_STR)
BREAK
END
SET ##SUBSTR = ##SUBSTR + ','
SET #NUM_STR = SUBSTRING(#NUM_STR, CHARINDEX(',',#NUM_STR,0) + 1, LEN(#NUM_STR))
END
RETURN
END
I assume you know how to write the overall transform but need help with the tokenization of the string containing the store numbers.
If you're using XSLT 2.0, look at the definition of the tokenize() function. This will split the string value at a specified delimiter, allowing you to perform this transformation. In XSLT 1 you could look at EXSLT regex extension functions.