Xavier S.
Xavier S.

Reputation: 1167

XSL: transform a XML file with another to filter it

Its me again, but this time i have a real problem... I have one XML, and i have to transform it in another XML by using a filter of another XML

File_in.xml:

    <?xml version="1.0" encoding="ISO-8859-15"?>
    <root>
        <item>
            <server>001023541</server>
            <name>P1</name>
            <desc>Production</desc>
            <status>1</status>
            <ram>1024</ram>
            <hdd>8 To</hdd>
        </item>
        <item>
            <server>201012345</server>
            <name>P2</name>
            <desc>Production</desc>
            <status>4</status>
            <ram>2048</ram>
            <hdd>8 To</hdd>
        </item>
        <item>
            <server>120332416</server>
            <name>P1</name>
            <desc>Production</desc>
            <status>2</status>
            <ram>8196</ram>
            <hdd>8 To</hdd>
        </item>
    </root>

And another XML:

filter.xml

    <?xml version="1.0" encoding="ISO-8859-15"?>
    <Filtre>
        <Bloc5>
            <Part1>
                <EAN>001023541</EAN>
                <EAN>012356549</EAN>
                <EAN>012356559</EAN>
                <EAN>012356569</EAN>
            </Part1>
            <Part2>
                <EAN>201012345</EAN>
                <EAN>201012346</EAN>
                <EAN>201012347</EAN>
            </Part2>
        </Bloc5>
    </Filtre>

If /root/item/server matches with an element of /Filtre/Bloc5/Part1/EAN, i replace

        <item>
            <server>001023541</server>
            <name>P1</name>
            <desc>Production</desc>
            <status>1</status>
            <ram>1024</ram>
            <hdd>8 To</hdd>
        </item>

by

        <item>
            <server>MAIN</server>
            <status>PRODUCTION</status>
        </item>

otherwise if /root/item/server matches with an element of /Filtre/Bloc5/Part2/EAN, i replace

        <item>
            <server>201012345</server>
            <name>P2</name>
            <desc>Production</desc>
            <status>4</status>
            <ram>2048</ram>
            <hdd>8 To</hdd>
        </item>

by

        <item>
            <server>BACKUP</server>
            <status>STOPPED</status>
        </item>

and the other are automatically replaced like:

        <item>
            <server>120332416</server>
            <name>P1</name>
            <desc>Production</desc>
            <status>2</status>
            <ram>8196</ram>
            <hdd>8 To</hdd>
        </item>

by

        <item>
            <name>OFFLINE</name>
            <desc>Production</desc>
        </item>

I get the name of the XML (used to filter) by this way:

            <!-- filter settings -->
            <xsl:param name="filter_xml" />
            <xsl:variable name="filter" select="document('$filter_xml')" />

And there is my code :

        <xsl:for-each select="root/item">
            <xsl:choose>
                <xsl:when test="index-of(($filter/Filtre/Bloc5/Part1/EAN), ./server)">
                <item>
                    <server>MAIN</server>
                    <status>PRODUCTION</status>
                </item>
                </xsl:when>
            <xsl:when test="index-of(($filter/Filtre/Bloc5/Part2/EAN), ./server)" >
                <item>
                    <server>BACKUP</server>
                    <status>STOPPED</status>
                </item>
            </xsl:when >
            <xsl:otherwise>
                <item>
                    <name>OFFLINE</name>
                    <desc>Production</desc>
                </item>
            </xsl:otherwise>
        </xsl:for-each>

But this does not work...

Upvotes: 1

Views: 1390

Answers (2)

Sean B. Durkin
Sean B. Durkin

Reputation: 12729

A much cleaner, simpler and efficient solution is to use template matching instead of tortuous branching. I am assuming XSLT 2.0 because you did not tag your question with the XSLT version number, but if you need XSLT 1.0, then adjustments can easily be made.

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />

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

<xsl:template match="/" >
 <root>
   <xsl:apply-templates select="*/item" />
 </root>
</xsl:template>

<xsl:template match="item[server=$filter/Bloc5/Part1/EAN]" >
  <item>
    <server>MAIN</server>
    <status>PRODUCTION</status>
  </item>
</xsl:template>

<xsl:template match="item[server=$filter/Bloc5/Part2/EAN]" >
  <item>
    <server>BACKUP</server>
    <status>STOPPED</status>
  </item>
</xsl:template>

<xsl:template match="item" >
  <item>
    <name>OFFLINE</name>
    <desc>Production</desc>
  </item>
</xsl:template>

</xsl:stylesheet>

Update: Icing on the cake

Thanks for your acceptance. As icing on the cake, you could also replace the first two item templates with this ONE more generic and extensible template. But it's debatable whether it is more simpler or more complex.

Alternative generic item template (only works for XSLT 2.0) ...

<xsl:template match="item[server=$filter/Bloc5/*/EAN]" >
  <xsl:variable name="part-no"
    select="substring-after(local-name(($filter/Bloc5/*[EAN=current()/server])[1]),
            'Part') cast as xs:integer" />
  <xsl:copy>
    <server><xsl:value-of select="('MAIN'      ,'BACKUP' )[$part-no]" /></server>
    <status><xsl:value-of select="('PRODUCTION','STOPPED')[$part-no]" /></status>
  </xsl:copy>
</xsl:template>

Upvotes: 2

Mads Hansen
Mads Hansen

Reputation: 66783

Two things that you need to adjust:

  1. The path for the document() needs to be the value of the filter variable. You are currently passing the string "$filter". You need to change <xsl:variable name="filter" select="document('$filter_xml')" /> to <xsl:variable name="filter" select="document($filter_xml)" />

  2. The contains() function expects string inputs for each of the parameters. In order to evaluate the contains() test against each of the filter's EAN, perform the test inside of a predicate filter on the EAN elements. For example, change: <xsl:when test="index-of(($filter/Filtre/Bloc5/Part1/EAN), ./server)"> to <xsl:when test="$filter/Filtre/Bloc5/Part1/EAN[contains(., ./server)]">. If anything matches, the result will be at least one EAC, which will evaluate to true() in the test.

Applied to an example XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:output indent="yes"/>
    <xsl:param name="filter_xml" />
    <xsl:variable name="filter" select="document($filter_xml)" />

    <xsl:template match="/">

        <xsl:for-each select="root/item">
            <xsl:choose>
                <xsl:when test="$filter/Filtre/Bloc5/Part1/EAN[contains(., ./server)]">
                    <item>
                        <server>MAIN</server>
                        <status>PRODUCTION</status>
                    </item>
                </xsl:when>
                <xsl:when test="$filter/Filtre/Bloc5/Part2/EAN[contains(., ./server)]" >
                    <item>
                        <server>BACKUP</server>
                        <status>STOPPED</status>
                    </item>
                </xsl:when >
                <xsl:otherwise>
                    <item>
                        <name>OFFLINE</name>
                        <desc>Production</desc>
                    </item>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 1

Related Questions