Fairly advance XSLT question.

B

Bostonasian

I have following original xml :
<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

Then I wrote following xslt :

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*" use="name()"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="//*"/>
</EventCalendar>
</xsl:template>

<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select="key('DateKey',@TransactionDate)">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

To return following :
<EventCalendar>
<DateKey date="1/1/2007">
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/2/2007">
<criteria name="BodyFat"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/3/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
<DateKey date="1/4/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
</EventCalendar>

But the problem is that I need to return unique value of "name"
attribute in "criteria" element. Thefore, for <DateKey
date="1/1/2007">, I want to return <criteria name="BloodPressure"/>
once, instead of three times.
What am I missing?
Thanks bunch
 
D

Dimitre Novatchev

You need to read and understand more about grouping. In this case you have
nested grouping and have to use concatenation of key values in the second
xsl:key

The following transformation produces the wanted result:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*"
use="concat(@TransactionDate, '+', name())"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="/*"/>
</EventCalendar>
</xsl:template>
<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select=
"key('DateKey',@TransactionDate)
[generate-id()
=
generate-id(key('NameKey',
concat(@TransactionDate, '+', name())
)[1])
]
">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

when applied on the originally provided xml document:

<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

the result is:

<EventCalendar>
<DateKey Date="1/1/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="HeartRate"/>
</DateKey>
<DateKey Date="1/3/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="BodyFat"/>
</DateKey>
<DateKey Date="1/4/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="BodyFat"/>
</DateKey>
<DateKey Date="1/2/2007">
<Criteria Name="BodyFat"/>
<Criteria Name="HeartRate"/>
</DateKey>
</EventCalendar>


Hope this helped.

Cheers,
Dimitre Novatchev



Bostonasian said:
I have following original xml :
<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

Then I wrote following xslt :

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*" use="name()"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="//*"/>
</EventCalendar>
</xsl:template>

<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select="key('DateKey',@TransactionDate)">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

To return following :
<EventCalendar>
<DateKey date="1/1/2007">
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/2/2007">
<criteria name="BodyFat"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/3/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
<DateKey date="1/4/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
</EventCalendar>

But the problem is that I need to return unique value of "name"
attribute in "criteria" element. Thefore, for <DateKey
date="1/1/2007">, I want to return <criteria name="BloodPressure"/>
once, instead of three times.
What am I missing?
Thanks bunch
 
M

Myron Turner

Dimitre said:
You need to read and understand more about grouping. In this case you have
nested grouping and have to use concatenation of key values in the second
xsl:key

The following transformation produces the wanted result:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*"
use="concat(@TransactionDate, '+', name())"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="/*"/>
</EventCalendar>
</xsl:template>
<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select=
"key('DateKey',@TransactionDate)
[generate-id()
=
generate-id(key('NameKey',
concat(@TransactionDate, '+', name())
)[1])
]
">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

when applied on the originally provided xml document:

<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

the result is:

<EventCalendar>
<DateKey Date="1/1/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="HeartRate"/>
</DateKey>
<DateKey Date="1/3/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="BodyFat"/>
</DateKey>
<DateKey Date="1/4/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="BodyFat"/>
</DateKey>
<DateKey Date="1/2/2007">
<Criteria Name="BodyFat"/>
<Criteria Name="HeartRate"/>
</DateKey>
</EventCalendar>


Hope this helped.

Cheers,
Dimitre Novatchev



Bostonasian said:
I have following original xml :
<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

Then I wrote following xslt :

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*" use="name()"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="//*"/>
</EventCalendar>
</xsl:template>

<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select="key('DateKey',@TransactionDate)">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

To return following :
<EventCalendar>
<DateKey date="1/1/2007">
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/2/2007">
<criteria name="BodyFat"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/3/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
<DateKey date="1/4/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
</EventCalendar>

But the problem is that I need to return unique value of "name"
attribute in "criteria" element. Thefore, for <DateKey
date="1/1/2007">, I want to return <criteria name="BloodPressure"/>
once, instead of three times.
What am I missing?
Thanks bunch
If you use PHP, you can download XML_PullParser from
http://www.mturner.org/XML_PullParser/
and use a script such as this:

<?php
require_once "XML_PullParser/XML_PullParser.inc";
$doc = <<<DOCUMENT
<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>
DOCUMENT;

XML_PullParser_caseSensitive(TRUE);
$tags = array("HealthHistory");
$child_tags = array();

$parser = new XML_PullParser_doc($doc,$tags,$child_tags);

$parser->XML_PullParser_getToken();
$attrs = $parser->XML_PullParser_setAttrLoop();

while($attr_loop = $parser->XML_PullParser_nextAttr()) {
// $attr_loop[0] is name of element
// $attr_loop[1] is array of atttributes for element
$date = $attr_loop[1]['TransactionDate'];
$output[$date][$attr_loop[0]] = true;
}


$output = array();

echo "<EventCalendar>\n";

foreach($output as $DateKey=>$criteria) {
echo " <DateKey date = \"$DateKey\">\n";
foreach(array_keys($criteria) as $name) {
echo " <criteria name = \"$name\" />\n";
}
echo " </DateKey>\n";
}

