0

I have a XML file which looks roughly like this (actual file is much more complex, everything has been truncated in this example):

<?xml version="1.0" encoding="utf-8"?>
<root>
    <element>
        <tag1>1</tag1>
        <tag2>stuff</tag2>
        <type>String</type>
        <tag3>stuff</tag3>
    </element>
    <element>
        <tag1>2</tag1>
        <tag2>stuff</tag2>
        <type>String</type>
        <type>Date</type>
        <type>Float</type>
        <tag3>stuff</tag3>
    </element>
    <element>
        <tag1>3</tag1>
        <tag2>stuff</tag2>
        <type>DateTime</type>
        <tag3>stuff</tag3>
    </element>
    <element>
        <tag1>4</tag1>
        <tag2>stuff</tag2>
        <type>Float</type>
        <type>String</type>
        <type>Date</type>
        <tag3>stuff</tag3>
    </element>
</root>

I process it with the following XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="element">
        <xsl:element name="xs:element">
            <xsl:attribute name="type"><xsl:call-template name="type"/></xsl:attribute>
        </xsl:element>
    </xsl:template>

    <xsl:template name="type">
        <xsl:variable name="initialType" select="translate(type,' ','')"/>
        <xsl:choose>
            <xsl:when test="$initialType='String'">
                <xsl:text>xs:string</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='Date'">
                <xsl:text>xs:date</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='DateTime'">
                <xsl:text>xs:dateTime</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='Float'">
                <xsl:text>xs:float</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='Integer'">
                <xsl:text>xs:int</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$initialType"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

And I get this resulting file:

<?xml version="1.0" encoding="UTF-8"?>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:float"/>

My problem here is that only the first <type> tag is taken into account. What I would like is to concatenate all the type tag contents into the type tag of the output, preceded by a sign indicating that the tag is an agglomerate if applicable. However, to avoid creating artificially numerous types, the content of the tags must be alphabetically sorted first. In this example, the <element> number 2 and 4 are both made of only Float, String, and Date, albeit in a different order. They need to have the same type in the output.

The following output would be acceptable:

<?xml version="1.0" encoding="UTF-8"?>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:datexs:floatxs:string"/>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
    <xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:datexs:floatxs:string"/>

I am very new to XLST, and I have not managed to get anywhere close to the desired output so far. The code I have tried is just below, and fails horribly, notably because I failed to understand how to get <xsl:sort> working:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="element">
        <xsl:element name="xs:element">
            <xsl:attribute name="type">
                    <xsl:apply-templates>
                      <xsl:sort select="."/>
                    </xsl:apply-templates>
                    <xsl:call-template name="type"/>
                </xsl:attribute>
        </xsl:element>
    </xsl:template>

    <xsl:template name="type">
        <xsl:choose>
            <xsl:when test="following-sibling::type">
                <xs:text>union</xs:text>
                <xsl:for-each select="following-sibling::type">
                    <xs:text>translate(type,' ','')</xs:text>
                </xsl:for-each>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="initialType" select="translate(type,' ','')"/>
                <xsl:choose>
                    <xsl:when test="$initialType='String'">
                        <xsl:text>xs:string</xsl:text>
                    </xsl:when>
                    <xsl:when test="$initialType='Date'">
                        <xsl:text>xs:date</xsl:text>
                    </xsl:when>
                    <xsl:when test="$initialType='DateTime'">
                        <xsl:text>xs:dateTime</xsl:text>
                    </xsl:when>
                    <xsl:when test="$initialType='Float'">
                        <xsl:text>xs:float</xsl:text>
                    </xsl:when>
                    <xsl:when test="$initialType='Integer'">
                        <xsl:text>xs:int</xsl:text>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$initialType"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
Efferalgan
  • 1,681
  • 1
  • 14
  • 24

2 Answers2

1

