BizTalk Mapping Single and Multiple elements - xslt-1.0

I am trying to transform via the map a single birth name node and multiple surname nodes into a repeating other surname nodes. I'm running into some difficulties that when the birth name node is not present then multiple surname nodes fail to be written.
I've attempted multiple implementations around functoids and xslt call template neither appear to be working, as soon as the birth name is missing no surname elements are output.
Can this be done in functoids from the map ? or does this have to be done via a xslt call template?
Schema Input
<root>
<Subject>
<birthname>
<name>Birthname</name>
</birthname>
<multiplesurname>
<name>surname</name>
</multiplesurname>
<multiplesurname>
<name>surname2</name>
</multiplesurname>
<multiplesurname>
<name>surname3</name>
</multiplesurname>
</Subject>
<Mother></Mother>
<Farther></Farther>
<Other></Other>
</root>
Schema Output
<root>
<persona>
<Othername>Birthname</Othername>
<Othername>surname</Othername>
<Othername>surname2</Othername>
<Othername>surname3</Othername>
</persona>
<personb></personb>
</root>

I think your problems may be caused by having a name node and then an descendant node also named name. This might be causing an infinite loop for you. Here is some XSLT code that will get the job done for you.
<xsl:template match="name">
<xsl:copy>
<xsl:apply-templates select=".//name" mode="secondName"/>
</xsl:copy>
</xsl:template>
<xsl:template match="name" mode="secondName">
<xsl:element name="Othername">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- Identity. -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>

You could probably do this with a table looper, but a XSLT call template for it shouldn't be too bad - something like this should work for you:
<xsl:template name="nameFlattener">
<xsl:param name="birthname"/>
<xsl:element name="Othername">
<xsl:value-of select="$birthname"/>
</xsl:element>
<xsl:for-each select="//multiplesurname">
<xsl:element name="Othername">
<xsl:value-of select="name"/>
</xsl:element>
</xsl:for-each>
</xsl:template>
Have your name node from birthname going into that template as the first parameter, and output it to the Othername repeating node on the destination.

After running into further difficulties with XSLT call-template I found that a solution using functoids was possible and achieved using a combination of looping functoids from the birthname and multiplesurname along with straight link from the source node to the destination node.

Related

I need all the values in my xml to be increased by 1 using xslt

In my xml I want all the values to be increased by 1.
So in the xml below I have only 2 times value and I want value 8 to be 9 and value 5 to be 6.
But this should work for all possible values.
This is my xml:
<?xml version="1.0" encoding="UTF-8"?><equipmentActualProperties>
<equipmentActualProperty>
<action>U</action>
<dataType>Text</dataType>
<equipmentActualPropertyID>CUST_0002</equipmentActualPropertyID>
<equipmentActualPropertyValueTexts>
<equipmentActualPropertyValueText>
<languageID>EN</languageID>
<value>8</value>
</equipmentActualPropertyValueText>
</equipmentActualPropertyValueTexts>
<equipmentPropertyID>CUST_0002</equipmentPropertyID>
<equipmentUse>Other</equipmentUse>
<hierarchyScope>default</hierarchyScope>
<requiredByRequestedSegmentResponse>Other</requiredByRequestedSegmentResponse>
</equipmentActualProperty>
<equipmentActualProperty>
<action>U</action>
<dataType>Text</dataType>
<equipmentActualPropertyID>CUST_0001</equipmentActualPropertyID>
<equipmentActualPropertyValueTexts>
<equipmentActualPropertyValueText>
<languageID>EN</languageID>
<value>5</value>
</equipmentActualPropertyValueText>
</equipmentActualPropertyValueTexts>
<equipmentPropertyID>CUST_0001</equipmentPropertyID>
<equipmentUse>Other</equipmentUse>
<hierarchyScope>default</hierarchyScope>
<requiredByRequestedSegmentResponse>Other</requiredByRequestedSegmentResponse>
</equipmentActualProperty>
You can use the identity template and push programming to do this. Also, for this to work, your snippet needs to be within a root node.
<!-- Identity template. -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="value">
<xsl:copy>
<xsl:value-of select=". + 1"/>
</xsl:copy>
</xsl:template>

