Loopable custom functoid in BizTalk - biztalk

I've got the following custom functoid in two kinds.
First one:
public class MyClass : BaseFunctoid
{
public MyClass() : base()
{
// Settings for functoid
this.SetExternalFunctionName(this.GetType().Assembly.FullName, this.GetType().FullName, "MyMethod");
this.SetMinParams(2);
this.SetMaxParams(2);
this.AddInputConnectionType(ConnectionType.Element | ConnectionType.Field);
this.AddInputConnectionType(ConnectionType.Element | ConnectionType.Field | ConnectionType.FunctoidCount | ConnectionType.FunctoidMath | ConnectionType.FunctoidString);
this.OutputConnectionType = ConnectionType.Record | ConnectionType.Element | ConnectionType.Field | ConnectionType.FunctoidLooping;
this.HasSideEffects = false;
}
public string[] MyMethod(string arg1, int arg2)
{
// do something
return returnArray;
}
}
This creates the following xsl:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0" xmlns:ns0="urn:mySchema1/1.0.0" xmlns:s0="urn:mySchema2/1.0.0" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s0:Root" />
</xsl:template>
<xsl:template match="/s0:Root">
<ns0:DestinationRoot>
<ns0:sync>
<xsl:for-each select="s0:RepeatingNode">
<xsl:variable name="var:v1" select="position()" />
<ns0:statement>
<xsl:attribute name="sqlType">
<xsl:text>Update</xsl:text>
</xsl:attribute>
<xsl:attribute name="script">
<xsl:value-of select="$var:v1" />
</xsl:attribute>
</ns0:statement>
</xsl:for-each>
</ns0:sync>
</ns0:DestinationRoot>
</xsl:template>
<msxsl:script language="C#" implements-prefix="userCSharp"><![CDATA[
]]></msxsl:script>
</xsl:stylesheet>
Second one:
public class MyClass : BaseFunctoid
{
public MyClass() : base()
{
// Settings for functoid
this.AddScriptTypeSupport(ScriptType.CSharp);
this.SetScriptBuffer(ScriptType.CSharp, GetCSharpBuffer());
this.SetMinParams(2);
this.SetMaxParams(2);
this.AddInputConnectionType(ConnectionType.Element | ConnectionType.Field);
this.AddInputConnectionType(ConnectionType.Element | ConnectionType.Field | ConnectionType.FunctoidCount | ConnectionType.FunctoidMath | ConnectionType.FunctoidString);
this.OutputConnectionType = ConnectionType.Record | ConnectionType.Element | ConnectionType.Field | ConnectionType.FunctoidLooping;
this.HasSideEffects = false;
}
private string GetCSharpBuffer()
{
StringBuilder sb = new StringBuilder();
sb.Append("\n");
sb.Append("public string[] MyMethod(string arg1, int arg2)\n");
sb.Append("{\n");
sb.Append("\t// do something\n");
sb.Append("\treturn returnArray;\n");
sb.Append("}");
return sb.ToString();
}
public string[] MyMethod(string arg1, int arg2)
{
// do something
return returnArray;
}
}
creates the following xsl:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0" xmlns:ns0="urn:mySchema1/1.0.0" xmlns:s0="urn:mySchema2/1.0.0" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s0:Root" />
</xsl:template>
<xsl:template match="/s0:Root">
<ns0:DestinationRoot>
<ns0:sync>
<xsl:for-each select="s0:RepeatingNode">
<xsl:variable name="var:v1" select="position()" />
<ns0:statement>
<xsl:attribute name="sqlType">
<xsl:text>Update</xsl:text>
</xsl:attribute>
<xsl:attribute name="script">
<xsl:value-of select="$var:v1" />
</xsl:attribute>
</ns0:statement>
</xsl:for-each>
</ns0:sync>
</ns0:DestinationRoot>
</xsl:template>
<msxsl:script language="C#" implements-prefix="userCSharp"><![CDATA[
public string[] TestMethod(string input, int maxPerItem)
{
return returnArray;
}
]]></msxsl:script>
</xsl:stylesheet>
In both cases the following XML is created:
<ns0:DestinationRoot xmlns:ns0="urn:mySchema1/1.0.0">
<ns0:sync>
<ns0:statement sqlType="Update" script="1" />
</ns0:sync>
</ns0:DestinationRoot>
With XmlNodeList as return type I get an item for each node in this list. But only the counter is raising the number. The code will not be executed.
The Intention of this functoid is that I want to split a field from the input XML by certain conditions (not by a specified character) and for each item I need an element in the output XML with the value of this item. But in this configuration I get only a counter and the functoid function isn't call.
What have I to do that the functoid is loopable by the looping functoid and the code of the functoid is called.
Thanks in advance.

