0
votes

I have elements such as

<elem attr1="value1 someValue" attr2="value2 someOtherValue"/>

elements with a variable amount of attributes that each have a variable amount of values.

Some of these attributes' names and some of their values are saved in variables beforehand and the elements are checked against these variables.

I need to check if the element has attributes specified in that variable (which I got working) and if for each attribute, at least one of its values is one of the values specified in another variable.

So if the variable contains "Value1 Value2" (or more values)

The element

<elem attr1="Value1 SomeValue" attr2="Value1 someOtherValue AthirdValue" attr3="value2 otherValue"/>

meets the requirements (provided that attr1, attr2 and attr3 are the correct attribute names, which I check before), but element:

<elem attr1="Value1 SomeValue" attr2="anything someOtherValue" attr3="value otherValue"/>

doesn't meet the requirements, because one of the attributes has no value that is contained in the variable (attr2: neither anything nor someOtherValue are specified in the variable).

I tried tokenizing the values, but then the elemens are considered correct even if only one attribute has one of the correct values. I need to make sure, all of the attributes have at least one of the correct values.

Help and tips highly appreciated!

EDIT:

The way the values of the variable are obtained:

<xsl:variable name="filterName" select="$someDoc//someElem[@id = $curID]//filters/@value"/>

The other document:

<?xml version="1.0"/>
<doc>
<filters name="attr1" value="value1"/>
<filters name="attr2" value="value2"/>
<filters name="attr3" value="value2"/>
</doc>

**EDIT 2:**

Looking at the filters elements above, the elems can look like this:

<elem/> <!-- no attribute -->
<elem someattr="somevalue"/> <!-- some irrelevant attribute -->
<elem attr1="value1"/> <!-- one of the attributes -->
<elem attr1="value1" attr2="value"/> <!-- two of the attributes -->

So the @name attribute of the filters element specifies the name of the attribute on the elem element and the @value attribute of the filters element specifies the value that that attribute must have in order to meet the requirements.

SOLUTION (adapted from answer post)

<xsl:when test="count(@*[local-name() = $filterNames]) > 1">
<!-- element has more then one filter attribute -->
    <xsl:variable name="currentFilterValues">
        <xsl:value-of select="$filterValues"/>
    </xsl:variable>
    <xsl:variable name="attributeNamesOfCurrentElement">
        <xsl:value-of select="@*[local-name() = $filterNames]/fn:local-name()"/>
    </xsl:variable>
    <xsl:variable name="errors">
        <xsl:for-each select="tokenize($attributeNamesOfCurrentElement, '\s+')">
            <xsl:variable name="currentName" select="."/>
            <xsl:variable name="currentValue" select="$currentElement/@*[fn:local-name() = $currentName]"/>
            <xsl:if test="not(tokenize($currentValue, '\s+') = tokenize($currentFilterValues, ' '))">
                <error/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:choose>
        <xsl:when test="$errors/error">
            <!-- at least one attribute doesnt have any of the required values -->
        </xsl:when>
        <xsl:otherwise>
            <!-- all attributes have at least one of the required values -->
            <xsl:copy>
                <xsl:apply-templates select="$currentElement_2/@*"/>
                <xsl:if test="child::*">
                    <xsl:for-each select="child::*">
                        <xsl:call-template name="recurse">
                            <xsl:with-param name="filterNames" select="$filterNames"/>
                            <xsl:with-param name="filterValues" select="$filterValues"/>
                            <xsl:with-param name="filterType" select="$filterType"/>
                            <xsl:with-param name="currentElement_2" select="."/>
                        </xsl:call-template>
                    </xsl:for-each>
                </xsl:if>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:when>
1
Think (and ask) from a different perspective: in terms of what input should be transformed to what output. Show us a minimal, complete and verifiable sample of your XML input, the stylesheet you currently have and the output you expect.Mathias Müller
I don't understand your explanation. Why does the first element meet the requirements, when the third attribute contains none of the values listed in the variable?michael.hor257k
sorry, a typo! Now it's correct.user3629892

1 Answers

1
votes

This is all a bit abstract, but to demonstrate a principle, let us have the following:

XML

<root>
    <elem attr1="alpha charlie" attr2="delta alpha echo" attr3="foxtrot bravo golf"/>
    <elem attr1="alpha charlie" attr2="hotel delta" attr3="bravo india"/> 
</root>

XSLT 2.0

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

<xsl:variable name="values" select="('alpha', 'bravo')"/>

<xsl:template match="/root">
    <xsl:copy>
        <xsl:apply-templates select="elem"/>
    </xsl:copy>
</xsl:template>     

<xsl:template match="elem">
    <xsl:copy>
        <xsl:choose>
            <xsl:when test="@*[not(tokenize(., ' ')=$values)]">
                <xsl:text>NO</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:text>YES</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Result

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <elem>YES</elem>
   <elem>NO</elem>
</root>

Explanation

The test:

test="@*[not(tokenize(., ' ')=$values)]"

returns true when there is at least one attribute that, after tokenizing, does not contain any token that matches one of the values in the $values variable.


Edit

In response to your modified requirements, I believe this should work:

XSLT 2.0

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

<xsl:variable name="filter-doc" select="document('filter.xml')"/>

<xsl:key name="filter-by-name" match="filters" use="@name" />

<xsl:template match="/root">
    <xsl:copy>
        <xsl:apply-templates select="elem"/>
    </xsl:copy>
</xsl:template>     

<xsl:template match="elem">
    <xsl:variable name="strikes">
        <xsl:for-each select="@*[key('filter-by-name', name(), $filter-doc)]">
            <xsl:variable name="values" select="key('filter-by-name', name(), $filter-doc)/@value" />
            <xsl:if test="not(tokenize(., ' ')=$values)">
                <strike/>
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>

    <xsl:copy>
        <xsl:choose>
            <xsl:when test="$strikes/strike">
                <xsl:text>NO</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:text>YES</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

This could probably be streamlined a bit, but first I would like to know if it provides the correct answers.