xslt replace value of multiple elements

I need to replace values of multiple elements of an input xml. The input file has around 300 elements of which I need to replace around 100 elements. I have used identity template before, but I think it would require me to write 100 different templates each for replacing a single element value. I am not very good at xslts, so, am I thinking it right, or is there a better an elegant approach? Please advice.
Edit
Here is the Link of sample input xml.
The output will have almost the same structure, but different values for some of the elements.
Well, something I did similar before before so I am happy to share ... modified to your example BUT does not do the value mapping, it does name mapping.
First create a simple mapping file like this (NOTE: you would need the namespaces to do this right
<env:map xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<elem old="CardCode" new="CodeCardNEW"/>
<elem old="CardName" new="IAMNew"/>
</env:map>
Then one template will do you with a lookup. There are likely better ways to do the mapping, I am just for-each looping over them all ... a key would be better. But this gets you there.
Output from your file with above:
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<GetByKeyResponse>
<BOM>
<BO>
<AdmInfo>
<Object>oBusinessPartners</Object>
</AdmInfo>
<BusinessPartners>
<row>
<CodeCardNEW>CR43WEB</CodeCardNEW>
<IAMNew>Zack Burns</IAMNew>
<CardType>cCustomer</CardType>
...
And here's the XSL:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://www.w3.org/2003/05/soap-envelope"
version="1.0">
<xsl:param name="map" select="document('map.xml')/env:map"></xsl:param>
<xsl:template match="text()" priority="1">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:variable name="newname">
<xsl:call-template name="namesub">
<xsl:with-param name="name" select="name()"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="{$newname}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template name="namesub">
<xsl:param name="name"/>
<xsl:variable name="newname">
<xsl:for-each select="$map/elem">
<xsl:choose>
<xsl:when test="#old = $name">
<xsl:value-of select="#new"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($newname) > 0">
<xsl:value-of select="$newname"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$name"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You should be able to adapt this as long as you have a match on name ... if the name is the same in different hierarchies of the XML, you would need more work.

how to display content that appears once outside the repeating nodes in the expression of every node in xslt

I've got data in XML that has some header information then a series of items. I'm using XSLT to translate that into a different format, also with a header area and a series of items.
However in the post translation result, I want one piece of data only found in the header to be included in every instance of the items, even though it will simply be repeating the same value. (this value may change so I cannot hard code it)
Sample data (significantly simplified)
<rss>
<channel>
<title>Playlist One</title>
<items>
<item>
<title>Video One</title>
</item>
<item>
<title>Video Two</title>
</item>
<item>
<title>Video Three</title>
</item>
</items>
</channel>
</rss>
My desired result is something like this:
playlist_header_title=Playlist One
playlist_title=Playlist One
video_title=Video One
playlist_title=Playlist One
video_title=Video Two
playlist_title=Playlist One
video_title=Video Three
My XSLT is very complicated (and unfortunately I inherited it from someone else so I'm not sure what everything does, I've self-taught myself online but am in a bit over my head)
Roughly the key pieces look like this:
<xsl:template name="rss" match="/">
<xsl:variable name="playlist_title">
<xsl:value-of select="string(/rss/channel/title)"/>
</xsl:variable>
<xsl:for-each select="rss">
<xsl:apply-templates name="item" select="channel/items/item"/>
</xsl:for-each>
</xsl:template>
Then there's a massive template called "item" that I won't include here, but basically it outputs all the item data as desired, I just can't figure out how to access the "playlist_title".
When I try to call (from inside the template "item")
<xsl:value-of select="string($playlist_title)"/>
it returns a blank. I assume this is because that variable was created outside the for-each loop and so it not available. (it will display the data correctly when I output it before the for-each loop in the result's version of the header, but that doesn't get me far enough)
I've tried using with-param in the apply-templates, and also tried changing it to call-template inside another loop also using with-param, but they also display a blank.
I've also tried sending in a string rather than pulling the playlist_title from the XML just to confirm I am able to pass any value into the template, but they too come out blank.
For instance:
<xsl:for-each select="channel/items/item">
<xsl:call-template name="item">
<xsl:with-param name="playlist_title">blah</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
<xsl:template name="item">
<xsl:param name="playlist_title" />
playlist_title=<xsl:value-of select="string($playlist_title)"/>
video_title=...
...
</xsl:template>
This did not return the value "blah" but just a blank. (my hope was to then replace "blah" with the playlist_title value pulled from the XML, but none of it is getting through)
I'm stumped! Thanks for any help.
In your first example, attempting to reference the $playlist_title inside of your template for item is failing because the variable does not exist inside of that template. It was created inside of the template match for rss and is locally scoped.
If you want to be able to use the $playlist_title in other templates, you need to declare it outside of the rss template in the "global scope".
For instance:
<xsl:variable name="playlist_title">
<xsl:value-of select="string(/rss/channel/title)"/>
</xsl:variable>
<xsl:template name="rss" match="/">
<xsl:for-each select="rss">
<xsl:apply-templates name="item" select="channel/items/item"/>
</xsl:for-each>
</xsl:template>
As for your second attempt, referencing the xsl:param playlist_title, it worked for me. Double-check to ensure that you don't have a type-o or other difference between the name of the parameter that was declared and the name of the parameter that you are referencing.
You might try something like this to achieve your desired output, using an xsl:variable:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:variable name="playlist_title" select="/rss/channel/title" />
<xsl:template match="/">
<xsl:apply-templates select="rss/channel"/>
</xsl:template>
<xsl:template match="channel">
<xsl:text>playlist_header_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="items/item"/>
</xsl:template>
<xsl:template match="item">
<xsl:text>playlist_title=</xsl:text>
<xsl:value-of select="$playlist_title"/>
<xsl:text>
</xsl:text>
<xsl:text>video_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Or you can eliminate the xsl:variable and use an xsl:param:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="rss/channel"/>
</xsl:template>
<xsl:template match="channel">
<xsl:text>playlist_header_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="items/item">
<xsl:with-param name="playlist_title" select="title"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="item">
<xsl:param name="playlist_title" />
<xsl:text>playlist_title=</xsl:text>
<xsl:value-of select="$playlist_title"/>
<xsl:text>
</xsl:text>
<xsl:text>video_title=</xsl:text>
<xsl:value-of select="title"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

How can I sort file sequence in WiX?

AFAIK file sequence in File table(in MSI file) has effect on the time needed for un/installation: if file entries that have the same target directory are placed in sequence in File table the installer takes less time doing un/installation than when those files are scattered in the File table.
But, it seems that my installer built from my wix project is in the latter case. It has its file entries that have the same target directory scattered in the File table. Is there any way to sort these file entries so that they are placed in sequence in the File table? (My .wxs file was generated by heat.exe)
Thank you.
Heat tool provides the ability to run a custom XSLT transformation at the end of the heating. This is a very powerful feature, because it gives you a way to do anything you want with the result. For example, the following XSL will change file IDs to a concatenation of all parent folders + file name.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
xmlns:my="my:my">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match='wix:File'>
<xsl:variable name='fileId'>
<xsl:value-of select="/wix:Wix/wix:Fragment/wix:DirectoryRef/#Id"/>_<xsl:value-of select="translate(substring-after(#Source, '\'), '\- ', '_')"/>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="Id">
<xsl:value-of select="$fileId"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match='wix:Directory'>
<xsl:variable name='parentPath'>
<xsl:for-each select='ancestor::wix:Directory/#Name'>
<xsl:value-of select="concat(.,'_')"/>
</xsl:for-each>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="Id">
<xsl:value-of select="/wix:Wix/wix:Fragment/wix:DirectoryRef/#Id"/>_<xsl:value-of select="translate($parentPath, '- ', '__')"/><xsl:value-of select="translate(#Name, '- ', '__')"/>
</xsl:attribute>
<xsl:apply-templates select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This approach does not quarantee uniqueness of IDs but works pretty well for me.
There is nothing built into the WiX toolset to sort in a particular way. However, if you have empirical evidence that shows a better sorting algorithm should be used then that would be a good thing to send to the wix-devs#lists.sourceforge.net mailing list. The WiX toolset should do the best optimization automatically.
HACK: You can try sorting your File/#Id alphabetically to control the order in current releases of the WiX toolset. This may or may not work in all versions of the WiX toolset.

Recursive transformations using xslt, xpath:document() and mediawiki

I want to use the Wikipedia API to find the French pages including the ''SQLTemplate:Infobox Scientifique'' missing in the English version. So, my idea was to process the following document with xproc:
http://fr.wikipedia.org/w/api.php?action=query&format=xml&list=embeddedin&eititle=Template:Infobox%20Scientifique&eilimit=400
and the following xslt stylesheet:
<?xml version='1.0' ?>
<xsl:stylesheet
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
version='1.0'
>
<xsl:output method='text' indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="api"/>
</xsl:template>
<xsl:template match="api">
<xsl:for-each select="query/embeddedin/ei">
<xsl:variable name="title" select="translate(#title,&apos; &apos;,&apos;_&apos;)"/>
<xsl:variable name="english-title">
<xsl:call-template name="englishTitle"><xsl:with-param name="title" select="#title"/></xsl:call-template>
</xsl:variable>
<xsl:value-of select="$english-title"/><xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="englishTitle">
<xsl:param name="title"/>
<xsl:variable name="uri1" select="concat(&apos;http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles=&apos;,translate($title,&apos; &apos;,&apos;_&apos;))"/>
<xsl:message><xsl:value-of select="$uri1"/></xsl:message>
<xsl:message>count=<xsl:value-of select="count(document($uri1,/api/query/pages/page/langlinks/ll))"/></xsl:message>
</xsl:template>
</xsl:stylesheet>
The XSLT extract all the articles containing the Template and for each article I wanted to call Wikipedia to get the links between the wikis. Here the template englishTitle calls the xpath function document().
But it always says that count(ll)=1 whereas there are plenty nodes. (e.g. http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles=Carl_Sagan ).
Can't I process the nodes returned by the document() function?
You should try:
<xsl:value-of select="count(document($uri1)/api/query/pages/page/langlinks/ll)"/>
On a different note - what is
translate(#title,&apos; &apos;,&apos;_&apos;)
supposed to mean? What's wrong with:
translate(#title, ' ', '_')
There is no need to encode single quotes in XML attributes unless you want to use a type of quote that delimits the attribute value. All of these are valid:
name="foo"'foo"
name='foo&apos;"foo'
Your entire transformation can be reduced to something like this:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:param name="baseUrl" select="'http://fr.wikipedia.org/w/api.php?action=query&format=xml&prop=langlinks&lllimit=500&titles='" />
<xsl:template match="ei">
<xsl:variable name="uri" select="concat($baseUrl ,translate(#title,' ','_'))"/>
<xsl:variable name="doc" select="document($uri)"/>
<xsl:value-of select="$uri"/>
<xsl:text>
</xsl:text>
<xsl:text>count=</xsl:text>
<xsl:value-of select="count($doc/api/query/pages/page/langlinks/ll)"/>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
Let the XSLT default templates work for you - they do all of the recursion in the background, all you have to do is catch the nodes you want to process (and prevent output of unnecessary text by overriding the default text() template with an empty one).