0

I have a list of conditions, and I want to check if the immediate sibling of an element matches any of those conditions.

If these conditions are simple tag names, this is easy enough.

<xsl:param name="tag-list" select="tokenize('img figure table', '\s+')"/>

<xsl:template match="* | text()">
<xsl:variable name="next-name" select="name(following-sibling::*[1])" />
<xsl:if test="$next-name = $tag-list">
<!-- DoSomething -->
</xsl:if>
</xsl:template>

This template will match any element or text node, and will DoSomething if the immediate following sibling of that node is either <img>, <figure> or <table>.

However, I want to check for more complex conditions. How can I only DoSomething for when the template matches against elements with a sibling with a specific attribute, child or text value? I would prefer to do this with a single <xsl:if>, as this list of sibling conditions could get pretty long.

<xsl:param name="tag-list" select="tokenize('img[@src] figure[text()] table[tbody]', '\s+')"/>

<xsl:template match="* | text()">
<xsl:if test="???">
<!-- DoSomething -->
</xsl:if>
</xsl:template>
Richard
  • 494
  • 7
  • 18
  • Shouldn't the condition simply be put in a predicate on the match pattern? Any, as you have used the XSLT 3 tag, there might be a way using static parameters and shadow attributes. Do you know how they work? https://stackoverflow.com/a/39368038/252228 has an example. – Martin Honnen Feb 04 '20 at 15:42

2 Answers2

2

Define a function representing your complex condition:

<xsl:function name="my:condition" as="xs:boolean">
  <xsl:param name="node" as="node()"/>
  ... your condition goes here ...
</xsl:function>

and then the test is:

<xsl:if test="following-sibling::*[1][my:condition(.)]">...</xsl:if>

If you want to modularize the condition into separate functions then you can of course do so:

<xsl:function name="my:condition" as="xs:boolean">
  <xsl:param name="node" as="node()"/>
  <xsl:sequence select="my:first($node) and my:second($node) and..."/>
</xsl:function>

If you want to make the logic a bit more dynamic, so the list of conditions is somehow supplied dynamically, then consider using higher order functions.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
1

If you use XSLT 3 consider whether a static parameter with a shadow attribute allows you to construct the right XPath expression on the fly before stylesheet compilation:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:param name="tag-list" static="yes" select="tokenize('img[@src] figure[text()] table[tbody]', '\s+')"/>

  <xsl:template match="* | text()">
    <xsl:if _test="{string-join($tag-list ! ('following-sibling::*[1][self::' || . || ']'), ' | ')}">
        <xsl:comment>DoSomething</xsl:comment>
    </xsl:if>
    <xsl:next-match/>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>

At https://xsltfiddle.liberty-development.net/3NSTbf3 that transforms

<root>
    <section><span>test</span><img src="foo.jpeg"/></section>
    <section>test<table><tbody>...</tbody></table><span>test</span><figure>...</figure></section>
</root>

into

<root>
    <section><!--DoSomething--><span>test</span><img src="foo.jpeg"/></section>
    <section><!--DoSomething-->test<table><tbody>...</tbody></table><!--DoSomething--><span>test</span><figure>...</figure></section>
</root>

The example is meant to show the use of _test as a shadow attribute, I have intentionally used a different template content as it seems easier to show the result of the code with the identity transformation plus a comment being output where the xsl:if kicks in.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110