XSLT xml到xml分组节点并转换为其他结构

时间:2015-02-10 09:30:49

标签: xml xslt xslt-grouping

我有一个我想要转换的xml文档:

<?xml version="1.0" encoding="UTF-8"?>
<import:configuration xmlns:import="http://schemas.company.com/wsdl/domain/v2/import">
    <import:input>
        <import:file headers="1" group="MAPPING">
            <import:name>file001.txt</import:name>
            <import:separator><![CDATA[;]]></import:separator>
            <import:table>TAB00008_TECSPEC</import:table>
            <import:field primary="true">
                <import:name>VEMAR</import:name>
                <import:target>VEMAR</import:target>
                <import:type dbs="oracle" type="text">VARCHAR(3)</import:type>
                <import:reference>
                    <import:table>TAB00006_TECSPEC</import:table>
                    <import:field>VEMAR</import:field>
                </import:reference>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field primary="true">
                <import:name>VENR</import:name>
                <import:target>VENR</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(7)</import:type>
                <import:reference>
                    <import:table>TAB00006_TECSPEC</import:table>
                    <import:field>VENR</import:field>
                </import:reference>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field primary="true">
                <import:name>KNR</import:name>
                <import:target>KNR</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(9)</import:type>
                <import:reference>
                    <import:table>TAB00003_TECSPEC</import:table>
                    <import:field>KNR</import:field>
                </import:reference>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field>
                <import:name>DATNEU</import:name>
                <import:target>DATNEU</import:target>
                <import:type dbs="oracle" type="date" format="YYYY.MM.DD">DATE</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field>
                <import:name>VTYPE</import:name>
                <import:target>VTYPE</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(1)</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:description><![CDATA[]]></import:description>
        </import:file>
    </import:input>
</import:configuration>

要:

<?xml version="1.0" encoding="UTF-8"?>
<import:configuration xmlns:import="http://schemas.company.com/wsdl/domain/v2/import">
    <import:input>
        <import:file headers="1" group="MAPPING">
            <import:name>file001.txt</import:name>
            <import:separator><![CDATA[;]]></import:separator>
            <import:table>TAB00008_TECSPEC</import:table>
            <import:field primary="true">
                <import:name>VEMAR</import:name>
                <import:target>VEMAR</import:target>
                <import:type dbs="oracle" type="text">VARCHAR(3)</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field primary="true">
                <import:name>VENR</import:name>
                <import:target>VENR</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(7)</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field primary="true">
                <import:name>KNR</import:name>
                <import:target>KNR</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(9)</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field>
                <import:name>DATNEU</import:name>
                <import:target>DATNEU</import:target>
                <import:type dbs="oracle" type="date" format="YYYY.MM.DD">DATE</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:field>
                <import:name>VTYPE</import:name>
                <import:target>VTYPE</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(1)</import:type>
                <import:description><![CDATA[]]></import:description>
            </import:field>
            <import:reference>
                <import:table>TAB00006_TECSPEC</table>
                <import:link>
                    <source>VEMAR</source>
                    <target>VEMAR</target>
                </import:link>
                <import:link>
                    <source>VENR</source>
                    <target>VENR</target>
                </import:link>              
            </import:reference>
            <import:reference>
                <import:table>TAB00003_TECSPEC</table>
                <import:link>
                    <source>KNR</source>
                    <target>KNR</target>
                </import:link>          
            </import:reference>         
            <import:description><![CDATA[]]></import:description>
        </import:file>
    </import:input>
</import:configuration>

我想对所有引用(</import:reference>下的<import:field>)进行分组,并按表格分组,将它们转换为一个元素,如上所述。

我正在阅读此帖子/问题:XML to CSV with XSLT - Grouping nodes但我无法通过此方式获得所需的输出。

我对xslt的了解不是很深。任何人都可以暗示我该怎么做?

2 个答案:

答案 0 :(得分:1)

