Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

distinct-values work-around for Xpath 1.0

5,227 views
Skip to first unread message

Barnum

unread,
Aug 27, 2009, 2:58:20 AM8/27/09
to
Hi,

I'm using Xpath 1.0, so I can't use 'distinct-values'. Is there a work-
around? Alternative implementation?

Thanks!

Martin Honnen

unread,
Aug 27, 2009, 6:47:29 AM8/27/09
to
Barnum wrote:

> I'm using Xpath 1.0, so I can't use 'distinct-values'. Is there a work-
> around? Alternative implementation?

With the XML being e.g.

<root>
<foo>a</foo>
<foo>b</foo>
<foo>a</foo>
<foo>c</foo>
<foo>c</foo>
<foo>d</foo>
</root>

the XPath /root/foo[not(. = preceding-sibling::foo)] selects only
distinct 'foo' elements e.g.

<foo>a</foo>
<foo>b</foo>
<foo>c</foo>
<foo>d</foo>

If you use XPath 1.0 inside XSLT 1.0 then you can also use Muenchian
grouping to group and process only the first item in each group.


--

Martin Honnen --- MVP XML
http://msmvps.com/blogs/martin_honnen/

Barnum

unread,
Aug 27, 2009, 7:25:35 AM8/27/09
to

Thanks for your reply!

For me, this does not work because the nodes are in a variable, that I
selected from another node-set. And my <foo> nodes do not have a
common parent.
I did something like this: <xsl:variable name="fie" select="root/foo
[..some predicate here....].
Maybe I can build a new node set with a root node with the _selected_
foos beneath? And then do the preceding-sibling trick?

The Muenchian approach is also difficult (impossible?) to apply,
because xsl:key is a top-level node only.

Again, thanks very much for your help!

Martin Honnen

unread,
Aug 27, 2009, 7:39:53 AM8/27/09
to
Barnum wrote:

> For me, this does not work because the nodes are in a variable, that I
> selected from another node-set. And my <foo> nodes do not have a
> common parent.
> I did something like this: <xsl:variable name="fie" select="root/foo
> [..some predicate here....].
> Maybe I can build a new node set with a root node with the _selected_
> foos beneath? And then do the preceding-sibling trick?

You will need to show the relevant XML and explain which nodes you are
interested in, then we can hopefully sort it out.

> The Muenchian approach is also difficult (impossible?) to apply,
> because xsl:key is a top-level node only.

xsk:key is a top level element but you can certainly specify a match
pattern for nodes at any level. For instance
<xsl:key name="k1" match="foo[your predicate here]" match="."/>
is certainly possible to define the key only for 'foo' elements
fulfilling your predicate.

Barnum

unread,
Aug 27, 2009, 8:10:34 AM8/27/09
to

Ok, here is what I have:

This is the XML input:
<root>
<foo @name="1">a</foo>
<foo @name="1">b</foo>
<foo @name="1">a</foo>
<foo @name="2">c</foo>
<foo @name="2">c</foo>
<foo @name="2">d</foo>
</root>

And here is what I would have done with distinct-values:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" version="1.0">

<!-- root template: -->
<xsl:template match="/root">
<xsl:call-template name="fum">
<xsl:with-param name="in" select="."/>
</xsl:call-template>
</xsl:template>

<!-- sub-template: -->
<xsl:template name="fum">
<xsl:param name="in"/>
<!-- this is the input -->
<xsl:variable name="fie" select="$in/foo[@name='1']" />
<xsl:variable name="fum" select="distinct-values($fie)" />
</xsl:template>

</xsl:stylesheet>

I expect to get only these nodes in fum:
<foo @name="1">a</foo>
<foo @name="1">b</foo>

(As a by-note, xsl:key can't be in the xsl:template......)

Thanks very much!

Martin Honnen

unread,
Aug 27, 2009, 8:23:58 AM8/27/09
to
Barnum wrote:

> This is the XML input:
> <root>
> <foo @name="1">a</foo>
> <foo @name="1">b</foo>
> <foo @name="1">a</foo>
> <foo @name="2">c</foo>
> <foo @name="2">c</foo>
> <foo @name="2">d</foo>
> </root>

I guess

<root>
<foo name="1">a</foo>
<foo name="1">b</foo>
<foo name="1">a</foo>
<foo name="2">c</foo>
<foo name="2">c</foo>
<foo name="2">d</foo>
</root>

is more what you have.

> I expect to get only these nodes in fum:
> <foo @name="1">a</foo>
> <foo @name="1">b</foo>

You simply need to apply the predicate @name = '1' e.g.

/root/foo[@name = '1'][not(. = preceding-sibling::foo[@name = '1'])]

or in that template

<xsl:variable name="fie" select="$in/foo[@name='1']" />

<xsl:variable name="fum" select="$fie[not(. =
preceding-sibling::foo[@name = '1'])]" />