Related

How to show all XSL elements?

The XML file contains Accounts and a list of Account (contains ID and AccountDescription).
In the below example, there are 2 Account.
<?xml version="1.0"?>
<Accounts>
<Account>
<ID>5</ID>
<AccountDescription>Account Description 5</AccountDescription>
</Account>
<Account>
<ID>8</ID>
<AccountDescription>Account Description 8</AccountDescription>
</Account>
</Accounts>
When using the below XSL, it creates a PDF file with 2 pages, and each page has the header ID and AccountDescription, but there is no data/content underneath it, like this:
On page 1:
ID AccountDescription
On page 2:
ID AccountDescription
I would like to show the data like this:
ID AccountDescription
5 Account Description 5
8 Account Description 8
How can I do that ? Thank you.
This is my current XSL:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="Accounts">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master
master-name="main"
margin-top="0px"
margin-bottom="0px"
margin-left="18px"
margin-right="18px">
<fo:region-body margin-top="0.75in" margin-bottom="2in" margin-left="18px" margin-right="18px"/>
<fo:region-before extent="0.75in"/>
<fo:region-after extent="1.5in"/>
<fo:region-end extent="75px"/>
</fo:simple-page-master>
</fo:layout-master-set>
<xsl:apply-templates select="Account"/>
</fo:root>
</xsl:template>
<xsl:template match="Account">
<fo:page-sequence master-reference="main">
<fo:flow flow-name="xsl-region-body">
<fo:table font-size="10pt">
<fo:table-column column-width="15mm"/>
<fo:table-column column-width="55mm"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="ID"/></fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="AccountDescription"/></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:flow>
</fo:page-sequence>
</xsl:template>
</xsl:stylesheet>
How can I show all data in the same page ?
You need to use only one fo:page-sequence. Move it from the Account template up into the Accounts template.
Updated XSLT
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="Accounts">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master
master-name="main"
margin-top="0px"
margin-bottom="0px"
margin-left="18px"
margin-right="18px">
<fo:region-body margin-top="0.75in" margin-bottom="2in" margin-left="18px" margin-right="18px"/>
<fo:region-before extent="0.75in"/>
<fo:region-after extent="1.5in"/>
<fo:region-end extent="75px"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="main">
<fo:flow flow-name="xsl-region-body">
<xsl:apply-templates select="Account"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
<xsl:template match="Account">
<fo:table font-size="10pt">
<fo:table-column column-width="15mm"/>
<fo:table-column column-width="55mm"/>
<fo:table-body>
<fo:table-row>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="ID"/></fo:block>
</fo:table-cell>
<fo:table-cell >
<fo:block text-align="right"><xsl:value-of select="AccountDescription"/></fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</xsl:template>
</xsl:stylesheet>

BizTalk Mapper: Link between siblings

