4

I've got this php snippet:

$xsltPath = $argv[1];
$xmlPath = $argv[2];

$xslt = file_get_contents($xsltPath);
$xml = file_get_contents($xmlPath);

$templateCMSObj = new \DOMDocument();
$templateCMSObj->loadXML($xslt);
$ekbXMLObj = new \DOMDocument();
$ekbXMLObj->loadXML($xml);
$proc = new \XSLTProcessor();
$proc->importStylesheet($templateCMSObj);
$html = $proc->transformToXML($ekbXMLObj);
echo($html);
exit;

Which simply applies an XSLT to a given XML document.

When I apply the below XSLT to the same XML doc I got a different behaviour of Windows wrt to Linux PHP version.

Here's php and libxml version detail:

Windows:

PHP 7.1.6 (cli) (built: Jun  8 2017 02:06:32) ( ZTS MSVC14 (Visual C++ 2015) x86 )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies

XML Support => active
XML Namespace Support => active
libxml2 Version => 2.9.4
XMLReader => enabled
XMLWriter => enabled
XSL => enabled
libxslt Version => 1.1.29
libxslt compiled against libxml Version => 2.9.4
EXSLT => enabled
libexslt Version => 0.8.17

Linux:

PHP 7.0.32-1~dotdeb+8.1 (cli) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.32-1~dotdeb+8.1, Copyright (c) 1999-2017, by Zend Technologies
    with Xdebug v2.5.5, Copyright (c) 2002-2017, by Derick Rethans

XML Support => active
XML Namespace Support => active
libxml2 Version => 2.9.1
XMLReader => enabled
XMLWriter => enabled
XSL => enabled
libxslt Version => 1.1.28
libxslt compiled against libxml Version => 2.9.1
EXSLT => enabled
libexslt Version => 1.1.28

Here's is the XSLT code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://new.webservice.namespace">
    <xsl:output method="xml" version="1.0" indent="yes"/>
    <xsl:param name="searchPath" select="11"/>
    <xsl:variable name="slash" select="'/'"/>
    <xsl:variable name="dot" select="'.'"/>
    <xsl:variable name="open_bracket" select="'{'"/>
    <xsl:variable name="closed_bracket" select="'}'"/>
    <xsl:template match="/">
        <ns:flat_pallet>
            <xsl:attribute name="tipo"><xsl:value-of select="//ns:EKB_piatto/@tipo"/></xsl:attribute>
            <xsl:apply-templates select=".//ns:gruppo_logico/ns:versione/ns:contenuto/ns:riferimento_oi"/>
        </ns:flat_pallet>
    </xsl:template>

    <!--restituisco tipo del box e l'indice relativo a nel sottoalbero-->
    <xsl:template match="ns:gruppo_logico">
        <xsl:variable name="tipo">
            <xsl:value-of select="@tipo"/>
        </xsl:variable>
        <xsl:variable name="index">
            <xsl:number level="single" count="node()[@tipo=$tipo]" format="1"/>
        </xsl:variable>
        <xsl:value-of select="normalize-space($tipo)" disable-output-escaping="yes"/>
        <xsl:value-of select="$open_bracket"/>
        <xsl:value-of select="$index -1" disable-output-escaping="yes"/>
        <xsl:value-of select="$closed_bracket"/>
        <xsl:value-of select="$slash"/>
    </xsl:template>
    <!--quando sono in una unit risalgo i box progenitori-->
    <xsl:template match="ns:riferimento_oi">
        <xsl:variable name="tipoUnit" select="./ns:tipo"/>
        <xsl:variable name="index">
            <xsl:number level="single" count="node()[node()/ns:tipo=$tipoUnit]" format="1"/>
        </xsl:variable>
        <xsl:variable name="labelPath">
            <xsl:apply-templates select="ancestor::ns:gruppo_logico"/>
            <xsl:value-of select="$tipoUnit"/>
            <xsl:value-of select="$open_bracket"/>
            <xsl:value-of select="$index -1"/>
            <xsl:value-of select="$closed_bracket"/>
        </xsl:variable>
        <xsl:copy>
            <xsl:attribute name="labelPath"><xsl:value-of select="$labelPath" disable-output-escaping="yes"/></xsl:attribute>
            <xsl:attribute name="unitIndex"><xsl:value-of select="$index" disable-output-escaping="yes"/></xsl:attribute>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@*|node()">
        <!-- just copy all my attributes and child nodes, except if there's a better template for some of them -->
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!--template per rimuovere nodi vuoti-->
    <xsl:template match="*[not(@*|*|comment()|processing-instruction()) and normalize-space()='']"/>
</xsl:stylesheet>