你的尝试太复杂了。我注意到的事情:

  • 您可以使用<xsl:copy><xsl:copy-of>制作输入节点的副本。您无需手动重新创建它们。
  • 您过度使用XPath函数。没有必要这个具体。
  • 如果您发现自己在单个模板中解决了一个完整的任务,那么您就是在滥用XSLT。 (对于每种编程语言都是如此 - 如果你把所有东西都塞进一个函数中,那就错了。)

您的任务是&#34;我需要输入文件的副本,但只需进行少量修改即可。多种。

这些任务的基础始终是身份转换

<xsl:transform
    version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:transform>

这是输入的逐字副本。现在进行小修改。 XSLT通过模板匹配工作,因此我们需要<xsl:template>来完成我们想要修改的所有内容。

  1. 您希望在最终输出中从<import:reference>中删除<import:field>。这很简单,为他们编写一个不产生输出的模板:

    <xsl:template match="import:reference" />
    
  2. 您希望<import:reference>内有一个新<import:file>,每<import:table>一个。这也不是那么困难。编写一个与import:file匹配的模板,复制大部分模板并为每个import:table组添加内容。

    <xsl:template match="import:file">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()" />
        <xsl:for-each-group select=".//import:reference" group-by="import:table">
          <xsl:copy>
            <xsl:copy-of select="import:table" />
            <import:link>
              <source><xsl:value-of select="../import:target" /></source>
              <target><xsl:value-of select="import:field" /></target>
            </import:link>
          </xsl:copy>
        </xsl:for-each-group>
      </xsl:copy>
    </xsl:template>
    
  3. 把它放在一起:

    <xsl:transform
      version="2.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:import="http://schemas.company.com/wsdl/domain/v2/import"
    >
      <xsl:output method="xml" encoding="UTF-8" indent="yes" />
      <xsl:strip-space elements="*" />
    
      <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="import:file">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()" />
          <xsl:for-each-group select=".//import:reference" group-by="import:table">
            <xsl:copy>
              <xsl:copy-of select="import:table" />
              <import:link>
                <source><xsl:value-of select="../import:target" /></source>
                <target><xsl:value-of select="import:field" /></target>
              </import:link>
            </xsl:copy>
          </xsl:for-each-group>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="import:reference" />
    </xsl:transform>
    

    产生几乎所需的结果:

    <import:configuration xmlns:import="http://schemas.company.com/wsdl/domain/v2/import">
       <import:input>
          <import:file headers="1" group="MAPPING">
             <import:name>file001.txt</import:name>
             <import:separator>;</import:separator>
             <import:table>TAB00008_TECSPEC</import:table>
             <import:field primary="true">
                <import:name>VEMAR</import:name>
                <import:target>VEMAR</import:target>
                <import:type dbs="oracle" type="text">VARCHAR(3)</import:type>
                <import:description/>
             </import:field>
             <import:field primary="true">
                <import:name>VENR</import:name>
                <import:target>VENR</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(7)</import:type>
                <import:description/>
             </import:field>
             <import:field primary="true">
                <import:name>KNR</import:name>
                <import:target>KNR</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(9)</import:type>
                <import:description/>
             </import:field>
             <import:field>
                <import:name>DATNEU</import:name>
                <import:target>DATNEU</import:target>
                <import:type dbs="oracle" type="date" format="YYYY.MM.DD">DATE</import:type>
                <import:description/>
             </import:field>
             <import:field>
                <import:name>VTYPE</import:name>
                <import:target>VTYPE</import:target>
                <import:type dbs="oracle" type="numeric">NUMBER(1)</import:type>
                <import:description/>
             </import:field>
             <import:description/>
             <import:reference>
                <import:table>TAB00006_TECSPEC</import:table>
                <import:link>
                   <source>VEMAR</source>
                   <target>VEMAR</target>
                </import:link>
             </import:reference>
             <import:reference>
                <import:table>TAB00003_TECSPEC</import:table>
                <import:link>
                   <source>KNR</source>
                   <target>KNR</target>
                </import:link>
             </import:reference>
          </import:file>
       </import:input>
    </import:configuration>
    

    这不包括输出中的xmlns:xsi属性(不是原始问题的一部分)。您可以通过编写修改import:configuration

    的模板以相同的方式添加它
    <xsl:template match="import:configuration">
      <xsl:copy>
        <xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance">http://schemas.company.com/wsdl/domain/v2/import C:/Users/Ruben/Downloads/tmp/new/xsd/import_config.xsd</xsl:attribute>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>
    

    您可以将xsi名称空间前缀声明移动到XSLT中的根级别。

