0

I am trying to write an XSLT "X_start" that constructs another XSLT "X_generated" from an xml file containing rules. My problem is to create a sequence for comparisons beween what I come across in the xml file and what I've generated in X_generated. The idea is to do this comparison, and if this doesn't match I will generate some text. In my examples I am comparing if I actually can find the value, because if I try to see if the value is outside the allowed values, I will always succeed unfortunately.

The attribute value "conditional" means that if the template matches, then the used value must be according to some set of values.

Any help/hint is appreciated, and let me know if you want me to clarify something.

Example on rules:

<?xml version="1.0" encoding="UTF-8"?>
<rules>
    <rule type="conditional">
        <xPath>//elemA/@attribute1</xPath>
        <val allow="A"/>
        <val allow="B"/>
    </rule>
    <rule type="conditional">
        <xPath>//elemC/@attribute1</xPath>
        <val allow="C"/>
        <val allow="D"/>
    </rule>
    <rule type="conditional">
        <xPath>//elemB</xPath>
        <val allow="one"/>
        <val allow="two"/>
        <val allow="three"/>
    </rule>
</rules>

My current draft XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
    version="3.0">
    <xsl:output method="xml" indent="true" encoding="UTF-8"/>
    <xsl:mode on-no-match="shallow-skip"/>
    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>
    
    <xsl:template match="/">
        <axsl:stylesheet>
            <xsl:attribute name="expand-text" select="'true'"/>
            <xsl:attribute name="version" select="'3.0'"/>
            <xsl:apply-templates select="rules/*"/>
        </axsl:stylesheet>
    </xsl:template>
    
    <xsl:template match="rule">
        
        <!-- This creates ("A", "B") -->
        <xsl:variable name="vals_from_for_each" as="xs:string*">
            <xsl:for-each select="val/@allow">
                <xsl:value-of select="."/>
            </xsl:for-each>
        </xsl:variable>
        
        <!-- This also creates ("A", "B") -->
        <xsl:variable name="vals_from_seq_text" as="xs:string*">
            <xsl:sequence select="val/@allow"/>
        </xsl:variable>
        
        
        <axsl:template>            
            <xsl:attribute name="match" select="xPath"/>
            
            <!-- Attempt to move the sequence into the generated template -->
            <axsl:variable name="vals" as="xs:string*">
                <!-- But this generates "A B", and is therefore not valid for comparison. 
                    I cannot compare a value like ". = A B", it needs to be ". = ('A', 'B')-->
                <xsl:sequence select="$vals_from_seq_text"/> 
            </axsl:variable>
            <axsl:if test=". = $vals">
                <axsl:message>Success</axsl:message>
            </axsl:if>
            
            <!-- trying by building a comparison tree (that doesn't work either... -->
            <axsl:variable name="value_list" as="element()*">
                <root>
                    <xsl:for-each select="val/@allow">
                        <val><xsl:value-of select="."/></val>
                    </xsl:for-each>
                </root>
            </axsl:variable>
            
            <axsl:if test=". = $value_list/root/val">
                <axsl:message>Found in tree - Success</axsl:message>
            </axsl:if>
        </axsl:template>
    </xsl:template>

Resulting XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    expand-text="true"
    version="3.0">
    <xsl:template match="//elemA/@attribute1">
        <xsl:variable name="vals" as="xs:string*">A B</xsl:variable>
        <xsl:if test=". = $vals">
            <xsl:message>Success</xsl:message>
        </xsl:if>
        <xsl:variable name="value_list" as="element()*">
            <root>
                <val>A</val>
                <val>B</val>
            </root>
        </xsl:variable>
        <xsl:if test=". = $value_list/root/val">
            <xsl:message>Found in tree - Success</xsl:message>
        </xsl:if>
    </xsl:template>
    <xsl:template match="//elemC/@attribute1">
        <xsl:variable name="vals" as="xs:string*">C D</xsl:variable>
        <xsl:if test=". = $vals">
            <xsl:message>Success</xsl:message>
        </xsl:if>
        <xsl:variable name="value_list" as="element()*">
            <root>
                <val>C</val>
                <val>D</val>
            </root>
        </xsl:variable>
        <xsl:if test=". = $value_list/root/val">
            <xsl:message>Found in tree - Success</xsl:message>
        </xsl:if>
    </xsl:template>
    <xsl:template match="//elemB">
        <xsl:variable name="vals" as="xs:string*">one two three</xsl:variable>
        <xsl:if test=". = $vals">
            <xsl:message>Success</xsl:message>
        </xsl:if>
        <xsl:variable name="value_list" as="element()*">
            <root>
                <val>one</val>
                <val>two</val>
                <val>three</val>
            </root>
        </xsl:variable>
        <xsl:if test=". = $value_list/root/val">
            <xsl:message>Found in tree - Success</xsl:message>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

So none of the messages triggers, I fail in creating some comparable thing in my resulting XSLT. Any created sequence is created as separate values in a string(?), and I cannot do a lookup in the generated tree segment (which I guessed wouldn't work, but desperate as one can be, I tried anyway). While writing this, I started thinking that one way could be to use a separate mode where I could create a completely separate xml document with lookup values, but this seems a bit worse than creating a lookup within each template. So, any ideas on how I can transfer my allowed values from my source XML to the generated XSLT?

4 Answers 4

2

IIUC, you do NOT want to create "a sequence of strings". You want to create a string that, when evaluated as an expression, will create a sequence of strings.

Consider something like:

XSLT 3.0

<xsl:stylesheet version="3.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

<xsl:template match="/rules">
    <axsl:stylesheet version="3.0">
        <xsl:apply-templates/>
    </axsl:stylesheet>
    </xsl:template>

<xsl:template match="rule">
    <axsl:template match="{xPath}">            
        <axsl:if test=". = ({string-join(val/@allow ! concat('''', ., ''''), ',')})">
            <axsl:message>Success</axsl:message>
        </axsl:if>
    </axsl:template>
</xsl:template>

</xsl:stylesheet>

P.S. Note that starting a match pattern with // does not accomplish anything.

Sign up to request clarification or add additional context in comments.

1 Comment

This works, and I understand the suggested solution. About the // in each match, they come from the source rules, and I think they've been added to let people use them directly in a tool that perhaps parses the entire document and applies the expressions in some XPath functionality. I figured XSLT could solve the checking somehow.
1

Ahm... Taking a hint from Michael Kay's answer, why don't you bypass the entire "sequence of strings" issue and do simply:

<xsl:template match="rule">
    <axsl:template match="{xPath}"> 
        <axsl:variable name="vals">
            <xsl:copy-of select="val"/>
        </axsl:variable>     
        <axsl:if test=". = $vals/val/@allow">
            <axsl:message>Success</axsl:message>
        </axsl:if>
    </axsl:template>
</xsl:template>

6 Comments

Ok, so this also works. Wondering why my built variable (that only had text content) that looks kind of similar didn't work for comparison?
Which variable are you referring to?
I tried creating a variable with elements called "value_list". But I'm guessing it has something to do with variables and context?
I would need more time to investigate this, but I believe your attempt using the $value_list variable will work if you remove the as="element()*" attribute.
Yes. Alternatively, you could keep the as attribute but then you would need to change the test from test=". = $value_list/root/val" to test=". = $value_list/val" - see: w3.org/TR/xslt-30/#variable-values.
|
1

One way, as you have simple string values, would be to store them in an XDM array you serialize as JSON and inject serialized in the generated XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
    version="3.0">
    <xsl:output method="xml" indent="true" encoding="UTF-8"/>
    <xsl:mode on-no-match="shallow-skip"/>
    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>
    
    <xsl:template match="/">
        <axsl:stylesheet>
            <xsl:attribute name="expand-text" select="'true'"/>
            <xsl:attribute name="version" select="'3.0'"/>
            <xsl:apply-templates select="rules/*"/>
        </axsl:stylesheet>
    </xsl:template>
    
    <xsl:template match="rule">

        
        <!-- This also creates ("A", "B") -->
        <xsl:variable name="vals_from_seq_text" as="array(xs:string)" select="array {val/@allow/string() }"/>

        
        <axsl:template>   
        
            <xsl:attribute name="match" select="xPath"/>
            
            <!-- Attempt to move the sequence into the generated template -->
            <axsl:variable name="vals" as="array(xs:string)" select="{serialize($vals_from_seq_text, map{'method':'json'})}"/>

            <axsl:if test=". = $vals?*">
                <axsl:message>Success</axsl:message>
            </axsl:if>
            

        </axsl:template>
    </xsl:template>
    
</xsl:stylesheet>

3 Comments

This works, and I can see that the result is an array. Could you perhaps point me to some easy to understand source on the map{'method':'json'} part? I really would like to understand theese tools, but I haven't got it by reading the spec unfortunately.
@Zug_Bug, I suppose you know e.g. <xsl:output method="html"/> or <xsl:output method="text"/> or <xsl:output method="xml"/> to declare the serialization method for an XSLT 2 stylesheet? Well, in XSLT 3 there are two additional methods, one named json, one named adaptive. In addition the is an XPath 3.1 function named serialize that as the first argument takes any XSLT/XDM/XPath sequence and as the second argument the serialization parameters, not in the form of an xsl:output declaration, but as an XPath 3.1 "map".
So all I have done there is to use <xsl:output method="json"/>, but in XPath 3.1 syntax, not on the XSLT level. The syntax for XSLT maps is explained in the XSLT 3 spec and the XPath 3.1 spec: w3.org/TR/xpath-31/#id-maps
1

It may be simplest to generate the sequence of strings as an XML structure:

<axsl:variable name="strings-as-xml" as="element(s)*">
  <xsl:for-each select="$vals_from_seq_text">
    <s>{.}</s>
  </xsl:for-each>
</axsl:variable>

<axsl:variable name="value_list" as="xs:string*"
               select="$strings-as-xml!string()"/>

3 Comments

Another solution that ofcourse works :-) As I asked in another given answer - I'm wondering why my constructed variable containing elements didn't work for comparison? I can get the text value from each included element and run "string()" on it, but I can't get them into a sequence for comparison that I would normally be allowed, if the element node was in the document I was running the XSLT on? Confused...
I thought you answered that yourself in a comment in your code: your code is generating <xsl:variable name="..">A B C</xsl:variable> which is a single string, not a sequence of strings.
Yes, that one I saw, but I meant the variable $value_list. @michael.hor257k thought it might work if I remove the type from the variable. I'll try that just to see if it works so that I might understand this a bit better.

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.