XSLT-решение, необходимое для: атрибутного узла не может быть создан после дочерних элементов содержащего элемента

Предыстория состоит в том, что это старое приложение ColdFusion, которое обновляется до ColdFusion 11. Я уже определил предметную ошибку, которую я сейчас вижу, потому что процессорам XSLT 1.0 было разрешено игнорировать эту ошибку и "восстанавливать", игнорируя xsl: инструкция атрибута. (Спасибо, реф. Майкл Кей.)

Я не могу полностью обдумать решение, поэтому вот несколько упрощенных кодов:

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>

Вы можете распознать это как старый формат WDDX. XSLT ниже преобразует этот уродливый, глубоко вложенный формат в более чистый / простой формат XML.

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>

К сожалению, случай с атрибутами атрибутов WDDX не согласуется, поэтому здесь есть команды преобразования, которые, к сожалению, делают код немного более сложным.

Ожидаемый результат

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

Эта проблема

Проблема возникает из-за того факта, что в приведенном выше примере "код поставщика" может быть добавлен к узлу "поставщика" до того, как будет обнаружен узел "типа", в результате чего "узел атрибута не может быть создан после дочерних элементов содержащего элемент"error.o

Обратите внимание, что я упростил приведенный выше пример для ясности. Существует примерно полдюжины узлов "var" с определенными атрибутами "name", которые должны быть вставлены как атрибуты, в то время как другие простые добавляются непосредственно как узлы.

Любая помощь в решении этой проблемы будет принята с благодарностью.

Заранее спасибо.

Павел

2 ответа

Решение

К сожалению, случай с атрибутами атрибутов WDDX не согласуется, поэтому здесь есть команды преобразования, которые, к сожалению, делают код немного более сложным.

Жаль, что вы не взяли это (и другие несущественные) из своего примера - см.: https://stackru.com/help/mcve

IIUC, что-то вроде следующего решит вашу проблему. Идея состоит в том, чтобы применять шаблоны, создающие атрибуты, перед шаблонами, которые создают дочерние элементы.

...
<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> 
...

Приведенная ниже таблица стилей основана на приведенном вами минимальном примере и некоторых упомянутых вами требованиях (несовместимые случаи имен атрибутов). Я не уверен, что этого достаточно для работы с документами реального мира, но это может как-то помочь вам и, возможно, дать вам некоторые идеи.

Я использовал три шаблона, чтобы справиться с var элемент, основанный на типе потомка, который он содержит (array, struct или же string), а некоторые предикаты проверяют некоторые значения атрибутов (например, providercode). Если применить минимальный пример, который вы предоставили, он даст ожидаемый результат. Возможно, вам придется внести некоторые коррективы, чтобы адаптировать его к вашей реальной проблеме.

<?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>

Я объявил lower а также upper в качестве переменных, поэтому было бы проще использовать translate() функция для преобразования строк в нижний регистр.

Вы можете опробовать этот код в этой скрипте XSLT, поэкспериментировать и посмотреть результаты преобразования.

Другие вопросы по тегам