I have the following XML that I need to transform:
<?xml version="1.0" encoding="utf-8"?>
<TestRecords>
<TestData>
<Users>
<User>
<Id>BG123</Id>
<Name>Bill Gates</Name>
</User>
<User>
<Id>SN123</Id>
<Name>Satya Nadella</Name>
</User>
</Users>
<UserDetails>
<UserDetail>
<UserId>SN123</UserId>
<CompanyName>Microsoft Corp</CompanyName>
</UserDetail>
<UserDetail>
<UserId>
<UserId>BG123</UserId>
<CompanyName>Bill Gates Foundation</CompanyName>
</UserId>
</UserDetail>
</UserDetails>
I need to map this XML into the following XML:
<?xml version="1.0" encoding="utf-8"?>
<TestRecords>
<TestData>
<Users>
<User>
<Id>BG123</Id>
<Name>Bill Gates</Name>
<CompanyName>Bill Gates Foundation</CompanyName>
</User>
<User>
<Id>SN123</Id>
<Name>Satya Nadella</Name>
<CompanyName>Microsoft Corp</CompanyName>
</User>
</Users>
</TestData>
</TestRecords>
When I loop over Users/User, I need to find the UserDetail where UserDetail/UserId is equal to the current User/Id
Thank you and best regards
Michael
If you don't want to do Custom XSLT as suggested by FCR the only other option when you have different looping structures is to have an intermediate schema and two maps.
Which produces
<TestRecords>
<TestData>
<Users>
<User>
<Id>BG123</Id>
<Name>Bill Gates</Name>
<UserDetails>
<UserID>SN123</UserID>
<CompanyName>Microsoft Corp</CompanyName>
</UserDetails>
<UserDetails>
<UserID>BG123</UserID>
<CompanyName>Bill Gates Foundation</CompanyName>
</UserDetails>
</User>
<User>
<Id>SN123</Id>
<Name>Satya Nadella</Name>
<UserDetails>
<UserID>SN123</UserID>
<CompanyName>Microsoft Corp</CompanyName>
</UserDetails>
<UserDetails>
<UserID>BG123</UserID>
<CompanyName>Bill Gates Foundation</CompanyName>
</UserDetails>
</User>
</Users>
</TestData>
</TestRecords>
Which you can then run through this second map to produce the desired outcome.
This will become very inefficient however if the second list is large.
This is a common lookup pattern in xslt and there is also the opportunity to use xsl:key to create an index which can boost performance on large documents. Refer here if you need to convert a .btm to xslt.
(Also, I'm assuming that there isn't a double wrapper UserId at on the last UserDetails/UserDetail element):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="userLookup"
match="/TestRecords/TestData/UserDetails/UserDetail" use="UserId"/>
<!--identity template - copy everything by default -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!--i.e.match only Users in the first Users/User tree. Actually the explicit
ancestor qualifier is redundant because of the other suppress template -->
<xsl:template match="User[ancestor::Users]">
<User>
<xsl:copy-of select="child::*" />
<CompanyName>
<xsl:value-of select="key('userLookup', Id)/CompanyName"/>
</CompanyName>
</User>
</xsl:template>
<!--Suppress the second userdetails part of the tree entirely -->
<xsl:template match="UserDetails" />
</xsl:stylesheet>
Fiddle here

processing css stylesheet with xml document