Just a few adjustments on your existing code were needed.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="element">
        <xsl:element name="xs:element">
            <xsl:attribute name="type">
                <xsl:variable name="sorted">
                    <xsl:for-each select="type">
                        <xsl:sort select="."/>
                        <xsl:copy-of select="."/>    
                    </xsl:for-each>
                </xsl:variable>
                <xsl:apply-templates select="$sorted/type"/>
            </xsl:attribute>
        </xsl:element>
    </xsl:template>

    <xsl:template match="type">
        <xsl:variable name="initialType" select="translate(., ' ', '')"/>
        <xsl:if test="count(preceding-sibling::type) = 0 and count(following-sibling::type) > 0">
            <xsl:text>union</xsl:text>
        </xsl:if>
        <!-- HINT remove if you dont want any seperator -->
        <xsl:if test="count(preceding-sibling::type) > 0">
            <xsl:text> </xsl:text>
        </xsl:if>
        <xsl:choose>
            <xsl:when test="$initialType='String'">
                <xsl:text>xs:string</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='Date'">
                <xsl:text>xs:date</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='DateTime'">
                <xsl:text>xs:dateTime</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='Float'">
                <xsl:text>xs:float</xsl:text>
            </xsl:when>
            <xsl:when test="$initialType='Integer'">
                <xsl:text>xs:int</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$initialType"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

Please verify, for me i get the output (see HINT inline in XSLT):

<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:date xs:float xs:string"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="xs:dateTime"/>
<xs:element xmlns:xs="http://www.w3.org/2001/XMLSchema" type="unionxs:date xs:float xs:string"/>
uL1
  • 2,117
  • 2
  • 17
  • 28
  • This works fine on my example file. However, it does not solve my whole problem: the 2nd and 4th elements share basically the same types, but not in the same order and that results in them having different types in the output, which is not wanted. Is there any way to order the types before concatenating? – Efferalgan Sep 26 '16 at 09:16
  • i added "the"/a sorting. – uL1 Sep 26 '16 at 09:17
  • You are welcome. Maybe you can merge it with the solution of @Martin Honnen. It's a bit more elegant for creating the mappings. – uL1 Sep 26 '16 at 09:42
  • For some reason, what works on the simple example file does not on the real one. I'll take a lot at Martin's answer and try to mix it with yours. – Efferalgan Sep 26 '16 at 09:50
  • If possible, post your orignal file via pastebin and we might help you out. – uL1 Sep 26 '16 at 09:53
  • Thank you but I'm afraid this is not possible, the input file is somehow confidential, I'm unsure how much. I'll figure it out on my own, now that the biggest part is done. Side note: in your code, `count(following-sibling::type) > 1` will only work if there are at least 3 ``; this value should be changed to zero. – Efferalgan Sep 26 '16 at 10:15
  • Hi, it's me again. This code works fine when running it from Altova XMLSpy, however MSXSL throws me the error `Expression must evaluate to a node-set: -->$sorted<--/type`. Any idea on how to fix this? – Efferalgan Oct 24 '16 at 13:55
  • 1
    Try: add `xmlns:msxsl="urn:schemas-microsoft-com:xslt"` in `` + change to ``. There are some other extentions as well. See http://exslt.org/exsl/functions/node-set/ – uL1 Oct 24 '16 at 14:51
  • Thanks, it works. I tried some [fancy stuff](http://stackoverflow.com/a/92511/3728414) after reading your comment, didn't work, I'll stick to your answer, it'll do the trick. – Efferalgan Oct 24 '16 at 15:43
1

I would do it like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="element">
        <xsl:element name="xs:element">
            <xsl:attribute name="type">
                <xsl:apply-templates select="type">
                    <xsl:sort select="."/>
                </xsl:apply-templates>
            </xsl:attribute>
        </xsl:element>
    </xsl:template>

    <xsl:template match="type[translate(., ' ', '') = 'String']">xs:string</xsl:template>

    <xsl:template match="type[translate(., ' ', '') = 'Date']">xs:date</xsl:template>

    <xsl:template match="type[translate(., ' ', '') = 'DateTime']">xs:dateTime</xsl:template>

    <xsl:template match="type[translate(., ' ', '') = 'Float']">xs:float</xsl:template>

    <xsl:template match="type[translate(., ' ', '') = 'Integer']">xs:int</xsl:template>

    <xsl:template match="type">
        <xsl:value-of select="translate(., ' ', '')"/>
    </xsl:template>

</xsl:stylesheet>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110