Defining a key is also easy:
<xsl:key name="k1" match="foo[@name = '1']" use="."/>
then you can use Muenchian grouping with that key.

Barnum

unread,
Aug 27, 2009, 9:21:51 AM8/27/09
to

Thanks, that worked!
The thing I missed was to use the predicate [@name = '1'] also after
the preceding-sibling::foo.

Thanks very much, this saved my day!

Barnum

unread,
Sep 2, 2009, 10:27:26 AM9/2/09
to
> Thanks very much, this saved my day!– Skjul sitert tekst –
>
> – Vis sitert tekst –

One follow-up, if I would want the distinct attributes (in this
example distinct @names) as opposed to elements, how would the
preceding-sibling predicate look?

Thanks!

Martin Honnen

unread,
Sep 2, 2009, 12:11:03 PM9/2/09
to
Barnum wrote:

> One follow-up, if I would want the distinct attributes (in this
> example distinct @names) as opposed to elements, how would the
> preceding-sibling predicate look?

/root/foo/@name[not(. = ../preceding-sibling::foo/@name)]

Barnum

unread,
Sep 3, 2009, 5:30:08 AM9/3/09
to

Of course, thanks!

Barnum

unread,
Sep 22, 2009, 6:56:32 AM9/22/09
to
Hmmm.... I've been thinking about the fact that I need to use the
predicate also in the preceding-sibling clause.
This must mean that my first select: <xsl:variable name="fie"

select="$in/foo[@name='1']" />
gives a node set where the nodes still have the original sibling
information.

So, a select only gives a selection from a node set, and the original
node set information is still there?

Thanks for any insight.

Martin Honnen

unread,
Sep 22, 2009, 7:09:41 AM9/22/09
to

I am not sure I understand what you are asking but XPath operates on a
tree model and any node selection selects nodes in that tree where the
tree remains intact meaning any parent/child/sibling/ancestor
relationships continue to exist.

Barnum

unread,
Sep 22, 2009, 7:29:21 AM9/22/09
to

That was exactly what I meant: "any parent/child/sibling/ancestor
relationships continue to exist."
Thanks!

Richard Braman