I am new to XML (a couple of days now...)and everything is going good, however, I can't seem to get the xml processed with css style
here is my perl...
#!/usr/bin/perl -w
use strict;
use warnings;
use diagnostics;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1 });
my $ifile="elementarray.csv";
my $ofile="elementarray.xml";
open my $fh, "<", $ifile or die $ifile.": $!";
open my $out, "> ".$ofile or die "Cannot write ".$ofile.": $!";
print $out <<EOT;
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="elementarray.xsl"?>
<?xml-stylesheet href="elementarray.css" title="Default style"?>
<EMAILCOMMENTS>
EOT
# First record contains list of fieldnames
#my $fields = $csv->getline($fh);
#print #$fields;
while (my $row = $csv->getline($fh)) {
last unless $row && #$row;
# Encode "<" characters as "<" and "&" as "&" in all fields
foreach (#$row) {
s/&/&/g;
s/</</g;
}
# Create hash of fields using hash slice
my %row;
#row{"Subject","Body","From: (Name)","From: (Address)","To: (Name)","To:(Address)","Date","Time"} = #$row;
print $out <<EOT;
<EMAIL>
<HEADER>
<ORIGNAME>$row{"From: (Name)"}</ORIGNAME>
<ORIGADDRESS>$row{"From: (Address)"}</ORIGADDRESS>
<DESTNAME>$row{"To: (Name)"}</DESTNAME>
<DESTADDRESS>$row{"To: (Address)"}</DESTADDRESS>
<SUBJECT>$row{"Subject"}</SUBJECT>
</HEADER>
<BODY>$row{"Body"}</BODY>
<DATE id="date">$row{"Date"}</DATE>
<TIME id="time">$row{"Time"}</TIME>
</EMAIL>
EOT
}
print $out "</EMAILCOMMENTS>\n";
$csv->eof or $csv->error_diag;
close $fh or die $ifile.": $!";
close $out or die $ofile.": $!";
Here is my xsl...
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="html"/>
<xsl:template match="/"><!-- a 'pattern', / matches the root node -->
<html>
<head>
<title>E-mail</title>
</head>
<body>
<xsl:apply-templates/><!-- an instruction -->
</body>
</html>
</xsl:template>
<xsl:template match="HEADER">
<span STYLE="color:red; font-weight:bold; font-style:italic">
<xsl:value-of select="SUBJECT"/>
</span>
From: <xsl:call-template name="formatEmail">
<xsl:with-param name="address" select="ORIGADDRESS"/>
</xsl:call-template>
To: <xsl:call-template name="formatEmail">
<xsl:with-param name="address" select="DESTADDRESS"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="EMAIL">
<xsl:apply-templates select="HEADER"/>
<pre>
<xsl:value-of select="BODY"/>
</pre>
<div>
<span>
<xsl:attribute name="id"><xsl:value-of select="date"/></xsl:attribute>
<!-- --><xsl:value-of select="DATE"/><!-- --></span>
<span>
<xsl:attribute name="id"><xsl:value-of select="time"/></xsl:attribute>
<!-- --><xsl:value-of select="TIME"/><!-- --></span>
</div>
<br />
</xsl:template>
<xsl:template name="formatEmail">
<xsl:param name="address"/>
<a>
<xsl:attribute name="href"><xsl:value-of select="concat('mailto:',$address)"/></xsl:attribute>
<xsl:value-of select="$address"/>
</a>
</xsl:template>
</xsl:stylesheet>
here is my xml (1 record)...
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="elementarray.xsl"?>
<?xml-stylesheet href="elementarray.css" title="Default style"?>
<EMAILCOMMENTS>
<EMAIL>
<HEADER>
<ORIGNAME>William Holt</ORIGNAME>
<ORIGADDRESS><x#gmail.com></ORIGADDRESS>
<DESTNAME>X</DESTNAME>
<DESTADDRESS>bill#elementarray.com</DESTADDRESS>
<SUBJECT>welcome to the neighborhood</SUBJECT>
</HEADER>
<BODY>just thought i'd say hello</BODY>
<DATE id="date">07/18/2013</DATE>
<TIME id="time">11:53</TIME>
</EMAIL>
</EMAILCOMMENTS>
and here is css ( style for just to see if it is working, and it isn't :( )...
DATE{background-color:red;}
TIME{background-color:green;}
#date{background-color:yellow;}
#time{background-color:blue;}
BODY{background-color:grey;}
Sorry for the length of the post but I think you need all these files...
Although you can indeed style XML with CSS, using the xml-stylesheet processing instruction you have included in your XML, you can't transform it with XSLT at the same time. That is to say you should only really have one stylesheet processing instruction in your XML. Either you simply style your XML with CSS (which is not really a common thing to do), or your transform it to HTML with XSLT and style the HTML output with CSS.
In the latter case, remove the <?xml-stylesheet href="elementarray.css"?> instruction from your XML document, and instead output a reference to the CSS document in your first template.
<xsl:template match="/">
<html>
<head>
<title>E-mail</title>
<link href="elementarray.css" type="text/css" rel="stylesheet" />
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
Now, in your CSS, I see you already have selectors for the date and time ids, so you need to ensure the relevant elements in your HTML have these ids.
At the moment, you are doing this in your XSLT
<span>
<xsl:attribute name="id"><xsl:value-of select="date"/></xsl:attribute>
<!-- --><xsl:value-of select="DATE"/><!-- --></span>
<span>
But this is setting the id attribute to have the value of the date element in your XML, but such an element does not exist! I think you meaning to use the literal string here. Now, you could do this...
<xsl:attribute name="id"><xsl:value-of select="'date'"/></xsl:attribute>
But this is verbose. You can just do this:
<xsl:attribute name="id">date</xsl:attribute>
But better still, just write it out as an attribute in the normal way:
<span id="date">
<xsl:value-of select="DATE"/>
</span>
And similarly for 'time'. This should ensure the two span tags get styled using your CSS in the HTML you output.

XSL datetime, remove and condense timezone into time

I'm saving some timestamps in my XML results in standard UTC format.
What I'd like to be able to do is re-convert this to human readable times. Without the timezone addendum. As far as I've been able to get so far is:
format-dateTime(
xs:dateTime(
adjust-dateTime-to-timezone(
xs:dateTime(#thevalue),xs:dayTimeDuration('P0DT4H')
)
),'[M01]/[D01]/[Y0001] [H01]:[m01]:[s01]'
)
where #thevalue is like: 2006-02-15T17:00:00
It's giving me a headache because the formatter returns a time of 17:00. If I peel back a layer of the format-dateTime to see what the adjust-dateTime function returns, it gives
2006-02-15T17:00:00+04:00
... and all I really want to see is 21:00... so very frustrated. Anyone deal with this before?
Here is a transformation that does what you want:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDateTime" as="xs:dateTime"
select="xs:dateTime('2006-02-15T17:00:00+00:00')"/>
<xsl:template match="/">
<xsl:sequence select=
"adjust-dateTime-to-timezone($vDateTime,
xs:dayTimeDuration('P0DT4H')
)"/>
</xsl:template>
</xsl:stylesheet>
When applied to any XML document (not used), the result is:
2006-02-15T21:00:00+04:00
And the complete solution is:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDateTime" as="xs:dateTime"
select="xs:dateTime('2006-02-15T17:00:00+00:00')"/>
<xsl:template match="/">
<xsl:variable name="vadjustedDateTime" select=
"adjust-dateTime-to-timezone($vDateTime,
xs:dayTimeDuration('P0DT4H')
)"/>
<xsl:sequence select=
"format-dateTime($vadjustedDateTime,
'[M01]/[D01]/[Y0001] [H01]:[m01]:[s01]'
)
"/>
</xsl:template>
</xsl:stylesheet>
which produces this result:
02/15/2006 21:00:00

