Test seems to catch more than the template match should select

Here should go questions about transforming XML with XSLT and FOP.
DanOvergaard
Posts: 22
Joined: Thu Jan 07, 2021 10:44 am

Test seems to catch more than the template match should select

Post by DanOvergaard »

Hi,

I can't understand why the check below fails, as I think the template only "returns" the 2 "cbc:Description" nodes with the context (match) selected, but is seems that is also catch the node "cbc:Name".

The test should catch any nodes with the same name that has the same value in the "languageID" attribute

I hope someone can enlighten me :-)

/Dan


Template

Code: Select all

 <xsl:template match="doc:Invoice/cac:InvoiceLine/cac:Item/cbc:Description"/>

 <xsl:if test="self::*[not(@languageID = following-sibling::*/@languageID)]">
         <Error>
            <Description>[W-LIB222] The attribute languageID should be used when more than one Description element is present</Description>
          </Error>
     </xsl:if>

 </xsl:template>
XML

Code: Select all

 
 <cac;Invoice>
	 <cac:InvoiceLine>
        <cac:Item>
            <cbc:Description languageID="uk">Lorem ipsum dolor sit amet cons</cbc:Description>
            <cbc:Description languageID="da">Lorem ipsum dolor sit amet cons</cbc:Description>
            <cbc:Name languageID="da">Lorem ipsum dolo</cbc:Name>
            <cac:SellersItemIdentification>
                <cbc:ID>1</cbc:ID>
            </cac:SellersItemIdentification>
        </cac:Item>
    </cac:InvoiceLine>
</cac;Invoice>
 
Last edited by DanOvergaard on Fri Dec 09, 2022 3:30 pm, edited 1 time in total.
Radu
Posts: 9059
Joined: Fri Jul 09, 2004 5:18 pm

Re: Test seems to catch more than the template match should select

Post by Radu »

Hi,

An xpath like this "following-sibling::*" matches all following siblings, not just the next sibling.
So in the context of "<cbc:Description languageID="uk">" the "following-sibling::*" xpath returns a sequence of "<cbc:Description languageID="da">", "<cbc:Name languageID="da">", "<cac:SellersItemIdentification>".
Probably your xpath expression should look like this:

Code: Select all

<xsl:if test="self::*[not(@languageID = following-sibling::*[1]/@languageID)]">
to look only at the first following sibling element.
In such cases you can use <xsl:message> expressions to debug the returned values, for example add an xsl:message before the xsl:if:

Code: Select all

<xsl:message><xsl:copy-of select="following-sibling::*/@languageID"/></xsl:message>
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
DanOvergaard
Posts: 22
Joined: Thu Jan 07, 2021 10:44 am

Re: Test seems to catch more than the template match should select

Post by DanOvergaard »

Hi Radu,

Thanks for your feedback - your explanation makes sense.

Unfortunate, your suggestion didn't solve my problem. What I want is to check if any of the "cac:Description" have the same "languageID". Do you have any suggestions ?


This should not fail

Code: Select all

<cbc:Description languageID="uk">Lorem ipsum dolor sit amet cons</cbc:Description>
<cbc:Description languageID="da">Lorem ipsum dolor sit amet cons</cbc:Description>
<cbc:Name languageID="da">Lorem ipsum dolo</cbc:Name>
 
This should fail

Code: Select all

<cbc:Description languageID="da">Lorem ipsum dolor sit amet cons</cbc:Description>
<cbc:Description languageID="uk">Lorem ipsum dolor sit amet cons</cbc:Description>
<cbc:Description languageID="da">Lorem ipsum dolor sit amet cons</cbc:Description>
<cbc:Name languageID="da">Lorem ipsum dolo</cbc:Name>
Note
I have added a zip-file with the full xml-file and Schematron
Xml_and_XLS.zip
(107.4 KiB) Downloaded 125 times
- The error occurs in line 32892
Martin Honnen
Posts: 97
Joined: Tue Aug 19, 2014 12:04 pm

Re: Test seems to catch more than the template match should select

Post by Martin Honnen »

If you use a context of e.g. `cac:Item[cbc:Description[2]]` you can then check e.g. `count(distinct-values(cbc:Description/@languageID)) lt count(cbc:Description/@languageID)`.

Or perhaps in terms of Schematron