And the XML Doc which gaves different results in win and linux XSLT:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns:dettaglioFormEKBSOUT xmlns:ns="http://new.webservice.namespace">
    <ns:EKB_piatto>
        <ns:campo_GL>
            <ns:gruppo_logico tipo="Standard">
                <ns:versione id="1">
                    <ns:contenuto>
                        <ns:riferimento_oi>
                            <ns:tipo>Standard</ns:tipo>
                            <ns:natura_OI>
                                <ns:UNI>2.1</ns:UNI>
                            </ns:natura_OI>
                        </ns:riferimento_oi>
                    </ns:contenuto>
                    <ns:contenuto>
                        <ns:riferimento_oi>
                            <ns:tipo>Standard</ns:tipo>
                            <ns:natura_OI>
                                <ns:UNI>2.2</ns:UNI>
                            </ns:natura_OI>
                        </ns:riferimento_oi>
                    </ns:contenuto>
                </ns:versione>
            </ns:gruppo_logico>
            <ns:gruppo_logico tipo="Standard_due">
                <ns:versione id="1">
                    <ns:contenuto>
                        <ns:riferimento_oi>
                            <ns:tipo>Standard</ns:tipo>
                            <ns:natura_OI>
                                <ns:UNI>2.1</ns:UNI>
                            </ns:natura_OI>
                        </ns:riferimento_oi>
                    </ns:contenuto>
                    <ns:contenuto>
                        <ns:riferimento_oi>
                            <ns:tipo>Standard_due</ns:tipo>
                            <ns:natura_OI>
                                <ns:UNI>2.2</ns:UNI>
                            </ns:natura_OI>
                        </ns:riferimento_oi>
                    </ns:contenuto>
                </ns:versione>
            </ns:gruppo_logico>
        </ns:campo_GL>
    </ns:EKB_piatto>
</ns:dettaglioFormEKBSOUT>

Linux output This is the desired output which actually results from applying XSLT on Linux:

<?xml version="1.0"?>
<ns:flat_pallet xmlns:ns="http://new.webservice.namespace" tipo="">
    <ns:riferimento_oi labelPath="Standard{0}/Standard{0}" unitIndex="1">
        <ns:tipo>Standard</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.1</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
    <ns:riferimento_oi labelPath="Standard{0}/Standard{1}" unitIndex="2">
        <ns:tipo>Standard</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.2</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
    <ns:riferimento_oi labelPath="Standard_due{0}/Standard{0}" unitIndex="1">
        <ns:tipo>Standard</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.1</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
    <ns:riferimento_oi labelPath="Standard_due{0}/Standard_due{0}" unitIndex="1">
        <ns:tipo>Standard_due</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.2</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
</ns:flat_pallet>

Windows output This is the wrong result produces on Windows:

<?xml version="1.0"?>
<ns:flat_pallet xmlns:ns="http://new.webservice.namespace" tipo="">
    <ns:riferimento_oi labelPath="Standard{0}/Standard{0}" unitIndex="1">
        <ns:tipo>Standard</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.1</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
    <ns:riferimento_oi labelPath="Standard{0}/Standard{1}" unitIndex="2">
        <ns:tipo>Standard</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.2</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
    <ns:riferimento_oi labelPath="Standard_due{NaN}/Standard{0}" unitIndex="1">
        <ns:tipo>Standard</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.1</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
    <ns:riferimento_oi labelPath="Standard_due{NaN}/Standard_due{NaN}" unitIndex="">
        <ns:tipo>Standard_due</ns:tipo>
        <ns:natura_OI>
            <ns:UNI>2.2</ns:UNI>
        </ns:natura_OI>
    </ns:riferimento_oi>
</ns:flat_pallet>

The problem lies in the NaN put instead of relative index: it surely has to do with xsl:number element, but I can't figure out how to fix this...


Edit after accepted answer

By changing xsl:number count attribute to * instead of node() the xslt template works as expected on both Win and Linux.

Here's the updated code:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns="http://new.webservice.namespace">
    <xsl:output method="xml" version="1.0" indent="yes"/>
    <xsl:param name="searchPath" select="11"/>
    <xsl:variable name="slash" select="'/'"/>
    <xsl:variable name="dot" select="'.'"/>
    <xsl:variable name="open_bracket" select="'{'"/>
    <xsl:variable name="closed_bracket" select="'}'"/>
    <xsl:template match="/">
        <ns:flat_pallet>
            <xsl:attribute name="tipo"><xsl:value-of select="//ns:EKB_piatto/@tipo"/></xsl:attribute>
            <xsl:apply-templates select=".//ns:gruppo_logico/ns:versione/ns:contenuto/ns:riferimento_oi"/>
        </ns:flat_pallet>
    </xsl:template>

    <!--restituisco tipo del box e l'indice relativo a nel sottoalbero-->
    <xsl:template match="ns:gruppo_logico">
        <xsl:variable name="tipo">
            <xsl:value-of select="@tipo"/>
        </xsl:variable>
        <xsl:variable name="index">
            <xsl:number level="single" count="*[@tipo=$tipo]" format="1"/>
        </xsl:variable>
        <xsl:value-of select="normalize-space($tipo)" disable-output-escaping="yes"/>
        <xsl:value-of select="$open_bracket"/>
        <xsl:value-of select="$index -1" disable-output-escaping="yes"/>
        <xsl:value-of select="$closed_bracket"/>
        <xsl:value-of select="$slash"/>
    </xsl:template>
    <!--quando sono in una unit risalgo i box progenitori-->
    <xsl:template match="ns:riferimento_oi">
        <xsl:variable name="tipoUnit" select="./ns:tipo"/>
        <xsl:variable name="index">
            <xsl:number level="single" count="*[*/ns:tipo=$tipoUnit]" format="1"/>
        </xsl:variable>
        <xsl:variable name="labelPath">
            <xsl:apply-templates select="ancestor::ns:gruppo_logico"/>
            <xsl:value-of select="$tipoUnit"/>
            <xsl:value-of select="$open_bracket"/>
            <xsl:value-of select="$index -1"/>
            <xsl:value-of select="$closed_bracket"/>
        </xsl:variable>
        <xsl:copy>
            <xsl:attribute name="labelPath"><xsl:value-of select="$labelPath" disable-output-escaping="yes"/></xsl:attribute>
            <xsl:attribute name="unitIndex"><xsl:value-of select="$index" disable-output-escaping="yes"/></xsl:attribute>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@*|node()">
        <!-- just copy all my attributes and child nodes, except if there's a better template for some of them -->
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <!--template per rimuovere nodi vuoti-->
    <xsl:template match="*[not(@*|*|comment()|processing-instruction()) and normalize-space()='']"/>
