[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

[xsl] Re: How to handle dynamic XPath


Subject: [xsl] Re: How to handle dynamic XPath
From: fred@xxxxxxxxxxxxx
Date: Mon, 13 Apr 2009 08:28:21 +0200

This list is incredible!
I was struggling all Easter sunday with a problem, posted it, went to
sleep and overnight three experts came with solutions. Thank you, Ken,
Michael and Florent!

Mike's solution works, although I would prefer a solution without
extension functions.
Keeping a key table as Ken suggests is a bit difficult, as beforehand
(design time) I do not know the structure of the documents. The XSL
takes any XML schema that obeys certain rules of well-formedness.
Creating the key table at run time could be a solution though.
Florents suggestion of a meta-stylesheet is worth wile investigating,
but that would complicate the application even more.

Hope I didnt spoil your Easter too much.

Fred




------------------------------

Date: Sun, 12 Apr 2009 21:07:29 +0200
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
From: fred@xxxxxxxxxxxxx
Subject: How to handle dynamic XPath
Message-ID: <20090412210729.uv66b6hsw0sgksw0@xxxxxxxxxxxxxxxxxxxxx>

Hi,

I am using XSLT to walk through an XML Schema to construct an Xforms =20
(output) instance.
In parallel I am scanning an (input) instance (of a document defined =20
by the schema) to
include default values in the form.

Scanning of the input instance is done by means of a variable that =20
keeps track of the
path. The variable is updated (renewed) each time a template is =20
(recursively) called with
the variable as parameter:

<xsl:with-param name=3D"instance.path"
select=3D"concat($instance.path,'/',$element.prefix,':',@name)" />

The template calls another template that writes the output instance. =20
In that template I
try to look up the default value in the input instance (an external
document=
).

BUT:

<xsl:value-of select=3D"document('file:/C:/dir/order.xml')/$instance.path"
/=

writes a path string to the output instance instead of the value of =20
the element, such as:
/rsm:PurchaseOrder/rsm:Identifier, so does copy-of select.

<xsl:value-of
select=3D"document('file:/C:/dir/order.xml')//rsm:DeliveryDate=
" />
writes correctly the content of the delivery date, but
<xsl:value-of select=3D"document('file:/C:/dir/order.xml')//$deldate" />
(where $deldate contains the string "rsm:DeliveryDate") writes nothing.

Concluding the problem is node-format vs text format, I tried the =20
saxon evaluate
function, but:
<xsl:copy-of =20

select=3D"saxon:evaluate(document('file:/C:/dir/order.xml')/$instance.path)"=
 =20
/>
writes nothing, neither does value-of.

I am desperate. Does anyone have a clue how to solve this?

Thanks in advance,

Fred van Blommestein

------------------------------

Date: Sun, 12 Apr 2009 15:34:14 -0400
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
From: "G. Ken Holman" <gkholman@xxxxxxxxxxxxxxxxxxxx>
Subject: Re: [xsl] How to handle dynamic XPath
Message-Id: <7.0.1.0.2.20090412152437.02699d30@xxxxxxxxxxxxxxxxxxxxxx>

At 2009-04-12 21:07 +0200, fred@xxxxxxxxxxxxx wrote:
I am using XSLT to walk through an XML Schema to construct an Xforms
(output) instance.
In parallel I am scanning an (input) instance (of a document defined
by the schema) to
include default values in the form.

I'm assuming the structures of the two documents are identical.


Scanning of the input instance is done by means of a variable that
keeps track of the
path. The variable is updated (renewed) each time a template is
(recursively) called with
the variable as parameter:

<xsl:with-param name="instance.path"
select="concat($instance.path,'/',$element.prefix,':',@name)" />

The template calls another template that writes the output instance.
In that template I
try to look up the default value in the input instance (an external
 document).

BUT:

<xsl:value-of select="document('file:/C:/dir/order.xml')/$instance.path"
/>
writes a path string to the output instance instead of the value of
the element, such as:
/rsm:PurchaseOrder/rsm:Identifier, so does copy-of select.

Correct, because there is no such thing as "eval()" in XPath. The final step of your XPath address is the value of a variable, so you are getting the value of the variable as you should.

And who is to say that your prefixes in your input document match the
prefixes in your default value document?

<xsl:value-of
select="document('file:/C:/dir/order.xml')//rsm:DeliveryDate" />
writes correctly the content of the delivery date, but
<xsl:value-of select="document('file:/C:/dir/order.xml')//$deldate" />
(where $deldate contains the string "rsm:DeliveryDate") writes nothing.

I would expect you to see the string "rsm:DeliveryDate", not nothing.


Concluding the problem is node-format vs text format, I tried the
saxon evaluate
function, but:
<xsl:copy-of
select="saxon:evaluate(document('file:/C:/dir/order.xml')/$instance.path)"
/>
writes nothing, neither does value-of.

I am desperate. Does anyone have a clue how to solve this?

You could use a key table, where the lookup value for each element is that element's fully-qualified XPath address. And you could use the "{namespace-uri}local-name" syntax for each element in the path in order to be independent of namespace prefixes.

Then you use:

  <xsl:value-of select="document('file:/C:/dir/order.xml')/
                        key('myTable',$instance.path))"/>

and it will return the value of the element.

You don't give any sample data to test with, and you don't talk about
sibling elements of the same name, but I think this approach would
work for you.  An example is below where each element of fred.xml is
replaced with the corresponding value from fred-default.xml.

I hope this helps.

. . . . . . . . . . . . . Ken