Select/Output unique values

I need to loop through an XML document (no problem over there) and check if a value that i find is already in a (a) tag in a div in my XSL document that i am generating, only if the value is not in that (a) tag i should create a new (a) tag for it and put in in the div that i am checking...
Any one knows how to do it dynamically in XSLT?
<div id="tags"><span class="l_cap"> </span>
all
<xsl:for-each select="root/nodes/node/data/genres">
<xsl:for-each select="value">
**<xsl:if test="not(contains())">**
<xsl:value-of select="current()"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
 
sorry for before, what i am trying to do is: in the if statement, check if the current value is already exist in the div if not, add it, if is, don't do anything...
10x again
It sounds like you're trying to create a distinct list of all of the "genres" in your list.
Assuming a data structure which looks a bit like this:
<root>
<nodes>
<node>
<data>
<genres>
<value>One</value>
<value>Two</value>
<value>Two</value>
<value>Three</value>
<value>Two</value>
</genres>
</data>
</node>
</nodes>
</root>
And a stylesheet which looks a bit like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="genres" match="value" use="."/>
<xsl:template match="/">
<div>
<xsl:for-each select="/root/nodes/node/data/genres/value">
<xsl:if test="generate-id(.) = generate-id(key('genres', .)[1])">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
Then you will end up with something like this:
<div>
One
Two
Three
</div>
This is a fairly standard XSLT 1.0 technique. It uses keys (described here: http://www.xml.com/pub/a/2002/02/06/key-lookups.html ) to create a sort of index of all the /root/nodes/node/data/genres/value entries. Then it loops through all of the entries, but only prints the first one of each type. The end result is that each value will only be output once.

Resources