使用XSLT在组级别合并多个XML文件

时间:2013-05-31 21:00:15

标签: xml xslt

首先,请允许我说我很喜欢阅读有关合并多个XML文件的几十个提示。我也很喜欢实施其中的很多。但我还没有实现我的目标。

我不想简单地合并XML文件,以便在生成的XML文件中重复使用XML文件。我有需要合并的重复元素的组:

<SAN>
  <EQLHosts>
    <WindowsHosts>
      <WindowsHost>
        more data and structures down here...
      </WindowsHost>
    </WindowsHosts>
    <LinuxHosts>
      <LinuxHost>
        ...and here...
      </LinuxHost>
    </LinuxHosts>
  </EQLHosts>
</SAN>

每个单独的XML文件可能都有Windows和/或Linux主机。因此,如果XML文件1包含Windows主机 A B C 的数据,则XML文件2包含Windows主机 D的数据 E F ,生成的XML应如下所示:

<SAN>
  <EQLHosts>
    <WindowsHosts>
      <WindowsHost>
        <Name>A</Name>
      </WindowsHost>
      <WindowsHost>
        <Name>B</Name>
      </WindowsHost>
      <WindowsHost>
        <Name>C</Name>
      </WindowsHost>
      <WindowsHost>
        <Name>D</Name>
      </WindowsHost>
      <WindowsHost>
        <Name>E</Name>
      </WindowsHost>
      <WindowsHost>
        <Name>F</Name>
      </WindowsHost>
    </WindowsHosts>
    <LinuxHosts>
      <LinuxHost/>
    </LinuxHosts>
  </EQLHosts>
</SAN>

我已经使用过这个XSLT来实现这个目的:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:variable name="file1" select="document('CorralData1.xml')"/>
  <xsl:variable name="file2" select="document('CorralData2.xml')"/>
  <xsl:variable name="file3" select="document('CorralData3.xml')"/>

  <xsl:template match="/">
    <SAN>
      <xsl:copy-of select="/SAN/*"/>
      <xsl:copy-of select="$file1/SAN/*"/>
      <xsl:copy-of select="$file2/SAN/*"/>
      <xsl:copy-of select="$file3/SAN/*"/>
    </SAN>
  </xsl:template>

</xsl:stylesheet>

此文件生成一个组合的XSLT,正确地包含树中的所有数据,但具有多个WindowsHosts实例。不要那样。

有没有办法告诉XSLT如何用最少的语法执行此操作,或者我是否需要在XSLT文件中专门添加每个元素和子元素?


我应该检查一下。但我继续使用collection()并使用Saxon HE XSLT处理器获得了完美的解决方案。

但是我在InfoPath环境中运行,并且只有一个XSLT 1.0处理器。有没有人建议在XSLT 1.0环境中替换collection()命令?我可以以某种方式回到使用document()吗?


所以我现在有这个文件......

<?xml version="1.0" encoding="windows-1252"?>

<files>
    <file name="CorralData1.xml"/>
    <file name="CorralData2.xml"/>
</files>

...我使用包含...的样式表

<xsl:variable name="windowsHosts" select="/SAN/WindowsHosts/WindowsHost"/>
<xsl:variable name="vmwareHosts" select="/SAN/VMwareHosts/VMwareHost"/>
<xsl:variable name="linuxHosts" select="/SAN/LinuxHosts/LinuxHost"/>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="/">
    <xsl:for-each select="/files/file">
        <xsl:apply-templates select="document(@name)/SAN"/>
    </xsl:for-each>
    <SAN>
        <EQLHosts>
            <WindowsHosts>
                <xsl:for-each select="$windowsHosts">
                    <xsl:copy-of select="."/>
                </xsl:for-each>
            </WindowsHosts>
            <VMwareHosts>
                <xsl:for-each select="$vmwareHosts">
                    <xsl:copy-of select="."/>
                </xsl:for-each>                 
            </VMwareHosts>
            <LinuxHosts>
                <xsl:for-each select="$linuxHosts">
                    <xsl:copy-of select="."/>
                </xsl:for-each>                 
            </LinuxHosts>
        </EQLHosts>
    </SAN>
</xsl:template>

...但这会让我获得多个/ SAN根源。我很接近,但有些事情仍然有点不对。

2 个答案:

答案 0 :(得分:2)