Code: Select all

 <pattern>
        <rule context="cac:Item[cbc:Description[2]]">
            <assert test="count(distinct-values(cbc:Description/@languageID)) eq count(cbc:Description/@languageID)">languageID needs to be unique</assert>
        </rule>
    </pattern>
DanOvergaard
Posts: 22
Joined: Thu Jan 07, 2021 10:44 am

Re: Test seems to catch more than the template match should select

Post by DanOvergaard »

Hi,

Appreciate your help but unfortunately, your suggestion didn't work (or I implemented it wrong) as it still failed if there was a "languageID" matching outside the "Description" context.

I found the solution listed below, but is there a way to translate this to "Schematron schema" (sch:)

Code: Select all

<xsl:for-each-group select="cac:Item/cbc:Description" group-by="@languageID"> 
        
         <xsl:if test="count(current-group()) &gt; 1">
            <Error>
               <Description>[W-LIB223] Multilanguage error. </Description>
            </Error>
         </xsl:if>
        
      </xsl:for-each-group>
Regs,
Dan
Martin Honnen
Posts: 97
Joined: Tue Aug 19, 2014 12:04 pm

Re: Test seems to catch more than the template match should select

Post by Martin Honnen »

Schematron, if implemented in XSLT, usually allows you to make use of xsl:function or xsl:key so if you want to use that grouping construct you could try to put it into a function you call then from your Schematron rule or, as grouping/keying is basically the same approach, use e.g.

Code: Select all

<xsl:key name="language-group" match="cac:Item/cbc:Description" use="@languageID"/>
and then in Schematron

Code: Select all

<rule context="cac:Item/cbc:Description">
  <assert test="not(key('language-group', 
@languageID)[2])">languageID should be unique</assert>
</rule>
DanOvergaard
Posts: 22
Joined: Thu Jan 07, 2021 10:44 am

Re: Test seems to catch more than the template match should select

Post by DanOvergaard »

Hi Martin,

I will put your suggestion to test during the week – Thanks

My “for-each-group” solution has run into a problem which I hope you can help with. The solution I found is not working in the production environment, as the more than 100 different calls are done with a “too” specific “Context” for the “for-each-group” to work.

The rule was original created as stated below and then called with a context ending on “cbc:Description” (eg.: doc:Invoice/cac:InvoiceLine/cac:Item/cbc:Description"), but it’s not working as it catch more then just the “cbc:Description” elements.

What I need is a rule that catch if there are multiple "languageID’s" with the same value on multiple “cbc:Description” elements in the same segment (See the "Xml example" below), but as the rule already is called from countless places with different context I can’t change the context.

Can you see any solution where a test can work in the context described?


Original rule

Code: Select all

<sch:rule abstract="true" id="DescriptionLanguageCheck">
    <sch:report test="following-sibling::*/@languageID = self::*/@languageID">[W-LIB223] Multilanguage error. Replicated Description elements with same languageID attribute value</sch:report>
</sch:rule>

Example of two different calls from the production

Code: Select all

<sch:rule context="doc:Invoice/cac:InvoiceLine/cac:Item">
   <sch:extends rule="DescriptionLanguageCheck"/>
</sch:rule>

Code: Select all

<sch:rule context="doc:Invoice/cac:InvoiceLine/cac:Item/cac:TransactionConditions/cbc:Description">
   <sch:extends rule="DescriptionLanguageCheck"/>
</sch:rule>

Xml example

Code: Select all

<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
    xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
    xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
    <cac:InvoiceLine>
        <cac:Item>
            <cbc:Description languageID="uk">Lorem ipsum dolor sit amet cons</cbc:Description>
            <cbc:Description languageID="da">Lorem ipsum dolor sit amet cons</cbc:Description>
            <cbc:Description languageID="da">Lorem ipsum dolor sit amet cons1</cbc:Description>
            <cbc:Name languageID="da">Lorem ipsum dolo</cbc:Name>
            <cac:SellersItemIdentification>
                <cbc:ID>1</cbc:ID>
            </cac:SellersItemIdentification>
        <cac:TransactionConditions>
            <cbc:Description languageID="da">Lorem ipsum dolor sit amet cons</cbc:Description>
        </cac:TransactionConditions>
        </cac:Item>
    </cac:InvoiceLine>
</Invoice>
Post Reply