summing nodes' values grouping by pattern of other nodes' values - regex

The set-up
I have an XML file with (this is simplified from actual):
<feeds xmlns...>
<feed>
<week>
<start-date>...</start-date>
<end-date>...</end-date>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/news/releases/2011-12-05/xyzzy"/>
<numeric name="bar" value="463284">
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/en-US"/>
<numeric name="bar" value="4332">
</entry>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/"/>
<numeric name="bar" value="23232">
</entry>
</week>
...
</feed>
...
</feeds>
Each week has many entrys; each entry has just two data elements, one with name="foo" and the other with name="path", and a single numeric element with name="bar" and value an integer. There can be partial-duplicate entrys, even within a week: entrys can have the same foo or the same path, but no two entrys within a week that have the same foo and the same path.
What I want
I'd like to separate my paths into categories. For example, I want all paths matching the regex /ISOcodes/ to be considered separately (as "ISOcodes", say) and all paths matching ^/news as a separate category ("news").
I'm trying to sum the value of bar across multiple entrys within a single week, grouping by foo and by type (as in previous paragraph) of path. That is, for each week, for each value of foo, for each category of path (as in the preceding paragraph), I want the sum() of the values of bar.
Is there a way to do this? How?

An XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="week">
<xsl:for-each-group select="entry"
group-by="concat(data[#name='foo']/#value, '-',
if (matches(data[#name='path']/#value, '/ISOcodes/'))
then 'ISOcodes'
else if (matches(data[#name='path']/#value, '^/news'))
then 'news'
else 'no_category')">
[<xsl:value-of select="current-grouping-key()"/>]
<xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
On the following input:
<feeds>
<feed>
<week>
<start-date>...</start-date>
<end-date>...</end-date>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/news/releases/2011-12-05/xyzzy"/>
<numeric name="bar" value="463284"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/test"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/en-US"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/japan"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/"/>
<numeric name="bar" value="23232"/>
</entry>
</week>
</feed>
</feeds>
Produces:
[bar-news]
463284
[baz-ISOcodes]
12996
[bar-no_category]
23232
Obviously, you'll need to format additional elements to taste, but this should demonstrate the grouping method.

XSLT 2.0 solution:
<xsl:template match="/">
<xsl:for-each select="//week">
<xsl:for-each-group select="entry" group-by="./data[#name = 'foo']/#value">
<xsl:for-each-group select="current-group()" group-by="data[#name = 'path']/#value">
<xsl:message>
<xsl:choose>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '/ISOcodes/')]">
Sum of ISO codes : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '^/news')]">
Sum of news : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:otherwise>
Sum of other categories : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:message>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each>
</xsl:template>
When applied to this .xml file :
<feeds>
<feed>
<week>
<start-date>...</start-date>
<end-date>...</end-date>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/news/releases/2011-12-05/xyzzy"/>
<numeric name="bar" value="463284"/>
</entry>
<entry>
<data name="foo" value="baz"/>
<data name="path" value="/pages/ISOcodes/en-US"/>
<numeric name="bar" value="4332"/>
</entry>
<entry>
<data name="foo" value="bar"/>
<data name="path" value="/"/>
<numeric name="bar" value="23232"/>
</entry>
</week>
...
</feed>
...
</feeds>
The output is:
[xslt] Sum of news : 463284
[xslt]
[xslt] Sum of other categories : 23232
[xslt]
[xslt] Sum of ISO codes : 4332
Edit:
I thought you wanted the sum? So my code printed the sum :)
<xsl:template match="/">
<xsl:for-each select="//week">
<xsl:for-each-group select="entry" group-by="./data[#name = 'foo']/#value">
<xsl:variable name="foo" select="current-grouping-key()"/>
<xsl:for-each-group select="current-group()" group-by="data[#name = 'path']/#value">
<xsl:message>
<xsl:choose>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '/ISOcodes/')]">
Sum of <xsl:value-of select="$foo"/>-<xsl:value-of select="current-grouping-key()"/> : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:when test="current-group()/data[#name = 'path' and matches(#value, '^/news')]">
Sum of <xsl:value-of select="$foo"/>-<xsl:value-of select="current-grouping-key()"/> : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:when>
<xsl:otherwise>
Sum of other categories : <xsl:value-of select="sum(current-group()/numeric/#value)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:message>
</xsl:for-each-group>
</xsl:for-each-group>
</xsl:for-each>
</xsl:template>
This prints all the bells and whistles too :)