答案 1 :(得分:0)

这有效(它使我接近我想要的目标):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns0="http://schemas.company.com/wsdl/domain/v2/import" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="ns0 xs fn">
    <xsl:output method="xml" encoding="UTF-8" byte-order-mark="no" indent="yes"/>
    <xsl:template match="/">
        <configuration xmlns="http://schemas.company.com/wsdl/domain/v2/import">
            <xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance" select="'http://schemas.company.com/wsdl/domain/v2/import C:/Users/Ruben/Downloads/tmp/new/xsd/import_config.xsd'"/>
            <xsl:for-each select="ns0:configuration">
                <input>
                    <xsl:for-each select="ns0:input/ns0:file">
                        <xsl:variable name="var1_group" as="node()?" select="@group"/>
                        <file>
                            <xsl:attribute name="headers" namespace="" select="xs:string(xs:integer(fn:string(@headers)))"/>
                            <xsl:if test="fn:exists($var1_group)">
                                <xsl:attribute name="group" namespace="" select="fn:string($var1_group)"/>
                            </xsl:if>
                            <name>
                                <xsl:sequence select="fn:string(ns0:name)"/>
                            </name>
                            <xsl:for-each select="ns0:directory">
                                <directory>
                                    <xsl:sequence select="fn:string(.)"/>
                                </directory>
                            </xsl:for-each>
                            <separator>
                                <xsl:sequence select="fn:string(ns0:separator)"/>
                            </separator>
                            <table>
                                <xsl:sequence select="fn:string(ns0:table)"/>
                            </table>
                            <xsl:for-each select="ns0:field">
                                <xsl:variable name="var2_primary" as="node()?" select="@primary"/>
                                <field>
                                    <xsl:if test="fn:exists($var2_primary)">
                                        <xsl:attribute name="primary" namespace="" select="xs:string(xs:boolean(fn:string($var2_primary)))"/>
                                    </xsl:if>
                                    <name>
                                        <xsl:sequence select="fn:string(ns0:name)"/>
                                    </name>
                                    <target>
                                        <xsl:sequence select="fn:string(ns0:target)"/>
                                    </target>
                                    <xsl:for-each select="ns0:type">
                                        <type>
                                            <xsl:sequence select="(./@node(), ./node())"/>
                                        </type>
                                    </xsl:for-each>
                                    <description>
                                        <xsl:sequence select="fn:string(ns0:description)"/>
                                    </description>
                                </field>
                            </xsl:for-each>
                            <xsl:for-each select="ns0:field">
                                <xsl:variable name="var3_current" as="node()" select="."/>
                                <xsl:for-each select="ns0:reference">
                                    <reference>
                                        <table>
                                            <xsl:sequence select="fn:string(ns0:table)"/>
                                        </table>
                                        <link>
                                            <source>
                                                <xsl:sequence select="fn:string($var3_current/ns0:target)"/>
                                            </source>
                                            <target>
                                                <xsl:sequence select="fn:string(ns0:field)"/>
                                            </target>
                                        </link>
                                    </reference>
                                </xsl:for-each>
                            </xsl:for-each>
                            <description>
                                <xsl:sequence select="fn:string(ns0:description)"/>
                            </description>
                        </file>
                    </xsl:for-each>
                </input>
                <description>
                    <xsl:sequence select="fn:string(ns0:description)"/>
                </description>
            </xsl:for-each>
        </configuration>
    </xsl:template>
</xsl:stylesheet>