echo "</EventCalendar>\n";

?>

--

_____________________
Myron Turner
http://www.room535.org
http://www.bstatzero.org
http://www.mturner.org/XML_PullParser/
 
B

Bostonasian

Thank you very much

Myron said:
Dimitre said:
You need to read and understand more about grouping. In this case you have
nested grouping and have to use concatenation of key values in the second
xsl:key

The following transformation produces the wanted result:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*"
use="concat(@TransactionDate, '+', name())"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="/*"/>
</EventCalendar>
</xsl:template>
<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select=
"key('DateKey',@TransactionDate)
[generate-id()
=
generate-id(key('NameKey',
concat(@TransactionDate, '+', name())
)[1])
]
">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

when applied on the originally provided xml document:

<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

the result is:

<EventCalendar>
<DateKey Date="1/1/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="HeartRate"/>
</DateKey>
<DateKey Date="1/3/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="BodyFat"/>
</DateKey>
<DateKey Date="1/4/2007">
<Criteria Name="BloodPressure"/>
<Criteria Name="BodyFat"/>
</DateKey>
<DateKey Date="1/2/2007">
<Criteria Name="BodyFat"/>
<Criteria Name="HeartRate"/>
</DateKey>
</EventCalendar>


Hope this helped.

Cheers,
Dimitre Novatchev



Bostonasian said:
I have following original xml :
<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>

Then I wrote following xslt :

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes"/>
<xsl:key name="DateKey" match="*" use="@TransactionDate"/>
<xsl:key name="NameKey" match="*" use="name()"/>
<xsl:template match="/">
<EventCalendar>
<xsl:apply-templates select="//*"/>
</EventCalendar>
</xsl:template>

<xsl:template match="*">
<xsl:for-each
select="*[generate-id()=generate-id(key('DateKey',@TransactionDate)[1])]">
<DateKey>
<xsl:attribute name="Date">
<xsl:value-of select="@TransactionDate"/>
</xsl:attribute>
<xsl:for-each select="key('DateKey',@TransactionDate)">
<Criteria>
<xsl:attribute name="Name">
<xsl:value-of select="name()"/>
</xsl:attribute>
</Criteria>
</xsl:for-each>
</DateKey>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

To return following :
<EventCalendar>
<DateKey date="1/1/2007">
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="BloodPressure"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/2/2007">
<criteria name="BodyFat"/>
<criteria name="HeartRate"/>
</DateKey>
<DateKey date="1/3/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
<DateKey date="1/4/2007">
<criteria name="BloodPressure"/>
<criteria name="BodyFat"/>
</DateKey>
</EventCalendar>

But the problem is that I need to return unique value of "name"
attribute in "criteria" element. Thefore, for <DateKey
date="1/1/2007">, I want to return <criteria name="BloodPressure"/>
once, instead of three times.
What am I missing?
Thanks bunch
If you use PHP, you can download XML_PullParser from
http://www.mturner.org/XML_PullParser/
and use a script such as this:

<?php
require_once "XML_PullParser/XML_PullParser.inc";
$doc = <<<DOCUMENT
<HealthHistory>
<BloodPressure low="80" high="120" TransactionDate="1/1/2007"/>
<BloodPressure low="90" high="140" TransactionDate="1/1/2007"/>
<BloodPressure low="70" high="110" TransactionDate="1/1/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/3/2007"/>
<BloodPressure low="80" high="120" TransactionDate="1/4/2007"/>
<BodyFat value="22" TransactionDate="1/2/2007"/>
<BodyFat value="22" TransactionDate="1/3/2007"/>
<BodyFat value="22" TransactionDate="1/4/2007"/>
<HeartRate value="87" TransactionDate="1/1/2007"/>
<HeartRate value="87" TransactionDate="1/2/2007"/>
</HealthHistory>
DOCUMENT;

XML_PullParser_caseSensitive(TRUE);
$tags = array("HealthHistory");
$child_tags = array();

$parser = new XML_PullParser_doc($doc,$tags,$child_tags);

$parser->XML_PullParser_getToken();
$attrs = $parser->XML_PullParser_setAttrLoop();

while($attr_loop = $parser->XML_PullParser_nextAttr()) {
// $attr_loop[0] is name of element
// $attr_loop[1] is array of atttributes for element
$date = $attr_loop[1]['TransactionDate'];
$output[$date][$attr_loop[0]] = true;
}


$output = array();

echo "<EventCalendar>\n";

foreach($output as $DateKey=>$criteria) {
echo " <DateKey date = \"$DateKey\">\n";
foreach(array_keys($criteria) as $name) {
echo " <criteria name = \"$name\" />\n";
}
echo " </DateKey>\n";
}

echo "</EventCalendar>\n";

?>

--

_____________________
Myron Turner
http://www.room535.org
http://www.bstatzero.org
http://www.mturner.org/XML_PullParser/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,708
Latest member
SherleneF1

Latest Threads

Top