我要做的是使用distinct-values()获取每个唯一的主机名。您也可以使用collection()使其更容易一些。 (用法可能因实施而异。我使用了Saxon 9.4。)

示例...

输入目录“input_dir” ...

中的文件

<强> CorralData1.xml

<SAN>
    <EQLHosts>
        <WindowsHosts>
            <WindowsHost>
                <Name>Windows-A</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-B</Name>
            </WindowsHost>
        </WindowsHosts>
        <LinuxHosts>
            <LinuxHost>
                <Name>Linux-A</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-B</Name>
            </LinuxHost>
        </LinuxHosts>
    </EQLHosts>
</SAN>

CorralData2.xml (重复Windows-A和Windows-B)

<SAN>
    <EQLHosts>
        <WindowsHosts>
            <WindowsHost>
                <Name>Windows-C</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-D</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-A</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-B</Name>
            </WindowsHost>
        </WindowsHosts>
        <LinuxHosts>
            <LinuxHost>
                <Name>Linux-C</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-D</Name>
            </LinuxHost>
        </LinuxHosts>
    </EQLHosts>
</SAN>

CorralData3.xml (重复Windows-A和Windows-B)

<SAN>
    <EQLHosts>
        <WindowsHosts>
            <WindowsHost>
                <Name>Windows-E</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-F</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-A</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-B</Name>
            </WindowsHost>          
        </WindowsHosts>
        <LinuxHosts>
            <LinuxHost>
                <Name>Linux-E</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-F</Name>
            </LinuxHost>
        </LinuxHosts>
    </EQLHosts>
</SAN>

XSLT 2.0

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:variable name="collection">
        <xsl:copy-of select="collection('input_dir?strip-space=yes;select=*.xml')/*"/>
    </xsl:variable>
    <xsl:variable name="windowsHosts" select="distinct-values($collection/SAN/EQLHosts/WindowsHosts/WindowsHost/Name)"/>
    <xsl:variable name="linuxHosts" select="distinct-values($collection/SAN/EQLHosts/LinuxHosts/LinuxHost/Name)"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/">
        <SAN>
            <EQLHosts>
                <WindowsHosts>
                    <xsl:for-each select="$windowsHosts">
                        <xsl:apply-templates select="($collection/SAN/EQLHosts/WindowsHosts/WindowsHost[Name=current()])[1]"/>
                    </xsl:for-each>
                </WindowsHosts>
                <LinuxHosts>
                    <xsl:for-each select="$linuxHosts">
                        <xsl:apply-templates select="($collection/SAN/EQLHosts/LinuxHosts/LinuxHost[Name=current()])[1]"/>
                    </xsl:for-each>                 
                </LinuxHosts>
            </EQLHosts>
        </SAN>
    </xsl:template>

</xsl:stylesheet>

<强>输出

<SAN>
    <EQLHosts>
        <WindowsHosts>
            <WindowsHost>
                <Name>Windows-A</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-B</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-C</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-D</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-E</Name>
            </WindowsHost>
            <WindowsHost>
                <Name>Windows-F</Name>
            </WindowsHost>
        </WindowsHosts>
        <LinuxHosts>
            <LinuxHost>
                <Name>Linux-A</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-B</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-C</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-D</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-E</Name>
            </LinuxHost>
            <LinuxHost>
                <Name>Linux-F</Name>
            </LinuxHost>
        </LinuxHosts>
    </EQLHosts>
</SAN>

答案 1 :(得分:0)

我使用了两个XSLT文件进行此操作。第一个只是附加所有文件:

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="/">
    <SAN>
        <xsl:apply-templates select="document('MainDataSource.xml')/SAN/*"/>
        <xsl:apply-templates select="document('CorralData1.xml')/SAN/*"/>
        <xsl:apply-templates select="document('CorralData2.xml')/SAN/*"/>
    </SAN>
</xsl:template>

,第二个按组合并数据:

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="*">
    <SAN>
        <ClientProfile>
        </ClientProfile>
        <STACKMEMBERS>
            <xsl:for-each select="/SAN/STACKMEMBERS/STACKMEMBER">
                <xsl:copy-of select="."/>
            </xsl:for-each>
        </STACKMEMBERS>
        <Force10StackMembers>
            <xsl:for-each select="/SAN/Force10StackMembers/Force10StackMember">
                <xsl:copy-of select="."/>
            </xsl:for-each>
        </Force10StackMembers>
    </SAN>
</xsl:template>