Re: [xsl] How to handle dynamic XPath

Subject: Re: [xsl] How to handle dynamic XPath
From: "G. Ken Holman" <gkholman@xxxxxxxxxxxxxxxxxxxx>
Date: Sun, 12 Apr 2009 15:34:14 -0400
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

Current Thread