1

I have two sample documents defined below. In module_meta.xml only the effect nodes on xpath /mdata/effectivity are relevant. As seen below, they contain a path attribute and a effrg attribute. The goal is now to evaluate the xpath (which is defined in the module_meta.xml as the path attribute) on the module.xml and append the effrg to it. See desired_output.xml for the desired result. The xsl transformation is applied on module.xml. I know that I have to use the document() function to "include" module_meta.xml, but so far I am at a loss.

module.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE proc>
    
<procbody>
 <info>
  <action lid="a">
  </action>
  <action lid="b">
  </action>
  <action lid="c">
  </action>
 </info>
</procbody>

module_meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mdata>
<mdata>
  <metadata>
    <metadata-item name="n1" value="v1" />
    <metadata-item name="n2" value="v2" />
    <metadata-item name="n3" value="v3" />
  </metadata>
  <effectivity>
    <effect path="//*[@lid='a']" effrg="0074 0080 0087" />
    <effect path="//*[@lid='b']" effrg="0136 0146 0174" />
  </effectivity>
</mdata>

desired_output.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE proc>
        
<procbody>
 <info>
  <action lid="a" effrg="0074 0080 0087">
  </action>
  <action lid="b" effrg="0136 0146 0174">
  </action>
  <action lid="c">
  </action>
 </info>
</procbody>
1
  • XSLT 3 (as supported by Saxon 10 and later all editions, Saxon 9.8 and later PE and EE, by Saxon-JS 2 and by Altova XML 2017 R3 and later) has xsl:evaluate. Some older implementations provide extension functions or let you set one up for XPath evaluation. Or you can chain two stylesheets. Commented Nov 11, 2021 at 8:52

1 Answer 1

1

In XSLT 3 with xsl:evaluate support:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  xmlns:mf="http://example.com/mf"
  expand-text="yes">
  
  <xsl:function name="mf:evaluate" as="element(action)?">
    <xsl:param name="effect" as="element(effect)"/>
    <xsl:evaluate xpath="$effect/@path" context-item="$main-doc"/>
  </xsl:function>

  <xsl:variable name="main-doc" select="/"/>
  
  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="info/action">
    <xsl:copy>
      <xsl:apply-templates select="@*, $meta//effect[current() is mf:evaluate(.)]/@effrg"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:param name="meta">
<mdata>
  <metadata>
    <metadata-item name="n1" value="v1" />
    <metadata-item name="n2" value="v2" />
    <metadata-item name="n3" value="v3" />
  </metadata>
  <effectivity>
    <effect path="//*[@lid='a']" effrg="0074 0080 0087" />
    <effect path="//*[@lid='b']" effrg="0136 0146 0174" />
  </effectivity>
</mdata>
  </xsl:param>
  
</xsl:stylesheet>

For self-containedness of the example the second document is inline as a parameter but you can of course use <xsl:param name="meta" select="doc('module_meta.xml')"/> instead.

It might be much more efficient to use the xsl:evaluate only once for each effect element, for instance, by declaring a key doing that, and store the generated-id if there is one:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all"
  xmlns:mf="http://example.com/mf"
  expand-text="yes">
  
  <xsl:key name="ref" match="mdata/effectivity/effect">
    <xsl:variable name="referenced-node" as="node()?">
      <xsl:evaluate xpath="@path" context-item="$main-doc" as="node()?"/>
    </xsl:variable>
    <xsl:sequence select="generate-id($referenced-node)"/>
  </xsl:key>

  <xsl:variable name="main-doc" select="/"/>
  
  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:template match="info/action">
    <xsl:copy>
      <xsl:apply-templates select="@*, key('ref', generate-id(), $meta)/@effrg"/>
    </xsl:copy>
  </xsl:template>
  
  <xsl:param name="meta">
<mdata>
  <metadata>
    <metadata-item name="n1" value="v1" />
    <metadata-item name="n2" value="v2" />
    <metadata-item name="n3" value="v3" />
  </metadata>
  <effectivity>
    <effect path="//*[@lid='a']" effrg="0074 0080 0087" />
    <effect path="//*[@lid='b']" effrg="0136 0146 0174" />
  </effectivity>
</mdata>
  </xsl:param>
  
</xsl:stylesheet>

If you want to match any element referenced in the other document and copy any attribute but the path one you could change the template having match="info/action" to

  <xsl:template match="*[key('ref', generate-id(), $meta)]">
    <xsl:copy>
      <xsl:apply-templates select="@*, key('ref', generate-id(), $meta)/(@* except @path), node()"/>
    </xsl:copy>
  </xsl:template>
Sign up to request clarification or add additional context in comments.

6 Comments

Thank you for the answer. I see that you added the match="info/action" on the template. The thing is, I can't be sure, that the xpath will evaluate on /info/action. It has to be dynamic. Technically it could evaluate to /foo/bar and then one has to add the effrg to the bar tag. How would one solve this?
@sboti8m, I considered your first input document the primary input document to the XSLT and processed the elements in it with matching templates, trying to use some precise match patterns based on the sample you have shown. That is why the code obviously does xsl:template match="info/action". That has nothing to do with the XPath evaluation, that happens elsewhere in the function I set up. There I indeed assumed, based on the samples, that the path attribute select an action element or nothing, as declared by as="element(action)?" on the function. The function would work without that.
To say "Technically it could evaluate to /foo/bar and then one has to add the effrg to the bar tag" if the input doesn't have any foo/bar elements doesn't make sense. The xsl:evaluate will find elements if they exist and the code to process e.g. the effrg attribute doesn't change. You can also change the match="info/action" to something more general but it is not possible to guess and code all options and variants your XML might have without a clear description. So you will need to edit the question and be more specific which kind of elements in the primary XML you expect to be changed
Hi, thank you for the answer. One can assume, that the xpath will evaluate to a valid node. Thus in order to make it more general, I changed from info/action to node(). But this does not seem to work. Your solution is working splendid, how ever the module_meta.xml file could change down the line while module.xml remains the same. Thus a different xpath can appear, which evaluates to a completely different node (lets say procbody/info) "Hardcoding" info/action in match is not general enough.
@sboti8m, see whether the last suggestion I added helps, the match pattern was not hard-coded to make the approach work, rather inferred from your given input structure. But XSLT is certainly flexible enough to match on all elements (match="*") or all referenced elements (see the edit of the answer).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.