2
votes

The backstory is that this is an old ColdFusion Application that is being upgraded to ColdFusion 11. I've already determined the subject error I'm now seeing is because XSLT 1.0 processors were allowed to ignore this error and "recover" by ignoring the xsl:attribute instruction. (Thank you ref Michael Kay.)

I can't quite wrap my brain around a solution, so here's some simplified code:

The XML

<?xml version="1.0" encoding="UTF-8"?>
<Organization>
    <Subscriber>
        <data>
            <struct>
                <var name='DEPENDENT'>
                    <array length='1'>
                        <struct>
                            <var name='PROVIDER'>
                                <struct>
                                    <var name='PROVIDERCODE'>
                                        <string>A4321</string>
                                    </var>
                                    <var name='TYPE'>
                                        <string>Primary Care Provider</string>
                                    </var>
                                </struct>
                            </var>
                        </struct>
                    </array>
                </var>
            </struct>
        </data>
    </Subscriber>
</Organization>

You may recognize this as old WDDX format. The XSLT below converts this ugly, deeply-nested format to a cleaner/simpler XML format.

The XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="/">
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="var">
    <xsl:choose>
        <xsl:when test="translate(@name, 'TEX', 'tex') = 'text'">
            <xsl:value-of select="./string"/>
        </xsl:when>
        <xsl:when test="translate(@name, 'TYPE', 'type') = 'type'">
            <xsl:attribute name="type">
                <xsl:value-of select="./string"/>
            </xsl:attribute>
        </xsl:when>        
        <xsl:when test="child::array">
            <xsl:apply-templates/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:element name="{translate(@name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')}">
                <xsl:apply-templates/>
            </xsl:element>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="struct">
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="array">
    <xsl:param name="parentname" select="translate(../@name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')"/>
    <xsl:for-each select="struct">
        <xsl:element name="{$parentname}">
            <xsl:if test="./var[translate(@name, 'RELATIONSHPCD', 'relationshpcd') = 'relationshipcode']">
                <xsl:attribute name="relationshipcode"><xsl:value-of select="./var[translate(@name, 'RELATIONSHPCD', 'relationshpcd') = 'relationshipcode']"/></xsl:attribute>
            </xsl:if>
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:for-each>
</xsl:template>

<xsl:template match="string|number">
    <xsl:value-of select="."/>
</xsl:template>

<xsl:template match="boolean">
    <xsl:value-of select="@value"/>
</xsl:template>

<xsl:template match="Subscriber">
    <subscriber>
        <xsl:apply-templates/>
    </subscriber>
</xsl:template>

<xsl:template match="Organization">
    <organization>
        <xsl:apply-templates/>
    </organization>
</xsl:template>
</xsl:stylesheet>

Unfortunately, the case of the WDDX name attributes is inconsistent, so the translate commands are there to address that, and unfortunately, make the code a bit more complex.

The Expected Output

<?xml version="1.0" encoding="UTF-8"?>
<organization>
    <subscriber>
        <dependent>
            <provider type="Primary Care Provider">
                <providercode>A4321</providercode>
            </provider>
        </dependent>
    </subscriber>
</organization>

The Problem

The problem arises from the fact that, in the example above, the "providercode" could get added to the "provider" node before the "type" node gets encountered, resulting in the "attribute node cannot be created after the children of the containing element" error.o

Note that I've simplified the example above for clarity. There are roughly a half-dozen "var" nodes with specific "name" attributes that should be inserted as attributes while the others simple get added directly as nodes.

Any help in how to solve this would be greatly appreciated.

Thanks in advance.

Paul

2

2 Answers

2
votes

Unfortunately, the case of the WDDX name attributes is inconsistent, so the translate commands are there to address that, and unfortunately, make the code a bit more complex.

It's too bad you did not take this (and other non-essentials) out of your example - see: https://stackoverflow.com/help/mcve

IIUC, something like the following would solve your problem. The idea is to apply templates creating attributes before templates that create children.

...
<xsl:template match="var[not(@name='TYPE')]">
    <xsl:choose>
        <xsl:when test="translate(@name, 'TEX', 'tex') = 'text'">
            <xsl:value-of select="./string"/>
        </xsl:when>
        <xsl:when test="child::array">
            <xsl:apply-templates/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:element name="{translate(@name,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')}">
                <xsl:apply-templates select="struct/var[@name='TYPE']"/>
                <xsl:apply-templates/>
            </xsl:element>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="var[@name='TYPE']">
    <xsl:attribute name="type">
        <xsl:value-of select="./string"/>
    </xsl:attribute>
</xsl:template>

<xsl:template match="struct">
    <xsl:apply-templates select="var[not(@name='TYPE')]"/>
</xsl:template> 
...
0
votes

The stylesheet below is based on the minimal example you provided and some of the requirements you mentioned (inconsistent cases of attribute names). I'm not sure if that is sufficient to deal with your real world documents, but it may help you out somehow and possibly give you some ideas.

I used three templates to deal with the var element, based on the kind of descendant it contains (array, struct or string), and some predicates test against some attribute values (such as providercode). If applied to the minimal example you provided, it will generate the result you expect. You might have to make some adjustments to adapt it to your actual problem.

<?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:strip-space elements="*"/>

    <xsl:variable name="lower">abcdefghijklmnopqrstuvwxyz</xsl:variable>
    <xsl:variable name="upper">ABCDEFGHIJKLMNOPQRSTUVWXYZ</xsl:variable>

    <xsl:template match="/">
        <organization>
            <subscriber>
                <xsl:apply-templates select="Organization/Subscriber/data/struct/var"/>
            </subscriber>
        </organization>
    </xsl:template>

    <xsl:template match="var[array]">
        <xsl:element name="{translate(@name,$upper,$lower)}">
            <xsl:apply-templates select="array/struct/var"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="var[struct]">
        <xsl:element name="{translate(@name,$upper,$lower)}">
            <xsl:attribute name="type">
                <xsl:value-of select="struct/var[translate(@name,$upper,$lower)='type']/string"/>
            </xsl:attribute>
            <xsl:apply-templates select="struct/var"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="var[string][translate(@name,$upper,$lower) = 'providercode']">
        <xsl:element name="{translate(@name,$upper,$lower)}">
            <xsl:value-of select="string"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="string"/>
</xsl:stylesheet>

I declared lower and upper as variables so it would be easier to use the translate() function to convert strings to lowercase.

You can try out this code in this XSLT fiddle, experiment and see the results of the transformation.