【问题标题】:Nested loops in XSLT for dynamically building XMLXSLT 中用于动态构建 XML 的嵌套循环
【发布时间】:2012-09-06 09:56:39
【问题描述】:

我从 Cognos 获得一个 XML 文档输出,我想将其用作 Crystal 报表的输入。 但是,Crystal Report 需要的 XML 格式与 Cognos 输出的 XML 格式不同。

我正在尝试使用 XSLT 转换输入 XML 文档 (Cognos) 以获得 Crystal 所需的 XML。

设置上下文后,下面是来自 Cognos 的输入 XML:

<?xml version="1.0"?>
<dataset>
<metadata>
    <item Name="EmpId" />
    <item Name="EmpName" />
    <item Name="DeptName" />
</metadata>
<data>
    <rows>
        <row>
            <value>1</value>
            <value>John</value>
            <value>Finance</value>
        </row>
        <row>
            <value>2</value>
            <value>Peter</value>
            <value>Admin</value>
        </row>
    </rows>
</data>

Crystal Report 所需的 XML 格式:

<?xml version="1.0"?>
<dataset>
<row>
    <EmpId>1</EmpId>
    <EmpName>John</EmpName>
    <DeptName>Finance</DeptName>
</row>
<row>
    <EmpId>2</EmpId>
    <EmpName>Peter</EmpName>
    <DeptName>Admin</DeptName>
</row>
</dataset>

我在 XSLT 下面写了所需的转换:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<dataset>
<xsl:for-each select="./dataset/data/rows/row">
    <row>
        <xsl:for-each select="/dataset/metadata/item">
            <xsl:element name="{@Name}">
                <xsl:for-each select="/dataset/data/rows/row/value">
                    <xsl:value-of select="."/>
                </xsl:for-each>                 
            </xsl:element>
        </xsl:for-each>             
    </row>
</xsl:for-each>
</dataset>
</xsl:template>
</xsl:stylesheet>

我低于输出:

<?xml version="1.0" encoding="UTF-16"?>
<dataset>
<row>
    <EmpId>1JohnFinance2PeterAdmin</EmpId>
    <EmpName>1JohnFinance2PeterAdmin</EmpName>
    <DeptName>1JohnFinance2PeterAdmin</DeptName>
</row>
<row>
    <EmpId>1JohnFinance2PeterAdmin</EmpId>
    <EmpName>1JohnFinance2PeterAdmin</EmpName>
    <DeptName>1JohnFinance2PeterAdmin</DeptName>
</row>

我哪里出错了?

