Search code examples
xsltxslt-2.0

XSLT output two documents based on array variable


I have xml file with structure like this:

<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">

    <changeSet author="system" id="65D7AFA9-17F1-4406-997E-D2B42A0E9008" dbms="oracle">
        <sql>GRANT SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOG TO ${appUser}</sql>
        <rollback>REVOKE SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOG FROM ${appUser}</rollback>
    </changeSet>
    <changeSet author="system" id="E2731435-DCEE-4B7E-BA60-20A87E75227A" dbms="oracle">
        <sql>GRANT SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOGATTRIBUTE TO ${appUser}</sql>
        <rollback>REVOKE SELECT,INSERT,UPDATE,DELETE ON ${dbSchema}.CATALOGATTRIBUTE FROM ${appUser}</rollback>
    </changeSet>
    <changeSet author="system" id="65D7AFA9-17F1-4406-997E-D2B42A0E9008" dbms="oracle">
        <createTable ...>
    </changeSet>

</databaseChangeLog>

I'd like to output all changeSets where sql contains one of tables to one document and all others (which doesn't contain the table name) to other document. I was thinking about appending stuff to variable and at the end print two variables, but that doesn't work (or I don't know how to process further).

<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
               xpath-default-namespace="http://www.liquibase.org/xml/ns/dbchangelog"
               xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
               xmlns:uuid="http://uuid.util.java"
               exclude-result-prefixes="uuid">

    <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

    <xsl:variable name="tables"
                  select="('CATALOG','CATALOGATTRIBUTE','EVENTTYPES')"/>

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>


    <xsl:template match="changeSet[sql]">
        <xsl:variable name="changeSet" select="."/>
        <xsl:variable name="sql" select="sql"/>
        <xsl:for-each select="$tables">
            <!--<xsl:variable name="selected" select="sql[contains(text(),concat('${dbSchema}.', ., ' '))]"/>-->
            <xsl:variable name="tableName">
                <xsl:value-of select="."/>
                <xsl:value-of select="' '"/>
            </xsl:variable>
            <xsl:choose>
                <xsl:when test="contains($sql, $tableName)">
                    <xsl:copy-of select="$changeSet"/>
                </xsl:when>
                <xsl:otherwise>

                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:transform>

can I somehow gather all changeSet which match tables and then all changeSets which doe not?

ps: I'm using Saxon-HE-9.8.0-14 for processing.

Thanks


Solution

  • Consider the following simplified example:

    XML

    <databaseChangeLog>
        <changeSet>
            <sql>ALPHA,BRAVO,CHARLIE</sql>
        </changeSet>
        <changeSet>
            <sql>BRAVO,CHARLIE,DELTA</sql>
        </changeSet>
        <changeSet>
            <sql>DELTA,ECHO,FOXTROT</sql>
        </changeSet>
        <changeSet>
            <sql>FOXTROT,GOLF,HOTEL</sql>
        </changeSet>
    </databaseChangeLog>
    

    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:param name="tables" select="('BRAVO', 'GOLF')"/>
    
    <xsl:template match="/databaseChangeLog">
        <xsl:variable name="group1" select="changeSet[some $t in $tables satisfies contains(sql, $t)]" />
        <xsl:copy>
            <group1>
                <xsl:copy-of select="$group1"/>
            </group1>
            <group2>
                <xsl:copy-of select="changeSet except $group1"/>
            </group2>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Result

    <?xml version="1.0" encoding="UTF-8"?>
    <databaseChangeLog>
       <group1>
          <changeSet>
            <sql>ALPHA,BRAVO,CHARLIE</sql>
          </changeSet>
          <changeSet>
            <sql>BRAVO,CHARLIE,DELTA</sql>
          </changeSet>
          <changeSet>
            <sql>FOXTROT,GOLF,HOTEL</sql>
          </changeSet>
       </group1>
       <group2>
          <changeSet>
            <sql>DELTA,ECHO,FOXTROT</sql>
          </changeSet>
       </group2>
    </databaseChangeLog>
    

    Demo: https://xsltfiddle.liberty-development.net/jyRYYiP


    A better solution would be to use tokenize():

    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:param name="tables" select="('BRAVO', 'GOLF')"/>
    
    <xsl:template match="/databaseChangeLog">
        <xsl:variable name="group1" select="changeSet[tokenize(sql, ',') = $tables]" />
        <xsl:copy>
            <group1>
                <xsl:copy-of select="$group1"/>
            </group1>
            <group2>
                <xsl:copy-of select="changeSet except $group1"/>
            </group2>
        </xsl:copy>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Demo: https://xsltfiddle.liberty-development.net/jyRYYiP/1