【问题标题】:Merge multiple XML files at group levels with XSLT使用 XSLT 在组级别合并多个 XML 文件
【发布时间】:2013-05-31 21:00:14
【问题描述】:

首先,让我说我喜欢阅读有关合并多个 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 主机 ABC 的数据,而 XML 文件 2 具有 Windows 主机 D 的数据EF,生成的 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 根。我已经很接近了,但还有一点点不对劲。

【问题讨论】:

    标签: xml xslt


    【解决方案1】:

    我要做的是使用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>
    

    【讨论】:

    • 为什么变量不简单设置为&lt;xsl:variable name="collection" select="collection('input_dir?strip-space=yes;select=*.xml')/*"/&gt;?执行copy-of 似乎没有必要且效率低下。
    • @MartinHonnen - 当我最初执行 select 时,我遇到了变量上的 xpaths 问题。我很着急,所以我使用了copy-of,它最终创建了一个结果树片段,使我的 xpath 工作。它应该被重构,但我并不太担心。
    • 与带有collection() 的XSLT 2.0 不同,它给了我正确的结果,我不能让XSLT 1.0 完全到达我想要的地方。我在原来的问题中添加了新信息。任何帮助将不胜感激。
    【解决方案2】:

    我为此操作使用了两个 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>
    

    【讨论】:

    • 我有一个新手附录问题。如何使文档名称成为变量,以收集目录中的所有 *.xml 文件?
    • @LOlliffe 如果您有一个列出目录中所有文件的 XML 文件,您可以在其上使用xsl:for-each(将之前的select="." 存储在xsl:for-each 范围之外的变量中) )。否则,您将不得不使用其他东西,因为 XSLT 没有内置文件 IO。
    猜你喜欢
    • 2019-01-29
    • 2013-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多