【问题讨论】:

    标签: xslt loops dynamic nested


    【解决方案1】:

    这个简短而简单的转换

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:variable name="vNames" select="/*/metadata/*/@Name"/> 
    
     <xsl:template match="/*/data">
         <dataset><xsl:apply-templates/></dataset>
     </xsl:template>
    
     <xsl:template match="row">
      <row><xsl:apply-templates/></row>
     </xsl:template>
    
     <xsl:template match="row/*">
      <xsl:variable name="vPos" select="position()"/>
      <xsl:element name="{$vNames[$vPos]}"><xsl:apply-templates/></xsl:element>
     </xsl:template>
    </xsl:stylesheet>
    

    应用于提供的 XML 文档时(添加了缺少的结束标记以使其格式正确):

    <dataset>
        <metadata>
            <item Name="EmpId" />
            <item Name="EmpName" />
            <item Name="DeptName" />
        </metadata>
        <data>
            <rows>
                <row>
                    <value>1</value>
                    <value>John</value>
                    <value>Finance</value>
                </row>
                <row>
                    <value>2</value>
                    <value>Peter</value>
                    <value>Admin</value>
                </row>
            </rows>
        </data>
    </dataset>
    

    产生想要的正确结果:

    <dataset>
       <row>
          <EmpId>1</EmpId>
          <EmpName>John</EmpName>
          <DeptName>Finance</DeptName>
       </row>
       <row>
          <EmpId>2</EmpId>
          <EmpName>Peter</EmpName>
          <DeptName>Admin</DeptName>
       </row>
    </dataset>
    

    说明

    1. 使用模板和 XSLT 模板选择机制来完成这项工作。作为 XSLT 中的一项规则,我们更喜欢 xsl:apply-templates 而不是 xsl:for-each —— 从而获得更简单、更可扩展、更易理解和可维护的代码。这是一个几乎 100% 的“推送式”解决方案示例。

    2. 使用 xsl:variable 获取(一次且永远)我们将经常使用的节点。

    3. position() 保存在变量中以供以后在其他上下文中使用 -- position() 取决于上下文。

    【讨论】:

    • 你好Dimitre,将Name-path写入变量vNames会不会更好?
    • @Peter,对不起,你是什么意思?请改写。
    • @Peter,从节点集中选择第 N 个节点比评估具有多个反向步骤的复杂 XPath 表达式更有效——每次我们处理 value
    • @Peter:两者都有。可读性和便利性。更短更简单。更灵活——如果需要更改选择必要名称的 XPath 表达式(由于 XML 文档的重组),那么我只需在一处(在变量定义中)更改 XPath 表达式。所有剩余的代码都将保持不变。
    • @DimitreNovaatchev,感谢您的解决方案和解释。但是,我删除了 codeomit-xml-declaration="yes" 以在输出 XML 中获得 code code
    【解决方案2】:

    我会使用&lt;template&gt;&lt;apply-templates&gt; 机制来解决这个问题。我认为For-each 不是正确的方式。

    XML 输入:

    <?xml version="1.0"?>
    <dataset>
    <metadata>
        <item Name="EmpId" />
        <item Name="EmpName" />
        <item Name="DeptName" />
    </metadata>
    <data>
        <rows>
            <row>
                <value>1</value>
                <value>John</value>
                <value>Finance</value>
            </row>
            <row>
                <value>2</value>
                <value>Peter</value>
                <value>Admin</value>
            </row>
        </rows>
    </data>
    </dataset>  
    

    将此样式表应用到它:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:template match="/">
        <dataset>
            <xsl:apply-templates select="//row"/>
        </dataset>
    </xsl:template>
    
    <xsl:template match="row">
        <row>
            <xsl:apply-templates select="value"/>
        </row>
    </xsl:template>
    
    <xsl:template match="value">
        <xsl:variable name="index">
            <xsl:number/>
        </xsl:variable>
        <xsl:element name="{../../../../metadata/item[position() = $index]/@Name}">
            <xsl:apply-templates select="@* | node()"/>
        </xsl:element>
    </xsl:template>
    
    </xsl:stylesheet>
    

    你得到这个输出:

    <?xml version="1.0" encoding="utf-8"?>
    <dataset>
    <row>
        <EmpId>1</EmpId>
        <EmpName>John</EmpName>
        <DeptName>Finance</DeptName>
    </row>
    <row>
        <EmpId>2</EmpId>
        <EmpName>Peter</EmpName>
        <DeptName>Admin</DeptName>
    </row>
    </dataset>
    

    我通过匹配和应用某些模板创建的datasetrow 元素。

    【讨论】:

    • 您可以在此处使用 xsl:key,以使查找元素名称更整洁、更高效。
    • Peter,与其使用//row 作为匹配模式,不如使用row 更简单(且等效)。
    • 感谢 Dimitre 的提示,但如果我使用 row 运行上述 XSL,则输出 XML 为空,它似乎需要 //
    • @Peter,我说的只是 匹配模式,而不是 xsl:apply-templatesselect 属性
    • 使用 position() 可能比使用 xsl:number 更有效,尤其是当 xsl:number 在结果树片段内时,这意味着您要将数字转换为字符串以保存为树在变量中,当您引用该变量时,您首先将其转换为字符串,最后转换为数字。
    【解决方案3】:

    自定义@DimitreNovatchev提供的解决方案后,我能够得到所需的XML转换。

    输入 XML

    <?xml version="1.0"?>
    <dataset  xmlns="http://developer.cognos.com/schemas/xmldata/1/"  xmlns:xs="http://www.w3.org/2001/XMLSchema-instance">
    <metadata>
        <item name="Employee Id" />
        <item name="Employee Name" />
        <item name="Department Name" />
    </metadata>
    <data>      
        <row>
            <value>1</value>
            <value Salutation="Dr." >John</value>
            <value>Finance</value>
        </row>
        <row>
            <value>2</value>
            <value Salutation="Mr." >Peter</value>
            <value>Admin</value>
        </row>      
    </data>
    

    XSLT 转换

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:c="http://developer.cognos.com/schemas/xmldata/1/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:variable name="vNames" select="/*/c:metadata/*/@name" />
    <xsl:template match="/*/c:data">
    <dataset>
        <xsl:apply-templates/>
    </dataset>
    </xsl:template>
    <xsl:template match="c:row">
    <row>
        <xsl:apply-templates/>
    </row>
    </xsl:template>
    <xsl:template match="c:row/*">
    <xsl:variable name="vPos" select="position()"/>
    <xsl:element name="{translate($vNames[$vPos], ' ', '_')}">
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates/>
    </xsl:element>
    </xsl:template>
    <xsl:template match="@*">
    <xsl:attribute name="{name()}">
        <xsl:value-of select="." />
    </xsl:attribute>
    </xsl:template>
    

    样式表>

    输出 XML

    <?xml version="1.0" encoding="UTF-16"?>
    <dataset xmlns:c="http://developer.cognos.com/schemas/xmldata/1/">
    <row>
        <Employee_Id>1</Employee_Id>
        <Employee_Name Salutation="Dr.">John</Employee_Name>
        <Department_Name>Finance</Department_Name>
    </row>
    <row>
        <Employee_Id>2</Employee_Id>
        <Employee_Name Salutation="Mr.">Peter</Employee_Name>
        <Department_Name>Admin</Department_Name>
    </row>
    

    【讨论】:

    • 感谢这帮助我将 Cognos Report Studio 数据导出为 XML。但是请注意:Input XML 末尾缺少“”,而 XSLT 转换 末尾缺少“”。 online xslt tester tool 给了我 XML 文档结构必须在同一个实体内开始和结束错误,直到我更正
    猜你喜欢
    • 1970-01-01
    • 2012-03-09
    • 2018-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多