T:\ftemp>type fred.xml
<a xmlns="urn:x-fred">
   <b>B1</b>
   <c>C1</c>
   <b>B2</b>
   <d>
     <e>E1</e>
   </d>
</a>
T:\ftemp>type fred-default.xml
<f:a xmlns:f="urn:x-fred">
   <f:b>def-B1</f:b>
   <f:c>def-C1</f:c>
   <f:b>def-B2</f:b>
   <f:d>
     <f:e>def-E1</f:e>
   </f:d>
</f:a>
T:\ftemp>call xslt2 fred.xml fred.xsl
<?xml version="1.0" encoding="UTF-8"?><a xmlns="urn:x-fred">
   <b>def-B1</b>
   <c>def-C1</c>
   <b>def-B2</b>
   <d>
     <e>def-E1</e>
   </d>
</a>
T:\ftemp>type fred.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 xmlns:f="urn:x-fred"
                 version="2.0">

<xsl:key name="paths" match="*" use="f:make-path(.)"/>

<!--walk the element tree-->
<xsl:template match="*">
   <xsl:param name="path"/>
   <!--determine the path to this element-->
   <xsl:variable name="this-path"
                 select="concat($path,f:make-step(.))"/>
   <!--preserve the element structure-->
   <xsl:copy>
     <!--preserve any attributes-->
     <xsl:apply-templates select="@*"/>
     <!--replace the value when there are no element children-->
     <xsl:choose>
       <xsl:when test="not(*)">
         <!--at a leaf element-->
         <xsl:value-of select="document('fred-default.xml')/
                               key('paths',$this-path)"/>
       </xsl:when>
       <xsl:otherwise>
         <!--at a branch element, keep going-->
         <xsl:apply-templates>
           <xsl:with-param name="path" select="$this-path"/>
         </xsl:apply-templates>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:copy>
</xsl:template>

<!--identity for all other nodes-->
<xsl:template match="@*|comment()|processing-instruction()">
   <xsl:copy/>
</xsl:template>

<!--create a lookup key-->
<xsl:function name="f:make-path">
   <xsl:param name="element"/>
   <xsl:sequence select="string-join( for $e in
$element/ancestor-or-self::*
                                      return f:make-step( $e ), '' )"/>
</xsl:function>

<xsl:function name="f:make-step">
   <xsl:param name="element"/>
   <xsl:for-each select="$element">
     <xsl:sequence select="concat('/{',namespace-uri(.),'}',local-name(.),

'[',count(preceding-sibling::*[node-name(.)=node-name(current())])+1,
        ']')"/>
   </xsl:for-each>
</xsl:function>

</xsl:stylesheet>

T:\ftemp>rem Done!

--
XSLT/XSL-FO/XQuery training in Los Angeles (New dates!) 2009-06-08
Training tools: Comprehensive interactive XSLT/XPath 1.0/2.0 video
Video lesson:    http://www.youtube.com/watch?v=PrNjJCh7Ppg&fmt=18
Video overview:  http://www.youtube.com/watch?v=VTiodiij6gE&fmt=18
G. Ken Holman                 mailto:gkholman@xxxxxxxxxxxxxxxxxxxx
Crane Softwrights Ltd.          http://www.CraneSoftwrights.com/s/
Male Cancer Awareness Nov'07  http://www.CraneSoftwrights.com/s/bc
Legal business disclaimers:  http://www.CraneSoftwrights.com/legal

------------------------------

Date: Sun, 12 Apr 2009 23:35:31 +0100
To: <xsl-list@xxxxxxxxxxxxxxxxxxxxxx>
From: "Michael Kay" <mike@xxxxxxxxxxxx>
Subject: RE: [xsl] How to handle dynamic XPath
Message-ID: <4FA95E4459EE4A4B85EEF35A0C54B439@Sealion>

Concluding the problem is node-format vs text format, I tried
the saxon evaluate function, but:
<xsl:copy-of
select="saxon:evaluate(document('file:/C:/dir/order.xml')/$ins
tance.path)"
/>
writes nothing, neither does value-of.

You're on the right lines here, but the syntax would be


select="document('file:/C:/dir/order.xml')/saxon:evaluate($instance.path)"

I strongly suspect that there are much better solutions to this problem
that
don't involve dynamic evaluation. However, it's Easter Sunday.

Michael Kay
http://www.saxonica.com/

------------------------------

Date: Sun, 12 Apr 2009 19:52:19 +0000 (GMT)
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
From: Florent Georges <lists@xxxxxxxxxxxx>
Subject: Re : [xsl] How to handle dynamic XPath
Message-ID: <816269.32982.qm@xxxxxxxxxxxxxxxxxxxxxxxxxxx>

fred@xxxxxxxxxxxxx wrote:

Hi,

<xsl:with-param name=3D"instance.path"
select=3D"concat($instance.path,'/',$element.prefix,':',@name)"
/>

You cannot evaluate an XPath expression supplied as a string in plain
XSL=
T. You can use extensions though for that purpose (of your own or
existing=
one.) I haven't looked deeply into your problem, but I'd say I'd rather
u=
ser either:

1/ maintaining a pointer in the instance and selecting its correct
child(=
ren) using local-name() and namespace-uri() for each step;

2/ or using a meta-stylesheet: your stylesheet generates another XSLT
sty=
lesheet that contains the XPath expressions (you "compile" the schema to
th=
e equivalent stylesheet) and you apply this stylesheet to the instance in
a=
second execution.

Regards,

--=20
Florent Georges
http://www.fgeorges.org/

=0A=0A=0A

------------------------------

End of xsl-list Digest
***********************************


Current Thread