【问题标题】:XSLT solution needed for: attribute node cannot be created after the children of the containing element需要的 XSLT 解决方案:属性节点不能在包含元素的子元素之后创建
【发布时间】:2014-07-31 22:48:21
【问题描述】:

背景故事是,这是一个正在升级到 ColdFusion 11 的旧 ColdFusion 应用程序。我已经确定我现在看到的主题错误是因为 XSLT 1.0 处理器被允许忽略此错误并通过以下方式“恢复”忽略 xsl:attribute 指令。 (谢谢ref Michael Kay。)

我无法完全理解解决方案,所以这里有一些简化的代码:

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 format。下面的 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>

问题

问题在于,在上面的示例中,可能会在遇到“type”节点之前将“providercode”添加到“provider”节点,导致无法创建“属性节点”在包含元素的子元素之后" error.o

请注意,为了清楚起见,我已经简化了上面的示例。大约有六个具有特定“名称”属性的“var”节点应作为属性插入,而其他简单的节点则直接作为节点添加。

对于如何解决此问题的任何帮助将不胜感激。

提前致谢。

保罗

【问题讨论】:

    标签: xml xslt coldfusion xalan wddx


    【解决方案1】:

    很遗憾,WDDX名称属性的大小写不一致, 所以翻译命令可以解决这个问题,并且 不幸的是,让代码更复杂一点。

    你没有从你的例子中去掉这个(和其他非必需品)太糟糕了 - 请参阅:https://stackoverflow.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> 
    ...
    

    【讨论】:

    • 谢谢,您提供的逻辑正是我完成这项工作所需要的。还要感谢 Holderdarocha 提供了更好的翻译实现,并让我了解了 http://xsltransform.net/ 上的 XSLT Fiddle 工具
    【解决方案2】:

    下面的样式表基于您提供的最小示例和您提到的一些要求(属性名称不一致的情况)。我不确定这是否足以处理您的真实世界文档,但它可能会以某种方式帮助您并可能给您一些想法。

    我使用了三个模板来处理 var 元素,基于它包含的后代类型(arraystructstring),以及一些谓词针对某些属性值(例如 @ 987654328@)。如果应用于您提供的最小示例,它将生成您期望的结果。您可能需要进行一些调整以使其适应您的实际问题。

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

    我将lowerupper 声明为变量,因此使用translate() 函数将字符串转换为小写会更容易。

    您可以在this XSLT fiddle中试用这段代码,试验一下,看看转换的结果。

    【讨论】:

    • 谢谢,我们越来越近了。我看到3个问题。请记住,节点嵌套的深度是可变的。您可以让 var[struct] 节点嵌套 6-7 层深。您的解决方案:1.“type”属性添加到每个 var[struct] 节点,当只有名为“type”的节点应该作为属性添加时 2.所有其他节点名称(有数百个)应该作为新的子节点插入.即你不能专门匹配'providercode'。 3. 嵌入更深的 var[struct] var[struct] 没有被处理并添加到输出中。感谢您的及时和周到的回复。
    • 您是否有涵盖所有这些案例的文档(例如,在 pastebin 或 gist 中)可以用作测试案例?
    猜你喜欢
    • 2012-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-03
    • 1970-01-01
    • 2014-12-05
    • 1970-01-01
    相关资源
    最近更新 更多