XQuery: Merge Nodes of Same Name - xquery

How would I take all element nodes of the same name, and combine them together into one that retains the child elements of each?
Example input:
<topic>
<title />
<language />
<more-info>
<itunes />
</more-info>
<more-info>
<imdb />
</more-info>
<more-info>
<netflix />
</more-info>
</topic>
Example output (all of the more-infos are collapsed into a single element):
<topic>
<title />
<language />
<more-info>
<itunes />
<imdb />
<netflix />
</more-info>
</topic>
Edit: I am looking for a way to do this without knowing which node names reoccur. So, with the example above, I could not use a script that only targeted more-info, as there may be other elements that also need to have the same process applied to them.

Use:
declare option saxon:output "omit-xml-declaration=yes";
<topic>
<title />
<language />
<more-info>
{for $inf in /*/more-info/node()
return $inf
}
</more-info>
</topic>
When this XQuery is applied on the provided XML document:
<topic>
<title />
<language />
<more-info>
<itunes />
</more-info>
<more-info>
<imdb />
</more-info>
<more-info>
<netflix />
</more-info>
</topic>
the wanted, correct result is produced:
<topic>
<title/>
<language/>
<more-info>
<itunes/>
<imdb/>
<netflix/>
</more-info>
</topic>

I came with that:
for $n in $nodes/node()
let $lname := local-name($n)
group by $lname
return element {$lname} {
$n/node()
}
Where $nodes contains the input document.
It uses a group by which will bind the $n variable to the list of grouped nodes.
So, the expression $n/node() represent a sequence of node.
To make it recursive, we have to declare a function and call it:
declare function local:recurse($node){
for $n in $node/text() return $n,
for $n in $node/element()
let $lname := local-name($n)
group by $lname
return element {$lname} {
for $m in $n return local:recurse($m)
}
};
local:recurse($nodes)
The first line ends with a comma. It's a list concatenation. So, we output text nodes first, then element nodes with the group by sheningan explained above.
XML Input
<topic>
<title>Test</title>
<language />
<more-info>
<itunes>
<playlist>
<item>2</item>
</playlist>
<playlist>
<item>3</item>
</playlist>
</itunes>
</more-info>
<more-info>
<imdb>Imdb info</imdb>
</more-info>
<more-info>
<netflix>Netflix info</netflix>
</more-info>
</topic>
XML Output
<title>Test</title>
<language/>
<more-info>
<itunes>
<playlist>
<item>2</item>
<item>3</item>
</playlist>
</itunes>
<imdb>Imdb info</imdb>
<netflix>Netflix info</netflix>
</more-info>
Remarks
I have no clues why XSLT is deemed easier. Maybe the apply-templates is masquerading the recursion, making it less intimidating.
Also, the fact that the matching is declared outside the "loop" make it easier (then, has to be paired with mode for full control) compared to XQuery which required it inside the "loop".
Whatever, in this peculiar example, XQuery seems to be very appropriate.

This seems like a better job for XSLT if you can use it.
XML Input
<topic>
<title />
<language />
<more-info>
<itunes />
</more-info>
<more-info>
<imdb />
</more-info>
<more-info>
<netflix />
</more-info>
</topic>
XSLT 2.0
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="*" group-by="name()">
<xsl:copy>
<xsl:apply-templates select="current-group()/#*"/>
<xsl:apply-templates select="current-group()/*"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML Output
<topic>
<title/>
<language/>
<more-info>
<itunes/>
<imdb/>
<netflix/>
</more-info>
</topic>

Related

How to replace attributes in XML transforms without the name attribute

I am using SlowCheetah to transform my Log4Net files when I publish. However, it can't seem to distinguish between the attributes in different appender sections.
My Log4Net.config looks basically like this:
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="DevEmail" />
<from value="DevEmail" />
<subject value="Dev Warning" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="Time: %date%newlineHost: %property{log4net:HostName}%newlineClass: %logger%newlineUser: %property{user}%newlineMessage: %message%newline%newline%newline" />
</layout>
<threshold value="WARN" />
</appender>
<appender name="FatalSmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="DevEmail" />
<from value="DevEmail" />
<subject value="Dev Fatal" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="Time: %date%newlineHost: %property{log4net:HostName}%newlineClass: %logger%newlineUser: %property{user}%newlineMessage: %message%newline%newline%newline" />
</layout>
<threshold value="FATAL" />
</appender>
</log4net>
And my transform file looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<log4net xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="ProductionEmail" xdt:Transform="SetAttributes" />
<from value="ProductionEmail" xdt:Transform="SetAttributes" />
<subject value="Production Warning" xdt:Transform="SetAttributes" />
</appender>
<appender name="FatalSmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="ProductionEmail" xdt:Transform="SetAttributes" />
<from value="ProductionEmail" xdt:Transform="SetAttributes" />
<subject value="Production Fatal" xdt:Transform="SetAttributes" />
</appender>
</log4net>
The problem is that the transformed config has the same subject attribute value for both appenders; I guess when it hits the SetAttributes it can't tell which tag it's looking for, so it transforms all of them. What it the correct syntax to tell it to only find the elements within the same appender? I assume I need to use the xdt:Locator attribute, but I can't do Match(name) like I do for web.config because these elements don't have a name attribute. The appender element has a name attribute, but I don't know how to tell it to match based on the parent element's name.
I know that I could use replace on the appender node, with the match(Name), but then I would be replacing the entire node, including a bunch of elements such as the layout which I don't want to be transformed (and thus have multiple copy-pastes of the same code, which I would like to avoid).
I found the answer in this MSDN article: http://msdn.microsoft.com/en-us/library/dd465326.aspx.
I needed to use xdt:Locator="Match(name)" on the parent <appender> node, and then xdt:Transform on the child nodes. I had tried this previously but had used xdt:locator="Match(name)" instead of xdt:Locator="Match(name)"... The attribute is case sensitive.

How to move a portlet into content's #content area?

I would like to modify the content on the fly so I can later feed the modified version into my theme's content slot. The usecase is positioning the calendar portlet inside a collective.cover row/column/cell.
Here's what I tried:
<replace css:content="#content .row:nth-child(2) .cell:nth-child(2) .tile.tile-edge">
<!-- These work (meaning levels above current selection CAN be copied) -->
<xsl:copy-of select="." />
<xsl:copy-of select="../.." />
<xsl:copy-of select="/" />
<!-- However, neither of these do -->
<xsl:copy-of css:select=".portletCalendar:first-child" />
<xsl:copy-of select="//div[contains(concat(' ', normalize-space(#class), ' '), ' portletCalendar ')]" />
<xsl:copy-of select="//div[#id='portal-personaltools']" />
</replace>
It may be that the only problem you were having was relying on Diazo's facility for translating css selectors in XSL commands. It only works if the target is the currently selected node or a child of it. So, replace it with an XPath selector:
<!-- replace one part of content with another -->
<replace css:content="#content .row:nth-child(2) .cell:nth-child(2) .tile.tile-edge">
<xsl:copy-of select="//dl[#class='portlet portletCalendar']" />
<xsl:apply-templates mode="raw" />
</replace>
<!-- make sure it doesn't show up in two places -->
<drop content="//dl[#class='portlet portletCalendar']" />

BizTalk Map, Prevent Looping For-Each

In the source XSD of the following map, InvTransTable and ToWarehouse elements are unbounded.
I want to map values only for the first iteration in each case. My problem is, the XSLT generated by the map contains two loops, as can be seen in the following:
<ns0:Record>
<xsl:for-each select="s1:InvTransTable">
<xsl:variable name="var:v4" select="position()" />
<xsl:for-each select="s1:ToWarehouse">
<xsl:variable name="var:v5" select="position()" />
<xsl:variable name="var:v6" select="userCSharp:LogicalEq(string($var:v5) , "1")" />
<xsl:variable name="var:v7" select="userCSharp:LogicalEq(string($var:v4) , "1")" />
<xsl:variable name="var:v8" select="userCSharp:LogicalAnd(string($var:v6) , string($var:v7))" />
<xsl:variable name="var:v12" select="userCSharp:StringUpperCase("B")" />
<xsl:variable name="var:v13" select="userCSharp:StringUpperCase("M")" />
<xsl:variable name="var:v14" select="userCSharp:StringUpperCase("4")" />
<ns0:Header>
<xsl:if test="string($var:v8)='true'">
<xsl:variable name="var:v9" select="s1:EXDRefCustAccount/text()" />
<ns0:AccountNo>
<xsl:value-of select="$var:v9" />
</ns0:AccountNo>
</xsl:if>
<xsl:if test="string($var:v7)='true'">
<xsl:variable name="var:v10" select="../s1:ShipDate/text()" />
<ns0:DateExpected>
<xsl:value-of select="$var:v10" />
</ns0:DateExpected>
</xsl:if>
This results in the created XML containing multiple header elements, which is not what I want!
I've tried to prevent this throug the use of Iteration (checking for a value of 1) and Conditional functoids but this hasn't worked. Can anyone please advise how I can achieve what is needed without resorting to a scripting functoid or replacing the map with XSLT?
If you know you only need to map the first instance of each, use the Index functoid with an index of 1, instead of the Iteration functoid.

xslt transform list and take max

I want to:
select all attributes "foo", which are string values, and store the values in a list.
transform each value of attribute "foo" in this list using some map in my xslt to a number.
select the max value of the list and output that.
So given the following xml:
<t>
<tag foo="A">apples</tag>
<tag foo="C">oranges</tag>
<tag foo="B">trees</tag>
</t>
And the following mapping:
<xsl:variable name="myMap">
<entry key="A">1</entry>
<entry key="B">2</entry>
<entry key="C">3</entry>
</xsl:variable>
The output would be:
<max>3</max>
Another question, why can't I indent my code? I'm putting spaces but it's not working.
I This standard XSLT 1.0 transformation (most resembling your approach):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
<entry key="X" value="8"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#foo">
<xsl:attribute name="foo">
<xsl:value-of select="$vMap[#key = current()]/#value"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (as you didn't provide any):
<t foo="X">
<a foo="A">
<b foo="B"/>
</a>
<c foo="C"/>
</t>
produces the wanted, correct result:
<t foo="8">
<a foo="1">
<b foo="2"/>
</a>
<c foo="3"/>
</t>
Explanation: Appropriate use of the XSLT current() function.
II. XSLT 1.0 solution using keys for speed
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kValFromKey" match="entry/#value" use="../#key"/>
<xsl:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
<entry key="X" value="8"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#foo">
<xsl:variable name="vCur" select="."/>
<xsl:attribute name="foo">
<xsl:for-each select="document('')">
<xsl:value-of select="key('kValFromKey', $vCur)"/>
</xsl:for-each>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced.
Explanation:
Use of <xsl:for-each select="document('')"> to set the current document to the stylesheet, so that the key() function will use the key index built for this document.
Saving the node matched by the template in a variable so that we can use it inside the xsl:for-each -- current() cannot be correctly used here, because it gets the current node on which xsl:for-each operates.
UPDATE: The OP has now clarified in a comment that his biggest problem is finding the maximum.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="/">
<xsl:variable name="vDoc" select="."/>
<xsl:variable name="vFoosMapped"
select="$vMap[#key = $vDoc/*/*/#foo]"/>
<max>
<xsl:value-of select=
"$vFoosMapped
[not($vFoosMapped/#value > #value)]
/#value
"/>
</max>
</xsl:template>
</xsl:stylesheet>
When given this XML document (the one provided by the OP lacks a singlr top element):
<t>
<tag foo="A">apples</tag>
<tag foo="C">oranges</tag>
<tag foo="B">trees</tag>
</t>
the wanted, correct result is produced:
<max>3</max>
Remark: A more efficient way of calculating maximum (or minimum -- in a similar way) in XSLT 1.0 is to do this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="/">
<xsl:variable name="vDoc" select="."/>
<xsl:variable name="vFoosMapped"
select="$vMap[#key = $vDoc/*/*/#foo]"/>
<max>
<xsl:for-each select="$vFoosMapped">
<xsl:sort select="#value" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="#value"/>
</xsl:if>
</xsl:for-each>
</max>
</xsl:template>
</xsl:stylesheet>
Another question, why can't I indent my code? I'm putting spaces but
it's not working.
This is a SO bug that they failed to fix for many months.
Most probably you are using IE. If your version is 9, then do the following:
Press F12.
In the window that pops up click on the right-most menu and select: "Document mode: IE9 Standards"
Now you should be able to see the code with indentation.

Record Count functoid returns aggregate count for non-flattend target message

I tried to use the Record Count functoid to map the number of sub-records of an record that itself occurs 0 to unbounded to a message with each record containing a field holding the number of sub-records:
root+ +root
| |
+foo+ +foo+
| |
+bar+ -RecordCount- barcount
|
+xyz
However my current map aggregates the count of all bar records and returns it in every foo\barcount.
Sample source message
<root>
<foo>
<Id>1</Id>
<bar>
<xyz />
</bar>
<bar>
<xyz />
</bar>
</foo>
<foo>
<Id>2</Id>
<bar>
<xyz />
</bar>
<bar>
<xyz />
</bar>
</foo>
</root>
... and the result is
<root>
<foo>
<Id>1</Id>
<barcount>4</barcount>
</foo>
<foo>
<Id>2</Id>
<barcount>4</barcount>
</foo>
</root>
... whereas I expected
<root>
<foo>
<Id>1</Id>
<barcount>2</barcount>
</foo>
<foo>
<Id>2</Id>
<barcount>2</barcount>
</foo>
</root>
I solved this issue by replacing the Record Count functoid with a Call XSLT Template Scripting functoid.
The XSLT template looks like this:
<xsl:template name="CountMyBar">
<xsl:param name="fooId" />
<xsl:element name="barcount">
<xsl:value-of select="count(//foo[Id=$fooId]/bar)" />
</xsl:element>
</xsl:template>
and the input to the scripting functoid is the Id field from foo.

Resources