</xsl:stylesheet>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
lbrutti
  • 1,181
  • 13
  • 19
  • I see no difference between the Linux and Windows outputs. – MonkeyZeus Nov 20 '18 at 17:39
  • @MonkeyZeus : question fixed, thanks for pointing it out!!! :) – lbrutti Nov 20 '18 at 17:45
  • [Can you please verify that the version numbers match using `phpinfo();`?](https://i.stack.imgur.com/sTPGQ.png) Please add the version numbers to your question. – MonkeyZeus Nov 20 '18 at 17:49
  • 1
    This is very interesting! Issue can even be reproduced in [xslt fiddle](https://xsltfiddle.liberty-development.net/nc4NzRq), toggling the engine between Saxon and others to "client-side processor". – Parfait Nov 20 '18 at 17:53
  • Any luck with identifying the version numbers? – MonkeyZeus Nov 20 '18 at 18:01
  • @MonkeyZeus, just added version details. anyway the issue seems to be version indepent (see below answer and comment) – lbrutti Nov 21 '18 at 08:23
  • 1
    This would be much easier (and more useful to others) if the example were minimized to only what's necessary to show the problem - see: [mcve]. – michael.hor257k Nov 21 '18 at 08:32
  • @michael.hor257k i think the only redundancy here is the code of php script i use to run xslt transformation. i may remove it since it's not the core of the issue, but i thinks it's useful to reproduce the issue. – lbrutti Nov 21 '18 at 09:12
  • 1
    At https://xsltfiddle.liberty-development.net/6qVRKxe/2 I have tried to make a reduced test case, with Chrome (at least my current version 70.0.3538.102 on Windows 10 64-bit) it shows the problem. @nwellnhof, is that a bug in the Windows version of libxslt? I am having a hard time to find an up to date binary of xsltproc/libxslt for Windows to test myself. – Martin Honnen Nov 21 '18 at 10:18
  • Yes, I've seen the answer and I'm glad it solves your problem. I would be more interested to see if this problem occurs when all the version numbers match; especially the `libexslt` since there is a difference of one major version number. – MonkeyZeus Nov 21 '18 at 13:29
  • 2
    For what it's worth, I tried the latest PHP release 7.2.12 on Windows which has the latest libxslt 1.1.32 and it exhibits the problem, so I have opened an issue on libxslt at https://gitlab.gnome.org/GNOME/libxslt/issues/6 to see whether they consider it a bug. – Martin Honnen Nov 21 '18 at 14:24

1 Answers1

3

In the select attribute of xsl:number, use * instead of node().

When you use node(), that can be an element, text, comment, or processing instruction node.

When you use *, that's only an element. That makes more sense in your use of xsl:number.

See https://xsltfiddle.liberty-development.net/nc4NzRq/1 for a working fiddle. (Thanks @parfait!)

Daniel Haley
  • 51,389
  • 6
  • 69
  • 95
  • And so this use of `node()` can differ across XSLT engines? – Parfait Nov 20 '18 at 18:39
  • 2
    @Parfait, I would say it is a bug in libxslt in that particular PHP or Chrome version, not sure whether it is OS related or not. Both Chrome and PHP use libxslt for XSLT processing, so it is not surprising the XSLT problem occurs in PHP and Chrome. – Martin Honnen Nov 20 '18 at 21:39
  • Indeed @MartinHonnen. Chrome was the only browser that I could replicate the `NaN` behavior. And interestingly, the bug is OS-dependent. Modules in Perl, Python, even R (much of the open source) uses libxslt. [Bugs](http://xmlsoft.org/XSLT/bugs.html) can be reported via [bugzilla](https://bugzilla.gnome.org/buglist.cgi?product=libxslt), with some entries for `xsl:number`. – Parfait Nov 20 '18 at 22:40