Related

How to create an attribute node and attach it to output node..?

I'm not able to figure out a way to attach an attribute node to output node in below scenario..
input xml:
<record>
<user>
<field name="LastName">user33</field>
<field name="FirstName">user33</field>
<field name="Title"/>
<field name="Email">user33#gmail.com</field>
<field name="WorkPhone"/>
<field name="Fax"/>
<field name="Description">new user</field>
<field name="Group Member"> group1</field>
</user>
</record>
Expected output:
<add class="user" id-val="user33 user33" >
<add-value attr="LastName">
<value type="string">user33</value>
</add-attr>
<add-value attr="FirstName">
<value type="string">user33</value>
</add-value>
<add-value attr="Email">
<value type="string">user33#gamil.com</value>
</add-value>
<add-value attr="Description">
<value type="string">new user</value>
</add-value>
</add>
this is the snippet of xslt that i have so far.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:variable name="lessThan" select="'<'"/>
<xsl:variable name="GreaterThan" select="'>'"/>
<xsl:template match="user">
<xsl:variable name="temp1" select="concat(field[#name=$srcdn-field-name1],' ')"/>
<xsl:variable name="temp2" select="concat($temp1,field[#name=$srcdn-field-name2])"/>
<xsl:variable name="src" select="translate($temp2,'+=,.\','-----')"/>
<xsl:value-of disable-output-escaping="yes" select="$lessThan"/>
<xsl:text>add</xsl:text>
<xsl:value-of disable-output-escaping="yes" select="$GreaterThan"/>
<!-- it is required to add attribute id-val to element <add> with value of $src-->
<xsl:for-each select="field[string()]">
<xsl:variable name="fieldValue" select="normalize-space(.)"/>
<xsl:choose>
<xsl:when test="#name !='Group Member'">
<add-value attr="{#name}">
<value type="string">
<xsl:value-of select="$fieldValue"/>
</value>
</add-value>
</xsl:when>
<xsl:otherwise>
<xsl:value-of disable-output-escaping="yes" select="$lessThan"/>
<xsl:text>/add</xsl:text>
<xsl:value-of disable-output-escaping="yes" select="$GreaterThan"/>
<!--perform some other operations-->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Now my requirement is to have id-val and class as attributes of <add>.. In this context the <xsl:attribute> isn't working. What changes do i need to make to my xslt.?
You can't add an attribute to something that's not an element. That's one of the many reasons not to try to manually construct start and end tags. Try this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="user">
<xsl:variable name="temp1" select="concat(field[#name=$srcdn-field-name1],' ')"/>
<xsl:variable name="temp2" select="concat($temp1,field[#name=$srcdn-field-name2])"/>
<xsl:variable name="src" select="translate($temp2,'+=,.\','-----')"/>
<add class="user" id-val="{$src}">
<!-- it is required to add attribute id-val to element <add> with value of $src-->
<xsl:for-each select="field[string()]
[not((. | preceding-sibling::field)
[#name = 'Group Member'])]">
<add-value attr="{#name}">
<value type="string">
<xsl:value-of select="normalize-space()"/>
</value>
</add-value>
</xsl:for-each>
</add>
</xsl:template>
</xsl:stylesheet>

XSL conditional formatting

I am using xsl to transform xml to kml format. I would like to add conditional logic to the xsl to switch the styleUrl based on part of an attribute value. The attribute name is FROM_SYSTEM_ID. The format of the attribute value is "A-123-CAM-1" where "CAM" is part of the string to determine which style definition to use (in this case CAM stands for Camera, CAB stands for Cabinet, etc).
How can I parse this attribute to perform the needed style definition switch?
Following is my xsl template:
<xsl:template match="Line">
<Folder>
<name>
Lines
<!--<xsl:value-of select="#name"/>-->
</name>
<xsl:for-each select="Row">
<Placemark>
<name>
<xsl:value-of select="#FROM_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#TO_SYSTEM_ID"/>
</description>
<styleUrl>#msn_open-diamond00</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>
Following is a sample of the XML:
<Line>
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAB-1"
TO_SYSTEM_ID="A-123-CAM-3" FromLat="42.624948852000"
FromLong="-83.107221652500"
ToLat="42.624940325900" ToLong="-83.107353167000" />
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAM-1"
TO_SYSTEM_ID="A-123-HH-16" FromLat="42.641662528600"
FromLong="-83.151500129600"
ToLat="42.641709802200" ToLong="-83.151552587600" />
<!-- additional rows here -->
</Line>
You can extract the CAM or CAB portion of the FROM_SYSTEM_ID attribute using a combination of substring-after and substring-before:
<xsl:value-of select="
substring-before(
substring-after(
substring-after(#FROM_SYSTEM_ID, '-'), '-'), '-')"/>
Putting this together with your stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Line">
<Folder>
<name>
Lines
<!--<xsl:value-of select="#name"/>-->
</name>
<xsl:for-each select="Row">
<Placemark>
<name>
<xsl:value-of select="#FROM_SYSTEM_ID"/>
</name>
<description>
<xsl:value-of select="#TO_SYSTEM_ID"/>
</description>
<styleUrl>
<xsl:value-of select="
substring-before(
substring-after(
substring-after(#FROM_SYSTEM_ID, '-'), '-'), '-')"/>
</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>
<xsl:value-of select="#FromLong"/>,<xsl:value-of select="#FromLat"/>,0 <xsl:value-of select="#ToLong"/>,<xsl:value-of select="#ToLat"/>,0
</coordinates>
</LineString>
</Placemark>
</xsl:for-each>
</Folder>
</xsl:template>
</xsl:stylesheet>
Applied to this input:
<Line>
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAB-1"
TO_SYSTEM_ID="A-123-CAM-3" FromLat="42.624948852000"
FromLong="-83.107221652500"
ToLat="42.624940325900" ToLong="-83.107353167000" />
<Row PrimaryRoute="A-123" FROM_SYSTEM_ID="A-123-CAM-1"
TO_SYSTEM_ID="A-123-HH-16" FromLat="42.641662528600"
FromLong="-83.151500129600"
ToLat="42.641709802200" ToLong="-83.151552587600" />
</Line>
Produces the following result:
<Folder>
<name>Lines</name>
<Placemark>
<name>A-123-CAB-1</name>
<description>A-123-CAM-3</description>
<styleUrl>CAB</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.107221652500,42.624948852000,0
-83.107353167000,42.624940325900,0
</coordinates>
</LineString>
</Placemark>
<Placemark>
<name>A-123-CAM-1</name>
<description>A-123-HH-16</description>
<styleUrl>CAM</styleUrl>
<LineString>
<tessellate>1</tessellate>
<coordinates>-83.151500129600,42.641662528600,0
-83.151552587600,42.641709802200,0
</coordinates>
</LineString>
</Placemark>
</Folder>

xsl to group nodes between other nodes?

I can't figure out how create xsl to group some nodes between other nodes. Basically, everytime I see a 'SPLIT' I have to end the div and create a new one.
The xml looks like this:
<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />
The output needs to look like this
<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>
I know how to do this by 'cheating', but would like to know if there is a proper way to do it:
<div>
<xsl:for-each select="data">
<xsl:choose>
<xsl:when test="#name='SPLIT'">
<xsl:text disable-output-escaping="yes"> </div> <div></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#name"/>
</xsl:otherwise>
</xsl:for-each>
</div>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:apply-templates
select="node()[1]|following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="data">
<div>
<xsl:call-template name="open"/>
</div>
<xsl:apply-templates
select="following-sibling::data[#name='SPLIT'][1]
/following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="data" mode="open" name="open">
<xsl:value-of select="concat(#name,'
')"/>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="open"/>
</xsl:template>
<xsl:template match="data[#name='SPLIT']" mode="open"/>
</xsl:stylesheet>
Output:
<div>
a
b
c
</div>
<div>
d
e
</div>
<div>
f
g
h
</div>
Note: Fine grained traversal.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kFollowing" match="data[not(#name='SPLIT')]"
use="generate-id(preceding-sibling::data[#name='SPLIT'][1])"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="data[#name='SPLIT']" name="regularSplit">
<div>
<xsl:apply-templates mode="copy"
select="key('kFollowing', generate-id())"/>
</div>
</xsl:template>
<xsl:template match="data[#name='SPLIT'][
not(preceding-sibling::data[#name='SPLIT'])]">
<div>
<xsl:apply-templates mode="copy"
select="preceding-sibling::data"/>
</div>
<xsl:call-template name="regularSplit"/>
</xsl:template>
<xsl:template match="*" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="data[not(#name='SPLIT')]"/>
</xsl:stylesheet>
when applied on the provided XML document (wrapped into a top element to become well-formed):
<t>
<data name="a" />
<data name="b" />
<data name="c" />
<data name="SPLIT" />
<data name="d" />
<data name="e" />
<data name="SPLIT" />
<data name="f" />
<data name="g" />
<data name="h" />
</t>
produces the wanted, correct result:
<t>
<div>
<data name="a"></data>
<data name="b"></data>
<data name="c"></data>
</div>
<div>
<data name="d"></data>
<data name="e"></data>
</div>
<div>
<data name="f"></data>
<data name="g"></data>
<data name="h"></data>
</div>
</t>
Do note: Keys are used to specify conveniently and verry efficiently all "non-SPLIT" elements that follow immediately a "SPLIT" element.

Using xsl param (if exists) to replcae attribute value

I would like an xsl that replaces the value attribute of the data elements only if the relevant param names are passed (they are passed from the calling java program).
Input
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="localhost"/>
<data name="PORT" value="8080"/>
<data name="SIZE" value="1000"/>
</applicationVariables>
So for example if passing in a param HOST1=myHost and PORT=9080 the output should be:
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="myHost"/>
<data name="PORT" value="9080"/>
<data name="SIZE" value="1000"/>
</applicationVariables>
Note that HOST and PORT where replaced but SIZE was not replaced because there was no parameter with name SIZE
I don't want a hardcoded check for each name, as below:
<xsl:when test="not($HOST)"> <!-- parameter has not been supplied -->
<xsl:attribute name="value"><xsl:value-of select="#value"/></xsl:attribute>
</xsl:when>
<xsl:otherwise> <!--parameter has been supplied -->
<xsl:attribute name="value"><xsl:value-of select="$HOST"/></xsl:attribute>
</xsl:otherwise>
I want a generic way of saying: replace the value attribute only if a param with the same name exists.
But how do i check if a param with name = #name exists?
In such case it is much better to pass all params as elements of a single <xsl:param>:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pParams">
<p name="HOST">myHost</p>
<p name="PORT">9080</p>
</xsl:param>
<xsl:variable name="vParams" select=
"document('')/*/xsl:param[#name='pParams']/*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#value">
<xsl:attribute name="value">
<xsl:value-of select=
"$vParams[#name=current()/../#name]
|
current()[not($vParams[#name=current()/../#name])]
"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="localhost"/>
<data name="PORT" value="8080"/>
<data name="SIZE" value="1000"/>
</applicationVariables>
the wanted, correct result is produced:
<applicationVariables applicationServer="tomcat">
<data name="HOST" value="myHost"></data>
<data name="PORT" value="9080"></data>
<data name="SIZE" value="1000"></data>
</applicationVariables>
You can combine logical conditions with xsl:if.

Grouping problem in XSLT

I need to group the following XML doc to show:
Parent Item Qty
----------------------------------
TopLevelAsy 1
SubAsy Part15 4
Top Assembly Part19 2
Top Assembly Part15 2
Top Assembly SubAsy 2
But what I get using my XSL is:
Parent Item Qty
----------------------------------
TopLevelAsy 1
SubAsy Part15 2
SubAsy Part15 2
Top Assembly Part19 2
Top Assembly Part15 2
Top Assembly SubAsy 2
Her is my XML:
<DOCUMENT>
<ProductRevision id="id41" name="Top Assembly" accessRefs="#id30" subType="ItemRevision" masterRef="#id47" revision="A"></ProductRevision>
<ProductRevision id="id15" name="PartA-15" accessRefs="#id30" subType="ItemRevision" masterRef="#id36" revision="A"></ProductRevision>
<ProductRevision id="id19" name="PartB-19" accessRefs="#id30" subType="ItemRevision" masterRef="#id46" revision="A"></ProductRevision>
<ProductRevision id="id48" name="SubAsy" accessRefs="#id30" subType="ItemRevision" masterRef="#id76" revision="A"></ProductRevision>
<ProductView id="id4" ruleRefs="#id2" rootRefs="id7" primaryOccurrenceRef="id7">
<Occurrence id="id7" instancedRef="#id41" occurrenceRefs="id15 id11 id17 id16 id18 id21">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/"></ApplicationRef>
<data>
<title>TopLevelAsy</title>
<year>1985</year>
</data>
</Occurrence>
<Occurrence id="id11" instancedRef="#id19" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/yBEAAAV4xLJc5D/"></ApplicationRef>
<data>
<title>Part19</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id15" instancedRef="#id15" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/sdljfjdkLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id17" instancedRef="#id19" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/yBEAAAV4xLJc5D/"></ApplicationRef>
<data>
<title>Part19</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id16" instancedRef="#id15" parentRef="#id7">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/sdljfjdkLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<!-- sub assembly Second occurrence -->
<Occurrence id="id21" instancedRef="#id48" parentRef="#id7" occurrenceRefs="id153 id135">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/"></ApplicationRef>
<data>
<title>Sub Assembly</title>
<year>1985</year>
</data>
</Occurrence>
<Occurrence id="id153" instancedRef="#id15" parentRef="#id21">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/jkdsdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id135" instancedRef="#id15" parentRef="#id21">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/jkdsdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<!-- sub assembly first occurrence -->
<Occurrence id="id18" instancedRef="#id48" parentRef="#id7" occurrenceRefs="id53 id35">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/"></ApplicationRef>
<data>
<title>Sub Assembly</title>
<year>1985</year>
</data>
</Occurrence>
<Occurrence id="id53" instancedRef="#id15" parentRef="#id18">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/vdsfdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
<Occurrence id="id35" instancedRef="#id15" parentRef="#id18">
<ApplicationRef application="CAD" label="i9BAAAV4xLJc5D/wesdjdLJc5D/vdsfdwV4xLJc5D/"></ApplicationRef>
<data>
<title>Part15</title>
<year>1988</year>
</data>
</Occurrence>
</ProductView>
</DOCUMENT>
The XSLT i have written is
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no" />
<!-- <xsl:key name="byref" match="Occurrence" use="#instancedRef"/> -->
<xsl:key name="byid" match="Occurrence" use="concat(#title,#instancedRef)" />
<xsl:key name="byRef" match="Occurrence" use="#instancedRef" />
<xsl:template match="/">
<table border="1">
<!-- generate the keys for instance occurance-->
<!-- generate the keys for parent id -->
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[generate-id(.)=generate-id(key('byid', concat(#instancedRef,#title))[1])]">
<xsl:sort select="#parentRef" />
<xsl:variable name="pRef" select="#parentRef" />
<xsl:variable name="instRef" select="#instancedRef" />
<xsl:variable name="pdOccId" select="substring-after($pRef,'#')" />
<xsl:variable name="pdRevIdTag" select="//DOCUMENT/ProductView/Occurrence[#id=$pdOccId]/#instancedRef" />
<xsl:variable name="pdRevId" select="substring-after($pdRevIdTag,'#')" />
<xsl:variable name="parentlabeltag" select="ApplicationRef/#label" />
<tr>
<td>
<xsl:text>Parent: </xsl:text>
<xsl:value-of select="//DOCUMENT/ProductRevision[#id=$pdRevId]/#name" />
</td>
<td align="right">
<xsl:value-of select="data/title" />
<xsl:text> </xsl:text>
</td>
<td>
<xsl:value-of select="count(key('byid', concat(#instancedRef,#title)))" />
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Any help will be great. Please help me figure this out. Thank you.
George,
Here's the solution:
You need to use 2 xslts for the same. The first xslt will generate an easily parseable xml. The second xslt will use this xml as its input to achieve the desired result.
Xslt1:
Your xml will be the input to this xslt
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="bytitle" match="Occurrence" use="data/title"/>
<xsl:template match="/">
<root>
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[not(#parentRef)]">
<xsl:element name="Data">
<xsl:attribute name="parentTitle" />
<xsl:attribute name="childTitle">
<xsl:value-of select="data/title"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[generate-id(.)=generate-id(key('bytitle', data/title)[1])]">
<xsl:sort select="#id"/>
<xsl:variable name="title" select="data/title" />
<xsl:variable name="driver" select="//DOCUMENT/ProductView/Occurrence[data/title = $title]/#id"/>
<xsl:for-each select="//DOCUMENT/ProductView/Occurrence[substring-after(#parentRef,'#') = $driver/.]">
<xsl:element name="Data">
<xsl:attribute name="parentTitle">
<xsl:value-of select="$title"/>
</xsl:attribute>
<xsl:attribute name="childTitle">
<xsl:value-of select="data/title"/>
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Output of this xslt:
<?xml version="1.0" encoding="utf-8"?>
<root>
<Data parentTitle="" childTitle="TopLevelAsy" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="Sub Assembly" childTitle="Part15" />
<Data parentTitle="TopLevelAsy" childTitle="Part19" />
<Data parentTitle="TopLevelAsy" childTitle="Part15" />
<Data parentTitle="TopLevelAsy" childTitle="Part19" />
<Data parentTitle="TopLevelAsy" childTitle="Part15" />
<Data parentTitle="TopLevelAsy" childTitle="Sub Assembly" />
<Data parentTitle="TopLevelAsy" childTitle="Sub Assembly" />
</root>
Xslt2:
The output of xslt 1 should be the input to this xslt
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:key name="bytitle" match="Data" use="concat(#parentTitle, #childTitle)"/>
<xsl:template match="/">
<table border="1">
<xsl:for-each select="/root/Data[generate-id(.)=generate-id(key('bytitle', concat(#parentTitle, #childTitle))[1])]">
<xsl:sort select="#parentTitle"/>
<xsl:variable name="parentTitle" select="#parentTitle"/>
<xsl:variable name="childTitle" select="#childTitle"/>
<tr>
<td>
<xsl:value-of select="#parentTitle"/>
</td>
<td>
<xsl:value-of select="#childTitle"/>
</td>
<td>
<xsl:value-of select="count(//root/Data[#parentTitle=$parentTitle and #childTitle = $childTitle])"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Output:
TopLevelAsy 1
Sub Assembly Part15 4
TopLevelAsy Part19 2
TopLevelAsy Part15 2
TopLevelAsy Sub Assembly 2
George, you are getting 2 rows for sub assembly as there are two elements for sub-assembly with different ids (id21 and id18) and out of the four Part15s, two belong to id21 and two belong to id18.
Since you are grouping according to the parent id, your output seems correct. If you want to have both the sub assembly elements grouped, then you need to specify the parent title rather than parent id in your concatenated key.
I just tweaked your xslt to see the grouping. This is how they are grouped. As you can see the Parent Ref for the 2nd and 3rd rows are different
Instance Ref:#id41 Parent Ref: Parent: TopLevelAsy 1
Instance Ref:#id15 Parent Ref:#id18 Parent: SubAsy Part15 2
Instance Ref:#id15 Parent Ref:#id21 Parent: SubAsy Part15 2
Instance Ref:#id19 Parent Ref:#id7 Parent: Top Assembly Part19 2
Instance Ref:#id15 Parent Ref:#id7 Parent: Top Assembly Part15 2
Instance Ref:#id48 Parent Ref:#id7 Parent: Top Assembly Sub Assembly 2
Edit
This is not a complete solution but you might have to do something on these lines:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no"/>
<!-- <xsl:key name="byref" match="Occurrence" use="#instancedRef"/> -->
<xsl:key name="bytitle" match="Occurrence" use="data/title"/>
<xsl:template match="/">
<table border="1">
<xsl:for-each select="DOCUMENT/ProductView/Occurrence[generate-id(.)=generate-id(key('bytitle', data/title)[1])]">
<xsl:sort select="#id"/>
<xsl:variable name="title" select="data/title" />
<br />
Title:<xsl:value-of select="$title"/>
<xsl:variable name="driver" select="//DOCUMENT/ProductView/Occurrence[data/title = $title]/#id"/>
Driver:<xsl:value-of select="$driver/."/>
<!--<xsl:for-each select ="$driver">
<xsl:value-of select="."/>
</xsl:for-each>-->
<xsl:for-each select="//DOCUMENT/ProductView/Occurrence[substring-after(#parentRef,'#') = $driver/.]">
Child:<xsl:value-of select="data/title"/>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>