unread,
Apr 10, 2010, 6:32:46 PM4/10/10
to
<?xml version="1.0" encoding="UTF-8"?>
<!--I am having a similar problem although my xml is a little more
complicated. I am trying to dedup the results so that Body Weight, Body
Height, etc only show up once. This uses XSLT 1.0, so distinct-values()
is not an option :(:-->
<entry typeCode="DRIV">
<organizer classCode="CLUSTER" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.35"/> <!-- Vital signs
organizer template -->
<id root="c6f88320-67ad-11db-bd13-0800200c9a66"/>
<code code="46680005" codeSystem="2.16.840.1.113883.6.96"
displayName="Vital signs"/>
<statusCode code="completed"/>
<effectiveTime value="19991114"/>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="c6f88321-67ad-11db-bd13-0800200c9a66"/>
<code code="50373000" codeSystem="2.16.840.1.113883.6.96"
displayName="Body height"/>
<statusCode code="completed"/>
<effectiveTime value="19991114"/>
<value xsi:type="PQ" value="177" unit="cm"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="c6f88322-67ad-11db-bd13-0800200c9a66"/>
<code code="27113001" codeSystem="2.16.840.1.113883.6.96"
displayName="Body weight"/>
<statusCode code="completed"/>
<effectiveTime value="19991114"/>
<value xsi:type="PQ" value="86" unit="kg"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="c6f88323-67ad-11db-bd13-0800200c9a66"/>
<code code="271649006" codeSystem="2.16.840.1.113883.6.96"
displayName="Systolic BP"/>
<statusCode code="completed"/>
<effectiveTime value="19991114"/>
<value xsi:type="PQ" value="132" unit="mm[Hg]"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="c6f88324-67ad-11db-bd13-0800200c9a66"/>
<code code="271650006" codeSystem="2.16.840.1.113883.6.96"
displayName="Diastolic BP"/>
<statusCode code="completed"/>
<effectiveTime value="19991114"/>
<value xsi:type="PQ" value="86" unit="mm[Hg]"/>
</observation>
</component>
</organizer>
</entry>
<entry typeCode="DRIV">
<organizer classCode="CLUSTER" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.35"/> <!-- Vital signs
organizer template -->
<id root="d11275e0-67ae-11db-bd13-0800200c9a66"/>
<code code="46680005" codeSystem="2.16.840.1.113883.6.96"
displayName="Vital signs"/>
<statusCode code="completed"/>
<effectiveTime value="20000407"/>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="d11275e1-67ae-11db-bd13-0800200c9a66"/>
<code code="50373000" codeSystem="2.16.840.1.113883.6.96"
displayName="Body height"/>
<statusCode code="completed"/>
<effectiveTime value="20000407"/>
<value xsi:type="PQ" value="177" unit="cm"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="d11275e2-67ae-11db-bd13-0800200c9a66"/>
<code code="27113001" codeSystem="2.16.840.1.113883.6.96"
displayName="Body weight"/>
<statusCode code="completed"/>
<effectiveTime value="20000407"/>
<value xsi:type="PQ" value="88" unit="kg"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="d11275e3-67ae-11db-bd13-0800200c9a66"/>
<code code="271649006" codeSystem="2.16.840.1.113883.6.96"
displayName="Systolic BP"/>
<statusCode code="completed"/>
<effectiveTime value="20000407"/>
<value xsi:type="PQ" value="145" unit="mm[Hg]"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.1.31"/> <!-- Result
observation template -->
<id root="d11275e4-67ae-11db-bd13-0800200c9a66"/>
<code code="271650006" codeSystem="2.16.840.1.113883.6.96"
displayName="Diastolic BP"/>
<statusCode code="completed"/>
<effectiveTime value="20000407"/>
<value xsi:type="PQ" value="88" unit="mm[Hg]"/>
</observation>
</component>
</organizer>
</entry>
</section>
</component>
<!--My current xslt:-->
<!-- Vital Signs-->
<xsl:template
match="n1:component/n1:section[n1:templateId/@root='2.16.840.1.113883.10
.20.1.16']">
<xsl:apply-templates select="n1:title" />
<div id="lblvitals">
<table width="100%" class="display" id="vitals">
<thead>
<tr>
<th align="left">Date</th>
<xsl:apply-templates select="n1:entry" mode="findDates" />
</tr>
</thead>
<tbody>
<xsl:apply-templates select="n1:entry//n1:observation/n1:code"
mode="vitals" />
</tbody>
</table>
</div>
</xsl:template>
<!-- Vital Date Header NOT COMPLETE-->
<xsl:template match="n1:entry" mode="findDates">
<xsl:variable name="effectiveTime"
select="n1:organizer/n1:effectiveTime/@value"/>
<xsl:if test="$effectiveTime">
<th>
<xsl:call-template name="formatMediumDate">
<xsl:with-param name="date" select="$effectiveTime"/>
</xsl:call-template>
</th>
</xsl:if>
</xsl:template>
<!-- For evey vital type write a row-->
<xsl:template match="n1:entry//n1:observation/n1:code" mode="vitals">
<xsl:variable name="code" select="@code"/>
<xsl:variable name="displayName" select="@displayName"/>
<tr>
<td>
<xsl:value-of select="$displayName" />
</td>
<xsl:call-template name="ed">
<xsl:with-param name="code" select="$code"></xsl:with-param>
<xsl:with-param name="node"
select="/n1:ClinicalDocument/n1:component/n1:structuredBody/n1:component
/n1:section[n1:templateId/@root='2.16.840.1.113883.10.20.1.16']//n1:obse
rvation/n1:code[@code=$code]/.."> </xsl:with-param>
</xsl:call-template>
</tr>
</xsl:template>

<xsl:template name="ed">
<xsl:param name="code" />
<xsl:param name="node"></xsl:param>
<xsl:for-each select="$node/n1:value">
<td><xsl:value-of select="@value"></xsl:value-of></td>
</xsl:for-each>
</xsl:template>
<!--Current Output:-->
<div id="lblvitals">
<table width="100%" class="display" id="vitals">
<thead>
<tr>
<th align="left">Date</th>
<th>14-NOV-99</th>
<th>07-APR-00</th>
</tr>
</thead>
<tbody>
<tr>
<td>Body height</td>
<td>177</td>
<td>177</td>
</tr>
<tr>
<td>Body weight</td>
<td>86</td>
<td>88</td>
</tr>
<tr>
<td>Systolic BP</td>
<td>132</td>
<td>145</td>
</tr>
<tr>
<td>Diastolic BP</td>
<td>86</td>
<td>88</td>
</tr>
<tr>
<td>Body height</td>
<td>177</td>
<td>177</td>
</tr>
<tr>
<td>Body weight</td>
<td>86</td>
<td>88</td>
</tr>
<tr>
<td>Systolic BP</td>
<td>132</td>
<td>145</td>
</tr>
<tr>
<td>Diastolic BP</td>
<td>86</td>
<td>88</td>
</tr>
</tbody>
</table>
</div>


*** Sent via Developersdex http://www.developersdex.com ***

0 new messages